@everwhen/temporal
Version:
_description_
216 lines (215 loc) • 6.16 kB
JavaScript
import { max } from "../fn/max.js";
import {} from "../interval.js";
export const Color = {
RED: 'RED',
BLACK: 'BLACK',
};
class IntervalNode {
interval;
max;
data;
color = Color.RED;
left = null;
right = null;
parent = null;
constructor(interval, item) {
this.interval = interval;
this.max = interval.end;
this.data = new Map([[item.id, item]]);
}
}
export class IntervalTree {
#root = null;
constructor(entries) {
if (entries) {
for (const [interval, data] of entries) {
this.insert(interval, data);
}
}
}
insert(interval, item) {
let parent = null;
let current = this.#root;
while (current) {
parent = current;
current.max = max(current.max, interval.end);
if (this.#compare(interval, current.interval) === 0) {
current.data.set(item.id, item);
return;
}
const cmpStart = interval.start.compare(current.interval.start);
current = cmpStart < 0 ? current.left : current.right;
}
const node = new IntervalNode(interval, item);
node.parent = parent;
if (parent === null) {
this.#root = node;
this.#root.color = Color.BLACK;
return;
}
if (interval.start.compare(parent.interval.start) < 0) {
parent.left = node;
}
else {
parent.right = node;
}
this.#insertFixup(node);
}
#compare(a, b) {
const startComparison = a.start.compare(b.start);
if (startComparison !== 0) {
return startComparison;
}
return a.end.compare(b.end);
}
search(query, predicate, mapper = (args) => args) {
const results = [];
for (const result of this.#traverse(this.#root, query)) {
if (predicate(result.interval)) {
results.push(mapper(result));
}
}
return results;
}
/**
* Returns number of items stored in the interval tree
*/
size() {
let count = 0;
for (const result of this) {
count = count + result.data.size;
}
return count;
}
/**
* Returns true if tree is empty
*/
isEmpty() {
return this.#root === null;
}
/**
* Clear tree
*/
clear() {
this.#root = null;
}
*[Symbol.iterator]() {
yield* this.#walk(this.#root);
}
*walk(query) {
if (query) {
yield* this.#traverse(this.#root, query);
}
else {
yield* this.#walk(this.#root);
}
}
*#walk(node) {
if (!node)
return;
yield* this.#walk(node.left);
yield { interval: node.interval, data: node.data };
yield* this.#walk(node.right);
}
*#traverse(node, query) {
if (!node)
return;
if (node.left && node.left.max.compare(query.start) >= 0) {
yield* this.#traverse(node.left, query);
}
yield { interval: node.interval, data: node.data };
if (node.right && query.end.compare(node.interval.start) >= 0) {
yield* this.#traverse(node.right, query);
}
}
#insertFixup(node) {
while (node.parent?.color === Color.RED) {
const parent = node.parent;
const grandparent = parent.parent;
if (parent === grandparent.left) {
const uncle = grandparent.right;
if (uncle?.color === Color.RED) {
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
node = grandparent;
}
else {
if (node === parent.right) {
node = parent;
this.#rotateLeft(node);
}
node.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
this.#rotateRight(node.parent.parent);
}
}
else {
const uncle = grandparent.left;
if (uncle?.color === Color.RED) {
parent.color = Color.BLACK;
uncle.color = Color.BLACK;
grandparent.color = Color.RED;
node = grandparent;
}
else {
if (node === parent.left) {
node = parent;
this.#rotateRight(node);
}
node.parent.color = Color.BLACK;
node.parent.parent.color = Color.RED;
this.#rotateLeft(node.parent.parent);
}
}
}
this.#root.color = Color.BLACK;
}
#rotateLeft(x) {
const y = x.right;
x.right = y.left;
if (y.left)
y.left.parent = x;
y.parent = x.parent;
if (!x.parent) {
this.#root = y;
}
else if (x === x.parent.left) {
x.parent.left = y;
}
else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
this.#updateMax(x);
this.#updateMax(y);
}
#rotateRight(y) {
const x = y.left;
y.left = x.right;
if (x.right)
x.right.parent = y;
x.parent = y.parent;
if (!y.parent) {
this.#root = x;
}
else if (y === y.parent.right) {
y.parent.right = x;
}
else {
y.parent.left = x;
}
x.right = y;
y.parent = x;
this.#updateMax(y);
this.#updateMax(x);
}
#updateMax(node) {
node.max = node.interval.end;
if (node.left)
node.max = max(node.max, node.left.max);
if (node.right)
node.max = max(node.max, node.right.max);
}
}