i2bplustree
Version:
A package to implement the Improved Interval B+ tree, in TypeScript
346 lines (275 loc) • 12.4 kB
text/typescript
import { IBplusNode, IBplusLeafNode, Interval, FlatInterval } from './internal';
export class IBplusInternalNode<T extends FlatInterval> extends IBplusNode<T> {
constructor(order: number = 4,
parent: IBplusInternalNode<T> = null,
keys: Array<number> = [],
maximums: Array<number> = [],
private children: Array<IBplusNode<T>> = []) {
super(order, parent, keys, maximums);
for (const child of this.children)
child.setParent(this);
}
getChildren(): Array<IBplusNode<T>> {
return this.children;
}
setChildren(children: Array<IBplusNode<T>>): void {
this.children = children;
}
/**
* Updates the current node structures, when a new maximum appears in a child node.
*
* @param node the child
*/
updateMax(node: IBplusNode<T>): void {
let newMax: number = node.getMax();
let prevMax: number = this.getMax();
this.maximums[this.children.indexOf(node)] = newMax;
if (!this.isRoot() && (newMax > prevMax || this.getMax() != prevMax))
this.parent.updateMax(this);
}
/**
* Updates the current node structures, when a new minimum key appears in a child node.
*
* @param node the child
*/
updateMin(node: IBplusNode<T>): void {
let newMin: number = node.getMinKey();
let prevMin: number = this.getMinKey();
let index: number = this.children.indexOf(node);
this.keys[index] = newMin;
if (!this.isRoot() && (newMin < prevMin || index == 0))
this.parent.updateMin(this);
}
exists(int: T): boolean {
for (let i: number = 0; i < this.keys.length; ++i)
if ((new FlatInterval(this.keys[i], this.maximums[i])).contains(int))
if (this.children[i].exists(int))
return true;
return false;
}
search(int: FlatInterval): Set<T> {
const intervals: Set<T> = new Set();
for (let i = 0; i < this.keys.length; ++i)
if (Interval.containsWithValues(
[this.keys[i], this.maximums[i]], [int.getLowerBound(), int.getUpperBound()])) {
const iterator = this.children[i].search(int).values();
for (let next = iterator.next(); next.done !== true; next = iterator.next())
intervals.add(next.value);
}
return intervals;
}
loneRangeSearch(int: FlatInterval) {
for (let i: number = 0; i < this.keys.length; ++i)
if (Interval.intersectsWithValues(
[int.getLowerBound(), int.getUpperBound()], [this.keys[i], this.maximums[i]]))
return this.children[i].loneRangeSearch(int)
return null;
}
allRangeSearch(int: FlatInterval) {
const intervals: Set<T> = new Set();
for (let i = 0; i < this.keys.length; ++i)
if (Interval.intersectsWithValues(
[int.getLowerBound(), int.getUpperBound()], [this.keys[i], this.maximums[i]])) {
const iterator = this.children[i].allRangeSearch(int).values();
for (let next = iterator.next(); next.done !== true; next = iterator.next())
intervals.add(next.value);
}
return intervals;
}
containedRangeSearch(int: FlatInterval) {
const intervals: Set<T> = new Set();
for (let i = 0; i < this.keys.length; ++i)
if (Interval.intersectsWithValues(
[int.getLowerBound(), int.getUpperBound()], [this.keys[i], this.maximums[i]])) {
const iterator = this.children[i].containedRangeSearch(int).values();
for (let next = iterator.next(); next.done !== true; next = iterator.next())
intervals.add(next.value);
}
return intervals;
}
findInsertNode(int: Interval<T>): IBplusLeafNode<T> | null {
if (this.children.length == 0)
return null;
for (let i = 0; i < this.keys.length; ++i)
if (int.getLowerBound() < this.keys[i])
return this.children[i > 0 ? i - 1 : i].findInsertNode(int);
return this.children[this.keys.length - 1].findInsertNode(int); // The biggest
}
/**
* When a split occurred in a child of this node, the node structures must be updated.
* Updates maximum and key of previous node and adds the newly created node to this node children.
*
* @param original The node that was split
* @param newNode The node that was created
*/
updateWithNewNode(original: IBplusNode<T>, newNode: IBplusNode<T>): void {
let originalIdx: number = this.children.indexOf(original);
this.maximums[originalIdx] = original.getMax();
this.children.splice(originalIdx + 1, 0, newNode);
this.keys.splice(originalIdx + 1, 0, newNode.getMinKey());
this.maximums.splice(originalIdx + 1, 0, newNode.getMax());
// Might be momentarily violating B+-trees order invariant
if (this.keys.length > this.order)
this.split();
}
split(): IBplusLeafNode<T> {
// If this is root, create a new root
if (this.parent == null)
this.parent = new IBplusInternalNode(
this.order,
null,
[this.getMinKey()],
[this.getMax()],
[this]
);
let divIdx: number = Math.ceil(this.keys.length / 2)
let sibSize: number = this.order + 1 - divIdx; //Need +1 because of the invariant violation
// Divide keys, maximums and children by this node and its sibling
let sibling: IBplusInternalNode<T> = new IBplusInternalNode<T>(
this.order,
this.parent,
this.keys.splice(divIdx, sibSize),
this.maximums.splice(divIdx, sibSize),
this.children.splice(divIdx, sibSize)
);
let rs: IBplusNode<T> = this.getRightSibling();
if (rs != null) rs.setLeftSibling(sibling);
sibling.setRightSibling(rs);
sibling.setLeftSibling(this);
this.setRightSibling(sibling);
this.parent.updateWithNewNode(this, sibling);
// When called in IBplusInternalNode, the split method is not handling direct insertions
// hence, its return value is irrelevant
return null;
}
findInterval(int: Interval<T>): [IBplusLeafNode<T>, number] | null {
let res: [IBplusLeafNode<T>, number] = null;
for (let i = 0; i < this.keys.length && res == null; ++i)
if (Interval.containsWithValues(
[this.keys[i], this.maximums[i]], [int.getLowerBound(), int.getUpperBound()]))
res = this.children[i].findInterval(int);
return res;
}
findIntervalWithCompounds(int: Interval<T>): Array<[IBplusLeafNode<T>, Interval<T>, number]> {
let res: Array<[IBplusLeafNode<T>, Interval<T>, number]> = [];
for (let i = 0; i < this.keys.length; ++i)
if (Interval.intersectsWithValues([int.getLowerBound(), int.getUpperBound()], [this.keys[i], this.maximums[i]]))
res.push(...this.children[i].findIntervalWithCompounds(int));
return res;
}
findIntervalsInRange(int: Interval<T> | FlatInterval): Array<[IBplusLeafNode<T>, Interval<T>]> {
let res: Array<[IBplusLeafNode<T>, Interval<T>]> = [];
for (let i = 0; i < this.keys.length; ++i)
if (Interval.intersectsWithValues(
[this.keys[i], this.maximums[i]], [int.getLowerBound(), int.getUpperBound()]))
res.push(...this.children[i].findIntervalsInRange(int));
return res;
}
/**
* Function to be called on successive deletions,
* where a previous removal can make an interval change leaf or index
*
* @param leaf the initial leaf
* @param int the initial index
* @return pair of leaf and the index of the interval on it
*/
private checkIntervalOnLeaf(leaf: IBplusLeafNode<T>, int: Interval<T>): [IBplusLeafNode<T>, number] {
let sibling: IBplusLeafNode<T> = leaf.getSubstituteSibling();
while (sibling) {
leaf = sibling;
sibling = sibling.getSubstituteSibling();
}
let childIdx: number = leaf.getChildren().indexOf(int);
if (childIdx < 0)
// Previous removals triggered borrows that moved the child
if (leaf.getLeftSibling() && int.getLowerBound() <= leaf.getMinKey())
// Sent to left sibling leaf
leaf = <IBplusLeafNode<T>>leaf.getLeftSibling();
else if (leaf.getRightSibling() && int.getLowerBound() > leaf.getMinKey())
// Sent to right sibling leaf
leaf = <IBplusLeafNode<T>>leaf.getRightSibling();
else
throw Error('Unable to find child in range remove.');
return [leaf, leaf.getChildren().indexOf(int)];
}
/**
* Deletes a given interval if it exists in one of the tree's (that have this node as root) leafs.
* The tree self-balances on deletion.
*
* @param int The interval to be deleted
*/
delete(int: Interval<T>, alpha: number): void {
if (!alpha || alpha == 0) {
let found: [IBplusLeafNode<T>, number] = this.findInterval(int);
if (found != null)
found[0].removeChild(found[1]);
} else {
let foundInts: Array<[IBplusLeafNode<T>, Interval<T>, number]> = this.findIntervalWithCompounds(int);
// Remove the first found
let [leaf, foundInt, idx] = foundInts[0];
leaf.removeChild(idx);
// If the eliminated interval is a compound, also eliminate the remaining Compounds referring the same interval\
if (foundInt != foundInt.getOriginalInterval()) {
const originInt = foundInt.getOriginalInterval();
for (let i = 1; i < foundInts.length; ++i) {
[leaf, int, idx] = foundInts[i];
[leaf, idx] = this.checkIntervalOnLeaf(leaf, int);
if (leaf.getChildren()[idx].getOriginalInterval() == originInt)
leaf.removeChild(idx);
}
}
}
}
/**
* Deletes all the interval contained in a given range.
* The tree self-balances on deletion.
*
* @param lowerBound the range's lower bound
* @param upperBound the range's upper bound
*/
rangeDelete(lowerBound: number, upperBound: number): void {
let foundInts: Array<[IBplusLeafNode<T>, Interval<T>]> =
this.findIntervalsInRange(
new FlatInterval(lowerBound, upperBound)
);
for (let [leaf, int] of foundInts) {
let childIdx;
[leaf, childIdx] = this.checkIntervalOnLeaf(leaf, int);
leaf.removeChild(childIdx);
}
}
protected setChildParentOnBorrow(newChild: IBplusNode<T>, insertId: number): void {
this.children.splice(insertId, 0, newChild);
newChild.setParent(this);
}
protected setChildrenParentOnMerge(newParent: IBplusInternalNode<T>): void {
for (const child of this.children)
child.setParent(newParent);
}
protected setSubstitutionNode(node: IBplusNode<T>): void {
// No interest in saving substitution node in Internal Nodes
}
isChildNewRoot(): boolean {
if (!this.isRoot() || this.children.length > 1)
return false;
return this.children[0] instanceof IBplusInternalNode &&
this.children[0].getLeftSibling() == null &&
this.children[0].getRightSibling() == null;
}
asString(acc: string = "", depth: number = 0): string {
// Adding tabs according to depth
let tabs: string = "";
for (let i: number = 0; i < depth; ++i)
tabs += '\t';
acc += `${tabs}- Keys |`;
for (let key of this.keys)
acc += `${key}|`;
acc += `\n${tabs} Maxs |`;
for (let max of this.maximums)
acc += `${max}|`;
acc += "\n";
for (let child of this.children)
acc = child.asString(acc, depth + 1);
return acc;
}
}