@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
211 lines • 8.16 kB
JavaScript
(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", "./RedBlackTreeMechanics"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const RedBlackTreeMechanics_1 = require("./RedBlackTreeMechanics");
/**
* Resources used to write this:
* * https://www.youtube.com/playlist?list=PL9xmBV_5YoZNqDI8qfOZgzbqahCUmUEin
* * https://www.youtube.com/watch?v=YCo2-H2CL6Q
* * https://www.youtube.com/watch?v=eO3GzpCCUSg
*/
class Tree {
constructor(nodeManager) {
this.nodeManager = nodeManager;
}
/**
* Add nodes to a tree one by one (for incremental updates)
*
* @param key
* @param value Value to be associated with a key
*/
addNode(key, value) {
this.addNodeInternal(key, value);
this.nodeManager.cleanup();
}
/**
* Remove a node from the tree
*
* @param key
*/
removeNode(key) {
this.removeNodeInternal(key);
this.nodeManager.cleanup();
}
/**
* Get the node value by target key
*
* @param targetKey
*
* @return
*/
getNodeValue(targetKey) {
const result = this.getClosestNodeInternal(targetKey);
if (result && this.nodeManager.compare(result[0], targetKey) === 0) {
return result[1];
}
return null;
}
/**
* Get the closest node key/value in a tree to a given target key
*
* @param targetKey
*
* @return The closest key and its value to the challenge or `null` if no nodes are available
*/
getClosestNode(targetKey) {
const result = this.getClosestNodeInternal(targetKey);
this.nodeManager.cleanup();
return result;
}
addNodeInternal(key, value) {
const nodeManager = this.nodeManager;
const nodeToInsert = nodeManager.addNode(key, value);
const root = nodeManager.getRoot();
if (!root) {
nodeToInsert.setIsRed(false);
nodeManager.setRoot(nodeToInsert);
}
else {
let currentNode = root;
const path = [];
while (true) {
path.push(currentNode);
switch (nodeManager.compare(nodeToInsert.getKey(), currentNode.getKey())) {
case -1:
const left = currentNode.getLeft();
if (left) {
currentNode = left;
break;
}
else {
currentNode.setLeft(nodeToInsert);
path.push(nodeToInsert);
RedBlackTreeMechanics_1.fixTree(this.nodeManager, path);
return;
}
case 1:
const right = currentNode.getRight();
if (right) {
currentNode = right;
break;
}
else {
currentNode.setRight(nodeToInsert);
path.push(nodeToInsert);
RedBlackTreeMechanics_1.fixTree(this.nodeManager, path);
return;
}
default:
// We do not insert the same key again
return;
}
}
}
}
removeNodeInternal(key) {
const nodeManager = this.nodeManager;
const root = nodeManager.getRoot();
if (!root) {
throw new Error("Tree is empty, nothing to delete");
}
if (!root.getLeft() && !root.getRight()) {
nodeManager.setRoot(null);
return;
}
let currentNode = root;
const path = [];
while (true) {
path.push(currentNode);
switch (nodeManager.compare(key, currentNode.getKey())) {
case -1:
const left = currentNode.getLeft();
if (left) {
currentNode = left;
break;
}
else {
throw new Error("Can't delete a key, it doesn't exist");
}
case 1:
const right = currentNode.getRight();
if (right) {
currentNode = right;
break;
}
else {
throw new Error("Can't delete a key, it doesn't exist");
}
default:
if (currentNode === root && !root.getLeft() && !root.getRight()) {
nodeManager.setRoot(null);
return;
}
RedBlackTreeMechanics_1.removeNodeImplementation(this.nodeManager, path);
nodeManager.removeNode(currentNode);
return;
}
}
}
getClosestNodeInternal(targetKey) {
const nodeManager = this.nodeManager;
let currentNode = nodeManager.getRoot();
if (!currentNode) {
return null;
}
const path = [];
while (true) {
const key = currentNode.getKey();
path.push(currentNode);
switch (nodeManager.compare(targetKey, key)) {
case -1:
const left = currentNode.getLeft();
if (left) {
currentNode = left;
break;
}
else {
const closestNode = this.pickClosestNode(path, targetKey);
return [closestNode.getKey(), closestNode.getValue()];
}
case 1:
const right = currentNode.getRight();
if (right) {
currentNode = right;
break;
}
else {
const closestNode = this.pickClosestNode(path, targetKey);
return [closestNode.getKey(), closestNode.getValue()];
}
default:
return [key, currentNode.getValue()];
}
}
}
pickClosestNode(nodes, targetKey) {
const nodeManager = this.nodeManager;
const distances = new Map();
for (const node of nodes) {
distances.set(node, nodeManager.distance(node.getKey(), targetKey));
}
return nodes.sort((nodeA, nodeB) => {
const distanceA = distances.get(nodeA);
const distanceB = distances.get(nodeB);
if (distanceA === distanceB) {
return 0;
}
return distanceA < distanceB ? -1 : 1;
})[0];
}
}
exports.Tree = Tree;
});
//# sourceMappingURL=Tree.js.map