trie-typed
Version:
Trie, prefix tree
547 lines (546 loc) • 19.8 kB
JavaScript
"use strict";
/**
* data-structure-typed
*
* @author Pablo Zeng
* @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com>
* @license MIT License
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RedBlackTree = exports.RedBlackTreeNode = void 0;
const bst_1 = require("./bst");
class RedBlackTreeNode extends bst_1.BSTNode {
/**
* Create a Red-Black Tree and optionally bulk-insert items.
* @remarks Time O(n log n), Space O(n)
* @param key - See parameter type for details.
* @param [value]- See parameter type for details.
* @param color - See parameter type for details.
* @returns New RedBlackTree instance.
*/
constructor(key, value, color = 'BLACK') {
super(key, value);
this.parent = undefined;
this._left = undefined;
this._right = undefined;
this._color = color;
}
/**
* Get the left child pointer.
* @remarks Time O(1), Space O(1)
* @returns Left child node, or null/undefined.
*/
get left() {
return this._left;
}
/**
* Set the left child and update its parent pointer.
* @remarks Time O(1), Space O(1)
* @param v - New left node, or null/undefined.
* @returns void
*/
set left(v) {
if (v) {
v.parent = this;
}
this._left = v;
}
/**
* Get the right child pointer.
* @remarks Time O(1), Space O(1)
* @returns Right child node, or null/undefined.
*/
get right() {
return this._right;
}
/**
* Set the right child and update its parent pointer.
* @remarks Time O(1), Space O(1)
* @param v - New right node, or null/undefined.
* @returns void
*/
set right(v) {
if (v) {
v.parent = this;
}
this._right = v;
}
}
exports.RedBlackTreeNode = RedBlackTreeNode;
/**
* RRRRed-Black Tree (self-balancing BST) supporting map-like mode and stable O(log n) updates.
* @remarks Time O(1), Space O(1)
* @template K
* @template V
* @template R
* 1. Efficient self-balancing, but not completely balanced. Compared with AVLTree, the addition and deletion efficiency is high but the query efficiency is slightly lower.
* 2. It is BST itself. Compared with Heap which is not completely ordered, RedBlackTree is completely ordered.
* @example
* // using Red-Black Tree as a price-based index for stock data
* // Define the structure of individual stock records
* interface StockRecord {
* price: number; // Stock price (key for indexing)
* symbol: string; // Stock ticker symbol
* volume: number; // Trade volume
* }
*
* // Simulate stock market data as it might come from an external feed
* const marketStockData: StockRecord[] = [
* { price: 142.5, symbol: 'AAPL', volume: 1000000 },
* { price: 335.2, symbol: 'MSFT', volume: 800000 },
* { price: 3285.04, symbol: 'AMZN', volume: 500000 },
* { price: 267.98, symbol: 'META', volume: 750000 },
* { price: 234.57, symbol: 'GOOGL', volume: 900000 }
* ];
*
* // Extend the stock record type to include metadata for database usage
* type StockTableRecord = StockRecord & { lastUpdated: Date };
*
* // Create a Red-Black Tree to index stock records by price
* // Simulates a database index with stock price as the key for quick lookups
* const priceIndex = new RedBlackTree<number, StockTableRecord, StockRecord>(marketStockData, {
* toEntryFn: stockRecord => [
* stockRecord.price, // Use stock price as the key
* {
* ...stockRecord,
* lastUpdated: new Date() // Add a timestamp for when the record was indexed
* }
* ]
* });
*
* // Query the stock with the highest price
* const highestPricedStock = priceIndex.getRightMost();
* console.log(priceIndex.get(highestPricedStock)?.symbol); // 'AMZN' // Amazon has the highest price
*
* // Query stocks within a specific price range (200 to 400)
* const stocksInRange = priceIndex.rangeSearch(
* [200, 400], // Price range
* node => priceIndex.get(node)?.symbol // Extract stock symbols for the result
* );
* console.log(stocksInRange); // ['GOOGL', 'META', 'MSFT']
*/
class RedBlackTree extends bst_1.BST {
constructor(keysNodesEntriesOrRaws = [], options) {
super([], options);
this._root = this.NIL;
if (keysNodesEntriesOrRaws) {
this.addMany(keysNodesEntriesOrRaws);
}
}
/**
* Get the current root node.
* @remarks Time O(1), Space O(1)
* @returns Root node, or undefined.
*/
get root() {
return this._root;
}
/**
* Create a red-black node for the given key/value (value ignored in map mode).
* @remarks Time O(1), Space O(1)
* @param key - See parameter type for details.
* @param [value] - See parameter type for details.
* @param color - See parameter type for details.
* @returns A new RedBlackTreeNode instance.
*/
_createNode(key, value, color = 'BLACK') {
return new RedBlackTreeNode(key, this._isMapMode ? undefined : value, color);
}
/**
* Type guard: check whether the input is a RedBlackTreeNode.
* @remarks Time O(1), Space O(1)
* @param keyNodeOrEntry - See parameter type for details.
* @returns True if the value is a RedBlackTreeNode.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof RedBlackTreeNode;
}
/**
* Remove all nodes and clear the key→value store (if in map mode).
* @remarks Time O(n), Space O(1)
* @returns void
*/
clear() {
super.clear();
this._root = this.NIL;
}
/**
* Insert or replace an entry using BST order and red-black fix-up.
* @remarks Time O(log n), Space O(1)
* @param keyNodeOrEntry - Key, node, or [key, value] entry to insert.
* @param [value]- See parameter type for details.
* @returns True if inserted or updated; false if ignored.
*/
add(keyNodeOrEntry, value) {
const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (!this.isRealNode(newNode))
return false;
const insertStatus = this._insert(newNode);
if (insertStatus === 'CREATED') {
if (this.isRealNode(this._root)) {
this._root.color = 'BLACK';
}
else {
return false;
}
if (this._isMapMode)
this._setValue(newNode.key, newValue);
this._size++;
return true;
}
if (insertStatus === 'UPDATED') {
if (this._isMapMode)
this._setValue(newNode.key, newValue);
return true;
}
return false;
}
/**
* Delete a node by key/node/entry and rebalance as needed.
* @remarks Time O(log n), Space O(1)
* @param keyNodeOrEntry - Key, node, or [key, value] entry identifying the node to delete.
* @returns Array with deletion metadata (removed node, rebalancing hint if any).
*/
delete(keyNodeOrEntry) {
if (keyNodeOrEntry === null)
return [];
const results = [];
let nodeToDelete;
if (this._isPredicate(keyNodeOrEntry))
nodeToDelete = this.getNode(keyNodeOrEntry);
else
nodeToDelete = this.isRealNode(keyNodeOrEntry) ? keyNodeOrEntry : this.getNode(keyNodeOrEntry);
if (!nodeToDelete) {
return results;
}
let originalColor = nodeToDelete.color;
let replacementNode;
if (!this.isRealNode(nodeToDelete.left)) {
if (nodeToDelete.right !== null) {
replacementNode = nodeToDelete.right;
this._transplant(nodeToDelete, nodeToDelete.right);
}
}
else if (!this.isRealNode(nodeToDelete.right)) {
replacementNode = nodeToDelete.left;
this._transplant(nodeToDelete, nodeToDelete.left);
}
else {
const successor = this.getLeftMost(node => node, nodeToDelete.right);
if (successor) {
originalColor = successor.color;
if (successor.right !== null)
replacementNode = successor.right;
if (successor.parent === nodeToDelete) {
if (this.isRealNode(replacementNode)) {
replacementNode.parent = successor;
}
}
else {
if (successor.right !== null) {
this._transplant(successor, successor.right);
successor.right = nodeToDelete.right;
}
if (this.isRealNode(successor.right)) {
successor.right.parent = successor;
}
}
this._transplant(nodeToDelete, successor);
successor.left = nodeToDelete.left;
if (this.isRealNode(successor.left)) {
successor.left.parent = successor;
}
successor.color = nodeToDelete.color;
}
}
if (this._isMapMode)
this._store.delete(nodeToDelete.key);
this._size--;
if (originalColor === 'BLACK') {
this._deleteFixup(replacementNode);
}
results.push({ deleted: nodeToDelete, needBalanced: undefined });
return results;
}
/**
* Transform entries into a like-kind red-black tree with possibly different key/value types.
* @remarks Time O(n), Space O(n)
* @template MK
* @template MV
* @template MR
* @param callback - Mapping function from (key, value, index, tree) to a new [key, value].
* @param [options] - See parameter type for details.
* @param [thisArg] - See parameter type for details.
* @returns A new RedBlackTree with mapped entries.
*/
map(callback, options, thisArg) {
const out = this._createLike([], options);
let index = 0;
for (const [key, value] of this) {
out.add(callback.call(thisArg, key, value, index++, this));
}
return out;
}
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
_setRoot(v) {
if (v) {
v.parent = undefined;
}
this._root = v;
}
_replaceNode(oldNode, newNode) {
newNode.color = oldNode.color;
return super._replaceNode(oldNode, newNode);
}
/**
* (Protected) Standard BST insert followed by red-black fix-up.
* @remarks Time O(log n), Space O(1)
* @param node - Node to insert.
* @returns Status string: 'CREATED' or 'UPDATED'.
*/
_insert(node) {
var _a, _b;
let current = this.root;
let parent = undefined;
while (this.isRealNode(current)) {
parent = current;
const compared = this._compare(node.key, current.key);
if (compared < 0) {
current = (_a = current.left) !== null && _a !== void 0 ? _a : this.NIL;
}
else if (compared > 0) {
current = (_b = current.right) !== null && _b !== void 0 ? _b : this.NIL;
}
else {
this._replaceNode(current, node);
return 'UPDATED';
}
}
node.parent = parent;
if (!parent) {
this._setRoot(node);
}
else if (this._compare(node.key, parent.key) < 0) {
parent.left = node;
}
else {
parent.right = node;
}
node.left = this.NIL;
node.right = this.NIL;
node.color = 'RED';
this._insertFixup(node);
return 'CREATED';
}
/**
* (Protected) Transplant a subtree in place of another during deletion.
* @remarks Time O(1), Space O(1)
* @param u - Node to replace.
* @param v - Replacement subtree root (may be undefined).
* @returns void
*/
_transplant(u, v) {
if (!u.parent) {
this._setRoot(v);
}
else if (u === u.parent.left) {
u.parent.left = v;
}
else {
u.parent.right = v;
}
if (v) {
v.parent = u.parent;
}
}
/**
* (Protected) Restore red-black properties after insertion (recolor/rotate).
* @remarks Time O(log n), Space O(1)
* @param z - Recently inserted node.
* @returns void
*/
_insertFixup(z) {
var _a, _b, _c, _d, _e;
while (((_a = z === null || z === void 0 ? void 0 : z.parent) === null || _a === void 0 ? void 0 : _a.color) === 'RED') {
if (z.parent === ((_b = z.parent.parent) === null || _b === void 0 ? void 0 : _b.left)) {
const y = z.parent.parent.right;
if ((y === null || y === void 0 ? void 0 : y.color) === 'RED') {
z.parent.color = 'BLACK';
y.color = 'BLACK';
z.parent.parent.color = 'RED';
z = z.parent.parent;
}
else {
if (z === z.parent.right) {
z = z.parent;
this._leftRotate(z);
}
if (z && this.isRealNode(z.parent) && this.isRealNode(z.parent.parent)) {
z.parent.color = 'BLACK';
z.parent.parent.color = 'RED';
this._rightRotate(z.parent.parent);
}
}
}
else {
const y = (_e = (_d = (_c = z === null || z === void 0 ? void 0 : z.parent) === null || _c === void 0 ? void 0 : _c.parent) === null || _d === void 0 ? void 0 : _d.left) !== null && _e !== void 0 ? _e : undefined;
if ((y === null || y === void 0 ? void 0 : y.color) === 'RED') {
z.parent.color = 'BLACK';
y.color = 'BLACK';
z.parent.parent.color = 'RED';
z = z.parent.parent;
}
else {
if (z === z.parent.left) {
z = z.parent;
this._rightRotate(z);
}
if (z && this.isRealNode(z.parent) && this.isRealNode(z.parent.parent)) {
z.parent.color = 'BLACK';
z.parent.parent.color = 'RED';
this._leftRotate(z.parent.parent);
}
}
}
}
if (this.isRealNode(this._root))
this._root.color = 'BLACK';
}
/**
* (Protected) Restore red-black properties after deletion (recolor/rotate).
* @remarks Time O(log n), Space O(1)
* @param node - Child that replaced the deleted node (may be undefined).
* @returns void
*/
_deleteFixup(node) {
var _a, _b, _c, _d;
if (!node || node === this.root || node.color === 'BLACK') {
if (node) {
node.color = 'BLACK';
}
return;
}
while (node && node !== this.root && node.color === 'BLACK') {
const parent = node.parent;
if (!parent) {
break;
}
if (node === parent.left) {
let sibling = parent.right;
if ((sibling === null || sibling === void 0 ? void 0 : sibling.color) === 'RED') {
sibling.color = 'BLACK';
parent.color = 'RED';
this._leftRotate(parent);
sibling = parent.right;
}
if (((_b = (_a = sibling === null || sibling === void 0 ? void 0 : sibling.left) === null || _a === void 0 ? void 0 : _a.color) !== null && _b !== void 0 ? _b : 'BLACK') === 'BLACK') {
if (sibling)
sibling.color = 'RED';
node = parent;
}
else {
if (sibling === null || sibling === void 0 ? void 0 : sibling.left)
sibling.left.color = 'BLACK';
if (sibling)
sibling.color = parent.color;
parent.color = 'BLACK';
this._rightRotate(parent);
node = this.root;
}
}
else {
let sibling = parent.left;
if ((sibling === null || sibling === void 0 ? void 0 : sibling.color) === 'RED') {
sibling.color = 'BLACK';
if (parent)
parent.color = 'RED';
this._rightRotate(parent);
if (parent)
sibling = parent.left;
}
if (((_d = (_c = sibling === null || sibling === void 0 ? void 0 : sibling.right) === null || _c === void 0 ? void 0 : _c.color) !== null && _d !== void 0 ? _d : 'BLACK') === 'BLACK') {
if (sibling)
sibling.color = 'RED';
node = parent;
}
else {
if (sibling === null || sibling === void 0 ? void 0 : sibling.right)
sibling.right.color = 'BLACK';
if (sibling)
sibling.color = parent.color;
if (parent)
parent.color = 'BLACK';
this._leftRotate(parent);
node = this.root;
}
}
}
if (node) {
node.color = 'BLACK';
}
}
/**
* (Protected) Perform a left rotation around x.
* @remarks Time O(1), Space O(1)
* @param x - Pivot node to rotate around.
* @returns void
*/
_leftRotate(x) {
if (!x || !x.right) {
return;
}
const y = x.right;
x.right = y.left;
if (this.isRealNode(y.left)) {
y.left.parent = x;
}
y.parent = x.parent;
if (!x.parent) {
this._setRoot(y);
}
else if (x === x.parent.left) {
x.parent.left = y;
}
else {
x.parent.right = y;
}
y.left = x;
x.parent = y;
}
/**
* (Protected) Perform a right rotation around y.
* @remarks Time O(1), Space O(1)
* @param y - Pivot node to rotate around.
* @returns void
*/
_rightRotate(y) {
if (!y || !y.left) {
return;
}
const x = y.left;
y.left = x.right;
if (this.isRealNode(x.right)) {
x.right.parent = y;
}
x.parent = y.parent;
if (!y.parent) {
this._setRoot(x);
}
else if (y === y.parent.left) {
y.parent.left = x;
}
else {
y.parent.right = x;
}
x.right = y;
y.parent = x;
}
}
exports.RedBlackTree = RedBlackTree;