i2bplustree
Version:
A package to implement the Improved Interval B+ tree, in TypeScript
384 lines (331 loc) • 12.7 kB
text/typescript
import {
IBplusLeafNode,
IBplusInternalNode,
Interval,
FlatInterval
} from './internal';
/**
* Class implementation of a generic Interval B+-Tree node
*/
export abstract class IBplusNode<T extends FlatInterval> {
private rightSibling: IBplusNode<T> = null;
private leftSibling: IBplusNode<T> = null;
/**
* @param order Node's order. Must be the same in all tree nodes.
* @param parent This node's parent node. If null, node is root
* @param keys Keys are the lower bounds of this tree subtree
* @param maximums Maximum end point of the intervals indexed by its subtree
*/
constructor(public readonly order: number,
protected parent: IBplusInternalNode<T>,
protected keys: Array<number>,
protected maximums: Array<number>) { }
/**
* Get this Node children.
*
* @returns the array containing the children, may it be other nodes or intervals.
*/
abstract getChildren(): Array<any>;
/**
* Get this node's parent node
*/
getParent(): IBplusInternalNode<T> | null {
return this.parent;
}
/**
* Set this node parent node
*
* @param newParent new parent node
*/
setParent(newParent: IBplusInternalNode<T>): void {
this.parent = newParent;
}
/**
* Get the minimum key out of this node keys
*/
getMinKey(): number {
return this.keys[0];
}
/**
* Get the maximum number out of this node maximums
*/
getMax(): number {
return Math.max(...this.maximums)
}
/**
* Gets this node right sibling.
*/
getRightSibling(): IBplusNode<T> {
return this.rightSibling;
}
/**
* Sets this node right sibling to the given value.
*
* @param sibling new right sibling
*/
setRightSibling(sibling: IBplusNode<T>): void {
this.rightSibling = sibling;
}
/**
* Gets this node left sibling.
*/
getLeftSibling(): IBplusNode<T> {
return this.leftSibling;
}
/**
* Sets this node left sibling to the given value.
*
* @param sibling new left sibling
*/
setLeftSibling(sibling: IBplusNode<T>): void {
this.leftSibling = sibling;
}
/**
* Function to be called on node deletion.
* Sets this node siblings' siblings to one each other, making this node no one's sibling
*/
protected concatSiblings() {
if (this.rightSibling !== null) this.rightSibling.setLeftSibling(this.leftSibling);
if (this.leftSibling !== null) this.leftSibling.setRightSibling(this.rightSibling);
}
/**
* Checks if this node is the root.
*
* @returns true if this node is Root, false otherwise.
*/
isRoot(): boolean {
return this.parent == null;
}
/**
* Verify if the given interval exists in the tree.
*
* @param int the interval to be searched
* @returns the object if exists, null otherwise.
*/
abstract exists(int: T): boolean;
/**
* Get all intervals stored in the tree with equal bounds to the given ones.
*
* @param int search interval
* @returns Array of found intervals.
*/
abstract search(int: FlatInterval): Set<T>;
/**
* Returns one interval (if there exists at least one) that intersects with the given search interval.
* That interval is also the one with the minimum beginning point.
*
* @param int search interval
* @returns first interval intersecting the given range, or null if none does.
*/
abstract loneRangeSearch(int: FlatInterval): T | null;
/**
* Returns a set with the original intervals that intersect with the given search interval.
*
* @param int search interval
*/
abstract allRangeSearch(int: FlatInterval): Set<T>;
/**
* Returns a set with the original intervals that are contained within the given search interval.
*
* @param int search interval
* @returns intervals contained in the given interval
*/
abstract containedRangeSearch(int: FlatInterval): Set<T>;
/**
* Returns the Leaf Node where the new interval shall be inserted.
* Returns null when the tree has no children.
*
* @param int the search interval
*/
abstract findInsertNode(int: Interval<T>): IBplusLeafNode<T> | null;
/**
* Splits a Node into two nodes and distibute in half the original node children.
* After splitting, inserts the interval that triggered the split into one of the nodes.
*
* @param int The interval being inserted that triggered this split
* @returns the Leaf were the Interval ended up being inserted
*/
protected abstract split(int: Interval<T>): IBplusLeafNode<T>;
/**
* Update the key and the maximum representing a node in its parent
*/
protected updateParentValues() {
this.parent.updateMin(this);
this.parent.updateMax(this);
}
/**
* Insert the given Interval in the tree.
* If alpha parameter is > 0, TimeSplit is applied to a leaf node N after a
* new interval is inserted into N and the end point of the new interval is
* greater than the maximum end point among the intervals that were already in N.
*
* @param int the interval to be inserted
* @param alpha the value of alpha used in the PickSplitPoint algorithm. If alpha <= 0, time splits aren't used.
*/
insert(int: Interval<T>, alpha: number): void {
// Insertions must always start in the root -> Valid since this function is not recursive
if (!this.isRoot())
return this.parent.insert(int, alpha);
// Perform a search to determine what leaf the new record should go into.
let insertionNode: IBplusLeafNode<T> = this.findInsertNode(int);
// No leafs in the tree
if (insertionNode === null && this instanceof IBplusInternalNode) {
insertionNode = new IBplusLeafNode(this.order, this);
this.setChildren([insertionNode]);
}
// If we are adding and the keys's size is already equal to order, split before insertion
if (insertionNode.keys.length >= this.order)
// Split also inserts
insertionNode = insertionNode.split(int);
else
insertionNode.addInterval(int);
// Comparison viable since maximums and minimums were not yet updated
if (alpha > 0 && int.getUpperBound() > this.getMax())
insertionNode.timeSplit(this, alpha);
insertionNode.updateParentValues();
}
/**
* Finds the given interval in its tree.
*
* @param int the interval to be found
* @returns if the given interval exists, returns the leaf where it's stored
* as well as its position in the keys of that leaf.
* Otherwise returns null.
*/
abstract findInterval(int: Interval<T>): [IBplusLeafNode<T>, number] | null;
/**
* Finds the given interval in the tree (including finding the wrapping Compound Intervals).
*
*
* @param int the interval to be found.
* @returns array of tuples containing the leaf, the interval and the index of the interval on the leaf
*/
abstract findIntervalWithCompounds(int: Interval<T>): Array<[IBplusLeafNode<T>, Interval<T>, number]>;
/**
* Finds all the intervals in the tree belonging to the given interval.
*
* Necessary the '| FlatInterval' because of some supposed problem with ts.
* For more info check the issue: https://github.com/Microsoft/TypeScript/issues/28154
*
* @param int the interval defining the limits for the found intervals.
* @returns array of Pairs containing the interval and the respective
* leaf where it's stored.
*/
abstract findIntervalsInRange(int: Interval<T> | FlatInterval): Array<[IBplusLeafNode<T>, Interval<T>]>;
/**
* Template Method.
* Add and Update this node's new child parent.
*
* @param newChild new child
* @param insertId insertion idx
*/
protected abstract setChildParentOnBorrow(newChild: IBplusNode<T> | Interval<T>, insertId: number): void;
/**
* Remove an entry from the sibling node and add it to this node
*
* @param sibling The sibling this node is going to borrow from
* @param insertId The index where the element will be inserted
* @param removeId The index of the element going to be removed from the sibling
*/
protected borrow(sibling: IBplusNode<T>, insertId: number, removeId: number): void {
this.keys.splice(insertId, 0, sibling.keys.splice(removeId, 1)[0]);
this.maximums.splice(insertId, 0, sibling.maximums.splice(removeId, 1)[0]);
this.setChildParentOnBorrow(sibling.getChildren().splice(removeId, 1)[0], insertId);
this.updateParentValues();
sibling.updateParentValues();
}
/**
* Template Method.
* Updates this node's children's parents, upon Merge.
*
* @param newParent The children new parent
*/
protected abstract setChildrenParentOnMerge(newParent: IBplusNode<T>): void;
/**
* Template Method.
* Set this node's substitution node, upon Merge.
*
* @param node the substitution node
*/
protected abstract setSubstitutionNode(node: IBplusNode<T>): void;
/**
* Merge this node into the given node
*
* @param sibling The sibling to merge with
* @param id The position where this node elements will be inserted
*/
protected merge(sibling: IBplusNode<T>, id: number): void {
sibling.keys.splice(id, 0, ...this.keys);
sibling.maximums.splice(id, 0, ...this.maximums);
sibling.getChildren().splice(id, 0, ...this.getChildren());
this.setSubstitutionNode(sibling);
this.setChildrenParentOnMerge(sibling);
this.concatSiblings();
this.parent.removeChild(this.parent.getChildren().indexOf(this));
if (!sibling.isRoot())
sibling.updateParentValues();
}
/**
* Handle underflow by borrowing form siblings or merging with them
*/
protected handleUnderflow() {
// Minimum number of entries a node must have
let minEntries: number = Math.floor(this.order / 2);
// Borrow from left sibling
if (this.leftSibling != null && this.leftSibling.keys.length > minEntries)
this.borrow(this.leftSibling, 0, this.leftSibling.keys.length - 1);
//Borrow from right sibling
else if (this.rightSibling != null && this.rightSibling.keys.length > minEntries)
this.borrow(this.rightSibling, this.keys.length, 0);
// Merge left sibling
else if (this.leftSibling != null)
this.merge(this.leftSibling, this.leftSibling.keys.length);
// Merge right sibling
else if (this.rightSibling != null)
this.merge(this.rightSibling, 0);
}
/**
* Template Method.
* When a Node, if it is Internal, only has one child, it means the child can be the new Root.
*/
abstract isChildNewRoot(): boolean;
/**
* Find out if this Node is in an underflow situation.
* Invariant Underflow: Every node but the root must always have at least floor(order/ 2) keys.
*
* @returns true if it is, false otherwise
*/
protected isUnderflow(): boolean {
return this.keys.length < Math.floor(this.order / 2);
}
/**
* Remove the node's child in the given idx
* http://sites.fas.harvard.edu/~cs165/papers/comer.pdf
* http://www.cburch.com/cs/340/reading/btree/index.html#s3
*
* @param idx idx of the interval
*/
removeChild(idx: number): void {
// Removing the interval
this.keys.splice(idx, 1);
this.maximums.splice(idx, 1);
this.getChildren().splice(idx, 1);
if (this.isChildNewRoot()) {
this.getChildren()[0].setParent(null); //new Root
return;
}
if (this.isUnderflow())
this.handleUnderflow();
else if (!this.isRoot())
this.updateParentValues(); // Update parent keys
}
/**
* Represents the current tree as a string.
* Useful for printing purposes.
*
* @param acc The accumulated results of the other nodes strings
* @param depth The current node depth
* @returns The accumulated string, incremented with this node
*/
abstract asString(acc: string, depth: number): string;
}