UNPKG

binary-type-tree

Version:
601 lines 21.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const basic_node_1 = require("../basic-node"); const bTreeUtils = require("../utils/bTreeUtils"); const tedb_utils_1 = require("tedb-utils"); /** * Used to create an AVL tree with a binary search tree as the root of each Node */ class AVLNode extends basic_node_1.Node { /** * Uses abstract class as base to build off of for this class. * @param options */ constructor(options) { super(options); this.options = options; /** * Used to hold the depth of the tree at this point. * @type {number} */ this.height = 0; } /** * To return this class if this class were to be extended. * @returns {AVLNode} */ returnThisAVL() { return this; } /** * Create a new AVL Node similar to this one except without the key and value * @param options * @returns {AVLNode} */ createSimilar(options) { options.unique = this.unique; this.compareKeys = options.compareKeys || bTreeUtils.defaultCompareKeysFunction; this.checkKeyEquality = options.checkKeyEquality || bTreeUtils.defaultCheckKeyEquality; this.checkValueEquality = options.checkValueEquality || bTreeUtils.defaultCheckValueEquality; return new AVLNode(options); } /** * Create a left child AVL Node with a reference to this parent AVL Node * @param options * @returns {AVLNode} */ createLeftChild(options) { const leftChild = this.createSimilar(options); leftChild.parent = this; this.left = leftChild; return leftChild; } /** * Create a right child AVL Node with a reference to this parent AVL Node * @param options * @returns {AVLNode} */ createRightChild(options) { const rightChild = this.createSimilar(options); rightChild.parent = this; this.right = rightChild; return rightChild; } /** * Calls upon super to check that all basic Node validation is met, along * with also checking the balance and height correctness of the AVL Node. */ checkisAVLT() { super.checkIsNode(); this.checkHeightCorrect(); this.checkBalanceFactors(); } /** * Retrieve a specific Node. * @param key * @returns {any} */ getAVLNodeFromKey(key) { let currentNode = this; while (true) { if (currentNode.compareKeys(key, currentNode.key) === 0) { return currentNode; } else if (currentNode.compareKeys(key, currentNode.key) < 0) { if (currentNode.left) { currentNode = currentNode.left; } else { return null; } } else { if (currentNode.right) { currentNode = currentNode.right; } else { return null; } } } } /** * Insert a key, value pair in the tree while maintaining the AVL tree height constraint * Return a pointer to the root node, which may have changed * @param key * @param value * @returns {AVLNode} * @private */ _insert(key, value) { const insertPath = []; let currentNode = this; // Empty tree, insert as root if (this.key === null) { this.key = key; this.value = value; this.height = 1; return this; } // Insert new leaf at the right place while (true) { // Same key: no change in the tree structure if (currentNode.compareKeys(currentNode.key, key) === 0) { if (currentNode.unique) { throw new Error(`Can't insert key ${key}, it violates the unique constraint: TYPE: uniqueViolated`); } else { currentNode.value = tedb_utils_1.rmArrDups(currentNode.value.concat(value)); } return this; } insertPath.push(currentNode); if (currentNode.compareKeys(key, currentNode.key) < 0) { if (!currentNode.left) { insertPath.push(currentNode.createLeftChild({ key, value })); break; } else { currentNode = currentNode.left; } } else { if (!currentNode.right) { insertPath.push(currentNode.createRightChild({ key, value })); break; } else { currentNode = currentNode.right; } } } return this.rebalanceAlongPath(insertPath); } /** * Delete a key or just a value and return the new root of the tree * @param key * @param value - Need to send the value as an array for comparison. * @returns {any} * @private */ _delete(key, value) { const deletePath = []; const newData = []; let replaceWith; let currentNode = this; // Empty tree if (key === null) { return this; } // Either no match is found and the function will return // from within the loop // Or a match is found and deletePath will contain the path // from the root to the basic-node to delete after the loop while (true) { if (currentNode.compareKeys(key, currentNode.key) === 0) { break; } deletePath.push(currentNode); // Is less go left if (currentNode.compareKeys(key, currentNode.key) < 0) { if (currentNode.left) { currentNode = currentNode.left; } else { return this; // node not found, no modification } } else { if (currentNode.right) { currentNode = currentNode.right; } else { return this; // node not found, no modification } } } // Delete only values (no tree modification) if (currentNode.value.length > 1 && value.length > 0 && (currentNode.value.length !== value.length) && (value !== undefined)) { currentNode.value.forEach((cV) => { value.forEach((v) => { if (!currentNode.checkValueEquality(cV, v)) { if (newData.indexOf(cV) === -1) { newData.push(cV); } } }); }); currentNode.value = newData; return this; } // Delete a whole basic-node // Leaf if (!currentNode.left && !currentNode.right) { // This leaf is also the root if (currentNode === this) { currentNode.key = null; currentNode.value = []; delete currentNode.height; return this; } else { if (currentNode.parent) { if (currentNode.parent.left === currentNode) { currentNode.parent.left = null; } else { currentNode.parent.right = null; } } else { throw new Error(" Cannot Delete Root Node, cannot have node with null parent."); } return this.rebalanceAlongPath(deletePath); } } // Node with only one child if (!currentNode.left || !currentNode.right) { replaceWith = currentNode.left ? currentNode.left : currentNode.right; if (currentNode === this) { if (replaceWith) { replaceWith.parent = null; return replaceWith; } else { throw new Error("Cannot have Node with only one Child and one Null."); } // height of replaceWith is necessarily 1 because the tree was balanced before deletion } else { // With TS upgrade these objects must exist. if (replaceWith && currentNode && currentNode.parent) { if (currentNode.parent.left === currentNode) { currentNode.parent.left = replaceWith; replaceWith.parent = currentNode.parent; } else { currentNode.parent.right = replaceWith; replaceWith.parent = currentNode.parent; } } else { throw new Error("Cannot delete root Node, cannot have child with null parent."); } return this.rebalanceAlongPath(deletePath); } } // Node with two children // Use the in-order predecessor (no need to randomize since we actively re-balance) deletePath.push(currentNode); replaceWith = currentNode.left; // Special case: the in-order predecessor is right below the basic-node to delete if (!replaceWith.right) { currentNode.key = replaceWith.key; currentNode.value = replaceWith.value; currentNode.left = replaceWith.left; if (replaceWith.left) { replaceWith.left.parent = currentNode; } return this.rebalanceAlongPath(deletePath); } // After this loop, replaceWith is the right-most leaf in the left subtree // and deletePath the path from the root (inclusive) to replaceWith (exclusive) while (true) { if (replaceWith.right) { deletePath.push(replaceWith); replaceWith = replaceWith.right; } else { break; } } currentNode.key = replaceWith.key; currentNode.value = replaceWith.value; // With upgrade to TS new check needed because parent // could be null if (replaceWith.parent) { replaceWith.parent.right = replaceWith.left; } else { throw new Error("Cannot have a child with a null parent."); } if (replaceWith.left) { replaceWith.left.parent = replaceWith.parent; } return this.rebalanceAlongPath(deletePath); } /** * Update the key of an index * Use when you change a fieldNames value, you will need to * update all they keys that are indexed. * @param key * @param newKey * @returns {AVLNode} * @private */ _updateKey(key, newKey) { let currentNode = this; // Empty tree // prevent operation of update if key is exact match. if (this.key === null || key === newKey) { return this; } // Cannot insert newKey that matches a another on a unique tree. if (currentNode.compareKeys(currentNode.key, newKey) === 0) { if (currentNode.unique) { throw new Error(`Can't insert key ${newKey}, it violates the unique constraint: TYPE: uniqueViolated`); } } // Either no match is found and the function will return from // within the loop. Or a match is found and updatePath will // contain the path from the root to the AVLNode to be updated // after the loop while (true) { // keys match. this is the key to update if (currentNode.compareKeys(key, currentNode.key) === 0) { break; } // Is less go left. given key < this.key if (currentNode.compareKeys(key, currentNode.key) < 0) { if (currentNode.left) { currentNode = currentNode.left; } else { return this; // node not found, no modification } } else { if (currentNode.right) { currentNode = currentNode.right; } else { return this; // node not found, no modification } } } // update only the key if (currentNode.checkKeyEquality(key, currentNode.key)) { // If unique simply update the key if (currentNode.unique) { const matchedNode = this.getAVLNodeFromKey(newKey); if (matchedNode) { throw new Error(`Cannot update key:${key} with newKey:${newKey}, it violates the unique constraint.`); } else { const currentValue = currentNode.value; this._delete(currentNode.key, currentNode.value); return this._insert(newKey, currentValue); } } else { const matchedNode = this.getAVLNodeFromKey(newKey); // node was found if (matchedNode) { matchedNode.value = tedb_utils_1.rmArrDups(matchedNode.value.concat(currentNode.value)); return this._delete(currentNode.key, currentNode.value); } else { const currentValue = currentNode.value; this._delete(currentNode.key, currentNode.value); return this._insert(newKey, currentValue); } } } return this; // node not found no modification } /** * Re-balance the tree along the given path. The path is given reversed * Returns the new root of the tree * Of course, the first element of the path must be the root of the tree * @param path * @returns {AVLNode} */ rebalanceAlongPath(path) { let newRoot = this; let rotated; if (this.key === null) { delete this.height; return this; } // Re-balance the tree and update all heights for (let i = path.length - 1; i >= 0; i--) { const selfOfLoop = path[i]; const arg1 = selfOfLoop.left ? selfOfLoop.left.height : 0; const arg2 = selfOfLoop.right ? selfOfLoop.right.height : 0; selfOfLoop.height = 1 + Math.max(arg1, arg2); if (selfOfLoop.balanceFactor() > 1) { rotated = selfOfLoop.rightTooSmall(); if (i === 0) { newRoot = rotated; } } if (selfOfLoop.balanceFactor() < -1) { rotated = selfOfLoop.leftTooSmall(); if (i === 0) { newRoot = rotated; } } } return newRoot; } /** * Check the recorded height for this AVL Node and all children. * Throws an error if one height does not match. */ checkHeightCorrect() { let leftH; let rightH; if (this.key === null) { return; } if (this.left && this.left.height === undefined) { throw new Error(`Undefined height for AVLNode ${this.left.key}`); } if (this.right && this.right.height === undefined) { throw new Error(`Undefined height for AVLNode ${this.right.key}`); } if (this.height === undefined) { throw new Error(`Undefined height for AVLNode ${this.key}`); } leftH = this.left ? this.left.height : 0; rightH = this.right ? this.right.height : 0; if (this.height !== 1 + Math.max(leftH, rightH)) { throw new Error(`Height constraint failed for AVLNode ${this.key}`); } if (this.left) { this.left.checkHeightCorrect(); } if (this.right) { this.right.checkHeightCorrect(); } } /** * Returns the balance factor. * @returns {number} */ balanceFactor() { const leftH = this.left ? this.left.height : 0; const rightH = this.right ? this.right.height : 0; return leftH - rightH; } /** * Check that the balance factors are between -1 and 1. Otherwise throw an error. */ checkBalanceFactors() { if (Math.abs(this.balanceFactor()) > 1) { throw new Error(`Tree is unbalanced at AVLNode ${this.key}`); } if (this.left) { this.left.checkBalanceFactors(); } if (this.right) { this.right.checkBalanceFactors(); } } /** * Perform a right rotation of the tree if possible * and return the root of the resulting tree * The resulting tree's nodes' heights are also updated * @returns {AVLNode} */ rightRotation() { const thisLeft = this.left; const currentNode = this; let thisLeftsRight; let thisLeftLeftH; let thisLeftRightH; let currentNodeRightH; // No change if (!thisLeft) { return this; } thisLeftsRight = thisLeft.right; // Alter tree structure, actual right rotation if (currentNode.parent) { thisLeft.parent = currentNode.parent; if (currentNode.parent.left === currentNode) { currentNode.parent.left = thisLeft; } else { currentNode.parent.right = thisLeft; } } else { thisLeft.parent = null; } thisLeft.right = currentNode; currentNode.parent = thisLeft; currentNode.left = thisLeftsRight; if (thisLeftsRight) { thisLeftsRight.parent = currentNode; } // Update heights thisLeftLeftH = thisLeft.left ? thisLeft.left.height : 0; thisLeftRightH = thisLeftsRight ? thisLeftsRight.height : 0; currentNodeRightH = currentNode.right ? currentNode.right.height : 0; currentNode.height = Math.max(thisLeftRightH, currentNodeRightH) + 1; thisLeft.height = Math.max(thisLeftLeftH, currentNode.height) + 1; return thisLeft; } /** * Perform a left rotation of the tree if possible * and return the root of the resulting tree * The resulting tree's nodes' heights are also updated * @returns {AVLNode} */ leftRotation() { const thisRight = this.right; const currentNode = this; let thisRightsLeft; let currentNodeLeftH; let thisRightsLeftH; let thisRightRightH; // No Change if (!thisRight) { return this; } thisRightsLeft = thisRight.left; // save this Rights left // Alter tree structure, actual left rotation if (currentNode.parent) { thisRight.parent = currentNode.parent; if (currentNode.parent.left === currentNode) { currentNode.parent.left = thisRight; } else { currentNode.parent.right = thisRight; } } else { thisRight.parent = null; } thisRight.left = currentNode; currentNode.parent = thisRight; currentNode.right = thisRightsLeft; if (thisRightsLeft) { thisRightsLeft.parent = currentNode; } // Update heights currentNodeLeftH = currentNode.left ? currentNode.left.height : 0; thisRightsLeftH = thisRightsLeft ? thisRightsLeft.height : 0; thisRightRightH = thisRight.right ? thisRight.right.height : 0; currentNode.height = Math.max(currentNodeLeftH, thisRightsLeftH) + 1; thisRight.height = Math.max(thisRightRightH, currentNode.height) + 1; return thisRight; } /** * Modify the tree if its right subtree is too small compared to the left. * Return the new root if any. * @returns {AVLNode} */ rightTooSmall() { // Right is not too small, don't change if (this.balanceFactor() <= 1) { return this; } if (this.left) { if (this.left.balanceFactor() < 0) { this.left.leftRotation(); } } return this.rightRotation(); } /** * Modify the tree if its left subtree is too small compared to the right. * Return the new root if any. * @returns {AVLNode} */ leftTooSmall() { // Left is not too small, don't change if (this.balanceFactor() >= -1) { return this; } if (this.right) { if (this.right.balanceFactor() > 0) { this.right.rightRotation(); } } return this.leftRotation(); } } exports.AVLNode = AVLNode; //# sourceMappingURL=avl.js.map