From a41e222481ecf2d1a589f2174a395bee3c893b94 Mon Sep 17 00:00:00 2001 From: Revone Date: Wed, 22 Nov 2023 17:18:37 +0800 Subject: [PATCH] feat: Implement forEach, filter, map, reduce, and [Symbol.iterator] methods for all binary tree data structures --- README.md | 2 +- src/data-structures/binary-tree/avl-tree.ts | 22 +++- .../binary-tree/binary-tree.ts | 114 +++++++++++++----- src/data-structures/binary-tree/bst.ts | 42 ++++--- src/data-structures/binary-tree/rb-tree.ts | 27 +++-- .../binary-tree/tree-multimap.ts | 25 +++- src/interfaces/binary-tree.ts | 6 +- src/types/common.ts | 2 +- .../data-structures/binary-tree/avl-tree.ts | 6 +- .../binary-tree/binary-tree.ts | 4 +- src/types/data-structures/binary-tree/bst.ts | 4 +- .../data-structures/binary-tree/rb-tree.ts | 4 +- .../binary-tree/tree-multimap.ts | 4 +- test/integration/index.html | 16 +-- .../binary-tree/rb-tree.test.ts | 7 ++ .../binary-tree/avl-tree.test.ts | 58 ++++++++- .../binary-tree/binary-tree.test.ts | 69 +++++++++-- .../data-structures/binary-tree/bst.test.ts | 57 ++++++++- .../binary-tree/rb-tree.test.ts | 60 ++++++++- 19 files changed, 430 insertions(+), 99 deletions(-) diff --git a/README.md b/README.md index b98751d..d103bbe 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Now you can use this library in Node.js and browser environments in CommonJS(req ### npm ```bash -npm i data-structure-typed +npm i --save data-structure-typed ``` ### yarn diff --git a/src/data-structures/binary-tree/avl-tree.ts b/src/data-structures/binary-tree/avl-tree.ts index 2a294af..c04cad7 100644 --- a/src/data-structures/binary-tree/avl-tree.ts +++ b/src/data-structures/binary-tree/avl-tree.ts @@ -6,8 +6,8 @@ * @license MIT License */ import { BST, BSTNode } from './bst'; -import type { AVLTreeNodeNested, AVLTreeOptions, BiTreeDeleteResult, BTNKey } from '../../types'; -import { BTNCallback } from '../../types'; +import type { AVLTreeNested, AVLTreeNodeNested, AVLTreeOptions, BiTreeDeleteResult, BTNKey } from '../../types'; +import { BTNCallback, IterationType } from '../../types'; import { IBinaryTree } from '../../interfaces'; export class AVLTreeNode = AVLTreeNodeNested> extends BSTNode { @@ -19,9 +19,12 @@ export class AVLTreeNode = AVLTreeNodeNeste } } -export class AVLTree = AVLTreeNode>> - extends BST - implements IBinaryTree { +export class AVLTree = AVLTreeNode>, TREE extends AVLTree = AVLTree>> + extends BST + implements IBinaryTree { + + override options: AVLTreeOptions; + /** * This is a constructor function for an AVL tree data structure in TypeScript. * @param {AVLTreeOptions} [options] - The `options` parameter is an optional object that can be passed to the @@ -30,6 +33,11 @@ export class AVLTree = AVLTreeNode a - b, ...options } + } else { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b }; + } } /** @@ -45,6 +53,10 @@ export class AVLTree = AVLTreeNode(key, value) as N; } + override createTree(options?: AVLTreeOptions) { + return new AVLTree({ ...this.options, ...options }) as TREE; + } + /** * Time Complexity: O(log n) - logarithmic time, where "n" is the number of nodes in the tree. The add method of the superclass (BST) has logarithmic time complexity. * Space Complexity: O(1) - constant space, as it doesn't use additional data structures that scale with input size. diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index d03887a..d6ec2bb 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -8,6 +8,7 @@ import type { BinaryTreeNodeNested, BinaryTreeOptions, BTNCallback, BTNKey } from '../../types'; import { + BinaryTreeNested, BinaryTreePrintOptions, BiTreeDeleteResult, DFSOrderPattern, @@ -114,9 +115,10 @@ export class BinaryTreeNode = BinaryTree * Represents a binary tree data structure. * @template N - The type of the binary tree's nodes. */ -export class BinaryTree = BinaryTreeNode>> - implements IBinaryTree { - iterationType: IterationType = IterationType.ITERATIVE; +export class BinaryTree = BinaryTreeNode>, TREE extends BinaryTree = BinaryTree>> + implements IBinaryTree { + + options: BinaryTreeOptions; /** * Creates a new instance of BinaryTree. @@ -124,9 +126,11 @@ export class BinaryTree = BinaryTreeNode */ constructor(options?: BinaryTreeOptions) { if (options) { - const { iterationType = IterationType.ITERATIVE } = options; - this.iterationType = iterationType; + this.options = { iterationType: IterationType.ITERATIVE, ...options } + } else { + this.options = { iterationType: IterationType.ITERATIVE }; } + this._size = 0; } @@ -158,6 +162,10 @@ export class BinaryTree = BinaryTreeNode return new BinaryTreeNode(key, value) as N; } + createTree(options?: BinaryTreeOptions): TREE { + return new BinaryTree({ ...this.options, ...options }) as TREE; + } + /** * Time Complexity: O(n) * Space Complexity: O(1) @@ -404,7 +412,7 @@ export class BinaryTree = BinaryTreeNode * values: * @returns the height of the binary tree. */ - getHeight(beginRoot: BTNKey | N | null | undefined = this.root, iterationType = this.iterationType): number { + getHeight(beginRoot: BTNKey | N | null | undefined = this.root, iterationType = this.options.iterationType): number { beginRoot = this.ensureNotKey(beginRoot); if (!beginRoot) return -1; @@ -453,7 +461,7 @@ export class BinaryTree = BinaryTreeNode * to calculate the minimum height of a binary tree. It can have two possible values: * @returns The function `getMinHeight` returns the minimum height of a binary tree. */ - getMinHeight(beginRoot: BTNKey | N | null | undefined = this.root, iterationType = this.iterationType): number { + getMinHeight(beginRoot: BTNKey | N | null | undefined = this.root, iterationType = this.options.iterationType): number { beginRoot = this.ensureNotKey(beginRoot); if (!beginRoot) return -1; @@ -575,7 +583,7 @@ export class BinaryTree = BinaryTreeNode callback: C = this._defaultOneParamCallback as C, onlyOne = false, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N[] { if ((!callback || callback === this._defaultOneParamCallback) && (identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; @@ -664,7 +672,7 @@ export class BinaryTree = BinaryTreeNode identifier: ReturnType | null | undefined, callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): boolean { if ((!callback || callback === this._defaultOneParamCallback) && (identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; @@ -723,7 +731,7 @@ export class BinaryTree = BinaryTreeNode identifier: ReturnType | null | undefined, callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N | null | undefined { if ((!callback || callback === this._defaultOneParamCallback) && (identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; @@ -842,7 +850,7 @@ export class BinaryTree = BinaryTreeNode identifier: ReturnType | null | undefined, callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): V | undefined { if ((!callback || callback === this._defaultOneParamCallback) && (identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; @@ -923,7 +931,7 @@ export class BinaryTree = BinaryTreeNode */ getLeftMost( beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N | null | undefined { beginRoot = this.ensureNotKey(beginRoot); @@ -969,7 +977,7 @@ export class BinaryTree = BinaryTreeNode */ getRightMost( beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N | null | undefined { // TODO support get right most by passing key in beginRoot = this.ensureNotKey(beginRoot); @@ -1010,7 +1018,7 @@ export class BinaryTree = BinaryTreeNode * possible values: * @returns a boolean value. */ - isSubtreeBST(beginRoot: BTNKey | N | null | undefined, iterationType = this.iterationType): boolean { + isSubtreeBST(beginRoot: BTNKey | N | null | undefined, iterationType = this.options.iterationType): boolean { // TODO there is a bug beginRoot = this.ensureNotKey(beginRoot); if (!beginRoot) return true; @@ -1057,7 +1065,7 @@ export class BinaryTree = BinaryTreeNode * expected to be * @returns a boolean value. */ - isBST(iterationType = this.iterationType): boolean { + isBST(iterationType = this.options.iterationType): boolean { if (this.root === null) return true; return this.isSubtreeBST(this.root, iterationType); } @@ -1104,7 +1112,7 @@ export class BinaryTree = BinaryTreeNode * @param iterationType - The `iterationType` parameter determines the type of traversal to be * performed on the subtree. It can have two possible values: * @param [includeNull=false] - The `includeNull` parameter is a boolean value that determines - * whether or not to include null values in the traversal. If `includeNull` is set to `true`, the + * whether to include null values in the traversal. If `includeNull` is set to `true`, the * traversal will include null values, otherwise it will skip them. * @returns The function `subTreeTraverse` returns an array of values that are the result of invoking * the `callback` function on each node in the subtree. The type of the array elements is determined @@ -1113,7 +1121,7 @@ export class BinaryTree = BinaryTreeNode subTreeTraverse>( callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType, + iterationType = this.options.iterationType, includeNull = false ): ReturnType[] { beginRoot = this.ensureNotKey(beginRoot); @@ -1383,7 +1391,7 @@ export class BinaryTree = BinaryTreeNode * @param iterationType - The `iterationType` parameter determines the type of iteration to be * performed during the breadth-first search (BFS). It can have two possible values: * @param [includeNull=false] - The `includeNull` parameter is a boolean flag that determines whether - * or not to include null values in the breadth-first search traversal. If `includeNull` is set to + * to include null values in the breadth-first search traversal. If `includeNull` is set to * `true`, null values will be included in the traversal, otherwise they will be skipped. * @returns an array of values that are the result of invoking the callback function on each node in * the breadth-first traversal of a binary tree. @@ -1391,7 +1399,7 @@ export class BinaryTree = BinaryTreeNode bfs>( callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType, + iterationType = this.options.iterationType, includeNull = false ): ReturnType[] { beginRoot = this.ensureNotKey(beginRoot); @@ -1484,7 +1492,7 @@ export class BinaryTree = BinaryTreeNode * @param iterationType - The `iterationType` parameter determines the type of iteration to be * performed on the tree. It can have two possible values: * @param [includeNull=false] - The `includeNull` parameter is a boolean value that determines - * whether or not to include null values in the resulting levels. If `includeNull` is set to `true`, + * whether to include null values in the resulting levels. If `includeNull` is set to `true`, * null values will be included in the levels. If `includeNull` is set to `false`, null values will * be excluded * @returns The function `listLevels` returns a two-dimensional array of type `ReturnType[][]`. @@ -1492,7 +1500,7 @@ export class BinaryTree = BinaryTreeNode listLevels>( callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | null | undefined = this.root, - iterationType = this.iterationType, + iterationType = this.options.iterationType, includeNull = false ): ReturnType[][] { beginRoot = this.ensureNotKey(beginRoot); @@ -1692,6 +1700,48 @@ export class BinaryTree = BinaryTreeNode return ans; } + forEach(callback: (entry: [BTNKey, V | undefined], tree: typeof this) => void): void { + for (const entry of this) { + callback(entry, this); + } + } + + filter(predicate: (entry: [BTNKey, V | undefined], tree: typeof this) => boolean) { + const newTree = this.createTree(); + for (const [key, value] of this) { + if (predicate([key, value], this)) { + newTree.add(key, value); + } + } + return newTree; + } + + // TODO Type error, need to return a TREE that is a value type only for callback function. + // map(callback: (entry: [BTNKey, V | undefined], tree: typeof this) => NV) { + // const newTree = this.createTree(); + // for (const [key, value] of this) { + // newTree.add(key, callback([key, value], this)); + // } + // return newTree; + // } + + map(callback: (entry: [BTNKey, V | undefined], tree: typeof this) => V) { + const newTree = this.createTree(); + for (const [key, value] of this) { + newTree.add(key, callback([key, value], this)); + } + return newTree; + } + + reduce(callback: (accumulator: T, entry: [BTNKey, V | undefined], tree: typeof this) => T, initialValue: T): T { + let accumulator = initialValue; + for (const [key, value] of this) { + accumulator = callback(accumulator, [key, value], this); + } + return accumulator; + } + + /** * The above function is an iterator for a binary tree that can be used to traverse the tree in * either an iterative or recursive manner. @@ -1701,32 +1751,32 @@ export class BinaryTree = BinaryTreeNode * @returns The `*[Symbol.iterator]` method returns a generator object that yields the keys of the * binary tree nodes in a specific order. */ - * [Symbol.iterator](node = this.root): Generator { - if (!node) { - return; - } + * [Symbol.iterator](node = this.root): Generator<[BTNKey, V | undefined], void, undefined> { + if (!node) return; - if (this.iterationType === IterationType.ITERATIVE) { + if (this.options.iterationType === IterationType.ITERATIVE) { const stack: (N | null | undefined)[] = []; let current: N | null | undefined = node; while (current || stack.length > 0) { - while (current) { + while (current && !isNaN(current.key)) { stack.push(current); current = current.left; } current = stack.pop(); - if (current) yield current.key; - if (current) current = current.right; + if (current && !isNaN(current.key)) { + yield [current.key, current.value]; + current = current.right; + } } } else { - if (node.left) { + if (node.left && !isNaN(node.key)) { yield* this[Symbol.iterator](node.left); } - yield node.key; - if (node.right) { + yield [node.key, node.value]; + if (node.right && !isNaN(node.key)) { yield* this[Symbol.iterator](node.right); } } diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index 0937cc5..e54f72e 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -5,7 +5,7 @@ * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import type { BSTComparator, BSTNodeNested, BSTOptions, BTNCallback, BTNKey } from '../../types'; +import type { BSTNested, BSTNodeNested, BSTOptions, BTNCallback, BTNKey } from '../../types'; import { CP, IterationType } from '../../types'; import { BinaryTree, BinaryTreeNode } from './binary-tree'; import { IBinaryTree } from '../../interfaces'; @@ -62,9 +62,12 @@ export class BSTNode = BSTNodeNested> extend } } -export class BST = BSTNode>> - extends BinaryTree - implements IBinaryTree { +export class BST = BSTNode>, TREE extends BST = BST>> + extends BinaryTree + implements IBinaryTree { + + override options: BSTOptions; + /** * The constructor function initializes a binary search tree with an optional comparator function. * @param {BSTOptions} [options] - An optional object that contains additional configuration options @@ -72,13 +75,12 @@ export class BST = BSTNode> */ constructor(options?: BSTOptions) { super(options); - this._root = undefined; - if (options !== undefined) { - const { comparator } = options; - if (comparator !== undefined) { - this._comparator = comparator; - } + if (options) { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b, ...options } + } else { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b }; } + this._root = undefined; } protected override _root?: N; @@ -102,6 +104,10 @@ export class BST = BSTNode> return new BSTNode(key, value) as N; } + override createTree(options?: BSTOptions): TREE { + return new BST({ ...this.options, ...options }) as TREE; + } + /** * Time Complexity: O(log n) - Average case for a balanced tree. In the worst case (unbalanced tree), it can be O(n). * Space Complexity: O(1) - Constant space is used. @@ -215,7 +221,7 @@ export class BST = BSTNode> keysOrNodes: (BTNKey | N | undefined)[], data?: (V | undefined)[], isBalanceAdd = true, - iterationType = this.iterationType + iterationType = this.options.iterationType ): (N | undefined)[] { // TODO this addMany function is inefficient, it should be optimized function hasNoUndefined(arr: (BTNKey | N | undefined)[]): arr is (BTNKey | N)[] { @@ -310,7 +316,7 @@ export class BST = BSTNode> * the key of the leftmost node if the comparison result is greater than, and the key of the * rightmost node otherwise. If no node is found, it returns 0. */ - lastKey(beginRoot: BTNKey | N | undefined = this.root, iterationType = this.iterationType): BTNKey { + lastKey(beginRoot: BTNKey | N | undefined = this.root, iterationType = this.options.iterationType): BTNKey { if (this._compare(0, 1) === CP.lt) return this.getRightMost(beginRoot, iterationType)?.key ?? 0; else if (this._compare(0, 1) === CP.gt) return this.getLeftMost(beginRoot, iterationType)?.key ?? 0; else return this.getRightMost(beginRoot, iterationType)?.key ?? 0; @@ -406,7 +412,7 @@ export class BST = BSTNode> callback: C = this._defaultOneParamCallback as C, onlyOne = false, beginRoot: BTNKey | N | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N[] { beginRoot = this.ensureNotKey(beginRoot); if (!beginRoot) return []; @@ -487,7 +493,7 @@ export class BST = BSTNode> callback: C = this._defaultOneParamCallback as C, lesserOrGreater: CP = CP.lt, targetNode: BTNKey | N | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): ReturnType[] { targetNode = this.ensureNotKey(targetNode); const ans: ReturnType>[] = []; @@ -550,7 +556,7 @@ export class BST = BSTNode> * values: * @returns The function `perfectlyBalance` returns a boolean value. */ - perfectlyBalance(iterationType = this.iterationType): boolean { + perfectlyBalance(iterationType = this.options.iterationType): boolean { const sorted = this.dfs(node => node, 'in'), n = sorted.length; this.clear(); @@ -602,7 +608,7 @@ export class BST = BSTNode> * to check if the AVL tree is balanced. It can have two possible values: * @returns a boolean value. */ - isAVLBalanced(iterationType = this.iterationType): boolean { + isAVLBalanced(iterationType = this.options.iterationType): boolean { if (!this.root) return true; let balanced = true; @@ -646,8 +652,6 @@ export class BST = BSTNode> return balanced; } - protected _comparator: BSTComparator = (a, b) => a - b; - protected _setRoot(v: N | undefined) { if (v) { v.parent = undefined; @@ -664,7 +668,7 @@ export class BST = BSTNode> * than), CP.lt (less than), or CP.eq (equal). */ protected _compare(a: BTNKey, b: BTNKey): CP { - const compared = this._comparator(a, b); + const compared = this.options.comparator!(a, b); if (compared > 0) return CP.gt; else if (compared < 0) return CP.lt; else return CP.eq; diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index c5892e1..b3f30f1 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -13,6 +13,7 @@ import { IterationType, RBTNColor, RBTreeOptions, + RedBlackTreeNested, RedBlackTreeNodeNested } from '../../types'; import { BST, BSTNode } from './bst'; @@ -38,10 +39,11 @@ export class RedBlackTreeNode = RedBla * 4. Red nodes must have black children. * 5. Black balance: Every path from any node to each of its leaf nodes contains the same number of black nodes. */ -export class RedBlackTree = RedBlackTreeNode>> - extends BST - implements IBinaryTree { +export class RedBlackTree = RedBlackTreeNode>, TREE extends RedBlackTree = RedBlackTree>> + extends BST + implements IBinaryTree { Sentinel: N = new RedBlackTreeNode(NaN) as unknown as N; + override options: RBTreeOptions; /** * The constructor function initializes a Red-Black Tree with an optional set of options. @@ -50,6 +52,11 @@ export class RedBlackTree = RedBlackTr */ constructor(options?: RBTreeOptions) { super(options); + if (options) { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b, ...options } + } else { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b }; + } this._root = this.Sentinel; } @@ -65,6 +72,14 @@ export class RedBlackTree = RedBlackTr return this._size; } + override createNode(key: BTNKey, value?: V, color: RBTNColor = RBTNColor.BLACK): N { + return new RedBlackTreeNode(key, value, color) as N; + } + + override createTree(options?: RBTreeOptions): TREE { + return new RedBlackTree({ ...this.options, ...options }) as TREE; + } + /** * Time Complexity: O(log n) on average (where n is the number of nodes in the tree) * Space Complexity: O(1) @@ -139,10 +154,6 @@ export class RedBlackTree = RedBlackTr this._size++; } - override createNode(key: BTNKey, value?: V, color: RBTNColor = RBTNColor.BLACK): N { - return new RedBlackTreeNode(key, value, color) as N; - } - /** * Time Complexity: O(log n) on average (where n is the number of nodes in the tree) * Space Complexity: O(1) @@ -279,7 +290,7 @@ export class RedBlackTree = RedBlackTr identifier: ReturnType | undefined, callback: C = this._defaultOneParamCallback as C, beginRoot: BTNKey | N | undefined = this.root, - iterationType = this.iterationType + iterationType = this.options.iterationType ): N | null | undefined { if ((identifier as any) instanceof BinaryTreeNode) callback = (node => node) as C; beginRoot = this.ensureNotKey(beginRoot); diff --git a/src/data-structures/binary-tree/tree-multimap.ts b/src/data-structures/binary-tree/tree-multimap.ts index cf320db..68e0387 100644 --- a/src/data-structures/binary-tree/tree-multimap.ts +++ b/src/data-structures/binary-tree/tree-multimap.ts @@ -6,7 +6,7 @@ * @license MIT License */ import type { BTNKey, TreeMultimapNodeNested, TreeMultimapOptions } from '../../types'; -import { BiTreeDeleteResult, BTNCallback, CP, FamilyPosition, IterationType } from '../../types'; +import { BiTreeDeleteResult, BTNCallback, CP, FamilyPosition, IterationType, TreeMultimapNested } from '../../types'; import { IBinaryTree } from '../../interfaces'; import { AVLTree, AVLTreeNode } from './avl-tree'; @@ -35,17 +35,26 @@ export class TreeMultimapNode< /** * The only distinction between a TreeMultimap and a AVLTree lies in the ability of the former to store duplicate nodes through the utilization of counters. */ -export class TreeMultimap = TreeMultimapNode>> - extends AVLTree - implements IBinaryTree { +export class TreeMultimap = TreeMultimapNode>, + TREE extends TreeMultimap = TreeMultimap>> + extends AVLTree + implements IBinaryTree { + + override options: TreeMultimapOptions; + /** * The constructor function for a TreeMultimap class in TypeScript, which extends another class and sets an option to * merge duplicated values. * @param {TreeMultimapOptions} [options] - An optional object that contains additional configuration options for the * TreeMultimap. */ - constructor(options?: TreeMultimapOptions) { + constructor(options: TreeMultimapOptions = { iterationType: IterationType.ITERATIVE }) { super(options); + if (options) { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b, ...options } + } else { + this.options = { iterationType: IterationType.ITERATIVE, comparator: (a, b) => a - b }; + } } private _count = 0; @@ -67,6 +76,10 @@ export class TreeMultimap = TreeMultim return new TreeMultimapNode(key, value, count) as N; } + override createTree(options?: TreeMultimapOptions): TREE { + return new TreeMultimap({ ...this.options, ...options }) as TREE; + } + /** * Time Complexity: O(log n) - logarithmic time, where "n" is the number of nodes in the tree. The add method of the superclass (AVLTree) has logarithmic time complexity. * Space Complexity: O(1) - constant space, as it doesn't use additional data structures that scale with input size. @@ -210,7 +223,7 @@ export class TreeMultimap = TreeMultim * values: * @returns a boolean value. */ - override perfectlyBalance(iterationType = this.iterationType): boolean { + override perfectlyBalance(iterationType = this.options.iterationType): boolean { const sorted = this.dfs(node => node, 'in'), n = sorted.length; if (sorted.length < 1) return false; diff --git a/src/interfaces/binary-tree.ts b/src/interfaces/binary-tree.ts index f190241..ba63ee3 100644 --- a/src/interfaces/binary-tree.ts +++ b/src/interfaces/binary-tree.ts @@ -1,7 +1,7 @@ -import { BinaryTreeNode } from '../data-structures'; -import { BinaryTreeNodeNested, BiTreeDeleteResult, BTNCallback, BTNKey } from '../types'; +import { BinaryTree, BinaryTreeNode } from '../data-structures'; +import { BinaryTreeNested, BinaryTreeNodeNested, BiTreeDeleteResult, BTNCallback, BTNKey } from '../types'; -export interface IBinaryTree = BinaryTreeNodeNested> { +export interface IBinaryTree = BinaryTreeNodeNested, TREE extends BinaryTree = BinaryTreeNested> { createNode(key: BTNKey, value?: N['value']): N; add(keyOrNode: BTNKey | N | null, value?: N['value']): N | null | undefined; diff --git a/src/types/common.ts b/src/types/common.ts index e0fb3e8..63d4274 100644 --- a/src/types/common.ts +++ b/src/types/common.ts @@ -20,4 +20,4 @@ export interface IterableWithLength extends Iterable { export type IterableWithSizeOrLength = IterableWithSize | IterableWithLength -export type BinaryTreePrintOptions = {isShowUndefined?: boolean, isShowNull?: boolean, isShowRedBlackNIL?: boolean} \ No newline at end of file +export type BinaryTreePrintOptions = { isShowUndefined?: boolean, isShowNull?: boolean, isShowRedBlackNIL?: boolean } \ No newline at end of file diff --git a/src/types/data-structures/binary-tree/avl-tree.ts b/src/types/data-structures/binary-tree/avl-tree.ts index 71364a5..5914d05 100644 --- a/src/types/data-structures/binary-tree/avl-tree.ts +++ b/src/types/data-structures/binary-tree/avl-tree.ts @@ -1,5 +1,9 @@ -import { AVLTreeNode } from '../../../data-structures'; +import { AVLTree, AVLTreeNode } from '../../../data-structures'; import { BSTOptions } from './bst'; export type AVLTreeNodeNested = AVLTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + +export type AVLTreeNested> = AVLTree>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + + export type AVLTreeOptions = BSTOptions & {}; diff --git a/src/types/data-structures/binary-tree/binary-tree.ts b/src/types/data-structures/binary-tree/binary-tree.ts index 02ccdcb..a3960d8 100644 --- a/src/types/data-structures/binary-tree/binary-tree.ts +++ b/src/types/data-structures/binary-tree/binary-tree.ts @@ -1,4 +1,4 @@ -import { BinaryTreeNode } from '../../../data-structures'; +import { BinaryTree, BinaryTreeNode } from '../../../data-structures'; /** * Enum representing different loop types. @@ -28,6 +28,8 @@ export type BiTreeDeleteResult = { deleted: N | null | undefined; needBalance export type BinaryTreeNodeNested = BinaryTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +export type BinaryTreeNested> = BinaryTree>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + export type BinaryTreeOptions = { iterationType?: IterationType } export type NodeDisplayLayout = [string[], number, number, number]; diff --git a/src/types/data-structures/binary-tree/bst.ts b/src/types/data-structures/binary-tree/bst.ts index d7a0a41..80b6733 100644 --- a/src/types/data-structures/binary-tree/bst.ts +++ b/src/types/data-structures/binary-tree/bst.ts @@ -1,4 +1,4 @@ -import { BSTNode } from '../../../data-structures'; +import { BST, BSTNode } from '../../../data-structures'; import type { BinaryTreeOptions, BTNKey } from './binary-tree'; export type BSTComparator = (a: BTNKey, b: BTNKey) => number; @@ -6,6 +6,8 @@ export type BSTComparator = (a: BTNKey, b: BTNKey) => number; // prettier-ignore export type BSTNodeNested = BSTNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +export type BSTNested> = BST>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + export type BSTOptions = BinaryTreeOptions & { comparator?: BSTComparator, } diff --git a/src/types/data-structures/binary-tree/rb-tree.ts b/src/types/data-structures/binary-tree/rb-tree.ts index 9a47ea7..89fd504 100644 --- a/src/types/data-structures/binary-tree/rb-tree.ts +++ b/src/types/data-structures/binary-tree/rb-tree.ts @@ -1,8 +1,10 @@ -import { RedBlackTreeNode } from '../../../data-structures'; +import { RedBlackTree, RedBlackTreeNode } from '../../../data-structures'; import { BSTOptions } from "./bst"; export enum RBTNColor { RED = 1, BLACK = 0} export type RedBlackTreeNodeNested = RedBlackTreeNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +export type RedBlackTreeNested> = RedBlackTree>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + export type RBTreeOptions = BSTOptions & {}; \ No newline at end of file diff --git a/src/types/data-structures/binary-tree/tree-multimap.ts b/src/types/data-structures/binary-tree/tree-multimap.ts index bc5ad86..e2a8d4e 100644 --- a/src/types/data-structures/binary-tree/tree-multimap.ts +++ b/src/types/data-structures/binary-tree/tree-multimap.ts @@ -1,6 +1,8 @@ -import { TreeMultimapNode } from '../../../data-structures'; +import { TreeMultimap, TreeMultimapNode } from '../../../data-structures'; import { AVLTreeOptions } from './avl-tree'; export type TreeMultimapNodeNested = TreeMultimapNode>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> +export type TreeMultimapNested> = TreeMultimap>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> + export type TreeMultimapOptions = Omit & {} diff --git a/test/integration/index.html b/test/integration/index.html index 9bca328..7f70105 100644 --- a/test/integration/index.html +++ b/test/integration/index.html @@ -3,8 +3,8 @@ CDN Test - - + + @@ -38,7 +38,7 @@ last = queue.dequeue(); } - console.log((performance.now() - startTime).toFixed(2) ,`Queue ${n.toLocaleString()} dequeue `); + console.log((performance.now() - startTime).toFixed(2), `Queue ${n.toLocaleString()} dequeue `); } catch (e) { console.error(e); @@ -69,7 +69,7 @@ tree.addMany([1, 6, 9, 8, 5, 2, 3, 4, 7]) tree.add(10); console.log(tree.isPerfectlyBalanced(), `tree.isPerfectlyBalanced()`); - tree.print(undefined, {isShowUndefined: true}); + tree.print(undefined, { isShowUndefined: true }); const node3 = tree.getNode(3); if (node3) node3.right = tree.createNode(1); @@ -79,7 +79,7 @@ tree.clear(); tree.addMany([1, null, 2, null, 3, null, 4, null, 5, null, 6, null]); console.log(tree.isPerfectlyBalanced(), `tree.isPerfectlyBalanced()`); - tree.print(undefined, {isShowNull: true}); + tree.print(undefined, { isShowNull: true }); } catch (e) { console.error(e); } @@ -95,12 +95,12 @@ for (let i = 0; i < n; i++) { rbTree.add(i, i); } - console.log(( performance.now() - tS).toFixed(2), `RedBlackTree ${n.toLocaleString()} add`); + console.log((performance.now() - tS).toFixed(2), `RedBlackTree ${n.toLocaleString()} add`); console.log(`rbTree.size`, rbTree.size); for (let i = 0; i < n - 20; i++) { rbTree.delete(i) } - rbTree.print(rbTree.root,{isShowRedBlackNIL: true}); + rbTree.print(rbTree.root, { isShowRedBlackNIL: true }); const cS = performance.now(); for (let i = 1; i < 100000; i++) { cRBTree.setElement(i, i); @@ -125,7 +125,7 @@ for (let i = 0; i < n; i++) { pq.pop(); } - console.log(( performance.now() - tS).toFixed(2), `PriorityQueue ${n.toLocaleString()} add`); + console.log((performance.now() - tS).toFixed(2), `PriorityQueue ${n.toLocaleString()} add`); console.log(pq.size, `pq.size`); const cS = performance.now(); const cpq = new CPriorityQueue(); diff --git a/test/performance/data-structures/binary-tree/rb-tree.test.ts b/test/performance/data-structures/binary-tree/rb-tree.test.ts index ca0dfcc..0b6f89e 100644 --- a/test/performance/data-structures/binary-tree/rb-tree.test.ts +++ b/test/performance/data-structures/binary-tree/rb-tree.test.ts @@ -41,4 +41,11 @@ suite } }); +suite.add(`${HUNDRED_THOUSAND.toLocaleString()} add & iterator`, () => { + rbTree.clear(); + for (let i = 0; i < arr.length; i++) rbTree.add(arr[i]); + const entries = [...rbTree]; + return entries.length === HUNDRED_THOUSAND +}); + export { suite }; diff --git a/test/unit/data-structures/binary-tree/avl-tree.test.ts b/test/unit/data-structures/binary-tree/avl-tree.test.ts index 04291b1..9a62c00 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -1,4 +1,4 @@ -import { AVLTree, AVLTreeNode, CP, IterationType } from '../../../../src'; +import { AVLTree, AVLTreeNode, BinaryTreeNode, BSTNode, CP, IterationType } from '../../../../src'; describe('AVL Tree Test', () => { it('should perform various operations on a AVL Tree', () => { @@ -286,3 +286,59 @@ describe('AVLTree', () => { }); }); }); + +describe('AVLTree iterative methods test', () => { + let avl: AVLTree; + beforeEach(() => { + avl = new AVLTree(); + avl.add(1, 'a'); + avl.add(2, 'b'); + avl.add(3, 'c'); + }); + + test('The node obtained by get Node should match the node type', () => { + const node3 = avl.getNode(3); + expect(node3).toBeInstanceOf(BinaryTreeNode); + expect(node3).toBeInstanceOf(BSTNode); + expect(node3).toBeInstanceOf(AVLTreeNode); + }); + + test('forEach should iterate over all elements', () => { + const mockCallback = jest.fn(); + avl.forEach((entry) => { + mockCallback(entry); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); + expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); + expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + }); + + test('filter should return a new tree with filtered elements', () => { + const filteredTree = avl.filter(([key]) => key > 1); + expect(filteredTree.size).toBe(2); + expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); + }); + + test('map should return a new tree with modified elements', () => { + const mappedTree = avl.map(([key]) => (key * 2).toString()); + expect(mappedTree.size).toBe(3); + expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); + }); + + test('reduce should accumulate values', () => { + const sum = avl.reduce((acc, [key]) => acc + key, 0); + expect(sum).toBe(6); + }); + + test('[Symbol.iterator] should provide an iterator', () => { + const entries = []; + for (const entry of avl) { + entries.push(entry); + } + + expect(entries.length).toBe(3); + expect(entries).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]); + }); +}); 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 082fdda..9af491f 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -189,9 +189,9 @@ describe('BinaryTree', () => { tree.add(4); tree.add(2); expect(tree.getHeight()).toBe(1); - tree.iterationType = IterationType.RECURSIVE; + tree.options.iterationType = IterationType.RECURSIVE; expect(tree.getHeight()).toBe(1); - tree.iterationType = IterationType.ITERATIVE; + tree.options.iterationType = IterationType.ITERATIVE; tree.add(6); tree.add(1); @@ -541,11 +541,11 @@ describe('BinaryTree', () => { tree.add(3, 'B'); tree.add(7, 'C'); - tree.iterationType = IterationType.ITERATIVE; - expect([...tree]).toEqual([3, 5, 7]); - tree.iterationType = IterationType.RECURSIVE; - expect([...tree]).toEqual([3, 5, 7]); - tree.iterationType = IterationType.ITERATIVE; + tree.options.iterationType = IterationType.ITERATIVE; + expect([...tree]).toEqual([[3, "B"], [5, "A"], [7, "C"]]); + tree.options.iterationType = IterationType.RECURSIVE; + expect([...tree]).toEqual([[3, "B"], [5, "A"], [7, "C"]]); + tree.options.iterationType = IterationType.ITERATIVE; const result = tree.morris(); expect(result).toEqual([3, 5, 7]); @@ -564,3 +564,58 @@ describe('BinaryTree', () => { expect(tree.getHeight()).toBe(-1); }); }); + + +describe('BinaryTree iterative methods test', () => { + let binaryTree: BinaryTree; + beforeEach(() => { + binaryTree = new BinaryTree(); + binaryTree.add(1, 'a'); + binaryTree.add(2, 'b'); + binaryTree.add(3, 'c'); + }); + + test('The node obtained by get Node should match the node type', () => { + const node3 = binaryTree.getNode(3); + expect(node3).toBeInstanceOf(BinaryTreeNode); + }); + + test('forEach should iterate over all elements', () => { + const mockCallback = jest.fn(); + binaryTree.forEach((entry) => { + mockCallback(entry); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0][0]).toEqual([2, 'b']); + expect(mockCallback.mock.calls[1][0]).toEqual([1, 'a']); + expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + }); + + test('filter should return a new tree with filtered elements', () => { + const filteredTree = binaryTree.filter(([key]) => key > 1); + expect(filteredTree.size).toBe(2); + expect([...filteredTree]).toEqual([[3, 'c'], [2, 'b']]); + }); + + test('map should return a new tree with modified elements', () => { + const mappedTree = binaryTree.map(([key]) => (key * 2).toString()); + expect(mappedTree.size).toBe(3); + expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); + }); + + test('reduce should accumulate values', () => { + const sum = binaryTree.reduce((acc, [key]) => acc + key, 0); + expect(sum).toBe(6); + }); + + test('[Symbol.iterator] should provide an iterator', () => { + const entries = []; + for (const entry of binaryTree) { + entries.push(entry); + } + + expect(entries.length).toBe(3); + expect(entries).toEqual([[2, 'b'], [1, 'a'], [3, 'c']]); + }); +}); \ No newline at end of file diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index 9a33680..5fa0de7 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -1,4 +1,4 @@ -import { BST, BSTNode, CP, IterationType } from '../../../../src'; +import { BinaryTreeNode, BST, BSTNode, CP, IterationType } from '../../../../src'; import { isDebugTest } from '../../../config'; const isDebug = isDebugTest; @@ -851,3 +851,58 @@ describe('BST Performance test', function () { ]); }); }); + +describe('BST iterative methods test', () => { + let bst: BST; + beforeEach(() => { + bst = new BST(); + bst.add(1, 'a'); + bst.add(2, 'b'); + bst.add(3, 'c'); + }); + + test('The node obtained by get Node should match the node type', () => { + const node3 = bst.getNode(3); + expect(node3).toBeInstanceOf(BinaryTreeNode); + expect(node3).toBeInstanceOf(BSTNode); + }); + + test('forEach should iterate over all elements', () => { + const mockCallback = jest.fn(); + bst.forEach((entry) => { + mockCallback(entry); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); + expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); + expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + }); + + test('filter should return a new tree with filtered elements', () => { + const filteredTree = bst.filter(([key]) => key > 1); + expect(filteredTree.size).toBe(2); + expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); + }); + + test('map should return a new tree with modified elements', () => { + const mappedTree = bst.map(([key]) => (key * 2).toString()); + expect(mappedTree.size).toBe(3); + expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); + }); + + test('reduce should accumulate values', () => { + const sum = bst.reduce((acc, [key]) => acc + key, 0); + expect(sum).toBe(6); + }); + + test('[Symbol.iterator] should provide an iterator', () => { + const entries = []; + for (const entry of bst) { + entries.push(entry); + } + + expect(entries.length).toBe(3); + expect(entries).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]); + }); +}); \ No newline at end of file diff --git a/test/unit/data-structures/binary-tree/rb-tree.test.ts b/test/unit/data-structures/binary-tree/rb-tree.test.ts index 88e78ce..26c129f 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/rb-tree.test.ts @@ -1,4 +1,4 @@ -import { IterationType, RBTNColor, RedBlackTree, RedBlackTreeNode } from '../../../../src'; +import { BinaryTreeNode, BSTNode, IterationType, RBTNColor, RedBlackTree, RedBlackTreeNode } from '../../../../src'; import { getRandomInt, getRandomIntArray, magnitude } from '../../../utils'; import { isDebugTest } from '../../../config'; import { OrderedMap } from 'js-sdsl'; @@ -490,7 +490,7 @@ describe('RedBlackTree', () => { it('duplicates', () => { tree.addMany([9, 8, 7, 8, 8, 8, 2, 3, 6, 5, 5, 4]); - tree.print(); + isDebug && tree.print(); expect(tree.size).toBe(8); expect(tree.isBST()).toBe(true); @@ -505,3 +505,59 @@ describe('RedBlackTree', () => { expect(tree.isAVLBalanced()).toBe(false); }) }); + +describe('RedBlackTree iterative methods test', () => { + let rbTree: RedBlackTree; + beforeEach(() => { + rbTree = new RedBlackTree(); + rbTree.add(1, 'a'); + rbTree.add(2, 'b'); + rbTree.add(3, 'c'); + }); + + test('The node obtained by get Node should match the node type', () => { + const node3 = rbTree.getNode(3); + expect(node3).toBeInstanceOf(BinaryTreeNode); + expect(node3).toBeInstanceOf(BSTNode); + expect(node3).toBeInstanceOf(RedBlackTreeNode); + }); + + test('forEach should iterate over all elements', () => { + const mockCallback = jest.fn(); + rbTree.forEach((entry) => { + mockCallback(entry); + }); + + expect(mockCallback.mock.calls.length).toBe(3); + expect(mockCallback.mock.calls[0][0]).toEqual([1, 'a']); + expect(mockCallback.mock.calls[1][0]).toEqual([2, 'b']); + expect(mockCallback.mock.calls[2][0]).toEqual([3, 'c']); + }); + + test('filter should return a new tree with filtered elements', () => { + const filteredTree = rbTree.filter(([key]) => key > 1); + expect(filteredTree.size).toBe(2); + expect([...filteredTree]).toEqual([[2, 'b'], [3, 'c']]); + }); + + test('map should return a new tree with modified elements', () => { + const mappedTree = rbTree.map(([key]) => (key * 2).toString()); + expect(mappedTree.size).toBe(3); + expect([...mappedTree]).toEqual([[1, '2'], [2, '4'], [3, '6']]); + }); + + test('reduce should accumulate values', () => { + const sum = rbTree.reduce((acc, [key]) => acc + key, 0); + expect(sum).toBe(6); + }); + + test('[Symbol.iterator] should provide an iterator', () => { + const entries = []; + for (const entry of rbTree) { + entries.push(entry); + } + + expect(entries.length).toBe(3); + expect(entries).toEqual([[1, 'a'], [2, 'b'], [3, 'c']]); + }); +}); \ No newline at end of file