@jbrowse/core
Version:
JBrowse 2 core libraries used by plugins
252 lines (251 loc) • 7.89 kB
JavaScript
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();
}
}