i2bplustree
Version:
A package to implement the Improved Interval B+ tree, in TypeScript
268 lines (212 loc) • 9.07 kB
text/typescript
import {
IBplusNode,
IBplusInternalNode,
Interval,
CompoundInterval,
FlatInterval
} from './internal';
export class IBplusLeafNode<T extends FlatInterval> extends IBplusNode<T> {
/**
* Sibling Leaf Node that substituted this node, upon the
* occurrence of a removal that triggered a merge.
*/
private substSibling: IBplusLeafNode<T> = null;
constructor(order: number = 4,
parent: IBplusInternalNode<T> = null,
keys: Array<number> = [],
maximums: Array<number> = [],
private children: Array<Interval<T>> = []) {
super(order, parent, keys, maximums);
}
getChildren(): Array<Interval<T>> {
return this.children;
}
/**
* Recursively get the node currently replacing this node.
*
* @returns the substitution sibling if exists, null otherwise
*/
getSubstituteSibling(): IBplusLeafNode<T> {
return this.substSibling;
}
exists(int: T): boolean {
for (const child of this.children)
if (child.getOriginalInterval().equals(int))
return true;
return false;
}
search(int: FlatInterval): Set<T> {
const intervals: Set<T> = new Set();
for (const child of this.children) {
const originInt: T = child.getOriginalInterval();
if (originInt.equals(int))
intervals.add(originInt);
}
return intervals;
}
loneRangeSearch(int: FlatInterval): T | null {
for (const child of this.children)
if (child.intersect(int))
return child.getOriginalInterval();
return null;
}
allRangeSearch(int: FlatInterval) {
const intervals: Set<T> = new Set();
for (const child of this.children)
if (child.intersect(int))
intervals.add(child.getOriginalInterval());
return intervals;
}
containedRangeSearch(int: FlatInterval) {
const intervals: Set<T> = new Set();
for (const child of this.children) {
const originInt: T = child.getOriginalInterval();
if (int.contains(originInt))
intervals.add(originInt);
}
return intervals;
}
findInsertNode(int: Interval<T>): IBplusLeafNode<T> {
return this;
}
/**
* Add the given interval to this leaf data structures.
*
* @param int The interval to be added
*/
addInterval(int: Interval<T>): void {
let i: number = 0;
for (; i < this.keys.length; ++i)
if (int.getLowerBound() < this.keys[i])
break;
this.keys.splice(i, 0, int.getLowerBound());
this.maximums.splice(i, 0, int.getUpperBound());
this.children.splice(i, 0, int);
}
split(int: Interval<T>): IBplusLeafNode<T> {
//Need special care on binary trees
let divIdx: number = Math.ceil(this.order / 2);
let sibSize: number = this.order - divIdx;
// Divide keys, maximums and children by this node and its sibling
let sibling: IBplusLeafNode<T> = new IBplusLeafNode<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);
// Inserting the interval in the respective node
const insertionLeaf: IBplusLeafNode<T>
= sibling.keys[0] < int.getLowerBound() ? sibling : this;
insertionLeaf.addInterval(int);
return insertionLeaf;
}
findInterval(int: Interval<T>): [IBplusLeafNode<T>, number] | null {
for (let i: number = 0; i < this.keys.length; ++i)
if (this.children[i].getOriginalInterval().equals(int))
return [this, i];
return null;
}
findIntervalWithCompounds(int: Interval<T>): Array<[IBplusLeafNode<T>, Interval<T>, number]> {
let res: Array<[IBplusLeafNode<T>, Interval<T>, number]> = [];
for (let i = 0; i < this.children.length; ++i)
if (int.equals(this.children[i].getOriginalInterval())) {
res.push([this, this.children[i], i]);
}
return res;
}
findIntervalsInRange(int: Interval<T> | FlatInterval): Array<[IBplusLeafNode<T>, Interval<T>]> {
let res: Array<[IBplusLeafNode<T>, Interval<T>]> = [];
for (let child of this.children)
if (int.contains(child.getOriginalInterval()))
res.push([this, child]);
return res;
}
protected setChildParentOnBorrow(newChild: Interval<T>, insertId: number): void {
// Child is Interval, it does not store parent information
this.children.splice(insertId, 0, newChild);
}
protected setChildrenParentOnMerge(newParent: IBplusNode<T>): void {
// Children are Intervals, they do not store parent information
}
protected setSubstitutionNode(sibling: IBplusNode<T>): void {
// Sibling on merge is forcefully a IBplusLeafNode
this.substSibling = <IBplusLeafNode<T>>sibling;
}
isChildNewRoot(): boolean {
return false; // A Leaf can never be a root
}
/**
* Time split intervals into smaller nodes in order to have better querying performance.
*
* @param rootNode the node were the insertions will happen
* @param alpha parameter to adjust space/ query_time tradeoff.
* See more regarding alpha in IBplusTree's constructor's documentation.
*/
timeSplit(rootNode: IBplusNode<T>, alpha: number): void {
if (this.isUnderflow())
return;
// Array containing the Intervals that will need to be inserted in the end (time split intervals)
let newInsertions: Array<Interval<T>> = [];
if (this.getRightSibling() != null) {
let splitIdx: number = this.pickSplitPoint(alpha);
if (this.maximums[splitIdx] == this.getMax())
return;
else
for (let i: number = 0; i < this.keys.length; ++i) {
if (this.maximums[i] > this.maximums[splitIdx]) {
// Creating new Interval
newInsertions.push(
new CompoundInterval(this.maximums[splitIdx] + 1, this.maximums[i], this.children[i])
);
//Updating the split Interval
this.children[i] = new CompoundInterval(
this.children[i].getLowerBound(), this.maximums[splitIdx], this.children[i]
);
this.maximums[i] = this.maximums[splitIdx];
}
}
}
// Inserting the generated intervals and saving them to the resultant array
for (let interval of newInsertions)
rootNode.insert(interval, alpha);
}
/**
* Algorithm which takes a leaf node as input and returns the index to the
* interval whose end point is the best split point (with the minimum cost).
*
* @param alpha parameter to adjust space/ query_time tradeoff.
* See more regarding alpha in IbplusTree's constructor's documentation.
*/
pickSplitPoint(alpha: number): number {
let maxbegin: number = Math.max(...this.keys);
let endlist: Array<number> = this.maximums.map(el => el < maxbegin ? maxbegin : el);
let cutcost: Array<number> = [];
for (let i: number = 0; i < endlist.length; ++i)
cutcost.push(endlist.map(el => Math.abs(endlist[i] - el)).reduce((acc, val) => acc + val)); // Doing it like this since formula ain't working
// cutcost.push(cutcost[i - 1] + (2 * i - endlist.length) * (endlist[i] - endlist[i - 1]));
let mincost: number = Math.min(...cutcost);
let numsplit: Array<number> = [];
for (let endval of endlist)
numsplit.push(this.maximums.map(el => el > endval ? 1 : 0).reduce((acc, val) => acc + val, 0));
let finalCost: Array<number> = cutcost.map((el, idx) => el + mincost * alpha * numsplit[idx]);
let finalCostIdx: number = Math.min(...finalCost.filter((_, idx) => this.maximums[idx] > maxbegin));
return finalCost.indexOf(finalCostIdx);
}
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}Leaf:\n`;
for (let interval of this.children)
acc += `${tabs}- [${interval.getLowerBound()}, ${interval.getUpperBound()}]\n`;
return acc;
}
}