tree-multimap-typed
Version:
904 lines (903 loc) • 36.5 kB
JavaScript
;
/**
* 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.BST = exports.BSTNode = void 0;
const binary_tree_1 = require("./binary-tree");
const queue_1 = require("../queue");
const utils_1 = require("../../utils");
const common_1 = require("../../common");
/**
* Represents a Node in a Binary Search Tree.
*
* @template K - The type of the key.
* @template V - The type of the value.
*/
class BSTNode extends binary_tree_1.BinaryTreeNode {
/**
* Creates an instance of BSTNode.
* @remarks Time O(1), Space O(1)
*
* @param key - The key of the node.
* @param [value] - The value associated with the key.
*/
constructor(key, value) {
super(key, value);
this.parent = undefined;
this._left = undefined;
this._right = undefined;
}
/**
* Gets the left child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The left child.
*/
get left() {
return this._left;
}
/**
* Sets the left child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the left child.
*/
set left(v) {
if (v)
v.parent = this;
this._left = v;
}
/**
* Gets the right child of the node.
* @remarks Time O(1), Space O(1)
*
* @returns The right child.
*/
get right() {
return this._right;
}
/**
* Sets the right child of the node and updates its parent reference.
* @remarks Time O(1), Space O(1)
*
* @param v - The node to set as the right child.
*/
set right(v) {
if (v)
v.parent = this;
this._right = v;
}
}
exports.BSTNode = BSTNode;
/**
* Represents a Binary Search Tree (BST).
* Keys are ordered, allowing for faster search operations compared to a standard Binary Tree.
* @template K - The type of the key.
* @template V - The type of the value.
* @template R - The type of the raw data object (if using `toEntryFn`).
*
* 1. Node Order: Each node's left child has a lesser value, and the right child has a greater value.
* 2. Unique Keys: No duplicate keys in a standard BST.
* 3. Efficient Search: Enables quick search, minimum, and maximum operations.
* 4. Inorder Traversal: Yields nodes in ascending order.
* 5. Logarithmic Operations: Ideal operations like insertion, deletion, and searching are O(log n) time-efficient.
* 6. Balance Variability: Can become unbalanced; special types maintain balance.
* 7. No Auto-Balancing: Standard BSTs don't automatically balance themselves.
* @example
* // Merge 3 sorted datasets
* const dataset1 = new BST<number, string>([
* [1, 'A'],
* [7, 'G']
* ]);
* const dataset2 = [
* [2, 'B'],
* [6, 'F']
* ];
* const dataset3 = new BST<number, string>([
* [3, 'C'],
* [5, 'E'],
* [4, 'D']
* ]);
*
* // Merge datasets into a single BinarySearchTree
* const merged = new BST<number, string>(dataset1);
* merged.addMany(dataset2);
* merged.merge(dataset3);
*
* // Verify merged dataset is in sorted order
* console.log([...merged.values()]); // ['A', 'B', 'C', 'D', 'E', 'F', 'G']
* @example
* // Find elements in a range
* const bst = new BST<number>([10, 5, 15, 3, 7, 12, 18]);
* console.log(bst.search(new Range(5, 10))); // [5, 7, 10]
* console.log(bst.rangeSearch([4, 12], node => node.key.toString())); // ['5', '7', '10', '12']
* console.log(bst.search(new Range(4, 12, true, false))); // [5, 7, 10]
* console.log(bst.rangeSearch([15, 20])); // [15, 18]
* console.log(bst.search(new Range(15, 20, false))); // [18]
* @example
* // Find lowest common ancestor
* const bst = new BST<number>([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]);
*
* // LCA helper function
* const findLCA = (num1: number, num2: number): number | undefined => {
* const path1 = bst.getPathToRoot(num1);
* const path2 = bst.getPathToRoot(num2);
* // Find the first common ancestor
* return findFirstCommon(path1, path2);
* };
*
* function findFirstCommon(arr1: number[], arr2: number[]): number | undefined {
* for (const num of arr1) {
* if (arr2.indexOf(num) !== -1) {
* return num;
* }
* }
* return undefined;
* }
*
* // Assertions
* console.log(findLCA(3, 10)); // 7
* console.log(findLCA(5, 35)); // 15
* console.log(findLCA(20, 30)); // 25
*/
class BST extends binary_tree_1.BinaryTree {
/**
* Creates an instance of BST.
* @remarks Time O(N log N) or O(N^2) depending on `isBalanceAdd` in `addMany` and input order. Space O(N).
*
* @param [keysNodesEntriesOrRaws=[]] - An iterable of items to add.
* @param [options] - Configuration options for the BST, including comparator.
*/
constructor(keysNodesEntriesOrRaws = [], options) {
super([], options);
this._root = undefined;
this._isReverse = false;
/**
* The default comparator function.
* @remarks Time O(1) (or O(C) if `specifyComparable` is used, C is complexity of that function).
*/
this._comparator = (a, b) => {
if ((0, utils_1.isComparable)(a) && (0, utils_1.isComparable)(b)) {
if (a > b)
return 1;
if (a < b)
return -1;
return 0;
}
if (this._specifyComparable) {
const va = this._specifyComparable(a);
const vb = this._specifyComparable(b);
if (va > vb)
return 1;
if (va < vb)
return -1;
return 0;
}
if (typeof a === 'object' || typeof b === 'object') {
throw TypeError(`When comparing object types, a custom specifyComparable must be defined in the constructor's options.`);
}
return 0;
};
if (options) {
const { specifyComparable, isReverse } = options;
if (typeof specifyComparable === 'function')
this._specifyComparable = specifyComparable;
if (isReverse !== undefined)
this._isReverse = isReverse;
}
if (keysNodesEntriesOrRaws)
this.addMany(keysNodesEntriesOrRaws);
}
/**
* Gets the root node of the tree.
* @remarks Time O(1)
*
* @returns The root node.
*/
get root() {
return this._root;
}
/**
* Gets whether the tree's comparison logic is reversed.
* @remarks Time O(1)
*
* @returns True if the tree is reversed (e.g., a max-heap logic).
*/
get isReverse() {
return this._isReverse;
}
/**
* Gets the comparator function used by the tree.
* @remarks Time O(1)
*
* @returns The comparator function.
*/
get comparator() {
return this._comparator;
}
/**
* Gets the function used to extract a comparable value from a complex key.
* @remarks Time O(1)
*
* @returns The key-to-comparable conversion function.
*/
get specifyComparable() {
return this._specifyComparable;
}
/**
* (Protected) Creates a new BST node.
* @remarks Time O(1), Space O(1)
*
* @param key - The key for the new node.
* @param [value] - The value for the new node (used if not in Map mode).
* @returns The newly created BSTNode.
*/
_createNode(key, value) {
return new BSTNode(key, this._isMapMode ? undefined : value);
}
/**
* Ensures the input is a node. If it's a key or entry, it searches for the node.
* @remarks Time O(log N) (height of the tree), O(N) worst-case.
*
* @param keyNodeOrEntry - The item to resolve to a node.
* @param [iterationType=this.iterationType] - The traversal method to use if searching.
* @returns The resolved node, or undefined if not found.
*/
ensureNode(keyNodeOrEntry, iterationType = this.iterationType) {
var _a;
return (_a = super.ensureNode(keyNodeOrEntry, iterationType)) !== null && _a !== void 0 ? _a : undefined;
}
/**
* Checks if the given item is a `BSTNode` instance.
* @remarks Time O(1), Space O(1)
*
* @param keyNodeOrEntry - The item to check.
* @returns True if it's a BSTNode, false otherwise.
*/
isNode(keyNodeOrEntry) {
return keyNodeOrEntry instanceof BSTNode;
}
/**
* Checks if the given key is valid (comparable).
* @remarks Time O(1)
*
* @param key - The key to validate.
* @returns True if the key is valid, false otherwise.
*/
isValidKey(key) {
return (0, utils_1.isComparable)(key, this._specifyComparable !== undefined);
}
/**
* Performs a Depth-First Search (DFS) traversal.
* @remarks Time O(N), visits every node. Space O(log N) for the call/explicit stack. O(N) worst-case.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [pattern='IN'] - The traversal order ('IN', 'PRE', 'POST').
* @param [onlyOne=false] - If true, stops after the first callback.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
dfs(callback = this._DEFAULT_NODE_CALLBACK, pattern = 'IN', onlyOne = false, startNode = this._root, iterationType = this.iterationType) {
return super.dfs(callback, pattern, onlyOne, startNode, iterationType);
}
/**
* Performs a Breadth-First Search (BFS) or Level-Order traversal.
* @remarks Time O(N), visits every node. Space O(N) in the worst case for the queue.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
bfs(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
return super.bfs(callback, startNode, iterationType, false);
}
/**
* Returns a 2D array of nodes, grouped by level.
* @remarks Time O(N), visits every node. Space O(N) for the result array and the queue/stack.
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on each node.
* @param [startNode=this._root] - The node to start from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns A 2D array of callback results.
*/
listLevels(callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
return super.listLevels(callback, startNode, iterationType, false);
}
/**
* Gets the first node matching a predicate.
* @remarks Time O(log N) if searching by key, O(N) if searching by predicate. Space O(log N) or O(N).
*
* @param keyNodeEntryOrPredicate - The key, node, entry, or predicate function to search for.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns The first matching node, or undefined if not found.
*/
getNode(keyNodeEntryOrPredicate, startNode = this._root, iterationType = this.iterationType) {
var _a;
return (_a = this.getNodes(keyNodeEntryOrPredicate, true, startNode, iterationType)[0]) !== null && _a !== void 0 ? _a : undefined;
}
/**
* Searches the tree for nodes matching a predicate, key, or range.
* @remarks This is an optimized search for a BST. If searching by key or range, it prunes branches.
* Time O(H + M) for key/range search (H=height, M=matches). O(N) for predicate search.
* Space O(log N) for the stack.
*
* @template C - The type of the callback function.
* @param keyNodeEntryOrPredicate - The key, node, entry, predicate, or range to search for.
* @param [onlyOne=false] - If true, stops after finding the first match.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - Whether to use 'RECURSIVE' or 'ITERATIVE' search.
* @returns An array of results from the callback function for each matching node.
*/
search(keyNodeEntryOrPredicate, onlyOne = false, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
if (keyNodeEntryOrPredicate === undefined)
return [];
if (keyNodeEntryOrPredicate === null)
return [];
startNode = this.ensureNode(startNode);
if (!startNode)
return [];
let predicate;
const isRange = this.isRange(keyNodeEntryOrPredicate);
if (isRange) {
predicate = node => {
if (!node)
return false;
return keyNodeEntryOrPredicate.isInRange(node.key, this._comparator);
};
}
else {
predicate = this._ensurePredicate(keyNodeEntryOrPredicate);
}
// Optimization: Pruning logic
const shouldVisitLeft = (cur) => {
if (!cur)
return false;
if (!this.isRealNode(cur.left))
return false;
if (isRange) {
// Range search: Only go left if the current key is >= the lower bound
const range = keyNodeEntryOrPredicate;
const leftS = this.isReverse ? range.high : range.low;
const leftI = this.isReverse ? range.includeHigh : range.includeLow;
return (leftI && this._compare(cur.key, leftS) >= 0) || (!leftI && this._compare(cur.key, leftS) > 0);
}
if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
// Key search: Only go left if current key > target key
const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
return benchmarkKey !== null && benchmarkKey !== undefined && this._compare(cur.key, benchmarkKey) > 0;
}
return true; // Predicate search: must visit all
};
const shouldVisitRight = (cur) => {
if (!cur)
return false;
if (!this.isRealNode(cur.right))
return false;
if (isRange) {
// Range search: Only go right if current key <= upper bound
const range = keyNodeEntryOrPredicate;
const rightS = this.isReverse ? range.low : range.high;
const rightI = this.isReverse ? range.includeLow : range.includeHigh;
return (rightI && this._compare(cur.key, rightS) <= 0) || (!rightI && this._compare(cur.key, rightS) < 0);
}
if (!isRange && !this._isPredicate(keyNodeEntryOrPredicate)) {
// Key search: Only go right if current key < target key
const benchmarkKey = this._extractKey(keyNodeEntryOrPredicate);
return benchmarkKey !== null && benchmarkKey !== undefined && this._compare(cur.key, benchmarkKey) < 0;
}
return true; // Predicate search: must visit all
};
return super._dfs(callback, 'IN', // In-order is efficient for range/key search
onlyOne, startNode, iterationType, false, shouldVisitLeft, shouldVisitRight, () => true, // shouldVisitRoot (always visit)
// shouldVisitRoot (always visit)
cur => !!cur && predicate(cur) // shouldProcessRoot (only process if predicate matches)
);
}
/**
* Performs an optimized search for nodes within a given key range.
* @remarks Time O(H + M), where H is tree height and M is the number of matches.
*
* @template C - The type of the callback function.
* @param range - A `Range` object or a `[low, high]` tuple.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - A function to call on matching nodes.
* @param [startNode=this._root] - The node to start the search from.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
rangeSearch(range, callback = this._DEFAULT_NODE_CALLBACK, startNode = this._root, iterationType = this.iterationType) {
const searchRange = range instanceof common_1.Range ? range : new common_1.Range(range[0], range[1]);
return this.search(searchRange, false, callback, startNode, iterationType);
}
/**
* Adds a new node to the BST based on key comparison.
* @remarks Time O(log N), where H is tree height. O(N) worst-case (unbalanced tree), O(log N) average. Space O(1).
*
* @param keyNodeOrEntry - The key, node, or entry to add.
* @param [value] - The value, if providing just a key.
* @returns True if the addition was successful, false otherwise.
*/
add(keyNodeOrEntry, value) {
const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (newNode === undefined)
return false;
if (this._root === undefined) {
this._setRoot(newNode);
if (this._isMapMode)
this._setValue(newNode === null || newNode === void 0 ? void 0 : newNode.key, newValue);
this._size++;
return true;
}
let current = this._root;
while (current !== undefined) {
if (this._compare(current.key, newNode.key) === 0) {
// Key exists, replace node
this._replaceNode(current, newNode);
if (this._isMapMode)
this._setValue(current.key, newValue);
return true;
}
else if (this._compare(current.key, newNode.key) > 0) {
// Go left
if (current.left === undefined) {
current.left = newNode;
if (this._isMapMode)
this._setValue(newNode === null || newNode === void 0 ? void 0 : newNode.key, newValue);
this._size++;
return true;
}
if (current.left !== null)
current = current.left;
}
else {
// Go right
if (current.right === undefined) {
current.right = newNode;
if (this._isMapMode)
this._setValue(newNode === null || newNode === void 0 ? void 0 : newNode.key, newValue);
this._size++;
return true;
}
if (current.right !== null)
current = current.right;
}
}
return false;
}
/**
* Adds multiple items to the tree.
* @remarks If `isBalanceAdd` is true, sorts the input and builds a balanced tree. Time O(N log N) (due to sort and balanced add).
* If false, adds items one by one. Time O(N * H), which is O(N^2) worst-case.
* Space O(N) for sorting and recursion/iteration stack.
*
* @param keysNodesEntriesOrRaws - An iterable of items to add.
* @param [values] - An optional parallel iterable of values.
* @param [isBalanceAdd=true] - If true, builds a balanced tree from the items.
* @param [iterationType=this.iterationType] - The traversal method for balanced add (recursive or iterative).
* @returns An array of booleans indicating the success of each individual `add` operation.
*/
addMany(keysNodesEntriesOrRaws, values, isBalanceAdd = true, iterationType = this.iterationType) {
const inserted = [];
const valuesIterator = values === null || values === void 0 ? void 0 : values[Symbol.iterator]();
if (!isBalanceAdd) {
// Standard O(N*H) insertion
for (let kve of keysNodesEntriesOrRaws) {
const val = valuesIterator === null || valuesIterator === void 0 ? void 0 : valuesIterator.next().value;
if (this.isRaw(kve))
kve = this._toEntryFn(kve);
inserted.push(this.add(kve, val));
}
return inserted;
}
// Balanced O(N log N) insertion
const realBTNExemplars = [];
let i = 0;
for (const kve of keysNodesEntriesOrRaws) {
realBTNExemplars.push({ key: kve, value: valuesIterator === null || valuesIterator === void 0 ? void 0 : valuesIterator.next().value, orgIndex: i++ });
}
// Sort items by key
const sorted = realBTNExemplars.sort(({ key: a }, { key: b }) => {
let keyA, keyB;
if (this.isRaw(a))
keyA = this._toEntryFn(a)[0];
else if (this.isEntry(a))
keyA = a[0];
else if (this.isRealNode(a))
keyA = a.key;
else
keyA = a;
if (this.isRaw(b))
keyB = this._toEntryFn(b)[0];
else if (this.isEntry(b))
keyB = b[0];
else if (this.isRealNode(b))
keyB = b.key;
else
keyB = b;
if (keyA != null && keyB != null)
return this._compare(keyA, keyB);
return 0;
});
// Recursive balanced build
const _dfs = (arr) => {
if (arr.length === 0)
return;
const mid = Math.floor((arr.length - 1) / 2);
const { key, value, orgIndex } = arr[mid];
if (this.isRaw(key)) {
const entry = this._toEntryFn(key);
inserted[orgIndex] = this.add(entry);
}
else {
inserted[orgIndex] = this.add(key, value);
}
_dfs(arr.slice(0, mid));
_dfs(arr.slice(mid + 1));
};
// Iterative balanced build
const _iterate = () => {
const n = sorted.length;
const stack = [[0, n - 1]];
while (stack.length > 0) {
const popped = stack.pop();
if (!popped)
continue;
const [l, r] = popped;
if (l > r)
continue;
const m = l + Math.floor((r - l) / 2);
const { key, value, orgIndex } = sorted[m];
if (this.isRaw(key)) {
const entry = this._toEntryFn(key);
inserted[orgIndex] = this.add(entry);
}
else {
inserted[orgIndex] = this.add(key, value);
}
stack.push([m + 1, r]);
stack.push([l, m - 1]);
}
};
if (iterationType === 'RECURSIVE')
_dfs(sorted);
else
_iterate();
return inserted;
}
/**
* Traverses the tree and returns nodes that are lesser or greater than a target node.
* @remarks Time O(N), as it performs a full traversal. Space O(log N) or O(N).
*
* @template C - The type of the callback function.
* @param [callback=this._DEFAULT_NODE_CALLBACK] - Function to call on matching nodes.
* @param [lesserOrGreater=-1] - -1 for lesser, 1 for greater, 0 for equal.
* @param [targetNode=this._root] - The node to compare against.
* @param [iterationType=this.iterationType] - The traversal method.
* @returns An array of callback results.
*/
lesserOrGreaterTraverse(callback = this._DEFAULT_NODE_CALLBACK, lesserOrGreater = -1, targetNode = this._root, iterationType = this.iterationType) {
const targetNodeEnsured = this.ensureNode(targetNode);
const ans = [];
if (!this._root || !targetNodeEnsured)
return ans;
const targetKey = targetNodeEnsured.key;
if (iterationType === 'RECURSIVE') {
const dfs = (cur) => {
const compared = this._compare(cur.key, targetKey);
if (Math.sign(compared) == lesserOrGreater)
ans.push(callback(cur));
if (this.isRealNode(cur.left))
dfs(cur.left);
if (this.isRealNode(cur.right))
dfs(cur.right);
};
dfs(this._root);
return ans;
}
else {
const queue = new queue_1.Queue([this._root]);
while (queue.length > 0) {
const cur = queue.shift();
if (this.isRealNode(cur)) {
const compared = this._compare(cur.key, targetKey);
if (Math.sign(compared) == lesserOrGreater)
ans.push(callback(cur));
if (this.isRealNode(cur.left))
queue.push(cur.left);
if (this.isRealNode(cur.right))
queue.push(cur.right);
}
}
return ans;
}
}
/**
* Rebuilds the tree to be perfectly balanced.
* @remarks Time O(N) (O(N) for DFS, O(N) for sorted build). Space O(N) for node array and recursion stack.
*
* @param [iterationType=this.iterationType] - The traversal method for the initial node export.
* @returns True if successful, false if the tree was empty.
*/
perfectlyBalance(iterationType = this.iterationType) {
const nodes = this.dfs(node => node, 'IN', false, this._root, iterationType);
const n = nodes.length;
this._clearNodes();
if (n === 0)
return false;
// Build balanced tree from sorted array
const build = (l, r, parent) => {
if (l > r)
return undefined;
const m = l + ((r - l) >> 1);
const root = nodes[m];
const leftChild = build(l, m - 1, root);
const rightChild = build(m + 1, r, root);
root.left = leftChild;
root.right = rightChild;
root.parent = parent;
return root;
};
const newRoot = build(0, n - 1, undefined);
this._setRoot(newRoot);
this._size = n;
return true;
}
/**
* Checks if the tree meets the AVL balance condition (height difference <= 1).
* @remarks Time O(N), as it must visit every node to compute height. Space O(log N) for recursion or O(N) for iterative map.
*
* @param [iterationType=this.iterationType] - The traversal method.
* @returns True if the tree is AVL balanced, false otherwise.
*/
isAVLBalanced(iterationType = this.iterationType) {
if (!this._root)
return true;
let balanced = true;
if (iterationType === 'RECURSIVE') {
// Recursive height check
const _height = (cur) => {
if (!cur)
return 0;
const leftHeight = _height(cur.left);
const rightHeight = _height(cur.right);
if (Math.abs(leftHeight - rightHeight) > 1)
balanced = false;
return Math.max(leftHeight, rightHeight) + 1;
};
_height(this._root);
}
else {
// Iterative post-order height check
const stack = [];
let node = this._root, last = undefined;
const depths = new Map();
while (stack.length > 0 || node) {
if (node) {
stack.push(node);
if (node.left !== null)
node = node.left;
}
else {
node = stack[stack.length - 1];
if (!node.right || last === node.right) {
node = stack.pop();
if (node) {
const left = node.left ? depths.get(node.left) : -1;
const right = node.right ? depths.get(node.right) : -1;
if (Math.abs(left - right) > 1)
return false;
depths.set(node, 1 + Math.max(left, right));
last = node;
node = undefined;
}
}
else
node = node.right;
}
}
}
return balanced;
}
/**
* Creates a new BST by mapping each [key, value] pair to a new entry.
* @remarks Time O(N * H), where N is nodes in this tree, and H is height of the new tree during insertion.
* Space O(N) for the new tree.
*
* @template MK - New key type.
* @template MV - New value type.
* @template MR - New raw type.
* @param callback - A function to map each [key, value] pair.
* @param [options] - Options for the new BST.
* @param [thisArg] - `this` context for the callback.
* @returns A new, mapped BST.
*/
map(callback, options, thisArg) {
const out = this._createLike([], options);
let index = 0;
// Iterates in-order
for (const [key, value] of this) {
out.add(callback.call(thisArg, key, value, index++, this));
}
return out;
}
/**
* Deletes the first node found that satisfies the predicate.
* @remarks Performs an in-order traversal. Time O(N) worst-case (O(log N) to find + O(log N) to delete). Space O(log N) for stack.
*
* @param predicate - A function to test each [key, value] pair.
* @returns True if a node was deleted, false otherwise.
*/
deleteWhere(predicate) {
const stack = [];
let cur = this._root;
let index = 0;
// In-order traversal to find the node
while (stack.length > 0 || cur !== undefined) {
while (cur !== undefined && cur !== null) {
stack.push(cur);
cur = cur.left;
}
const node = stack.pop();
if (!node)
break;
const key = node.key;
const val = node.value;
if (predicate(key, val, index++, this)) {
return this._deleteByKey(key); // Found, now delete
}
cur = node.right;
}
return false;
}
/**
* (Protected) Creates a new, empty instance of the same BST constructor.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [options] - Options for the new BST.
* @returns A new, empty BST.
*/
_createInstance(options) {
const Ctor = this.constructor;
return new Ctor([], Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
/**
* (Protected) Creates a new instance of the same BST constructor, potentially with different generic types.
* @remarks Time O(N log N) or O(N^2) (from constructor) due to processing the iterable.
*
* @template TK, TV, TR - Generic types for the new instance.
* @param [iter=[]] - An iterable to populate the new BST.
* @param [options] - Options for the new BST.
* @returns A new BST.
*/
_createLike(iter = [], options) {
const Ctor = this.constructor;
return new Ctor(iter, Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {})));
}
/**
* (Protected) Snapshots the current BST's configuration options.
* @remarks Time O(1)
*
* @template TK, TV, TR - Generic types for the options.
* @returns The options object.
*/
_snapshotOptions() {
return Object.assign(Object.assign({}, super._snapshotOptions()), { specifyComparable: this.specifyComparable, isReverse: this.isReverse });
}
/**
* (Protected) Converts a key, node, or entry into a standardized [node, value] tuple.
* @remarks Time O(1)
*
* @param keyNodeOrEntry - The input item.
* @param [value] - An optional value (used if input is just a key).
* @returns A tuple of [node, value].
*/
_keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value) {
const [node, entryValue] = super._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value);
if (node === null)
return [undefined, undefined]; // BST handles null differently (as undefined)
return [node, value !== null && value !== void 0 ? value : entryValue];
}
/**
* (Protected) Sets the root node and clears its parent reference.
* @remarks Time O(1)
*
* @param v - The node to set as root.
*/
_setRoot(v) {
if (v)
v.parent = undefined;
this._root = v;
}
/**
* (Protected) Compares two keys using the tree's comparator and reverse setting.
* @remarks Time O(1) (or O(C) if `specifyComparable` is used).
*
* @param a - The first key.
* @param b - The second key.
* @returns A number (1, -1, or 0) representing the comparison.
*/
_compare(a, b) {
return this._isReverse ? -this._comparator(a, b) : this._comparator(a, b);
}
/**
* (Private) Deletes a node by its key.
* @remarks Standard BST deletion algorithm. Time O(log N), O(N) worst-case. Space O(1).
*
* @param key - The key of the node to delete.
* @returns True if the node was found and deleted, false otherwise.
*/
_deleteByKey(key) {
var _a;
let node = this._root;
// 1. Find the node
while (node) {
const cmp = this._compare(node.key, key);
if (cmp === 0)
break;
node = cmp > 0 ? node.left : node.right;
}
if (!node)
return false; // Not found
// Helper to replace node `u` with node `v`
const transplant = (u, v) => {
const p = u === null || u === void 0 ? void 0 : u.parent;
if (!p) {
this._setRoot(v);
}
else if (p.left === u) {
p.left = v;
}
else {
p.right = v;
}
if (v)
v.parent = p;
};
// Helper to find the minimum node in a subtree
const minNode = (x) => {
if (!x)
return undefined;
while (x.left !== undefined && x.left !== null)
x = x.left;
return x;
};
// 2. Perform deletion
if (node.left === undefined) {
// Case 1: No left child
transplant(node, node.right);
}
else if (node.right === undefined) {
// Case 2: No right child
transplant(node, node.left);
}
else {
// Case 3: Two children
const succ = minNode(node.right); // Find successor
if (succ.parent !== node) {
transplant(succ, succ.right);
succ.right = node.right;
if (succ.right)
succ.right.parent = succ;
}
transplant(node, succ);
succ.left = node.left;
if (succ.left)
succ.left.parent = succ;
}
this._size = Math.max(0, ((_a = this._size) !== null && _a !== void 0 ? _a : 0) - 1);
return true;
}
}
exports.BST = BST;