@tylerbu/sorted-btree-es6
Version:
A sorted list of key-value pairs in a fast, typed in-memory B+ tree with a powerful API.
88 lines (87 loc) • 4.09 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.bulkLoad = bulkLoad;
exports.bulkLoadRoot = bulkLoadRoot;
const b_tree_1 = require("../b+tree");
const shared_1 = require("./shared");
/**
* Loads a B-Tree from a sorted list of entries in bulk. This is faster than inserting
* entries one at a time, and produces a more optimally balanced tree.
* Time and space complexity: O(n).
* @param keys Keys to load, sorted in strictly ascending order.
* @param values Values corresponding to each key.
* @param maxNodeSize The branching factor (maximum node size) for the resulting tree.
* @param compare Function to compare keys.
* @param loadFactor Desired load factor for created leaves. Must be between 0.5 and 1.0.
* @returns A new BTree containing the given entries.
* @throws Error if the entries are not sorted by key in strictly ascending order (duplicates disallowed) or if the load factor is out of the allowed range.
*/
function bulkLoad(keys, values, maxNodeSize, compare, loadFactor = 0.8) {
const root = bulkLoadRoot(keys, values, maxNodeSize, compare, loadFactor);
const tree = new b_tree_1.BTree(undefined, compare, maxNodeSize);
const target = tree;
target._root = root;
return tree;
}
/**
* Bulk loads, returns the root node of the resulting tree.
* @internal
*/
function bulkLoadRoot(keys, values, maxNodeSize, compare, loadFactor = 0.8) {
if (loadFactor < 0.5 || loadFactor > 1.0)
throw new Error("bulkLoad: loadFactor must be between 0.5 and 1.0");
if (keys.length !== values.length)
throw new Error("bulkLoad: keys and values arrays must be the same length");
maxNodeSize = (0, b_tree_1.fixMaxSize)(maxNodeSize);
// Verify keys are sorted
const totalPairs = keys.length;
if (totalPairs > 1) {
let previousKey = keys[0];
for (let i = 1; i < totalPairs; i++) {
const key = keys[i];
if (compare(previousKey, key) >= 0)
throw new Error("bulkLoad: keys must be sorted in strictly ascending order");
previousKey = key;
}
}
// Get ALL the leaf nodes with which the tree will be populated
let currentNodes = [];
(0, shared_1.makeLeavesFrom)(keys, values, maxNodeSize, loadFactor, currentNodes.push.bind(currentNodes));
if (currentNodes.length === 0)
return new b_tree_1.BNode();
const targetNodeSize = Math.ceil(maxNodeSize * loadFactor);
const isExactlyHalf = targetNodeSize === maxNodeSize / 2;
const minSize = Math.floor(maxNodeSize / 2);
for (let nextLevel; currentNodes.length > 1; currentNodes = nextLevel) {
const nodeCount = currentNodes.length;
if (nodeCount <= maxNodeSize && (nodeCount !== maxNodeSize || !isExactlyHalf)) {
currentNodes = [new b_tree_1.BNodeInternal(currentNodes, (0, b_tree_1.sumChildSizes)(currentNodes))];
break;
}
const nextLevelCount = Math.ceil(nodeCount / targetNodeSize);
(0, b_tree_1.check)(nextLevelCount > 1);
nextLevel = new Array(nextLevelCount);
let remainingNodes = nodeCount;
let remainingParents = nextLevelCount;
let childIndex = 0;
for (let i = 0; i < nextLevelCount; i++) {
const chunkSize = Math.ceil(remainingNodes / remainingParents);
const children = new Array(chunkSize);
let size = 0;
for (let j = 0; j < chunkSize; j++) {
const child = currentNodes[childIndex++];
children[j] = child;
size += child.size();
}
remainingNodes -= chunkSize;
remainingParents--;
nextLevel[i] = new b_tree_1.BNodeInternal(children, size);
}
// If last node is underfilled, balance with left sibling
const secondLastNode = nextLevel[nextLevelCount - 2];
const lastNode = nextLevel[nextLevelCount - 1];
while (lastNode.children.length < minSize)
lastNode.takeFromLeft(secondLastNode);
}
return currentNodes[0];
}