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

215 lines 8.91 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", "./RedBlackTreeMechanics"], factory); } })(function (require, exports) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const RedBlackTreeMechanics_1 = require("./RedBlackTreeMechanics"); class TreeAsync { constructor(nodeManager) { this.nodeManager = nodeManager; } /** * Add nodes to a tree one by one (for incremental updates) * * @param key A key to be indexed, e.g. a 32 byte piece id * @param value Value to be associated with a key */ async addNode(key, value) { await this.nodeManager.writeTransaction(() => { return this.addNodeInternal(key, value); }); this.nodeManager.cleanup(); } /** * Remove a node from the tree * * @param key A key to be removed, e.g. a 32 byte piece id */ async removeNode(key) { await this.nodeManager.writeTransaction(() => { return this.removeNodeInternal(key); }); this.nodeManager.cleanup(); } /** * Get the node value by target key * * @param targetKey * * @return */ async getNodeValue(targetKey) { const result = await 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 */ async getClosestNode(targetKey) { const result = await this.nodeManager.readTransaction(() => { return this.getClosestNodeInternal(targetKey); }); this.nodeManager.cleanup(); return result; } async addNodeInternal(key, value) { const nodeManager = this.nodeManager; const nodeToInsert = await nodeManager.addNodeAsync(key, value); const root = await nodeManager.getRootAsync(); if (!root) { nodeToInsert.setIsRed(false); nodeManager.setRoot(nodeToInsert); } else { let currentNode = root; const path = []; while (true) { path.push(currentNode); // Force reading both children, we may need them during tree fixing process and unless they are in cache, `getLeft()` and `getRight()` methods // will fail for `INodeAsync` const left = await currentNode.getLeftAsync(); const right = await currentNode.getRightAsync(); switch (nodeManager.compare(nodeToInsert.getKey(), currentNode.getKey())) { case -1: if (left) { currentNode = left; break; } else { currentNode.setLeft(nodeToInsert); path.push(nodeToInsert); RedBlackTreeMechanics_1.fixTree(this.nodeManager, path); return; } case 1: 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; } } } } async removeNodeInternal(key) { const nodeManager = this.nodeManager; const root = await nodeManager.getRootAsync(); if (!root) { throw new Error("Tree is empty, nothing to delete"); } if (!await root.getLeftAsync() && !await root.getRightAsync()) { nodeManager.setRoot(null); return; } let currentNode = root; const path = []; while (true) { path.push(currentNode); switch (nodeManager.compare(key, currentNode.getKey())) { case -1: const left = await currentNode.getLeftAsync(); if (left) { currentNode = left; break; } else { throw new Error("Can't delete a key, it doesn't exist"); } case 1: const right = await currentNode.getRightAsync(); 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; } await RedBlackTreeMechanics_1.removeNodeImplementationAsync(this.nodeManager, path); await nodeManager.removeNodeAsync(currentNode); return; } } } async getClosestNodeInternal(targetKey) { const nodeManager = this.nodeManager; let currentNode = await nodeManager.getRootAsync(); if (!currentNode) { return null; } const path = []; while (true) { const key = currentNode.getKey(); path.push(currentNode); switch (nodeManager.compare(targetKey, key)) { case -1: // TypeScript fails to infer type, so have to specify it explicitly const left = await currentNode.getLeftAsync(); if (left) { currentNode = left; break; } else { const closestNode = this.pickClosestNode(path, targetKey); return [closestNode.getKey(), await closestNode.getValueAsync()]; } case 1: // TypeScript fails to infer type, so have to specify it explicitly const right = await currentNode.getRightAsync(); if (right) { currentNode = right; break; } else { const closestNode = this.pickClosestNode(path, targetKey); return [closestNode.getKey(), await closestNode.getValueAsync()]; } default: return [key, await currentNode.getValueAsync()]; } } } 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.TreeAsync = TreeAsync; }); //# sourceMappingURL=TreeAsync.js.map