UNPKG

@jbrowse/core

Version:

JBrowse 2 core libraries used by plugins

252 lines (251 loc) 7.89 kB
const RB_TREE_COLOR_RED = 1; const RB_TREE_COLOR_BLACK = 0; class Interval { low; high; constructor(low, high) { this.low = low; this.high = high; } lessThan(other) { return (this.low < other.low || (this.low === other.low && this.high < other.high)); } equalTo(other) { return this.low === other.low && this.high === other.high; } intersects(other) { return !(this.high < other.low || other.high < this.low); } merge(other) { return new Interval(Math.min(this.low, other.low), Math.max(this.high, other.high)); } } class Node { left = null; right = null; parent = null; color = RB_TREE_COLOR_BLACK; key; values = []; max; constructor(key, value, left, right, parent, color) { if (left !== undefined) { this.left = left; } if (right !== undefined) { this.right = right; } if (parent !== undefined) { this.parent = parent; } if (color !== undefined) { this.color = color; } if (value !== undefined) { this.values.push(value); } if (key !== undefined) { if (Array.isArray(key)) { const [low, high] = key; this.key = low <= high ? new Interval(low, high) : new Interval(high, low); } else { this.key = key; } this.max = this.key; } } lessThan(other) { return this.key.lessThan(other.key); } equalTo(other) { return this.key.equalTo(other.key); } intersects(other) { return this.key.intersects(other.key); } updateMax() { this.max = this.key; if (this.right?.max && this.max) { this.max = this.max.merge(this.right.max); } if (this.left?.max && this.max) { this.max = this.max.merge(this.left.max); } } notIntersectLeftSubtree(searchNode) { if (!this.left) { return true; } const high = this.left.max?.high ?? this.left.key.high; return high < searchNode.key.low; } notIntersectRightSubtree(searchNode) { if (!this.right) { return true; } const low = this.right.max?.low ?? this.right.key.low; return searchNode.key.high < low; } } export class IntervalTree { root = null; nilNode = new Node(); insert(key, value) { const existing = this.treeSearch(this.root, new Node(key)); if (existing) { existing.values.push(value); return existing; } const insertNode = new Node(key, value, this.nilNode, this.nilNode, null, RB_TREE_COLOR_RED); this.treeInsert(insertNode); this.recalcMax(insertNode); return insertNode; } search(interval) { const searchNode = new Node(interval); const resultNodes = []; this.treeSearchInterval(this.root, searchNode, resultNodes); return resultNodes.flatMap(node => node.values); } recalcMax(node) { let current = node; while (current.parent != null) { current.parent.updateMax(); current = current.parent; } } treeInsert(insertNode) { let current = this.root; let parent = null; if (this.root == null || this.root === this.nilNode) { this.root = insertNode; } else { while (current !== this.nilNode) { parent = current; current = insertNode.lessThan(current) ? current.left : current.right; } insertNode.parent = parent; if (insertNode.lessThan(parent)) { parent.left = insertNode; } else { parent.right = insertNode; } } this.insertFixup(insertNode); } insertFixup(insertNode) { let current = insertNode; while (current !== this.root && current.parent.color === RB_TREE_COLOR_RED) { if (current.parent === current.parent.parent.left) { const uncle = current.parent.parent.right; if (uncle.color === RB_TREE_COLOR_RED) { current.parent.color = RB_TREE_COLOR_BLACK; uncle.color = RB_TREE_COLOR_BLACK; current.parent.parent.color = RB_TREE_COLOR_RED; current = current.parent.parent; } else { if (current === current.parent.right) { current = current.parent; this.rotateLeft(current); } current.parent.color = RB_TREE_COLOR_BLACK; current.parent.parent.color = RB_TREE_COLOR_RED; this.rotateRight(current.parent.parent); } } else { const uncle = current.parent.parent.left; if (uncle.color === RB_TREE_COLOR_RED) { current.parent.color = RB_TREE_COLOR_BLACK; uncle.color = RB_TREE_COLOR_BLACK; current.parent.parent.color = RB_TREE_COLOR_RED; current = current.parent.parent; } else { if (current === current.parent.left) { current = current.parent; this.rotateRight(current); } current.parent.color = RB_TREE_COLOR_BLACK; current.parent.parent.color = RB_TREE_COLOR_RED; this.rotateLeft(current.parent.parent); } } } this.root.color = RB_TREE_COLOR_BLACK; } treeSearch(node, searchNode) { if (node == null || node === this.nilNode) { return undefined; } if (searchNode.equalTo(node)) { return node; } return searchNode.lessThan(node) ? this.treeSearch(node.left, searchNode) : this.treeSearch(node.right, searchNode); } treeSearchInterval(node, searchNode, results) { if (node != null && node !== this.nilNode) { if (node.left !== this.nilNode && !node.notIntersectLeftSubtree(searchNode)) { this.treeSearchInterval(node.left, searchNode, results); } if (node.intersects(searchNode)) { results.push(node); } if (node.right !== this.nilNode && !node.notIntersectRightSubtree(searchNode)) { this.treeSearchInterval(node.right, searchNode, results); } } } rotateLeft(x) { const y = x.right; x.right = y.left; if (y.left !== this.nilNode) { y.left.parent = x; } y.parent = x.parent; if (x === this.root) { this.root = y; } else if (x === x.parent.left) { x.parent.left = y; } else { x.parent.right = y; } y.left = x; x.parent = y; x.updateMax(); y.updateMax(); } rotateRight(y) { const x = y.left; y.left = x.right; if (x.right !== this.nilNode) { x.right.parent = y; } x.parent = y.parent; if (y === this.root) { this.root = x; } else if (y === y.parent.left) { y.parent.left = x; } else { y.parent.right = x; } x.right = y; y.parent = x; y.updateMax(); x.updateMax(); } }