diff --git a/scripts/testToExample.ts b/scripts/testToExample.ts index a2c6891..b2761f8 100644 --- a/scripts/testToExample.ts +++ b/scripts/testToExample.ts @@ -216,9 +216,9 @@ function updateExamples(testDir: string, sourceBaseDir: string): void { const relativePath = path.relative(testDir, file); const sourceFilePath = path.resolve(sourceBaseDir, relativePath.replace('.test.ts', '.ts')); - const className = toPascalCase(path.basename(sourceFilePath, '.ts')); - - addExamplesToSourceFile(sourceFilePath, toPascalCase(className), examples); + let className = toPascalCase(path.basename(sourceFilePath, '.ts')); + if (className === 'Bst') className = 'BST'; + addExamplesToSourceFile(sourceFilePath, className, examples); const dirKey = dirMap[className]; if (!dirKey) { diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index c7369f1..1b3635d 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -434,7 +434,7 @@ export class AVLTree< */ protected _balancePath(node: BTNRep | R): void { node = this.ensureNode(node); - const path = this.getPathToRoot(node => node, node, false); // first O(log n) + O(log n) + const path = this.getPathToRoot(node, node => node, false); // first O(log n) + O(log n) for (let i = 0; i < path.length; i++) { // second O(log n) const A = path[i]; diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index 0b94a5f..7f20f0a 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -302,6 +302,13 @@ export class BinaryTree< return keyNodeEntryOrRaw instanceof BinaryTreeNode; } + /** + * The function `isRaw` checks if the input parameter is of type `R` by verifying if it is an object. + * @param {BTNRep | R} keyNodeEntryOrRaw - BTNRep | R + * @returns The function `isRaw` is checking if the `keyNodeEntryOrRaw` parameter is of type `R` by + * checking if it is an object. If the parameter is an object, the function will return `true`, + * indicating that it is of type `R`. + */ isRaw(keyNodeEntryOrRaw: BTNRep | R): keyNodeEntryOrRaw is R { return typeof keyNodeEntryOrRaw === 'object'; } @@ -585,6 +592,75 @@ export class BinaryTree< return deletedResult; } + /** + * Time Complexity: O(n) + * Space Complexity: O(k + log n) + * + * The `search` function in TypeScript performs a depth-first or breadth-first search on a tree + * structure based on a given predicate or key, with options to return multiple results or just one. + * @param {BTNRep | R | NodePredicate} keyNodeEntryRawOrPredicate - The + * `keyNodeEntryRawOrPredicate` parameter in the `search` function can accept three types of values: + * @param [onlyOne=false] - The `onlyOne` parameter in the `search` function is a boolean flag that + * determines whether the search should stop after finding the first matching node. If `onlyOne` is + * set to `true`, the search will return as soon as a matching node is found. If `onlyOne` is + * @param {C} callback - The `callback` parameter in the `search` function is a callback function + * that will be called on each node that matches the search criteria. It is of type `C`, which + * extends `NodeCallback`. The default value for `callback` is `this._DEFAULT_NODE_CALLBACK` if + * @param {BTNRep | R} startNode - The `startNode` parameter in the `search` function is + * used to specify the node from which the search operation should begin. It represents the starting + * point in the binary tree where the search will be performed. If no specific `startNode` is + * provided, the search operation will start from the root + * @param {IterationType} iterationType - The `iterationType` parameter in the `search` function + * specifies the type of iteration to be used when searching for nodes in a binary tree. It can have + * two possible values: + * @returns The `search` function returns an array of values that match the provided criteria based + * on the search algorithm implemented within the function. + */ + search>( + keyNodeEntryRawOrPredicate: BTNRep | R | NodePredicate, + onlyOne = false, + callback: C = this._DEFAULT_NODE_CALLBACK as C, + startNode: BTNRep | R = this._root, + iterationType: IterationType = this.iterationType + ): ReturnType[] { + if (keyNodeEntryRawOrPredicate === undefined) return []; + if (keyNodeEntryRawOrPredicate === null) return []; + startNode = this.ensureNode(startNode); + if (!startNode) return []; + const predicate = this._ensurePredicate(keyNodeEntryRawOrPredicate); + + const ans: ReturnType[] = []; + + if (iterationType === 'RECURSIVE') { + const dfs = (cur: NODE) => { + if (predicate(cur)) { + ans.push(callback(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 (predicate(cur)) { + ans.push(callback(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(k + log n) @@ -612,42 +688,7 @@ export class BinaryTree< startNode: BTNRep | 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; + return this.search(keyNodeEntryRawOrPredicate, onlyOne, node => node, startNode, iterationType); } /** @@ -675,7 +716,7 @@ export class BinaryTree< startNode: BTNRep | R = this._root, iterationType: IterationType = this.iterationType ): OptNodeOrNull { - return this.getNodes(keyNodeEntryRawOrPredicate, true, startNode, iterationType)[0] ?? null; + return this.search(keyNodeEntryRawOrPredicate, true, node => node, startNode, iterationType)[0] ?? null; } /** @@ -723,7 +764,7 @@ export class BinaryTree< iterationType: IterationType = this.iterationType ): V | undefined { if (this._isMapMode) { - const key = this._getKey(keyNodeEntryRawOrPredicate); + const key = this._extractKey(keyNodeEntryRawOrPredicate); if (key === null || key === undefined) return; return this._store.get(key); } @@ -756,7 +797,7 @@ export class BinaryTree< startNode: BTNRep | R = this._root, iterationType: IterationType = this.iterationType ): boolean { - return this.getNodes(keyNodeEntryRawOrPredicate, true, startNode, iterationType).length > 0; + return this.search(keyNodeEntryRawOrPredicate, true, node => node, startNode, iterationType).length > 0; } /** @@ -1023,9 +1064,9 @@ export class BinaryTree< * parameter. */ getPathToRoot>>( - callback: C = this._DEFAULT_NODE_CALLBACK as C, beginNode: BTNRep | R, - isReverse = true + callback: C = this._DEFAULT_NODE_CALLBACK as C, + isReverse = false ): ReturnType[] { const result: ReturnType[] = []; let beginNodeEnsured = this.ensureNode(beginNode); @@ -2144,16 +2185,16 @@ export class BinaryTree< * Time Complexity: O(1) * Space Complexity: O(1) * - * The function `_getKey` in TypeScript returns the key from a given input, which can be a node, + * The function `_extractKey` in TypeScript returns the key from a given input, which can be a node, * entry, raw data, or null/undefined. - * @param {BTNRep | R} keyNodeEntryOrRaw - The `_getKey` method you provided is a + * @param {BTNRep | R} keyNodeEntryOrRaw - The `_extractKey` method you provided is a * TypeScript method that takes in a parameter `keyNodeEntryOrRaw` of type `BTNRep | R`, * where `BTNRep` is a generic type with keys `K`, `V`, and `NODE`, and ` - * @returns The `_getKey` method returns the key value extracted from the `keyNodeEntryOrRaw` + * @returns The `_extractKey` method returns the key value extracted from the `keyNodeEntryOrRaw` * parameter. The return value can be a key value of type `K`, `null`, or `undefined`, depending on * the conditions checked in the method. */ - protected _getKey(keyNodeEntryOrRaw: BTNRep | R): K | null | undefined { + protected _extractKey(keyNodeEntryOrRaw: BTNRep | R): K | null | undefined { if (keyNodeEntryOrRaw === null) return null; if (keyNodeEntryOrRaw === undefined) return; if (keyNodeEntryOrRaw === this._NIL) return; diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index f5827fe..45cd80d 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -92,6 +92,58 @@ export class BSTNode = BSTNod * 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 + * // Find kth smallest element + * // Create a BST with some elements + * const bst = new BST([5, 3, 7, 1, 4, 6, 8]); + * const sortedKeys = bst.dfs(node => node.key, 'IN'); + * + * // Helper function to find kth smallest + * const findKthSmallest = (k: number): number | undefined => { + * return sortedKeys[k - 1]; + * }; + * + * // Assertions + * console.log(findKthSmallest(1)); // 1 + * console.log(findKthSmallest(3)); // 4 + * console.log(findKthSmallest(7)); // 8 + * @example + * // Find elements in a range + * const bst = new BST([10, 5, 15, 3, 7, 12, 18]); + * + * // Helper function to find elements in range + * const findElementsInRange = (min: number, max: number): number[] => { + * return bst.search(node => node.key >= min && node.key <= max, false, node => node.key); + * }; + * + * // Assertions + * console.log(findElementsInRange(4, 12)); // [10, 5, 7, 12] + * console.log(findElementsInRange(15, 20)); // [15, 18] + * @example + * // Find lowest common ancestor + * const bst = new BST([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]); + * + * function findFirstCommon(arr1: number[], arr2: number[]): number | undefined { + * for (const num of arr1) { + * if (arr2.indexOf(num) !== -1) { + * return num; + * } + * } + * return undefined; + * } + * + * // 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); + * }; + * + * // Assertions + * console.log(findLCA(3, 10)); // 7 + * console.log(findLCA(5, 35)); // 15 + * console.log(findLCA(20, 30)); // 25 */ export class BST< K = any, @@ -396,48 +448,54 @@ export class BST< * Time Complexity: O(log n) * Space Complexity: O(k + log n) * - * The function `getNodes` in TypeScript overrides the base class method to retrieve nodes based on a - * given keyNodeEntryRawOrPredicate and iteration type. - * @param {BTNRep | R | NodePredicate} keyNodeEntryRawOrPredicate - The `keyNodeEntryRawOrPredicate` - * parameter in the `getNodes` method is used to filter the nodes that will be returned. It can be a - * key, a node, an entry, or a custom keyNodeEntryRawOrPredicate function that determines whether a node should be - * included in the result. - * @param [onlyOne=false] - The `onlyOne` parameter in the `getNodes` method is a boolean flag that - * determines whether to return only the first node that matches the keyNodeEntryRawOrPredicate (`true`) or all nodes - * that match the keyNodeEntryRawOrPredicate (`false`). If `onlyOne` is set to `true`, the method will stop iterating - * and - * @param {BTNRep | R} startNode - The `startNode` parameter in the - * `getNodes` method is used to specify the starting point for traversing the tree when searching for - * nodes that match a given keyNodeEntryRawOrPredicate. It represents the root node of the subtree where the search - * should begin. If not explicitly provided, the default value for `begin - * @param {IterationType} iterationType - The `iterationType` parameter in the `getNodes` method - * specifies the type of iteration to be performed when traversing the nodes of a binary tree. It can - * have two possible values: - * @returns The `getNodes` method returns an array of nodes that satisfy the given keyNodeEntryRawOrPredicate. + * The function `search` in TypeScript overrides the search behavior in a binary tree structure based + * on specified criteria. + * @param {BTNRep | R | NodePredicate} keyNodeEntryRawOrPredicate - The + * `keyNodeEntryRawOrPredicate` parameter in the `override search` method can accept one of the + * following types: + * @param [onlyOne=false] - The `onlyOne` parameter is a boolean flag that determines whether the + * search should stop after finding the first matching node. If `onlyOne` is set to `true`, the + * search will return as soon as a matching node is found. If `onlyOne` is set to `false`, the + * @param {C} callback - The `callback` parameter in the `override search` function is a function + * that will be called on each node that matches the search criteria. It is of type `C`, which + * extends `NodeCallback`. The callback function should accept a node of type `NODE` as its + * argument and + * @param {BTNRep | R} startNode - The `startNode` parameter in the `override search` + * method represents the node from which the search operation will begin. It is the starting point + * for searching within the tree data structure. The method ensures that the `startNode` is a valid + * node before proceeding with the search operation. If the ` + * @param {IterationType} iterationType - The `iterationType` parameter in the `override search` + * function determines the type of iteration to be used during the search operation. It can have two + * possible values: + * @returns The `override search` method returns an array of values that match the search criteria + * specified by the input parameters. The method performs a search operation on a binary tree + * structure based on the provided key, predicate, and other options. The search results are + * collected in an array and returned as the output of the method. */ - override getNodes( + override search>( keyNodeEntryRawOrPredicate: BTNRep | R | NodePredicate, onlyOne = false, + callback: C = this._DEFAULT_NODE_CALLBACK as C, startNode: BTNRep | R = this._root, iterationType: IterationType = this.iterationType - ): NODE[] { + ): ReturnType[] { if (keyNodeEntryRawOrPredicate === undefined) return []; if (keyNodeEntryRawOrPredicate === null) return []; startNode = this.ensureNode(startNode); if (!startNode) return []; - const callback = this._ensurePredicate(keyNodeEntryRawOrPredicate); - const ans: NODE[] = []; + const predicate = this._ensurePredicate(keyNodeEntryRawOrPredicate); + const ans: ReturnType[] = []; if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE) => { - if (callback(cur)) { - ans.push(cur); + if (predicate(cur)) { + ans.push(callback(cur)); if (onlyOne) return; } if (!this.isRealNode(cur.left) && !this.isRealNode(cur.right)) return; if (!this._isPredicate(keyNodeEntryRawOrPredicate)) { - const benchmarkKey = this._getKey(keyNodeEntryRawOrPredicate); + const benchmarkKey = this._extractKey(keyNodeEntryRawOrPredicate); if ( this.isRealNode(cur.left) && benchmarkKey !== null && @@ -463,12 +521,12 @@ export class BST< const stack = [startNode]; while (stack.length > 0) { const cur = stack.pop()!; - if (callback(cur)) { - ans.push(cur); + if (predicate(cur)) { + ans.push(callback(cur)); if (onlyOne) return ans; } if (!this._isPredicate(keyNodeEntryRawOrPredicate)) { - const benchmarkKey = this._getKey(keyNodeEntryRawOrPredicate); + const benchmarkKey = this._extractKey(keyNodeEntryRawOrPredicate); if ( this.isRealNode(cur.right) && benchmarkKey !== null && diff --git a/src/data-structures/linked-list/doubly-linked-list.ts b/src/data-structures/linked-list/doubly-linked-list.ts index e7cbbd7..9430390 100644 --- a/src/data-structures/linked-list/doubly-linked-list.ts +++ b/src/data-structures/linked-list/doubly-linked-list.ts @@ -999,7 +999,7 @@ export class DoublyLinkedList extends IterableElementBase | ((node: DoublyLinkedListNode) => boolean) ): E | undefined { const predicate = this._ensurePredicate(elementNodeOrPredicate); diff --git a/src/data-structures/linked-list/singly-linked-list.ts b/src/data-structures/linked-list/singly-linked-list.ts index ef8e437..53954dc 100644 --- a/src/data-structures/linked-list/singly-linked-list.ts +++ b/src/data-structures/linked-list/singly-linked-list.ts @@ -223,7 +223,7 @@ export class SinglyLinkedList extends IterableElementBase | ((node: SinglyLinkedListNode) => boolean) ): E | undefined { const predicate = this._ensurePredicate(elementNodeOrPredicate); diff --git a/test/unit/data-structures/binary-tree/binary-tree.test.ts b/test/unit/data-structures/binary-tree/binary-tree.test.ts index dfabe5b..803b6fe 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -1155,8 +1155,8 @@ describe('BinaryTree', () => { tree.add([3, 'B']); tree.add([7, 'C']); - expect(tree.getPathToRoot(undefined, 7)).toEqual([5, 7]); - expect(tree.getPathToRoot(undefined, 1)).toEqual([]); + expect(tree.getPathToRoot(7)).toEqual([7, 5]); + expect(tree.getPathToRoot(1)).toEqual([]); }); it('should check if the tree is perfectly balanced', () => { diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index 9b01de8..446f768 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -1529,3 +1529,87 @@ describe('BST iterative methods not map mode test', () => { expect(balanced.leaves(node => balanced.get(node?.key))).toEqual(['a', 'f', 'd', 'i']); }); }); + +describe('classic use', () => { + // Test case for finding the kth smallest element + it('@example Find kth smallest element', () => { + // Create a BST with some elements + const bst = new BST([5, 3, 7, 1, 4, 6, 8]); + const sortedKeys = bst.dfs(node => node.key, 'IN'); + + // Helper function to find kth smallest + const findKthSmallest = (k: number): number | undefined => { + return sortedKeys[k - 1]; + }; + + // Assertions + expect(findKthSmallest(1)).toBe(1); + expect(findKthSmallest(3)).toBe(4); + expect(findKthSmallest(7)).toBe(8); + }); + + // Test case for finding elements in a given range + it('@example Find elements in a range', () => { + const bst = new BST([10, 5, 15, 3, 7, 12, 18]); + + // Helper function to find elements in range + const findElementsInRange = (min: number, max: number): number[] => { + return bst.search( + node => node.key >= min && node.key <= max, + false, + node => node.key + ); + }; + + // Assertions + expect(findElementsInRange(4, 12)).toEqual([10, 5, 7, 12]); + expect(findElementsInRange(15, 20)).toEqual([15, 18]); + }); + + // Test case for Huffman coding simulation + it('Huffman coding frequency simulation', () => { + // Create a BST to simulate Huffman tree + const frequencyBST = new BST([ + ['a', 5], + ['b', 9], + ['c', 12], + ['d', 13], + ['e', 16], + ['f', 45] + ]); + + // Sort nodes by frequency + const sortedFrequencies = frequencyBST.dfs(node => ({ char: node.key, freq: node.value }), 'IN'); + + // Build Huffman tree simulation + expect(sortedFrequencies[0].char).toBe('a'); + expect(sortedFrequencies[5].char).toBe('f'); + }); + + // Test case for Lowest Common Ancestor (LCA) + it('@example Find lowest common ancestor', () => { + const bst = new BST([20, 10, 30, 5, 15, 25, 35, 3, 7, 12, 18]); + + function findFirstCommon(arr1: number[], arr2: number[]): number | undefined { + for (const num of arr1) { + if (arr2.indexOf(num) !== -1) { + return num; + } + } + return undefined; + } + + // 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); + }; + + // Assertions + expect(findLCA(3, 10)).toBe(7); + expect(findLCA(5, 35)).toBe(15); + expect(findLCA(20, 30)).toBe(25); + }); +});