UNPKG

heap-typed

Version:

Heap. Javascript & Typescript Data Structure.

1,147 lines (1,033 loc) 95.5 kB
/** * data-structure-typed * * @author Pablo Zeng * @copyright Copyright (c) 2022 Pablo Zeng <zrwusa@gmail.com> * @license MIT License */ import { BinaryTreeDeleteResult, BinaryTreeNested, BinaryTreeNodeNested, BinaryTreeOptions, BinaryTreePrintOptions, BTNEntry, BTNRep, DFSOrderPattern, DFSStackItem, EntryCallback, FamilyPosition, IterationType, NodeCallback, NodeDisplayLayout, NodePredicate, OptNodeOrNull, ToEntryFn } from '../../types'; import { IBinaryTree } from '../../interfaces'; import { isComparable, trampoline } from '../../utils'; import { Queue } from '../queue'; import { IterableEntryBase } from '../base'; import { DFSOperation } from '../../constants'; /** * Represents a node in a binary tree. * @template V - The type of data stored in the node. * @template NODE - The type of the family relationship in the binary tree. */ export class BinaryTreeNode< K = any, V = any, NODE extends BinaryTreeNode<K, V, NODE> = BinaryTreeNode<K, V, BinaryTreeNodeNested<K, V>> > { key: K; value?: V; parent?: NODE; constructor(key: K, value?: V) { this.key = key; this.value = value; } protected _left?: OptNodeOrNull<NODE>; get left(): OptNodeOrNull<NODE> { return this._left; } set left(v: OptNodeOrNull<NODE>) { if (v) { v.parent = this as unknown as NODE; } this._left = v; } protected _right?: OptNodeOrNull<NODE>; get right(): OptNodeOrNull<NODE> { return this._right; } set right(v: OptNodeOrNull<NODE>) { if (v) { v.parent = this as unknown as NODE; } this._right = v; } get familyPosition(): FamilyPosition { const that = this as unknown as NODE; if (!this.parent) { return this.left || this.right ? 'ROOT' : 'ISOLATED'; } if (this.parent.left === that) { return this.left || this.right ? 'ROOT_LEFT' : 'LEFT'; } else if (this.parent.right === that) { return this.left || this.right ? 'ROOT_RIGHT' : 'RIGHT'; } return 'MAL_NODE'; } } /** * 1. Two Children Maximum: Each node has at most two children. * 2. Left and Right Children: Nodes have distinct left and right children. * 3. Depth and Height: Depth is the number of edges from the root to a node; height is the maximum depth in the tree. * 4. Subtrees: Each child of a node forms the root of a subtree. * 5. Leaf Nodes: Nodes without children are leaves. */ export class BinaryTree< K = any, V = any, R = object, NODE extends BinaryTreeNode<K, V, NODE> = BinaryTreeNode<K, V, BinaryTreeNodeNested<K, V>>, TREE extends BinaryTree<K, V, R, NODE, TREE> = BinaryTree<K, V, R, NODE, BinaryTreeNested<K, V, R, NODE>> > extends IterableEntryBase<K, V | undefined> implements IBinaryTree<K, V, R, NODE, TREE> { iterationType: IterationType = 'ITERATIVE'; /** * The constructor initializes a binary tree with optional options and adds keys, nodes, entries, or * raw data if provided. * @param keysNodesEntriesOrRaws - The `keysNodesEntriesOrRaws` parameter in the constructor * is an iterable that can contain elements of type `BTNRep<K, V, NODE>` or `R`. It is * initialized with an empty array `[]` by default. * @param [options] - The `options` parameter in the constructor is an object that can contain the * following properties: */ constructor(keysNodesEntriesOrRaws: Iterable<BTNRep<K, V, NODE> | R> = [], options?: BinaryTreeOptions<K, V, R>) { super(); if (options) { const { iterationType, toEntryFn, isMapMode } = options; if (iterationType) this.iterationType = iterationType; if (isMapMode !== undefined) this._isMapMode = isMapMode; if (typeof toEntryFn === 'function') this._toEntryFn = toEntryFn; else if (toEntryFn) throw TypeError('toEntryFn must be a function type'); } if (keysNodesEntriesOrRaws) this.addMany(keysNodesEntriesOrRaws); } protected _isMapMode = false; get isMapMode() { return this._isMapMode; } protected _store = new Map<K, V | undefined>(); get store() { return this._store; } protected _root?: OptNodeOrNull<NODE>; get root(): OptNodeOrNull<NODE> { return this._root; } protected _size: number = 0; get size(): number { return this._size; } protected _NIL: NODE = new BinaryTreeNode<K, V>(NaN as K) as unknown as NODE; get NIL(): NODE { return this._NIL; } protected _toEntryFn?: ToEntryFn<K, V, R>; get toEntryFn() { return this._toEntryFn; } /** * The function creates a new binary tree node with a specified key and optional value. * @param {K} key - The `key` parameter is the key of the node being created in the binary tree. * @param {V} [value] - The `value` parameter in the `createNode` function is optional, meaning it is * not required to be provided when calling the function. If a `value` is provided, it should be of * type `V`, which is the type of the value associated with the node. * @returns A new BinaryTreeNode instance with the provided key and value is being returned, casted * as NODE. */ createNode(key: K, value?: V): NODE { return new BinaryTreeNode<K, V, NODE>(key, value) as NODE; } /** * The function creates a binary tree with the specified options. * @param [options] - The `options` parameter in the `createTree` function is an optional parameter * that allows you to provide partial configuration options for creating a binary tree. It is of type * `Partial<BinaryTreeOptions<K, V, R>>`, which means you can pass in an object containing a subset * of properties * @returns A new instance of a binary tree with the specified options is being returned. */ createTree(options?: BinaryTreeOptions<K, V, R>): TREE { return new BinaryTree<K, V, R, NODE, TREE>([], { iterationType: this.iterationType, isMapMode: this._isMapMode, toEntryFn: this._toEntryFn, ...options }) as TREE; } /** * The function `keyValueNodeEntryRawToNodeAndValue` converts various input types into a node object * or returns null. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The * `keyValueNodeEntryRawToNodeAndValue` function takes in a parameter `keyNodeEntryOrRaw`, which * can be of type `BTNRep<K, V, NODE>` or `R`. This parameter represents either a key, a * node, an entry * @param {V} [value] - The `value` parameter in the `keyValueNodeEntryRawToNodeAndValue` function is * an optional parameter of type `V`. It represents the value associated with the key in the node * being created. If a `value` is provided, it will be used when creating the node. If * @returns The `keyValueNodeEntryRawToNodeAndValue` function returns an optional node * (`OptNodeOrNull<NODE>`) based on the input parameters provided. The function checks the type of the * input parameter (`keyNodeEntryOrRaw`) and processes it accordingly to return a node or null * value. */ keyValueNodeEntryRawToNodeAndValue( keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R, value?: V ): [OptNodeOrNull<NODE>, V | undefined] { if (keyNodeEntryOrRaw === undefined) return [undefined, undefined]; if (keyNodeEntryOrRaw === null) return [null, undefined]; if (this.isNode(keyNodeEntryOrRaw)) return [keyNodeEntryOrRaw, value]; if (this.isEntry(keyNodeEntryOrRaw)) { const [key, entryValue] = keyNodeEntryOrRaw; if (key === undefined) return [undefined, undefined]; else if (key === null) return [null, undefined]; const finalValue = value ?? entryValue; return [this.createNode(key, finalValue), finalValue]; } if (this.isKey(keyNodeEntryOrRaw)) return [this.createNode(keyNodeEntryOrRaw, value), value]; if (this.isRaw(keyNodeEntryOrRaw)) { if (this._toEntryFn) { const [key, entryValue] = this._toEntryFn(keyNodeEntryOrRaw); const finalValue = value ?? entryValue; if (this.isKey(key)) return [this.createNode(key, finalValue), finalValue]; } return [undefined, undefined]; } return [undefined, undefined]; } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * The function `ensureNode` in TypeScript checks if a given input is a node, entry, key, or raw * value and returns the corresponding node or null. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The `keyNodeEntryOrRaw` * parameter in the `ensureNode` function can be of type `BTNRep<K, V, NODE>` or `R`. It * is used to determine whether the input is a key, node, entry, or raw data. The * @param {IterationType} iterationType - The `iterationType` parameter in the `ensureNode` function * is used to specify the type of iteration to be performed. It has a default value of * `this.iterationType` if not explicitly provided. * @returns The `ensureNode` function returns either a node, `null`, or `undefined` based on the * conditions specified in the code snippet. */ ensureNode( keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R, iterationType: IterationType = this.iterationType ): OptNodeOrNull<NODE> { if (keyNodeEntryOrRaw === null) return null; if (keyNodeEntryOrRaw === undefined) return; if (keyNodeEntryOrRaw === this._NIL) return; if (this.isNode(keyNodeEntryOrRaw)) return keyNodeEntryOrRaw; if (this.isEntry(keyNodeEntryOrRaw)) { const key = keyNodeEntryOrRaw[0]; if (key === null) return null; if (key === undefined) return; return this.getNodeByKey(key, iterationType); } if (this._toEntryFn) { const [key] = this._toEntryFn(keyNodeEntryOrRaw as R); if (this.isKey(key)) return this.getNodeByKey(key); } if (this.isKey(keyNodeEntryOrRaw)) return this.getNodeByKey(keyNodeEntryOrRaw, iterationType); return; } /** * The function isNode checks if the input is an instance of BinaryTreeNode. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The parameter * `keyNodeEntryOrRaw` can be either a key, a node, an entry, or raw data. The function is * checking if the input is an instance of a `BinaryTreeNode` and returning a boolean value * accordingly. * @returns The function `isNode` is checking if the input `keyNodeEntryOrRaw` is an instance of * `BinaryTreeNode`. If it is, the function returns `true`, indicating that the input is a node. If * it is not an instance of `BinaryTreeNode`, the function returns `false`, indicating that the input * is not a node. */ isNode(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): keyNodeEntryOrRaw is NODE { return keyNodeEntryOrRaw instanceof BinaryTreeNode; } isRaw(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): keyNodeEntryOrRaw is R { return typeof keyNodeEntryOrRaw === 'object'; } /** * The function `isRealNode` checks if a given input is a valid node in a binary tree. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The `keyNodeEntryOrRaw` * parameter in the `isRealNode` function can be of type `BTNRep<K, V, NODE>` or `R`. * The function checks if the input parameter is a `NODE` type by verifying if it is not equal * @returns The function `isRealNode` is checking if the input `keyNodeEntryOrRaw` is a valid * node by comparing it to `this._NIL`, `null`, and `undefined`. If the input is not one of these * values, it then calls the `isNode` method to further determine if the input is a node. The * function will return a boolean value indicating whether the */ isRealNode(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): keyNodeEntryOrRaw is NODE { if (keyNodeEntryOrRaw === this._NIL || keyNodeEntryOrRaw === null || keyNodeEntryOrRaw === undefined) return false; return this.isNode(keyNodeEntryOrRaw); } /** * The function checks if a given input is a valid node or null. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The parameter * `keyNodeEntryOrRaw` in the `isRealNodeOrNull` function can be of type `BTNRep<K, * V, NODE>` or `R`. It is a union type that can either be a key, a node, an entry, or * @returns The function `isRealNodeOrNull` is returning a boolean value. It checks if the input * `keyNodeEntryOrRaw` is either `null` or a real node, and returns `true` if it is a node or * `null`, and `false` otherwise. */ isRealNodeOrNull(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): keyNodeEntryOrRaw is NODE | null { return keyNodeEntryOrRaw === null || this.isRealNode(keyNodeEntryOrRaw); } /** * The function isNIL checks if a given key, node, entry, or raw value is equal to the _NIL value. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - BTNRep<K, V, * NODE> | R * @returns The function is checking if the `keyNodeEntryOrRaw` parameter is equal to the `_NIL` * property of the current object and returning a boolean value based on that comparison. */ isNIL(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): boolean { return keyNodeEntryOrRaw === this._NIL; } /** * The function determines whether a given key, node, entry, or raw data is a leaf node in a binary * tree. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The parameter * `keyNodeEntryOrRaw` can be of type `BTNRep<K, V, NODE>` or `R`. It represents a * key, node, entry, or raw data in a binary tree structure. The function `isLeaf` checks whether the * provided * @returns The function `isLeaf` returns a boolean value indicating whether the input * `keyNodeEntryOrRaw` is a leaf node in a binary tree. */ isLeaf(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): boolean { keyNodeEntryOrRaw = this.ensureNode(keyNodeEntryOrRaw); if (keyNodeEntryOrRaw === undefined) return false; if (keyNodeEntryOrRaw === null) return true; return !this.isRealNode(keyNodeEntryOrRaw.left) && !this.isRealNode(keyNodeEntryOrRaw.right); } /** * The function `isEntry` checks if the input is a BTNEntry object by verifying if it is an array * with a length of 2. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The `keyNodeEntryOrRaw` * parameter in the `isEntry` function can be of type `BTNRep<K, V, NODE>` or type `R`. * The function checks if the provided `keyNodeEntryOrRaw` is of type `BTN * @returns The `isEntry` function is checking if the `keyNodeEntryOrRaw` parameter is an array * with a length of 2. If it is, then it returns `true`, indicating that the parameter is of type * `BTNEntry<K, V>`. If the condition is not met, it returns `false`. */ isEntry(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): keyNodeEntryOrRaw is BTNEntry<K, V> { return Array.isArray(keyNodeEntryOrRaw) && keyNodeEntryOrRaw.length === 2; } /** * Time Complexity O(1) * Space Complexity O(1) * * The function `isKey` checks if a given key is comparable. * @param {any} key - The `key` parameter is of type `any`, which means it can be any data type in * TypeScript. * @returns The function `isKey` is checking if the `key` parameter is `null` or if it is comparable. * If the `key` is `null`, the function returns `true`. Otherwise, it returns the result of the * `isComparable` function, which is not provided in the code snippet. */ isKey(key: any): key is K { if (key === null) return true; return isComparable(key); } /** * Time Complexity O(n) * Space Complexity O(1) * * The `add` function in TypeScript adds a new node to a binary tree while handling duplicate keys * and finding the correct insertion position. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw - The `add` method you provided * seems to be for adding a new node to a binary tree structure. The `keyNodeEntryOrRaw` * parameter in the method can accept different types of values: * @param {V} [value] - The `value` parameter in the `add` method represents the value associated * with the key that you want to add to the binary tree. When adding a key-value pair to the binary * tree, you provide the key and its corresponding value. The `add` method then creates a new node * with this * @returns The `add` method returns a boolean value. It returns `true` if the insertion of the new * node was successful, and `false` if the insertion position could not be found or if a duplicate * key was found and the node was replaced instead of inserted. */ add(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R, value?: V): boolean { const [newNode, newValue] = this.keyValueNodeEntryRawToNodeAndValue(keyNodeEntryOrRaw, value); if (newNode === undefined) return false; // If the tree is empty, directly set the new node as the root node if (!this._root) { this._setRoot(newNode); if (this._isMapMode) this._setValue(newNode?.key, newValue); this._size = 1; return true; } const queue = new Queue<NODE>([this._root]); let potentialParent: NODE | undefined; // Record the parent node of the potential insertion location while (queue.size > 0) { const cur = queue.shift(); if (!cur) continue; // Check for duplicate keys when newNode is not null if (newNode !== null && cur.key === newNode.key) { this._replaceNode(cur, newNode); if (this._isMapMode) this._setValue(cur.key, newValue); return true; // If duplicate keys are found, no insertion is performed } // Record the first possible insertion location found if (potentialParent === undefined && (cur.left === undefined || cur.right === undefined)) { potentialParent = cur; } // Continue traversing the left and right subtrees if (cur.left !== null) { if (cur.left) queue.push(cur.left); } if (cur.right !== null) { if (cur.right) queue.push(cur.right); } } // At the end of the traversal, if the insertion position is found, insert if (potentialParent) { if (potentialParent.left === undefined) { potentialParent.left = newNode; } else if (potentialParent.right === undefined) { potentialParent.right = newNode; } if (this._isMapMode) this._setValue(newNode?.key, newValue); this._size++; return true; } return false; // If the insertion position cannot be found, return undefined } /** * Time Complexity: O(k * n) * Space Complexity: O(1) * * The `addMany` function takes in multiple keys or nodes or entries or raw values along with * optional values, and adds them to a data structure while returning an array indicating whether * each insertion was successful. * @param keysNodesEntriesOrRaws - `keysNodesEntriesOrRaws` is an iterable that can contain a * mix of keys, nodes, entries, or raw values. Each element in this iterable can be of type * `BTNRep<K, V, NODE>` or `R`. * @param [values] - The `values` parameter in the `addMany` function is an optional parameter that * accepts an iterable of values. These values correspond to the keys or nodes being added in the * `keysNodesEntriesOrRaws` parameter. If provided, the function will iterate over the values and * assign them * @returns The `addMany` method returns an array of boolean values indicating whether each key, * node, entry, or raw value was successfully added to the data structure. Each boolean value * corresponds to the success of adding the corresponding key or value in the input iterable. */ addMany(keysNodesEntriesOrRaws: Iterable<BTNRep<K, V, NODE> | R>, values?: Iterable<V | undefined>): boolean[] { // TODO not sure addMany not be run multi times const inserted: boolean[] = []; let valuesIterator: Iterator<V | undefined> | undefined; if (values) { valuesIterator = values[Symbol.iterator](); } for (const keyNodeEntryOrRaw of keysNodesEntriesOrRaws) { let value: V | undefined | null = undefined; if (valuesIterator) { const valueResult = valuesIterator.next(); if (!valueResult.done) { value = valueResult.value; } } inserted.push(this.add(keyNodeEntryOrRaw, value)); } return inserted; } /** * Time Complexity: O(k * n) * Space Complexity: O(1) * * The `refill` function clears the existing data structure and then adds new key-value pairs based * on the provided input. * @param keysNodesEntriesOrRaws - The `keysNodesEntriesOrRaws` parameter in the `refill` * method can accept an iterable containing a mix of `BTNRep<K, V, NODE>` objects or `R` * objects. * @param [values] - The `values` parameter in the `refill` method is an optional parameter that * accepts an iterable of values of type `V` or `undefined`. */ refill(keysNodesEntriesOrRaws: Iterable<BTNRep<K, V, NODE> | R>, values?: Iterable<V | undefined>): void { this.clear(); this.addMany(keysNodesEntriesOrRaws, values); } /** * Time Complexity: O(n) * Space Complexity: O(1) * * The function `delete` in TypeScript implements the deletion of a node in a binary tree and returns * the deleted node along with information for tree balancing. * @param {BTNRep<K, V, NODE> | R} keyNodeEntryOrRaw * - The `delete` method you provided is used to delete a node from a binary tree based on the key, * node, entry or raw data. The method returns an array of * `BinaryTreeDeleteResult` objects containing information about the deleted node and whether * balancing is needed. * @returns The `delete` method returns an array of `BinaryTreeDeleteResult` objects. Each object in * the array contains information about the node that was deleted (`deleted`) and the node that may * need to be balanced (`needBalanced`). */ delete(keyNodeEntryOrRaw: BTNRep<K, V, NODE> | R): BinaryTreeDeleteResult<NODE>[] { const deletedResult: BinaryTreeDeleteResult<NODE>[] = []; if (!this._root) return deletedResult; const curr = this.getNode(keyNodeEntryOrRaw); if (!curr) return deletedResult; const parent: NODE | undefined = curr?.parent; let needBalanced: NODE | undefined; let orgCurrent: NODE | undefined = curr; if (!curr.left && !curr.right && !parent) { this._setRoot(undefined); } else if (curr.left) { const leftSubTreeRightMost = this.getRightMost(node => node, curr.left); 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; } } } else if (parent) { 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 { this._setRoot(curr.right); curr.right = undefined; } this._size = this._size - 1; deletedResult.push({ deleted: orgCurrent, needBalanced }); if (this._isMapMode && orgCurrent) this._store.delete(orgCurrent.key); return deletedResult; } /** * Time Complexity: O(n) * Space Complexity: O(k + log n) * * The function `getNodes` retrieves nodes from a binary tree based on a key, node, entry, raw data, * or predicate, with options for recursive or iterative traversal. * @param {BTNRep<K, V, NODE> | R | NodePredicate<NODE>} keyNodeEntryRawOrPredicate * - The `getNodes` function you provided takes several parameters: * @param [onlyOne=false] - The `onlyOne` parameter in the `getNodes` function is a boolean flag that * determines whether to return only the first node that matches the criteria specified by the * `keyNodeEntryRawOrPredicate` parameter. If `onlyOne` is set to `true`, the function will * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getNodes` function is used to specify the starting point for traversing the binary tree. It * represents the root node of the binary tree or the node from which the traversal should begin. If * not provided, the default value is set to `this._root * @param {IterationType} iterationType - The `iterationType` parameter in the `getNodes` function * determines the type of iteration to be performed when traversing the nodes of a binary tree. It * can have two possible values: * @returns The `getNodes` function returns an array of nodes that satisfy the provided condition * based on the input parameters and the iteration type specified. */ getNodes( keyNodeEntryRawOrPredicate: BTNRep<K, V, NODE> | R | NodePredicate<NODE>, onlyOne = false, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): NODE[] { if (keyNodeEntryRawOrPredicate === undefined) return []; if (keyNodeEntryRawOrPredicate === null) return []; startNode = this.ensureNode(startNode); if (!startNode) return []; const callback = this._ensurePredicate(keyNodeEntryRawOrPredicate); const ans: NODE[] = []; if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE) => { if (callback(cur)) { ans.push(cur); if (onlyOne) return; } if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return; if (this.isRealNode(cur.left)) dfs(cur.left); if (this.isRealNode(cur.right)) dfs(cur.right); }; dfs(startNode); } else { const stack = [startNode]; while (stack.length > 0) { const cur = stack.pop(); if (this.isRealNode(cur)) { if (callback(cur)) { ans.push(cur); if (onlyOne) return ans; } if (this.isRealNode(cur.left)) stack.push(cur.left); if (this.isRealNode(cur.right)) stack.push(cur.right); } } } return ans; } /** * Time Complexity: O(n) * Space Complexity: O(log n). * * The `getNode` function retrieves a node based on the provided key, node, entry, raw data, or * predicate. * @param {BTNRep<K, V, NODE> | R | NodePredicate<NODE>} keyNodeEntryRawOrPredicate * - The `keyNodeEntryRawOrPredicate` parameter in the `getNode` function can accept a key, * node, entry, raw data, or a predicate function. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getNode` function is used to specify the starting point for searching for a node in a binary * tree. If no specific starting point is provided, the default value is set to `this._root`, which * is typically the root node of the binary tree. * @param {IterationType} iterationType - The `iterationType` parameter in the `getNode` method is * used to specify the type of iteration to be performed when searching for a node. It has a default * value of `this.iterationType`, which means it will use the iteration type defined in the current * context if no specific value is provided * @returns The `getNode` function is returning the first node that matches the specified criteria, * or `null` if no matching node is found. */ getNode( keyNodeEntryRawOrPredicate: BTNRep<K, V, NODE> | R | NodePredicate<NODE>, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): OptNodeOrNull<NODE> { return this.getNodes(keyNodeEntryRawOrPredicate, true, startNode, iterationType)[0] ?? null; } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * The function `getNodeByKey` retrieves a node by its key from a binary tree structure. * @param {K} key - The `key` parameter is the value used to search for a specific node in a data * structure. * @param {IterationType} iterationType - The `iterationType` parameter is a type of iteration that * specifies how the tree nodes should be traversed when searching for a node with the given key. It * is an optional parameter with a default value of `this.iterationType`. * @returns The `getNodeByKey` function is returning an optional binary tree node * (`OptNodeOrNull<NODE>`). */ getNodeByKey(key: K, iterationType: IterationType = this.iterationType): OptNodeOrNull<NODE> { return this.getNode(key, this._root, iterationType); } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * This function overrides the `get` method to retrieve the value associated with a specified key, * node, entry, raw data, or predicate in a data structure. * @param {BTNRep<K, V, NODE> | R | NodePredicate<NODE>} keyNodeEntryRawOrPredicate * - The `keyNodeEntryRawOrPredicate` parameter in the `get` method can accept one of the * following types: * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the `get` * method is used to specify the starting point for searching for a key or node in the binary tree. * If no specific starting point is provided, the default starting point is the root of the binary * tree (`this._root`). * @param {IterationType} iterationType - The `iterationType` parameter in the `get` method is used * to specify the type of iteration to be performed when searching for a key in the binary tree. It * is an optional parameter with a default value of `this.iterationType`, which means it will use the * iteration type defined in the * @returns The `get` method is returning the value associated with the specified key, node, entry, * raw data, or predicate in the binary tree map. If the specified key or node is found in the tree, * the method returns the corresponding value. If the key or node is not found, it returns * `undefined`. */ override get( keyNodeEntryRawOrPredicate: BTNRep<K, V, NODE> | R, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): V | undefined { if (this._isMapMode) { const key = this._getKey(keyNodeEntryRawOrPredicate); if (key === null || key === undefined) return; return this._store.get(key); } return this.getNode(keyNodeEntryRawOrPredicate, startNode, iterationType)?.value; } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * The `has` function in TypeScript checks if a specified key, node, entry, raw data, or predicate * exists in the data structure. * @param {BTNRep<K, V, NODE> | R | NodePredicate<NODE>} keyNodeEntryRawOrPredicate * - The `keyNodeEntryRawOrPredicate` parameter in the `override has` method can accept one of * the following types: * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `override` method is used to specify the starting point for the search operation within the data * structure. It defaults to `this._root` if not provided explicitly. * @param {IterationType} iterationType - The `iterationType` parameter in the `override has` method * is used to specify the type of iteration to be performed. It has a default value of * `this.iterationType`, which means it will use the iteration type defined in the current context if * no value is provided when calling the method. * @returns The `override has` method is returning a boolean value. It checks if there are any nodes * that match the provided key, node, entry, raw data, or predicate in the tree structure. If there * are matching nodes, it returns `true`, indicating that the tree contains the specified element. * Otherwise, it returns `false`. */ override has( keyNodeEntryRawOrPredicate: BTNRep<K, V, NODE> | R | NodePredicate<NODE>, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): boolean { return this.getNodes(keyNodeEntryRawOrPredicate, true, startNode, iterationType).length > 0; } /** * Time Complexity: O(1) * Space Complexity: O(1) * * The `clear` function resets the root node and size of a data structure to empty. */ clear() { this._clearNodes(); if (this._isMapMode) this._clearValues(); } /** * Time Complexity: O(1) * Space Complexity: O(1) * * The `isEmpty` function in TypeScript checks if a data structure has no elements and returns a * boolean value. * @returns The `isEmpty()` method is returning a boolean value, specifically `true` if the `_size` * property is equal to 0, indicating that the data structure is empty, and `false` otherwise. */ isEmpty(): boolean { return this._size === 0; } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * The function checks if a binary tree is perfectly balanced by comparing its minimum height with * its height. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter is the starting * point for checking if the binary tree is perfectly balanced. It represents the root node of the * binary tree or a specific node from which the balance check should begin. * @returns The method `isPerfectlyBalanced` is returning a boolean value, which indicates whether * the tree starting from the `startNode` node is perfectly balanced or not. The return value is * determined by comparing the minimum height of the tree with the height of the tree. If the minimum * height plus 1 is greater than or equal to the height of the tree, then it is considered perfectly * balanced and */ isPerfectlyBalanced(startNode: BTNRep<K, V, NODE> | R = this._root): boolean { return this.getMinHeight(startNode) + 1 >= this.getHeight(startNode); } /** * Time Complexity: O(n) * Space Complexity: O(1) * * The function `isBST` in TypeScript checks if a binary search tree is valid using either recursive * or iterative methods. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the `isBST` * function represents the starting point for checking whether a binary search tree (BST) is valid. * It can be a node in the BST or a reference to the root of the BST. If no specific node is * provided, the function will default to * @param {IterationType} iterationType - The `iterationType` parameter in the `isBST` function * determines whether the function should use a recursive approach or an iterative approach to check * if the binary search tree (BST) is valid. * @returns The `isBST` method is returning a boolean value, which indicates whether the binary * search tree (BST) represented by the given root node is a valid BST or not. The method checks if * the tree satisfies the BST property, where for every node, all nodes in its left subtree have keys * less than the node's key, and all nodes in its right subtree have keys greater than the node's */ isBST(startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType): boolean { // TODO there is a bug startNode = this.ensureNode(startNode); if (!startNode) return true; if (iterationType === 'RECURSIVE') { const dfs = (cur: OptNodeOrNull<NODE>, min: number, max: number): boolean => { if (!this.isRealNode(cur)) return true; const numKey = Number(cur.key); if (numKey <= min || numKey >= max) return false; return dfs(cur.left, min, numKey) && dfs(cur.right, numKey, max); }; const isStandardBST = dfs(startNode, Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER); const isInverseBST = dfs(startNode, Number.MAX_SAFE_INTEGER, Number.MIN_SAFE_INTEGER); return isStandardBST || isInverseBST; } else { const checkBST = (checkMax = false) => { const stack = []; let prev = checkMax ? Number.MAX_SAFE_INTEGER : Number.MIN_SAFE_INTEGER; // @ts-ignore let curr: OptNodeOrNull<NODE> = startNode; while (this.isRealNode(curr) || stack.length > 0) { while (this.isRealNode(curr)) { stack.push(curr); curr = curr.left; } curr = stack.pop()!; const numKey = Number(curr.key); if (!this.isRealNode(curr) || (!checkMax && prev >= numKey) || (checkMax && prev <= numKey)) return false; prev = numKey; curr = curr.right; } return true; }; const isStandardBST = checkBST(false), isInverseBST = checkBST(true); return isStandardBST || isInverseBST; } } /** * Time Complexity: O(n) * Space Complexity: O(1) * * The `getDepth` function calculates the depth between two nodes in a binary tree. * @param {BTNRep<K, V, NODE> | R} dist - The `dist` parameter in the `getDepth` * function represents the node or entry in a binary tree map, or a reference to a node in the tree. * It is the target node for which you want to calculate the depth from the `startNode` node. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getDepth` function represents the starting point from which you want to calculate the depth of a * given node or entry in a binary tree. If no specific starting point is provided, the default value * for `startNode` is set to the root of the binary * @returns The `getDepth` method returns the depth of a given node `dist` relative to the * `startNode` node in a binary tree. If the `dist` node is not found in the path to the `startNode` * node, it returns the depth of the `dist` node from the root of the tree. */ getDepth(dist: BTNRep<K, V, NODE> | R, startNode: BTNRep<K, V, NODE> | R = this._root): number { let distEnsured = this.ensureNode(dist); const beginRootEnsured = this.ensureNode(startNode); let depth = 0; while (distEnsured?.parent) { if (distEnsured === beginRootEnsured) { return depth; } depth++; distEnsured = distEnsured.parent; } return depth; } /** * Time Complexity: O(n) * Space Complexity: O(1) * * The `getHeight` function calculates the maximum height of a binary tree using either a recursive * or iterative approach in TypeScript. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter is the starting * point from which the height of the binary tree will be calculated. It can be a node in the binary * tree or a reference to the root of the tree. If not provided, it defaults to the root of the * binary tree data structure. * @param {IterationType} iterationType - The `iterationType` parameter is used to determine the type * of iteration to be performed while calculating the height of the binary tree. It can have two * possible values: * @returns The `getHeight` method returns the height of the binary tree starting from the specified * root node. The height is calculated based on the maximum depth of the tree, considering either a * recursive approach or an iterative approach depending on the `iterationType` parameter. */ getHeight(startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType): number { startNode = this.ensureNode(startNode); if (!this.isRealNode(startNode)) return -1; if (iterationType === 'RECURSIVE') { const _getMaxHeight = (cur: OptNodeOrNull<NODE>): number => { if (!this.isRealNode(cur)) return -1; const leftHeight = _getMaxHeight(cur.left); const rightHeight = _getMaxHeight(cur.right); return Math.max(leftHeight, rightHeight) + 1; }; return _getMaxHeight(startNode); } else { const stack: { node: NODE; depth: number }[] = [{ node: startNode, depth: 0 }]; let maxHeight = 0; while (stack.length > 0) { const { node, depth } = stack.pop()!; if (this.isRealNode(node.left)) stack.push({ node: node.left, depth: depth + 1 }); if (this.isRealNode(node.right)) stack.push({ node: node.right, depth: depth + 1 }); maxHeight = Math.max(maxHeight, depth); } return maxHeight; } } /** * Time Complexity: O(n) * Space Complexity: O(log n) * * The `getMinHeight` function calculates the minimum height of a binary tree using either a * recursive or iterative approach in TypeScript. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getMinHeight` function represents the starting node from which the minimum height of the binary * tree will be calculated. It is either a node in the binary tree or a reference to the root of the * tree. If not provided, the default value is the root * @param {IterationType} iterationType - The `iterationType` parameter in the `getMinHeight` method * specifies the type of iteration to use when calculating the minimum height of a binary tree. It * can have two possible values: * @returns The `getMinHeight` method returns the minimum height of the binary tree starting from the * specified root node. The height is calculated based on the shortest path from the root node to a * leaf node in the tree. The method uses either a recursive approach or an iterative approach (using * a stack) based on the `iterationType` parameter. */ getMinHeight( startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): number { startNode = this.ensureNode(startNode); if (!startNode) return -1; if (iterationType === 'RECURSIVE') { const _getMinHeight = (cur: OptNodeOrNull<NODE>): number => { if (!this.isRealNode(cur)) return 0; if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return 0; const leftMinHeight = _getMinHeight(cur.left); const rightMinHeight = _getMinHeight(cur.right); return Math.min(leftMinHeight, rightMinHeight) + 1; }; return _getMinHeight(startNode); } else { const stack: NODE[] = []; let node: OptNodeOrNull<NODE> = startNode, last: OptNodeOrNull<NODE> = null; const depths: Map<NODE, number> = new Map(); while (stack.length > 0 || node) { if (this.isRealNode(node)) { stack.push(node); node = node.left; } else { node = stack[stack.length - 1]; if (!this.isRealNode(node.right) || last === node.right) { node = stack.pop(); if (this.isRealNode(node)) { const leftMinHeight = this.isRealNode(node.left) ? depths.get(node.left)! : -1; const rightMinHeight = this.isRealNode(node.right) ? depths.get(node.right)! : -1; depths.set(node, 1 + Math.min(leftMinHeight, rightMinHeight)); last = node; node = null; } } else node = node.right; } } return depths.get(startNode)!; } } /** * Time Complexity: O(log n) * Space Complexity: O(log n) * * The function `getPathToRoot` in TypeScript retrieves the path from a given node to the root of a * tree structure, applying a specified callback function along the way. * @param {C} callback - The `callback` parameter is a function that is used to process each node in * the path to the root. It is expected to be a function that takes a node as an argument and returns * a value based on that node. The return type of the callback function is determined by the generic * type `C * @param {BTNRep<K, V, NODE> | R} beginNode - The `beginNode` parameter in the * `getPathToRoot` function can be either a key, a node, an entry, or any other value of type `R`. * @param [isReverse=true] - The `isReverse` parameter in the `getPathToRoot` function determines * whether the resulting path from the given `beginNode` to the root should be in reverse order or * not. If `isReverse` is set to `true`, the path will be reversed before being returned. If `is * @returns The function `getPathToRoot` returns an array of the return values of the callback * function `callback` applied to each node in the path from the `beginNode` to the root node. The * array is either in reverse order or in the original order based on the value of the `isReverse` * parameter. */ getPathToRoot<C extends NodeCallback<OptNodeOrNull<NODE>>>( callback: C = this._DEFAULT_NODE_CALLBACK as C, beginNode: BTNRep<K, V, NODE> | R, isReverse = true ): ReturnType<C>[] { const result: ReturnType<C>[] = []; let beginNodeEnsured = this.ensureNode(beginNode); if (!beginNodeEnsured) return result; while (beginNodeEnsured.parent) { // Array.push + Array.reverse is more efficient than Array.unshift result.push(callback(beginNodeEnsured)); beginNodeEnsured = beginNodeEnsured.parent; } result.push(callback(beginNodeEnsured)); return isReverse ? result.reverse() : result; } /** * Time Complexity: O(log n) * Space Complexity: O(1) * * The function `getLeftMost` retrieves the leftmost node in a binary tree using either recursive or * tail-recursive iteration. * @param {C} callback - The `callback` parameter is a function that will be called with the leftmost * node of a binary tree or with `undefined` if the tree is empty. It is provided with a default * value of `_DEFAULT_NODE_CALLBACK` if not specified. * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getLeftMost` function represents the starting point for finding the leftmost node in a binary * tree. It can be either a key, a node, or an entry in the binary tree structure. If no specific * starting point is provided, the function will default * @param {IterationType} iterationType - The `iterationType` parameter in the `getLeftMost` function * specifies the type of iteration to be used when traversing the binary tree nodes. It can have two * possible values: * @returns The `getLeftMost` function returns the result of the callback function `C` applied to the * leftmost node in the binary tree starting from the `startNode` node. If the `startNode` node is * `NIL`, it returns the result of the callback function applied to `undefined`. If the `startNode` * node is not a real node, it returns the result of the callback */ getLeftMost<C extends NodeCallback<OptNodeOrNull<NODE>>>( callback: C = this._DEFAULT_NODE_CALLBACK as C, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): ReturnType<C> { if (this.isNIL(startNode)) return callback(undefined); startNode = this.ensureNode(startNode); if (!this.isRealNode(startNode)) return callback(startNode); if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE): NODE => { if (!this.isRealNode(cur.left)) return cur; return dfs(cur.left); }; return callback(dfs(startNode)); } else { // Indirect implementation of iteration using tail recursion optimization const dfs = trampoline((cur: NODE): NODE => { if (!this.isRealNode(cur.left)) return cur; return dfs.cont(cur.left); }); return callback(dfs(startNode)); } } /** * Time Complexity: O(log n) * Space Complexity: O(1) * * The function `getRightMost` retrieves the rightmost node in a binary tree using either recursive * or iterative traversal methods. * @param {C} callback - The `callback` parameter is a function that will be called with the result * of finding the rightmost node in a binary tree. It is of type `NodeCallback<OptNodeOrNull<NODE>>`, * which means it is a callback function that can accept either an optional binary tree node or null * as * @param {BTNRep<K, V, NODE> | R} startNode - The `startNode` parameter in the * `getRightMost` function represents the starting point for finding the rightmost node in a binary * tree. It can be either a key, a node, or an entry in the binary tree structure. If no specific * starting point is provided, the function will default * @param {IterationType} iterationType - The `iterationType` parameter in the `getRightMost` * function specifies the type of iteration to be used when traversing the binary tree nodes. It can * have two possible values: * @returns The `getRightMost` function returns the result of the callback function `C`, which is * passed as a parameter to the function. The callback function is called with the rightmost node in * the binary tree structure, determined based on the specified iteration type ('RECURSIVE' or * other). */ getRightMost<C extends NodeCallback<OptNodeOrNull<NODE>>>( callback: C = this._DEFAULT_NODE_CALLBACK as C, startNode: BTNRep<K, V, NODE> | R = this._root, iterationType: IterationType = this.iterationType ): ReturnType<C> { if (this.isNIL(startNode)) return callback(undefined); // TODO support get right most by passing key in startNode = this.ensureNode(startNode); if (!startNode) return callback(startNode); if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE): NODE => { if (!this.isRealNode(cur.right)) return cur; return dfs(cur.right); }; return callback(dfs(startNode)); } else { // Indirect implementation of iteration using tail recursion optimization const dfs = trampoline((cur: NODE) => { if (!this.isRealNode(cur.right)) return cur; return dfs.cont(cur.right); }); return callback(dfs(startNode)); } } /** * Time Complexity: O(log n) * Space Complexity: O(1) *