UNPKG

@subspace/red-black-tree

Version:

This library provides advanced implementation of Red-black tree, which is a kind of self-balancing binary search tree for JavaScript

640 lines 23.9 kB
(function (factory) { if (typeof module === "object" && typeof module.exports === "object") { var v = factory(require, exports); if (v !== undefined) module.exports = v; } else if (typeof define === "function" && define.amd) { define(["require", "exports", "./RuntimeError"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const RuntimeError_1 = require("./RuntimeError"); function isNullOrBlack(node) { return (!node || !node.getIsRed()); } function isRed(node) { return Boolean(node && node.getIsRed()); } function fixTree(nodeManager, path) { while (path.length) { const targetNode = path.pop(); if (!targetNode) { throw new RuntimeError_1.RuntimeError("Can't fix path without target node, this should never happen"); } const parent = path.pop(); // `targetNode` is root, nothing left to do if (!parent) { return; } // No conflict, nothing left to do if (!parent.getIsRed()) { return; } const grandParent = path.pop(); if (!grandParent) { parent.setIsRed(false); return; } const grandParentLeft = grandParent.getLeft(); const grandParentRight = grandParent.getRight(); const uncle = grandParentLeft === parent ? grandParentRight : grandParentLeft; // Here we handle `null` as black `nil` node implicitly, since we do not create `nil` nodes as such if (uncle && uncle.getIsRed()) { parent.setIsRed(!parent.getIsRed()); grandParent.setIsRed(grandParent === nodeManager.getRoot() ? false : !grandParent.getIsRed()); uncle.setIsRed(false); path.push(grandParent); continue; } // Triangle cases if (parent.getLeft() === targetNode && grandParentRight === parent) { rotateRight(nodeManager, parent, grandParent); path.push(grandParent, targetNode, parent); continue; } else if (parent.getRight() === targetNode && grandParentLeft === parent) { rotateLeft(nodeManager, parent, grandParent); path.push(grandParent, targetNode, parent); continue; } const grandGrandParent = path.pop() || null; // Line cases if (parent.getLeft() === targetNode) { rotateRight(nodeManager, grandParent, grandGrandParent); } else { rotateLeft(nodeManager, grandParent, grandGrandParent); } parent.setIsRed(!parent.getIsRed()); grandParent.setIsRed(!grandParent.getIsRed()); break; } } exports.fixTree = fixTree; /** * @param nodeManager * @param rotationNode * @param parent `null` if `rotationNode` is root */ function rotateLeft(nodeManager, rotationNode, parent) { const originalRightNode = rotationNode.getRight(); if (!originalRightNode) { throw new RuntimeError_1.RuntimeError('Right children of rotation node is null, this should never happen'); } rotationNode.setRight(originalRightNode.getLeft()); originalRightNode.setLeft(rotationNode); rotateFixParentConnection(nodeManager, rotationNode, originalRightNode, parent); } /** * @param nodeManager * @param rotationNode * @param parent `null` if `rotationNode` is root */ function rotateRight(nodeManager, rotationNode, parent) { const originalLeftNode = rotationNode.getLeft(); if (!originalLeftNode) { throw new RuntimeError_1.RuntimeError('Left children of rotation node is null, this should never happen'); } rotationNode.setLeft(originalLeftNode.getRight()); originalLeftNode.setRight(rotationNode); rotateFixParentConnection(nodeManager, rotationNode, originalLeftNode, parent); } function rotateFixParentConnection(nodeManager, rotationNode, originalNode, parent) { if (parent) { if (parent.getLeft() === rotationNode) { parent.setLeft(originalNode); } else { parent.setRight(originalNode); } } else { nodeManager.setRoot(originalNode); } } function removeNodeImplementation(nodeManager, path) { const nodeToRemove = path.pop(); const parentNode = path.pop() || null; const xPath = path.slice(); const xAndReplacement = determineXAndReplacement(nodeToRemove, parentNode, xPath); const [x, replacement] = xAndReplacement; let replacementParent = xAndReplacement[2]; if (!parentNode) { if (!replacement) { throw new Error('Deleting root mode, but replacement is null, this should never happen'); } nodeManager.setRoot(replacement); } else { if (parentNode.getLeft() === nodeToRemove) { parentNode.setLeft(replacement); } else { parentNode.setRight(replacement); } } if (replacement) { const nodeToRemoveLeft = nodeToRemove.getLeft(); const nodeToRemoveRight = nodeToRemove.getRight(); if (nodeToRemoveRight === replacement) { replacement.setLeft(nodeToRemoveLeft); if (replacement !== x) { replacement.setRight(x); replacementParent = replacement; xPath.pop(); } } else if (nodeToRemoveLeft === replacement) { replacement.setRight(nodeToRemoveRight); if (replacement !== x) { replacement.setLeft(x); replacementParent = replacement; xPath.pop(); } } else { replacement.setLeft(nodeToRemoveLeft); replacement.setRight(nodeToRemoveRight); if (replacementParent) { if (replacementParent.getLeft() === replacement) { replacementParent.setLeft(x); } else { replacementParent.setRight(x); } } } } const nodeToRemoveIsRed = nodeToRemove.getIsRed(); if (nodeToRemoveIsRed && (!replacement || replacement.getIsRed())) { return; } if (nodeToRemoveIsRed && replacement && !replacement.getIsRed()) { replacement.setIsRed(true); handleRemovalCases(nodeManager, x, replacementParent, xPath); return; } if (!nodeToRemoveIsRed && replacement && replacement.getIsRed()) { replacement.setIsRed(false); return; } handleRemovalCases(nodeManager, x, replacementParent, xPath); } exports.removeNodeImplementation = removeNodeImplementation; /** * @param nodeToRemove * @param nodeToRemoveParent * @param xPath * * @return [x, replacement, replacementParent, replacementToTheLeft] */ function determineXAndReplacement(nodeToRemove, nodeToRemoveParent, xPath) { const nodeToRemoveLeft = nodeToRemove.getLeft(); const nodeToRemoveRight = nodeToRemove.getRight(); if (!nodeToRemoveLeft || !nodeToRemoveRight) { const replacement = nodeToRemoveLeft || nodeToRemoveRight; return [ replacement, replacement, replacement ? nodeToRemove : nodeToRemoveParent, Boolean(nodeToRemoveLeft), ]; } let replacement = nodeToRemoveRight; let replacementParent = nodeToRemove; if (nodeToRemoveParent) { xPath.push(nodeToRemoveParent); } const xPathExtra = []; let left = replacement.getLeft(); while (left) { replacementParent = replacement; replacement = left; xPathExtra.push(replacementParent); left = replacement.getLeft(); } xPathExtra.pop(); xPath.push(replacement, ...xPathExtra); return [ replacement.getRight(), replacement, replacementParent, false, ]; } function handleRemovalCases(nodeManager, x, xParent, xPath) { while (true) { if (!xParent) { return; } let xParentLeft = xParent.getLeft(); let xParentRight = xParent.getRight(); if (!xParentLeft && !xParentRight) { xParent.setIsRed(false); return; } // Case 0 if (x && x.getIsRed()) { x.setIsRed(false); return; } let w = xParentLeft === x ? xParentRight : xParentLeft; // Case 1 if ((!x || !x.getIsRed()) && (w && w.getIsRed())) { w.setIsRed(false); xParent.setIsRed(true); const xParentParent = xPath.pop() || null; if (xParentLeft === x) { rotateLeft(nodeManager, xParent, xParentParent); } else { rotateRight(nodeManager, xParent, xParentParent); } xParentLeft = xParent.getLeft(); xParentRight = xParent.getRight(); xPath.push(w); w = xParentLeft === x ? xParentRight : xParentLeft; } let wLeft = w && w.getLeft(); let wRight = w && w.getRight(); // Case 2 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && (isNullOrBlack(wLeft) && isNullOrBlack(wRight))) { w.setIsRed(true); x = xParent; if (x.getIsRed()) { x.setIsRed(false); return; } else { xParent = xPath.pop(); if (!xParent) { return; } continue; } } // Case 3 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && ((xParentLeft === x && isRed(wLeft) && isNullOrBlack(wRight)) || (xParentRight === x && isRed(wRight) && isNullOrBlack(wLeft)))) { if (xParentLeft === x) { const left = wLeft; if (left) { left.setIsRed(false); } } else if (xParentRight === x) { const right = wRight; if (right) { right.setIsRed(false); } } w.setIsRed(true); if (xParentLeft === x) { rotateRight(nodeManager, w, xParent); } else { rotateLeft(nodeManager, w, xParent); } w = xParentLeft === x ? xParentRight : xParentLeft; wLeft = w && w.getLeft(); wRight = w && w.getRight(); } // Case 4 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && ((xParentLeft === x && isRed(wRight)) || (xParentRight === x && isRed(wLeft)))) { w.setIsRed(xParent.getIsRed()); xParent.setIsRed(false); const xParentParent = xPath.pop() || null; if (xParentLeft === x) { const right = wRight; if (right) { right.setIsRed(false); } rotateLeft(nodeManager, xParent, xParentParent); } else if (xParentRight === x) { const left = wLeft; if (left) { left.setIsRed(false); } rotateRight(nodeManager, xParent, xParentParent); } return; } } } // Functions below are exactly the same as above, but use asynchronous node API /** * @param nodeManager * @param rotationNode * @param parent `null` if `rotationNode` is root */ async function rotateLeftAsync(nodeManager, rotationNode, parent) { const originalRightNode = await rotationNode.getRightAsync(); if (!originalRightNode) { throw new RuntimeError_1.RuntimeError('Right children of rotation node is null, this should never happen'); } rotationNode.setRight(await originalRightNode.getLeftAsync()); originalRightNode.setLeft(rotationNode); rotateFixParentConnectionAsync(nodeManager, rotationNode, originalRightNode, parent); } /** * @param nodeManager * @param rotationNode * @param parent `null` if `rotationNode` is root */ async function rotateRightAsync(nodeManager, rotationNode, parent) { const originalLeftNode = await rotationNode.getLeftAsync(); if (!originalLeftNode) { throw new RuntimeError_1.RuntimeError('Left children of rotation node is null, this should never happen'); } rotationNode.setLeft(await originalLeftNode.getRightAsync()); originalLeftNode.setRight(rotationNode); rotateFixParentConnectionAsync(nodeManager, rotationNode, originalLeftNode, parent); } async function rotateFixParentConnectionAsync(nodeManager, rotationNode, originalNode, parent) { if (parent) { if (await parent.getLeftAsync() === rotationNode) { parent.setLeft(originalNode); } else { parent.setRight(originalNode); } } else { nodeManager.setRoot(originalNode); } } async function removeNodeImplementationAsync(nodeManager, path) { const nodeToRemove = path.pop(); const parentNode = path.pop() || null; const xPath = path.slice(); const xAndReplacement = await determineXAndReplacementAsync(nodeToRemove, parentNode, xPath); const [x, replacement] = xAndReplacement; let replacementParent = xAndReplacement[2]; if (!parentNode) { if (!replacement) { throw new Error('Deleting root mode, but replacement is null, this should never happen'); } nodeManager.setRoot(replacement); } else { if (await parentNode.getLeftAsync() === nodeToRemove) { parentNode.setLeft(replacement); } else { parentNode.setRight(replacement); } } if (replacement) { const nodeToRemoveLeft = await nodeToRemove.getLeftAsync(); const nodeToRemoveRight = await nodeToRemove.getRightAsync(); if (nodeToRemoveRight === replacement) { replacement.setLeft(nodeToRemoveLeft); if (replacement !== x) { replacement.setRight(x); replacementParent = replacement; xPath.pop(); } } else if (nodeToRemoveLeft === replacement) { replacement.setRight(nodeToRemoveRight); if (replacement !== x) { replacement.setLeft(x); replacementParent = replacement; xPath.pop(); } } else { replacement.setLeft(nodeToRemoveLeft); replacement.setRight(nodeToRemoveRight); if (replacementParent) { if (await replacementParent.getLeftAsync() === replacement) { replacementParent.setLeft(x); } else { replacementParent.setRight(x); } } } } const nodeToRemoveIsRed = nodeToRemove.getIsRed(); if (nodeToRemoveIsRed && (!replacement || replacement.getIsRed())) { return; } if (nodeToRemoveIsRed && replacement && !replacement.getIsRed()) { replacement.setIsRed(true); await handleRemovalCasesAsync(nodeManager, x, replacementParent, xPath); return; } if (!nodeToRemoveIsRed && replacement && replacement.getIsRed()) { replacement.setIsRed(false); return; } await handleRemovalCasesAsync(nodeManager, x, replacementParent, xPath); } exports.removeNodeImplementationAsync = removeNodeImplementationAsync; /** * @param nodeToRemove * @param nodeToRemoveParent * @param xPath * * @return [x, replacement, replacementParent, replacementToTheLeft] */ async function determineXAndReplacementAsync(nodeToRemove, nodeToRemoveParent, xPath) { const nodeToRemoveLeft = await nodeToRemove.getLeftAsync(); const nodeToRemoveRight = await nodeToRemove.getRightAsync(); if (!nodeToRemoveLeft || !nodeToRemoveRight) { const replacement = nodeToRemoveLeft || nodeToRemoveRight; return [ replacement, replacement, replacement ? nodeToRemove : nodeToRemoveParent, Boolean(nodeToRemoveLeft), ]; } let replacement = nodeToRemoveRight; let replacementParent = nodeToRemove; if (nodeToRemoveParent) { xPath.push(nodeToRemoveParent); } const xPathExtra = []; let left = await replacement.getLeftAsync(); while (left) { replacementParent = replacement; replacement = left; xPathExtra.push(replacementParent); left = await replacement.getLeftAsync(); } xPathExtra.pop(); xPath.push(replacement, ...xPathExtra); return [ await replacement.getRightAsync(), replacement, replacementParent, false, ]; } async function handleRemovalCasesAsync(nodeManager, x, xParent, xPath) { while (true) { if (!xParent) { return; } let xParentLeft = await xParent.getLeftAsync(); let xParentRight = await xParent.getRightAsync(); if (!xParentLeft && !xParentRight) { xParent.setIsRed(false); return; } // Case 0 if (x && x.getIsRed()) { x.setIsRed(false); return; } let w = xParentLeft === x ? xParentRight : xParentLeft; // Case 1 if ((!x || !x.getIsRed()) && (w && w.getIsRed())) { w.setIsRed(false); xParent.setIsRed(true); const xParentParent = xPath.pop() || null; if (xParentLeft === x) { await rotateLeftAsync(nodeManager, xParent, xParentParent); } else { await rotateRightAsync(nodeManager, xParent, xParentParent); } xParentLeft = await xParent.getLeftAsync(); xParentRight = await xParent.getRightAsync(); xPath.push(w); w = xParentLeft === x ? xParentRight : xParentLeft; } let wLeft = w && await w.getLeftAsync(); let wRight = w && await w.getRightAsync(); // Case 2 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && (isNullOrBlack(wLeft) && isNullOrBlack(wRight))) { w.setIsRed(true); x = xParent; if (x.getIsRed()) { x.setIsRed(false); return; } else { xParent = xPath.pop(); if (!xParent) { return; } continue; } } // Case 3 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && ((xParentLeft === x && isRed(wLeft) && isNullOrBlack(wRight)) || (xParentRight === x && isRed(wRight) && isNullOrBlack(wLeft)))) { if (xParentLeft === x) { const left = wLeft; if (left) { left.setIsRed(false); } } else if (xParentRight === x) { const right = wRight; if (right) { right.setIsRed(false); } } w.setIsRed(true); if (xParentLeft === x) { await rotateRightAsync(nodeManager, w, xParent); } else { await rotateLeftAsync(nodeManager, w, xParent); } w = xParentLeft === x ? xParentRight : xParentLeft; wLeft = w && await w.getLeftAsync(); wRight = w && await w.getRightAsync(); } // Case 4 if ((!x || !x.getIsRed()) && w && !w.getIsRed() && ((xParentLeft === x && isRed(wRight)) || (xParentRight === x && isRed(wLeft)))) { w.setIsRed(xParent.getIsRed()); xParent.setIsRed(false); const xParentParent = xPath.pop() || null; if (xParentLeft === x) { const right = wRight; if (right) { right.setIsRed(false); } await rotateLeftAsync(nodeManager, xParent, xParentParent); } else if (xParentRight === x) { const left = wLeft; if (left) { left.setIsRed(false); } await rotateRightAsync(nodeManager, xParent, xParentParent); } return; } } } }); //# sourceMappingURL=RedBlackTreeMechanics.js.map