data-balanced-tree
Version:
Efficient balanced tree implementation in TypeScript
544 lines • 18.4 kB
JavaScript
"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