This library provides advanced implementation of Red-black tree, which is a kind of self-balancing binary search tree for JavaScript
npm install @subspace/red-black-treeSource code is written in TypeScript and should run both in Node.js and browsers (although, you will not be able to use disk-based implementation).
This library aims to be the most complete and advanced implementation of Red-black tree available for JavaScript ecosystem.
Those features share the same mechanics of Red-black tree, but have different implementations on top.
Implementations consist of nodes that hold key, value and internal Red-black tree properties (color and children) and node managers that can manipulate those nodes in order to provide basic features above and more.
Node managers and nodes are separate entities, which means you can select one of existing flavors from this library or use interfaces provided and make your own implementation from scratch.
Node managers plug in into tree implementation. There are 2 kinds of trees available: regular synchronous Tree and asynchronous TreeAsync.
Asynchronous tree is currently only supported in combination with Disk-based node manager, which means you can have multi-terabytes trees on disk working with this implementation despite only having a small fraction of physical RAM.
#### Node managers: JavaScript
Supports any data type supported by JavaScript as key and value, there are 3 flavors available in this library that support following key types:
* number
* string
* Uint8Array
Others can be easily written by extending NodeManagerJs class.
Advantages: fast and flexible
Disadvantages: not very memory efficient, all nodes are kept in memory as objects all the time
#### Node managers: Binary Memory
Supports only Uint8Array both for keys and values. The whole tree is allocated in RAM as single continuous array of bytes.
Advantages: very high memory efficiency, especially on large number of keys (node objects are created only when working with tree and are cleaned up right after use)
Disadvantages: requires specifying max number of nodes upfront, about 5x slower for keys insertion and about 3.5x slower retrieval comparing to JavaScript node manager
#### Node managers: Binary Disk
Supports only Uint8Array both for keys and values. The whole tree is only stored on disk, which allows to work with trees that are many times larger than amount of available RAM.
Advantages: supports large trees that don't fit into RAM (node objects are created only when working with tree and are cleaned up right after use)
Disadvantages: requires specifying max number of nodes upfront, orders of magnitude slower than Binary Memory node manager (depending on disk performance)
bash
npm install @subspace/red-black-tree
`$3
You need 2 things: node manager and tree itself (note that different node managers are constructed in a different way).`typescript
// Synchronous JavaScript node manager
import {NodeManagerJsString, Tree} from "@subspace/red-black-tree";const nodeManager = new NodeManagerJsString();
const tree = new Tree(nodeManager);
tree.addNode('string key1', 'string value');
console.log(tree.getClosestNode('string key1'));
tree.removeNode('string key1');
``typescript
// Asynchronous JavaScript node manager
import {NodeManagerBinaryDisk, TreeAsync} from "@subspace/red-black-tree";const nodeManager = await NodeManagerBinaryDisk.create('/path/to/file.bin', 300, 2, 1);
const tree = new TreeAsync(nodeManager);
await tree.addNode(Uint8Array.of(0, 1), Uint8Array.of(1));
console.log(await tree.getClosestNode(Uint8Array.of(0, 1)));
await tree.removeNode(Uint8Array.of(0, 1));
await nodeManager.close();
`$3
#### redBlackTree.Tree(nodeManager: INodeManager)
Constructor, creates a new tree,
K and V are key and value data types accordingly.#### redBlackTree.Tree.addNode(key: K, value: V): void
Add a new node to the tree.
#### redBlackTree.Tree.removeNode(key: K): void
Remove node from the tree.
#### redBlackTree.Tree.getNodeValue(targetKey: K): V | null
Get the node value by target key,
null if key if not found.#### redBlackTree.Tree.getClosestNode(targetKey: K): [K, V] | null
Get closest node key/value to
targetKey (can be exact match or not, please check if needed) or null if not found.#### redBlackTree.TreeAsync(nodeManager: INodeManagerAsync)
The same as in regular
Tree, but for asynchronous node managers.#### redBlackTree.TreeAsync.addNode(key: K, value: V): Promise
The same as in regular
Tree, but for asynchronous node managers.#### redBlackTree.TreeAsync.removeNode(key: K): Promise
The same as in regular
Tree, but for asynchronous node managers.#### redBlackTree.Tree.getNodeValue(targetKey: K): Promise
The same as in regular
Tree, but for asynchronous node managers.#### redBlackTree.TreeAsync.getClosestNode(targetKey: K): Promise<[K, V] | null>
The same as in regular
Tree, but for asynchronous node managers.#### redBlackTree.NodeManagerJsNumber()
Constructor, creates JavaScript node manager with numbers as keys. There are no public methods you would need to use in most cases.
#### redBlackTree.NodeManagerJsString()
Constructor, creates JavaScript node manager with strings as keys. There are no public methods you would need to use in most cases.
#### redBlackTree.NodeManagerJsUint8Array()
Constructor, creates JavaScript node manager with Uint8Arrays as keys. There are no public methods you would need to use in most cases.
#### redBlackTree.NodeManagerBinaryMemory(numberOfNodes: number, keySize: number, valueSize: number)
Constructor, creates Binary Memory node manager with Uint8Arrays as both keys and values. There are no public methods you would need to use in most cases.
#### redBlackTree.NodeManagerBinaryDisk.create(pathToFile: string, numberOfNodes: number, keySize: number, valueSize: number): Promise
Asynchronous constructor, creates Binary Disk node manager with Uint8Arrays as both keys and values as a new file on disk.
#### redBlackTree.NodeManagerBinaryDisk.open(pathToFile: string, numberOfNodes: number, keySize: number, valueSize: number): Promise
Asynchronous constructor, opens Binary Disk node manager with Uint8Arrays as both keys and values from existing file on disk.
#### redBlackTree.NodeManagerBinaryDisk.close(): Promise
Closes file handlers to tree file on disk. This is the only public method you would typically use from
NodeManagerBinaryDisk implementation.$3
#### redBlackTree.NodeJs
Node implementation for JavaScript node manager in case you want to create custom implementation, please refer to source code for implementation details.
#### redBlackTree.NodeManagerJs
Abstract class for JavaScript node manager in case you want to create custom implementation, only requires comparison function to be implemented, please refer to source code for implementation details.
#### redBlackTree.INode
Basic node interface in case you want to create custom implementation, please refer to source code for implementation details.
#### redBlackTree.INodeAsync
Basic asynchronous node interface in case you want to create custom implementation, please refer to source code for implementation details.
#### redBlackTree.INodeManager
Basic node manager interface in case you want to create custom implementation, please refer to source code for implementation details.
#### redBlackTree.INodeManagerAsync
Basic asynchronous node manager interface in case you want to create custom implementation, please refer to source code for implementation details.
$3
There are some benchmarks under benchmarks directory:
* Buffer-vs-Uint8Array-vs-BigInt.ts was used to make a choice how to compare binary values with highest speed (this is why even in Node.js Uint8Array is used instead of more convenient Buffer)
* node-managers-vs-rocksdb-comparison.ts compares performance of different node managers in terms of insertion and lookup performance as well as memory consumption including comparison with RocksDBYou can run them with following commands:
`bash
npm run buffer-uint-bigint-benchmark
npm run node-managers-rocksdb-benchmark
`$3
Project is covered with tests that ensure things work as expected and do not regress, run them with usual npm test`.Concept of node managers was designed at a later stage specifically to decouple primary mechanics of Red-black tree like tree fixing after insertion or deletion, while making it possible to create asynchronous interface that works with disk yet still reuse most of the code.