UNPKG

data-balanced-tree

Version:

Efficient balanced tree implementation in TypeScript

544 lines 18.4 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const print_tree_1 = __importDefault(require("print-tree")); /* * Implementation inspired by http://www.eternallyconfuzzled.com/tuts/datastructures/jsw_tut_rbtree.aspx, * specifically the insert and delete methods. */ function getNodeValue(node, defaultValue = null) { if (node != null) return node.value; return defaultValue; } var BalancedColor; (function (BalancedColor) { BalancedColor[BalancedColor["Red"] = 0] = "Red"; BalancedColor[BalancedColor["Black"] = 1] = "Black"; })(BalancedColor = exports.BalancedColor || (exports.BalancedColor = {})); class BalancedNode { constructor(parent, value) { this.links = [null, null]; this.parent = parent; this.value = value; this.left = null; this.right = null; this.color = BalancedColor.Red; } get left() { return this.links[0]; } get right() { return this.links[1]; } set left(value) { this.links[0] = value; } set right(value) { this.links[1] = value; } get uncle() { if (this.parent == null) { return null; } return this.parent.sibling; } get sibling() { if (this.parent == null) { return null; } if (this.parent.left == this) { return this.parent.right; } else { return this.parent.left; } } get grandparent() { if (this.parent == null) { return null; } return this.parent.parent; } get depth() { return 1 + Math.max(this.right != null ? this.right.depth : 0, this.left != null ? this.left.depth : 0); } static toString(node) { if (node == null) { return "null"; } let color = node.color == BalancedColor.Black ? 'B' : 'R'; return `Node( ${color}, ${node.value.toString()}, ${this.toString(node.left)}, ${this.toString(node.right)} )`; } static getColor(node) { if (node == null) { return BalancedColor.Black; } return node.color; } } exports.BalancedNode = BalancedNode; class BalancedTree { constructor(comparator = exports.DefaultComparators.default) { this.size = 0; this.comparator = comparator; this.root = null; } get depth() { if (this.root == null) { return 0; } return this.root.depth; } isRed(node) { return node != null && node.color == BalancedColor.Red; } isBlack(node) { return !this.isRed(node); } rotateLeft(root) { const save = root.right; root.right = save.left; if (save.left) save.left.parent = root; save.left = root; save.parent = root.parent; root.parent = save; root.color = BalancedColor.Red; save.color = BalancedColor.Black; return save; } rotateRightLeft(root) { root.right = this.rotateRight(root.right); return this.rotateLeft(root); } rotateRight(root) { const save = root.left; root.left = save.right; if (save.right) save.right.parent = root; save.right = root; save.parent = root.parent; root.parent = save; root.color = BalancedColor.Red; save.color = BalancedColor.Black; return save; } rotateLeftRight(root) { root.left = this.rotateLeft(root.left); return this.rotateRight(root); } insertRecursive(root, value) { if (root == null) { root = new BalancedNode(root, value); return root; } else if (this.comparator(root.value, value) > 0) { root.left = this.insertRecursive(root.left, value); root.left.parent = root; if (this.isRed(root.left)) { if (this.isRed(root.right)) { root.color = BalancedColor.Red; root.left.color = BalancedColor.Black; root.right.color = BalancedColor.Black; } else { if (this.isRed(root.left.left)) { root = this.rotateRight(root); } else if (this.isRed(root.left.right)) { root = this.rotateLeftRight(root); } } } } else { root.right = this.insertRecursive(root.right, value); root.right.parent = root; if (this.isRed(root.right)) { if (this.isRed(root.left)) { root.color = BalancedColor.Red; root.left.color = BalancedColor.Black; root.right.color = BalancedColor.Black; } else { if (this.isRed(root.right.right)) { root = this.rotateLeft(root); } else if (this.isRed(root.right.left)) { root = this.rotateRightLeft(root); } } } } return root; } insert(value) { this.size++; this.root = this.insertRecursive(this.root, value); if (this.root && this.root.left) this.root.left.parent = this.root; if (this.root && this.root.right) this.root.right.parent = this.root; this.root.color = BalancedColor.Red; } rotate(node, dir) { if (dir == 0) { return this.rotateLeft(node); } else { return this.rotateRight(node); } } rotateDouble(node, dir) { if (dir == 0) { return this.rotateRightLeft(node); } else { return this.rotateLeftRight(node); } } delete(value) { if (this.root != null) { const head = new BalancedNode(null, null); let q, p, g; let f; let dir = 1; let ord; /* Set up helpers */ q = head; g = p = null; q.links[1] = this.root; /* Search and push a red down */ while (q.links[dir] != null) { let last = dir; /* Update helpers */ g = p, p = q; q = q.links[dir]; ord = this.comparator(q.value, value); dir = ord < 0 ? 1 : 0; /* Save found node */ if (ord == 0) { f = q; this.size--; } /* Push the red node down */ if (this.isBlack(q) && this.isBlack(q.links[dir])) { if (this.isRed(q.links[1 - dir])) { p = p.links[last] = this.rotate(q, dir); } else if (this.isBlack(q.links[1 - dir])) { const s = p.links[1 - last]; // struct jsw_node *s = p->link[!last]; if (s != null) { if (this.isBlack(s.links[1 - last]) && this.isBlack(s.links[last])) { /* Color flip */ p.color = BalancedColor.Black; s.color = BalancedColor.Red; q.color = BalancedColor.Red; } else { let dir2 = g.links[1] == p ? 1 : 0; if (this.isRed(s.links[last])) { g.links[dir2] = this.rotateDouble(p, last); // jsw_double(p, last); } else if (this.isRed(s.links[1 - last])) { g.links[dir2] = this.rotate(p, last); } /* Ensure correct coloring */ q.color = g.links[dir2].color = BalancedColor.Red; g.links[dir2].links[0].color = BalancedColor.Black; g.links[dir2].links[1].color = BalancedColor.Black; } } } } } /* Replace and remove if found */ if (f != null) { f.value = q.value; p.links[p.links[1] == q ? 1 : 0] = q.links[q.links[0] == null ? 1 : 0]; } /* Update root and make it black */ this.root = head.links[1]; if (this.root != null) { this.root.color = BalancedColor.Black; } } } clear() { this.root = null; this.size = 0; } find(value) { let node = this.root; while (node != null) { const order = this.comparator(node.value, value); if (order == 0) { return node; } else if (order > 0) { node = node.left; } else { node = node.right; } } return node; } previous(node) { // If there is a child on the left, we need to get the rightmost child of the left child if (node.left) { node = node.left; while (node.right != null) { node = node.right; } return node; } while (node.parent != null && node.parent.left == node) { node = node.parent; if (node.parent == null) { return null; } } return node.parent; } biggestNodeUnder(upperBound, included = false) { let biggest = null; let node = this.root; while (node != null) { const order = this.comparator(node.value, upperBound); if (order == 0) { if (included) { return node; } else { return this.previous(node); } // node.value < bound } else if (order < 0) { biggest = node; node = node.right; // node.value > bound } else { node = node.left; } } return biggest; } biggestUnder(upperBound, included = false, defaultValue = null) { return getNodeValue(this.biggestNodeUnder(upperBound, included), defaultValue); } smallestNodeAbove(lowerBound, included = false) { let smallest = null; let node = this.root; while (node != null) { const order = this.comparator(node.value, lowerBound); if (order == 0) { if (included) { return node; } else { return this.next(node); } // node.value < bound } else if (order < 0) { node = node.right; // node.value > bound } else { smallest = node; node = node.left; } } return smallest; } smallestAbove(lowerBound, included = false, defaultValue = null) { return getNodeValue(this.smallestNodeAbove(lowerBound, included), defaultValue); } smallestNodeUnder(upperBound, included = false) { const smallest = this.firstNode(); if (smallest) { const order = this.comparator(smallest.value, upperBound); if ((order == 0 && included) || order < 0) { return smallest; } } return null; } smallestUnder(upperBound, included = false, defaultValue = null) { return getNodeValue(this.smallestNodeUnder(upperBound, included), defaultValue); } biggestNodeAbove(lowerBound, included = false) { const biggest = this.lastNode(); if (biggest) { const order = this.comparator(biggest.value, lowerBound); if ((order == 0 && included) || order > 0) { return biggest; } } return null; } biggestAbove(lowerBound, included = false, defaultValue = null) { return getNodeValue(this.biggestNodeAbove(lowerBound, included), defaultValue); } closestNodes(bound) { return [this.biggestNodeUnder(bound, false), this.smallestNodeAbove(bound, false)]; } closest(bound) { return [this.biggestUnder(bound, false), this.smallestAbove(bound, false)]; } next(node) { // If there is a child on the left, we need to get the rightmost child of the left child if (node.right) { node = node.right; while (node.left != null) { node = node.left; } return node; } while (node.parent != null && node.parent.right == node) { node = node.parent; if (node.parent == null) { return null; } } return node.parent; } firstNode() { let node = this.root; while (node != null && node.left != null) { node = node.left; } return node; } first(defaultValue = null) { return getNodeValue(this.firstNode(), defaultValue); } lastNode() { let node = this.root; while (node != null && node.right != null) { node = node.right; } return node; } last(defaultValue = null) { return getNodeValue(this.lastNode(), defaultValue); } *[Symbol.iterator]() { for (let node of this.nodes()) { yield node.value; } } *nodes() { let node = this.firstNode(); while (node != null) { yield node; node = this.next(node); } } *nodesBetween(lower, upper, included = true) { if (this.comparator(lower, upper) < 0) { let lowerNode = this.smallestNodeAbove(lower, true); while (lowerNode != null) { const orderLower = this.comparator(lower, lowerNode.value); const orderUpper = this.comparator(upper, lowerNode.value); if (orderLower === 0 && included) { yield lowerNode; } else if (orderUpper == 0 && included) { yield lowerNode; } else if (orderUpper < 0) { break; } else if (orderLower < 0 && orderUpper > 0) { yield lowerNode; } lowerNode = this.next(lowerNode); } } else { [lower, upper] = [upper, lower]; let upperNode = this.biggestNodeUnder(upper, true); while (upperNode != null) { const orderLower = this.comparator(lower, upperNode.value); const orderUpper = this.comparator(upper, upperNode.value); if (orderLower === 0 && included) { yield upperNode; } else if (orderUpper == 0 && included) { yield upperNode; } else if (orderLower < 0 && orderUpper > 0) { yield upperNode; } else if (orderLower > 0) { break; } upperNode = this.previous(upperNode); } } } *between(lower, upper, included = true) { for (let node of this.nodesBetween(lower, upper, included)) yield node.value; } toArray() { return Array.from(this); } debugPrint(node) { print_tree_1.default(node || this.root, (n) => n ? '' + (n.color == BalancedColor.Red ? 'R' : 'B') + ' ' + n.value + ' ' + (n.parent ? n.parent.value : 'null') : '(null)', (n) => n ? [n.left, n.right] : []); } print(node) { print_tree_1.default(node || this.root, (n) => n ? '' + n.value : '(null)', (n) => n && (n.left || n.right) ? [n.left, n.right] : []); } } exports.BalancedTree = BalancedTree; const toString = (obj) => { //ECMA specification: http://www.ecma-international.org/ecma-262/6.0/#sec-tostring if (obj === null) { return "null"; } if (typeof obj === "boolean" || typeof obj === "number") { return (obj).toString(); } if (typeof obj === "string") { return obj; } if (typeof obj === "symbol") { throw new TypeError(); } return obj.toString(); }; exports.DefaultComparators = { default: (a, b) => { if (a === undefined && b === undefined) { return 0; } if (a === undefined) { return 1; } if (b === undefined) { return -1; } const xString = toString(a); const yString = toString(b); if (xString < yString) { return -1; } if (xString > yString) { return 1; } }, numbers: (a, b) => a - b, numbersReversed: (a, b) => b - a, string: (a, b) => a.localeCompare(b), stringReversed: (a, b) => a.localeCompare(b) * -1, }; //# sourceMappingURL=BalancedTree.js.map