UNPKG

trie-typed

Version:
375 lines (374 loc) 13.9 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.AVLTreeCounter = exports.AVLTreeCounterNode = void 0; const avl_tree_1 = require("./avl-tree"); /** * AVL node with an extra 'count' field; keeps parent/child links. * @remarks Time O(1), Space O(1) * @template K * @template V */ class AVLTreeCounterNode extends avl_tree_1.AVLTreeNode { /** * Create an AVL counter node. * @remarks Time O(1), Space O(1) * @param key - Key of the node. * @param [value] - Associated value (ignored in map mode). * @param [count] - Initial count for this node (default 1). * @returns New AVLTreeCounterNode instance. */ constructor(key, value, count = 1) { super(key, value); 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.AVLTreeCounterNode = AVLTreeCounterNode; /** * AVL tree that tracks an aggregate 'count' across nodes; supports balanced insert/delete and map-like mode. * @remarks Time O(1), Space O(1) * @template K * @template V * @template R */ class AVLTreeCounter extends avl_tree_1.AVLTree { /** * Create a AVLTreeCounter instance * @remarks Time O(n), Space O(n) * @param keysNodesEntriesOrRaws * @param options * @returns New AVLTreeCounterNode instance. */ constructor(keysNodesEntriesOrRaws = [], options) { super([], options); this._count = 0; if (keysNodesEntriesOrRaws) this.addMany(keysNodesEntriesOrRaws); } 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.count)); return sum; } _createNode(key, value, count) { return new AVLTreeCounterNode(key, this._isMapMode ? undefined : value, count); } /** * Type guard: check whether the input is an AVLTreeCounterNode. * @remarks Time O(1), Space O(1) * @returns True if the value is an AVLTreeCounterNode. */ isNode(keyNodeOrEntry) { return keyNodeOrEntry instanceof AVLTreeCounterNode; } /** * 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); if (newNode === undefined) return false; const orgNodeCount = (newNode === null || newNode === void 0 ? void 0 : newNode.count) || 0; const inserted = super.add(newNode, newValue); if (inserted) { this._count += orgNodeCount; } return true; } /** * 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) { var _a; const deletedResult = []; if (!this.root) return deletedResult; const curr = (_a = this.getNode(keyNodeOrEntry)) !== null && _a !== void 0 ? _a : undefined; if (!curr) return deletedResult; const parent = (curr === null || curr === void 0 ? void 0 : curr.parent) ? curr.parent : undefined; let needBalanced = undefined, orgCurrent = curr; if (curr.count > 1 && !ignoreCount) { curr.count--; this._count--; } else { if (!curr.left) { if (!parent) { if (curr.right !== undefined && curr.right !== null) this._setRoot(curr.right); } else { const { familyPosition: fp } = curr; if (fp === 'LEFT' || fp === 'ROOT_LEFT') { parent.left = curr.right; } else if (fp === 'RIGHT' || fp === 'ROOT_RIGHT') { parent.right = curr.right; } needBalanced = parent; } } else { const leftSubTreeRightMost = curr.left ? this.getRightMost(node => node, curr.left) : undefined; if (leftSubTreeRightMost) { const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; orgCurrent = this._swapProperties(curr, leftSubTreeRightMost); if (parentOfLeftSubTreeMax) { if (parentOfLeftSubTreeMax.right === leftSubTreeRightMost) { parentOfLeftSubTreeMax.right = leftSubTreeRightMost.left; } else { parentOfLeftSubTreeMax.left = leftSubTreeRightMost.left; } needBalanced = parentOfLeftSubTreeMax; } } } this._size = this._size - 1; if (orgCurrent) this._count -= orgCurrent.count; } deletedResult.push({ deleted: orgCurrent, needBalanced }); if (needBalanced) { this._balancePath(needBalanced); } return deletedResult; } /** * 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 === 0) 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]; root.left = build(l, m - 1, root); root.right = build(m + 1, r, root); root.parent = parent; const lh = root.left ? root.left.height : -1; const rh = root.right ? root.right.height : -1; root.height = Math.max(lh, rh) + 1; return root; }; const newRoot = build(0, n - 1, undefined); this._setRoot(newRoot); this._size = n; this._count = total; return true; } /** * 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(); if (this._isMapMode) { this.bfs(node => out.add(node.key, undefined, node.count)); } else { this.bfs(node => out.add(node.key, node.value, node.count)); } if (this._isMapMode) out._store = this._store; return out; } /** * Create a new AVLTreeCounter 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 AVLTreeCounter 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; } /** * (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 AVLTreeCounter 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, count), finalValue]; } return [this._createNode(keyNodeOrEntry, value, 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, height } = destNode; const tempNode = this._createNode(key, value, count); if (tempNode) { tempNode.height = height; destNode.key = srcNode.key; if (!this._isMapMode) destNode.value = srcNode.value; destNode.count = srcNode.count; destNode.height = srcNode.height; srcNode.key = tempNode.key; if (!this._isMapMode) srcNode.value = tempNode.value; srcNode.count = tempNode.count; srcNode.height = tempNode.height; } 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.AVLTreeCounter = AVLTreeCounter;