UNPKG

@jymfony/datastructure

Version:

Jymfony Data Structures

404 lines (349 loc) 10.2 kB
const GenericCollectionTrait = require('./Traits/GenericCollectionTrait'); // Integer compare function const compare_func = (a, b) => { if (a === b) { return 0; } else if (a > b) { return 1; } return -1; }; // Internal nodes: only use key and next // External nodes: only use key and value class Entry { /** * @param {*} key * @param {*} val * @param {Node} next */ constructor(key, val, next) { this.key = key; this.val = val; /** * Helper field to iterate over entries. * * @type {Node} */ this.next = next; } } // Helper B-tree node data type class Node { // Create a node with k children constructor(k) { this.m = k; /** * @type {[Entry]} */ this.children = []; } } /** * BTree */ class BTree extends mix(Object, GenericCollectionTrait) { /** * Comparison function can be defined passing it to cmp_function parameter. * The function should return 0 if elements are equals, 1 if the first argument * is greater of the second, -1 if is lesser. * * @param {Function} cmp_function */ constructor(cmp_function = compare_func) { super(); this.clear(); /** * The key compare function. * * @type {Function} * * @private */ this._cmp_function = cmp_function; } /** * Empties the tree. */ clear() { /** * Root node. * * @type {Node} * * @private */ this._root = new Node(0); /** * The height of the BTree. * * @type {int} * * @private */ this._height = 0; /** * The number of nodes in the BTree. * * @type {int} * * @private */ this._length = 0; } /** * The number of elements into the tree. * * @returns {int} */ get length() { return this._length; } /** * Returns the height of this BTree (for debugging). * * @returns {int} */ get height() { return this._height; } /** * Copies the tree. * * @returns {BTree} */ copy() { const cloned = new BTree(this._cmp_function); cloned._length = this._length; cloned._height = this._height; /** * @param {Node} node */ const cloneNode = node => { const cloned = new Node(node.m); cloned.children = [ ...node.children ]; for (let i = 0; i < cloned.children.length; i++) { const entry = cloned.children[i]; cloned.children[i] = new Entry(entry.key, entry.val, entry.next ? cloneNode(entry.next) : undefined); } return cloned; }; cloned._root = cloneNode(this._root); return cloned; } /** * Search an entry. * Returns a key-value pair with the exact key if found or: * * if comparison is set to COMPARISON_LESSER, the nearest lesser key. * * if comparison is set to COMPARISON_GREATER, the nearest greater key. * * undefined otherwise * * @param {*} key * @param {int} comparison * * @returns {Array} Gets a key-value pair if found or undefined */ search(key, comparison = BTree.COMPARISON_EQUAL) { const lt = (a, b) => 0 > this._cmp_function(a, b); const search = (node, key, height) => { const children = node.children; let nearest = undefined; if (0 === height) { // External node for (let j = 0; j < node.m; j++) { const compare = this._cmp_function(key, children[j].key); if (0 === compare) { return children[j]; } else if (BTree.COMPARISON_LESSER === comparison && 0 < compare) { nearest = children[j]; } else if (BTree.COMPARISON_GREATER === comparison && 0 > compare) { return children[j]; } } } else { // Internal node for (let j = 0; j < node.m; j++) { if (j + 1 === node.m || lt(key, children[j + 1].key)) { const result = search(children[j].next, key, height - 1); if (undefined !== result) { return result; } } } } return nearest; }; let result = search(this._root, key, this._height); if (undefined !== result) { result = [ result.key, result.val ]; } return result; } /** * Gets a value. * * @param {*} key * * @returns {*} */ get(key) { const found = this.search(key); if (undefined === found) { return found; } return found[1]; } /** * Inserts the key-value pair into the symbol table, overwriting the old value * with the new value if the key is already in the symbol table. * * @param {*} key * @param {*} val * * @throws {InvalidArgumentException} if key is null or undefined */ push(key, val) { const lt = (a, b) => 0 > this._cmp_function(a, b); /** * Splits a node. * * @param {Node} root * * @returns {Node} * * @private */ const split = (root) => { const t = new Node(2); root.m = 2; for (let j = 0; 2 > j; j++){ t.children[j] = root.children[2 + j]; } return t; }; /** * Insert/replace a node. * * @param {Node} root * @param {*} key * @param {*} val * @param {int} height * * @returns {undefined|boolean|Node} * * @private */ const insert = (root, key, val, height) => { let j; const newEntry = new Entry(key, val, undefined); if (0 === height) { // External node for (j = 0; j < root.m; j++) { const compare = this._cmp_function(key, root.children[j].key); if (0 === compare) { root.children[j].val = val; return true; } if (0 > compare) { break; } } } else { // Internal node for (j = 0; j < root.m; j++) { if ((j+1 === root.m) || lt(key, root.children[j+1].key)) { const u = insert(root.children[j++].next, key, val, height-1); if (! u || true === u) { return u; } newEntry.key = u.children[0].key; newEntry.next = u; break; } } } for (let i = root.m; i > j; i--) { root.children[i] = root.children[i - 1]; } root.children[j] = newEntry; root.m++; if (4 > root.m) { return undefined; } return split(root); }; if (null === key || key === undefined) { throw new InvalidArgumentException('Key cannot be null or undefined'); } const u = insert(this._root, key, val, this._height); if (true === u) { return; } this._length++; if (! u) { return; } // Need to split root const t = new Node(2); t.children[0] = new Entry(this._root.children[0].key, undefined, this._root); t.children[1] = new Entry(u.children[0].key, undefined, u); this._root = t; this._height++; } /** * Removes an entry by key. * * @param {*} key */ remove(key) { const eq = (a, b) => 0 === this._cmp_function(a, b); const lt = (a, b) => 0 > this._cmp_function(a, b); const search = (node, key, height) => { const children = node.children; if (0 === height) { // External node for (let j = 0; j < node.m; j++) { if (eq(key, children[j].key)) { this._length--; node.m--; delete children[j]; return; } } } else { // Internal node for (let j = 0; j < node.m; j++) { if (j + 1 === node.m || lt(key, children[j + 1].key)) { return search(children[j].next, key, height - 1); } } } }; search(this._root, key, this._height); } /** * Gets an element array copy (ordered) * * @returns {Array} */ toArray() { return Array.from(this[Symbol.iterator]()); } /** * Iterator (used in for..of loops) * * @returns {Generator} */ * [Symbol.iterator]() { const generator = function * (h, ht) { const children = h.children; if (0 === ht) { for (let j = 0; j < h.m; j++) { yield [ children[j].key, children[j].val ]; } } else { for (let j = 0; j < h.m; j++) { yield * generator(children[j].next, ht-1); } } }; yield * generator(this._root, this._height); } } BTree.COMPARISON_EQUAL = 0; BTree.COMPARISON_LESSER = -1; BTree.COMPARISON_GREATER = 1; globalThis.BTree = BTree;