UNPKG

tree-multimap-typed

Version:
414 lines (413 loc) 15.5 kB
"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.TreeCounter = exports.TreeCounterNode = void 0; const red_black_tree_1 = require("./red-black-tree"); /** * RB-tree node with an extra 'count' field; keeps parent/child links. * @remarks Time O(1), Space O(1) * @template K * @template V */ class TreeCounterNode extends red_black_tree_1.RedBlackTreeNode { /** * Create a tree counter node. * @remarks Time O(1), Space O(1) * @param key - Key of the node. * @param [value] - Value associated with the key (ignored in map mode). * @param [count] - Initial count for this node (default 1). * @param color - Initial color ('RED' or 'BLACK'). * @returns New TreeCounterNode instance. */ constructor(key, value, count = 1, color = 'BLACK') { super(key, value, color); this.parent = undefined; this._left = undefined; this._right = undefined; this.count = count; } /** * 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 child 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 child node, or null/undefined. * @returns void */ set right(v) { if (v) { v.parent = this; } this._right = v; } } exports.TreeCounterNode = TreeCounterNode; /** * Red-Black Tree–based counter map (key → value with per-node count). Supports O(log N) updates and map-like mode. * @remarks Time O(1), Space O(1) * @template K * @template V * @template R */ class TreeCounter extends red_black_tree_1.RedBlackTree { /** * Create a TreeCounter and optionally bulk-insert items. * @remarks Time O(N log N), Space O(N) * @param [keysNodesEntriesOrRaws] - Iterable of keys/nodes/entries/raw items to insert. * @param [options] - Options for TreeCounter (comparator, reverse, map mode). * @returns New TreeCounter instance. */ constructor(keysNodesEntriesOrRaws = [], options) { super([], options); this._count = 0; if (keysNodesEntriesOrRaws) this.addMany(keysNodesEntriesOrRaws); } /** * Get the total aggregate count across all nodes. * @remarks Time O(1), Space O(1) * @returns Total count. */ get count() { return this._count; } /** * Compute the total count by traversing the tree (sums node.count). * @remarks Time O(N), Space O(H) * @returns Total count recomputed from nodes. */ getComputedCount() { let sum = 0; this.dfs(node => (sum += node ? node.count : 0)); return sum; } _createNode(key, value, color = 'BLACK', count) { return new TreeCounterNode(key, this._isMapMode ? undefined : value, count, color); } /** * Type guard: check whether the input is a TreeCounterNode. * @remarks Time O(1), Space O(1) * @returns True if the value is a TreeCounterNode. */ isNode(keyNodeOrEntry) { return keyNodeOrEntry instanceof TreeCounterNode; } /** * Insert or increment a node and update aggregate count. * @remarks Time O(log N), Space O(1) * @param keyNodeOrEntry - Key, node, or [key, value] entry to insert. * @param [value] - Value when a bare key is provided (ignored in map mode). * @param [count] - How much to increase the node's count (default 1). * @returns True if inserted/updated; false if ignored. */ add(keyNodeOrEntry, value, count = 1) { const [newNode, newValue] = this._keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value, count); const orgCount = (newNode === null || newNode === void 0 ? void 0 : newNode.count) || 0; const isSuccessAdded = super.add(newNode, newValue); if (isSuccessAdded) { this._count += orgCount; return true; } else { return false; } } /** * Delete a node (or decrement its count) and rebalance if needed. * @remarks Time O(log N), Space O(1) * @param keyNodeOrEntry - Key, node, or [key, value] entry identifying the node. * @param [ignoreCount] - If true, remove the node regardless of its count. * @returns Array of deletion results including deleted node and a rebalance hint when present. */ delete(keyNodeOrEntry, ignoreCount = false) { 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; if (ignoreCount || nodeToDelete.count <= 1) { if (nodeToDelete.right !== null) { this._transplant(nodeToDelete, nodeToDelete.right); this._count -= nodeToDelete.count; } } else { nodeToDelete.count--; this._count--; results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } } else if (!this.isRealNode(nodeToDelete.right)) { replacementNode = nodeToDelete.left; if (ignoreCount || nodeToDelete.count <= 1) { this._transplant(nodeToDelete, nodeToDelete.left); this._count -= nodeToDelete.count; } else { nodeToDelete.count--; this._count--; results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } } 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 (ignoreCount || nodeToDelete.count <= 1) { if (successor.right !== null) { this._transplant(successor, successor.right); this._count -= nodeToDelete.count; } } else { nodeToDelete.count--; this._count--; results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } successor.right = nodeToDelete.right; if (this.isRealNode(successor.right)) { successor.right.parent = successor; } } if (ignoreCount || nodeToDelete.count <= 1) { this._transplant(nodeToDelete, successor); this._count -= nodeToDelete.count; } else { nodeToDelete.count--; this._count--; results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } successor.left = nodeToDelete.left; if (this.isRealNode(successor.left)) { successor.left.parent = successor; } successor.color = nodeToDelete.color; } } this._size--; if (originalColor === 'BLACK') { this._deleteFixup(replacementNode); } results.push({ deleted: nodeToDelete, needBalanced: undefined }); return results; } /** * Remove all nodes and reset aggregate counters. * @remarks Time O(N), Space O(1) * @returns void */ clear() { super.clear(); this._count = 0; } /** * Rebuild the tree into a perfectly balanced form using in-order nodes. * @remarks Time O(N), Space O(N) * @param [iterationType] - Traversal style to use when constructing the balanced tree. * @returns True if rebalancing succeeded (tree not empty). */ perfectlyBalance(iterationType = this.iterationType) { const nodes = this.dfs(node => node, 'IN', false, this._root, iterationType); const n = nodes.length; if (n < 1) return false; let total = 0; for (const nd of nodes) total += nd ? nd.count : 0; this._clearNodes(); 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; this._count = total; return true; } /** * Create a new TreeCounter by mapping each [key, value] entry. * @remarks Time O(N log N), Space O(N) * @template MK * @template MV * @template MR * @param callback - Function mapping (key, value, index, tree) → [newKey, newValue]. * @param [options] - Options for the output tree. * @param [thisArg] - Value for `this` inside the callback. * @returns A new TreeCounter 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; } /** * Deep copy this tree, preserving map mode and aggregate counts. * @remarks Time O(N), Space O(N) * @returns A deep copy of this tree. */ clone() { const out = this._createInstance(); this._clone(out); out._count = this._count; return out; } /** * (Protected) Create an empty instance of the same concrete class. * @remarks Time O(1), Space O(1) * @template TK * @template TV * @template TR * @param [options] - Optional constructor options for the like-kind instance. * @returns An empty like-kind instance. */ _createInstance(options) { const Ctor = this.constructor; return new Ctor([], Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {}))); } /** * (Protected) Create a like-kind instance and seed it from an iterable. * @remarks Time O(N log N), Space O(N) * @template TK * @template TV * @template TR * @param iter - Iterable used to seed the new tree. * @param [options] - Options merged with the current snapshot. * @returns A like-kind TreeCounter built from the iterable. */ _createLike(iter = [], options) { const Ctor = this.constructor; return new Ctor(iter, Object.assign(Object.assign({}, this._snapshotOptions()), (options !== null && options !== void 0 ? options : {}))); } /** * (Protected) Normalize input into a node plus its effective value and count. * @remarks Time O(1), Space O(1) * @param keyNodeOrEntry - Key, node, or [key, value] entry. * @param [value] - Value used when a bare key is provided. * @param [count] - Count increment to apply (default 1). * @returns Tuple [node, value] where node may be undefined. */ _keyValueNodeOrEntryToNodeAndValue(keyNodeOrEntry, value, count = 1) { if (keyNodeOrEntry === undefined || keyNodeOrEntry === null) return [undefined, undefined]; if (this.isNode(keyNodeOrEntry)) return [keyNodeOrEntry, value]; if (this.isEntry(keyNodeOrEntry)) { const [key, entryValue] = keyNodeOrEntry; if (key === undefined || key === null) return [undefined, undefined]; const finalValue = value !== null && value !== void 0 ? value : entryValue; return [this._createNode(key, finalValue, 'BLACK', count), finalValue]; } return [this._createNode(keyNodeOrEntry, value, 'BLACK', count), value]; } /** * (Protected) Swap keys/values/counters between the source and destination nodes. * @remarks Time O(1), Space O(1) * @param srcNode - Source node (or key) whose properties will be moved. * @param destNode - Destination node (or key) to receive properties. * @returns Destination node after swap, or undefined. */ _swapProperties(srcNode, destNode) { srcNode = this.ensureNode(srcNode); destNode = this.ensureNode(destNode); if (srcNode && destNode) { const { key, value, count, color } = destNode; const tempNode = this._createNode(key, value, color, count); if (tempNode) { tempNode.color = color; destNode.key = srcNode.key; if (!this._isMapMode) destNode.value = srcNode.value; destNode.count = srcNode.count; destNode.color = srcNode.color; srcNode.key = tempNode.key; if (!this._isMapMode) srcNode.value = tempNode.value; srcNode.count = tempNode.count; srcNode.color = tempNode.color; } return destNode; } return undefined; } /** * (Protected) Replace one node by another and adjust counters accordingly. * @remarks Time O(1), Space O(1) * @param oldNode - Node being replaced. * @param newNode - Replacement node. * @returns The new node after replacement. */ _replaceNode(oldNode, newNode) { newNode.count = oldNode.count + newNode.count; return super._replaceNode(oldNode, newNode); } } exports.TreeCounter = TreeCounter;