trie-typed
Version:
Trie, prefix tree
375 lines (374 loc) • 13.9 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.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;