@everwhen/temporal
Version:
189 lines • 6.01 kB
JavaScript
import { Interval } from '../interval.js';
import { TemporalNode } from './temporal-node.js';
export class TemporalTree {
constructor() {
Object.defineProperty(this, "root", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "_size", {
enumerable: true,
configurable: true,
writable: true,
value: 0
});
this.root = null;
}
_compareIntervals(a, b) {
if (a.equals(b))
return 0;
const startComparison = a.start.compare(b.start);
if (startComparison !== 0) {
return startComparison;
}
return a.end.compare(b.end);
}
get(interval) {
const node = Interval.from(interval);
let curr = this.root;
while (curr) {
if (node.start.equals(curr.interval.start) &&
node.end.equals(curr.interval.end)) {
return curr;
}
curr = node.isBefore(curr.interval) ? curr.left : curr.right;
}
}
set(interval, data) {
const target = Interval.from(interval);
if (!this.root) {
return this.insert(interval, data);
}
let next = this.root;
let curr = next;
let comparison = 0;
do {
curr = next;
comparison = this._compareIntervals(target, curr.interval);
if (comparison === 0) {
curr.data = data;
return curr;
}
next = comparison < 0 ? curr.left : curr.right;
} while (next);
const node = new TemporalNode(interval, data);
if (comparison < 0) {
curr.left = node;
}
else {
curr.right = node;
}
this.root = this._rebalance(this.root);
this._size++;
return node;
}
/**
* Inserts a new interval into the tree
* @param interval The interval to insert
* @param data Optional data to associate with the interval
* @returns The inserted node
*/
insert(interval, data) {
const node = new TemporalNode(interval, data);
this.root = this._insert(this.root, node);
this._size++;
return node;
}
delete(interval) {
const target = Interval.from(interval);
const node = this.get(target);
if (!node) {
return false;
}
this.root = this._removeNode(this.root, node);
this._size--;
return true;
}
/**
* Traverses the tree and collects results based on a predicate and mapper
* @param predicate Function that tests each node for inclusion
* @param mapper Function that transforms matching nodes into the desired output type
* @param options Traversal options including order
* @returns Array of transformed values from nodes that match the predicate
*/
select(predicate, mapper = (node) => node, options = {}) {
const { order = 'in' } = options;
const results = [];
const traverseNode = (node) => {
if (!node)
return;
if (order === 'pre' && predicate(node)) {
results.push(mapper(node));
}
traverseNode(node.left);
if (order === 'in' && predicate(node)) {
results.push(mapper(node));
}
traverseNode(node.right);
if (order === 'post' && predicate(node)) {
results.push(mapper(node));
}
};
traverseNode(this.root);
return results;
}
_insert(root, node) {
// Base case: if root is null, the new node becomes the root
if (!root) {
return node;
}
const cmp = this._compareIntervals(node.interval, root.interval);
if (cmp < 0) {
root.left = this._insert(root.left, node);
}
else {
root.right = this._insert(root.right, node);
}
return root.balance();
}
_rebalance(node) {
if (!node)
return node;
// If this is a leaf node, just update its height
if (!node.left && !node.right) {
node.updateHeight();
return node;
}
// Recursively balance children
if (node.left) {
node.left = this._rebalance(node.left);
}
if (node.right) {
node.right = this._rebalance(node.right);
}
return node.balance();
}
_removeNode(root, target) {
if (!root) {
return null;
}
const comparison = this._compareIntervals(target.interval, root.interval);
if (comparison < 0) {
root.left = this._removeNode(root.left, target);
}
else if (comparison > 0) {
root.right = this._removeNode(root.right, target);
}
else {
// Case 1: Leaf node
if (!root.left && !root.right) {
return null;
}
// Case 2: Node with only one child
if (!root.left) {
return root.right;
}
if (!root.right) {
return root.left;
}
// Case 3: Node with two children
// Find the smallest value in the right subtree (successor)
let successor = root.right;
while (successor.left) {
successor = successor.left;
}
// Copy successor's data and interval to this node
root.data = successor.data;
root.interval = successor.interval;
// Remove the successor
root.right = this._removeNode(root.right, successor);
}
return root.balance();
}
get size() {
return this._size;
}
}
//# sourceMappingURL=temporal-tree.js.map