diff --git a/README.md b/README.md index d94e45e..5e75843 100644 --- a/README.md +++ b/README.md @@ -823,43 +823,43 @@ Version 11.7.9 [//]: # (No deletion!!! Start of Replace Section)
heap
-
test nametime taken (ms)executions per secsample deviation
100,000 add7.49133.432.10e-4
100,000 add & poll44.0822.690.00
+
test nametime taken (ms)executions per secsample deviation
100,000 add7.55132.362.00e-4
100,000 add & poll44.1722.640.00
rb-tree
-
test nametime taken (ms)executions per secsample deviation
100,000 add79.1612.630.00
100,000 add randomly84.3511.850.00
100,000 get111.748.950.00
100,000 iterator26.6137.580.00
100,000 add & delete orderly160.056.250.02
100,000 add & delete randomly234.554.260.01
+
test nametime taken (ms)executions per secsample deviation
100,000 add77.0312.980.00
100,000 add randomly80.7412.380.00
100,000 get111.298.990.00
100,000 iterator24.9140.140.01
100,000 add & delete orderly153.126.530.00
100,000 add & delete randomly233.764.280.00
queue
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push40.7024.570.01
100,000 push & shift5.15194.256.57e-4
Native JS Array 100,000 push & shift2143.370.470.15
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push43.7922.830.01
100,000 push & shift5.09196.545.29e-4
Native JS Array 100,000 push & shift2134.490.470.18
deque
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push24.7440.430.00
1,000,000 push & pop31.3831.870.00
1,000,000 push & shift32.1231.130.00
100,000 push & shift3.39295.243.62e-4
Native JS Array 100,000 push & shift2348.520.430.21
100,000 unshift & shift3.28304.602.76e-4
Native JS Array 100,000 unshift & shift4062.430.250.12
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push25.3939.390.00
1,000,000 push & pop32.9330.370.01
1,000,000 push & shift32.3630.900.00
100,000 push & shift3.41293.263.14e-4
Native JS Array 100,000 push & shift2309.810.430.46
100,000 unshift & shift3.24308.353.36e-4
Native JS Array 100,000 unshift & shift4213.920.240.20
hash-map
-
test nametime taken (ms)executions per secsample deviation
1,000,000 set106.659.380.02
Native JS Map 1,000,000 set201.704.960.01
Native JS Set 1,000,000 add163.556.110.01
1,000,000 set & get115.568.650.02
Native JS Map 1,000,000 set & get264.293.780.01
Native JS Set 1,000,000 add & has171.965.820.01
1,000,000 ObjKey set & get326.273.060.05
Native JS Map 1,000,000 ObjKey set & get322.173.100.06
Native JS Set 1,000,000 ObjKey add & has241.344.140.03
+
test nametime taken (ms)executions per secsample deviation
1,000,000 set107.769.280.02
Native JS Map 1,000,000 set204.814.880.02
Native JS Set 1,000,000 add165.266.050.01
1,000,000 set & get121.138.260.02
Native JS Map 1,000,000 set & get265.523.770.01
Native JS Set 1,000,000 add & has167.985.950.01
1,000,000 ObjKey set & get331.863.010.04
Native JS Map 1,000,000 ObjKey set & get322.123.100.05
Native JS Set 1,000,000 ObjKey add & has267.743.730.03
trie
-
test nametime taken (ms)executions per secsample deviation
100,000 push44.2122.620.00
100,000 getWords85.2211.730.00
+
test nametime taken (ms)executions per secsample deviation
100,000 push42.7923.377.76e-4
100,000 getWords81.8212.220.00
avl-tree
-
test nametime taken (ms)executions per secsample deviation
100,000 add269.923.700.01
100,000 add randomly317.133.150.00
100,000 get127.747.830.00
100,000 iterator29.9933.340.01
100,000 add & delete orderly431.272.320.00
100,000 add & delete randomly580.911.720.00
+
test nametime taken (ms)executions per secsample deviation
100,000 add270.763.690.01
100,000 add randomly326.943.060.00
100,000 get129.137.740.00
100,000 iterator30.9632.300.00
100,000 add & delete orderly440.622.270.00
100,000 add & delete randomly589.061.700.00
binary-tree-overall
-
test nametime taken (ms)executions per secsample deviation
10,000 RBTree add randomly6.73148.591.04e-4
10,000 RBTree get randomly9.48105.501.07e-4
10,000 RBTree add & delete randomly18.4054.332.70e-4
10,000 AVLTree add randomly23.5742.431.73e-4
10,000 AVLTree get randomly9.70103.067.92e-5
10,000 AVLTree add & delete randomly44.4322.513.33e-4
+
test nametime taken (ms)executions per secsample deviation
10,000 RBTree add randomly6.89145.169.69e-5
10,000 RBTree get randomly9.22108.431.55e-4
10,000 RBTree add & delete randomly18.7053.461.16e-4
10,000 AVLTree add randomly24.4540.912.06e-4
10,000 AVLTree get randomly9.77102.341.09e-4
10,000 AVLTree add & delete randomly45.7121.884.07e-4
directed-graph
-
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109723.272.46e-6
1,000 addEdge6.26159.787.28e-4
1,000 getVertex0.042.54e+44.17e-7
1,000 getEdge22.6444.160.00
tarjan200.145.000.01
topologicalSort175.915.680.01
+
test nametime taken (ms)executions per secsample deviation
1,000 addVertex0.109817.839.67e-7
1,000 addEdge6.18161.721.55e-4
1,000 getVertex0.042.51e+45.08e-7
1,000 getEdge22.8243.820.00
tarjan211.734.720.02
topologicalSort188.715.300.02
doubly-linked-list
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push196.175.100.03
1,000,000 unshift203.734.910.04
1,000,000 unshift & shift186.325.370.04
1,000,000 addBefore298.353.350.05
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push212.174.710.03
1,000,000 unshift210.094.760.03
1,000,000 unshift & shift213.414.690.07
1,000,000 addBefore320.833.120.07
singly-linked-list
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push & shift210.624.750.09
10,000 push & pop226.024.420.02
10,000 addBefore249.354.010.01
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push & shift205.404.870.05
10,000 push & pop220.934.530.00
10,000 addBefore248.194.030.01
priority-queue
-
test nametime taken (ms)executions per secsample deviation
100,000 add27.2336.727.64e-4
100,000 add & poll76.6613.040.00
+
test nametime taken (ms)executions per secsample deviation
100,000 add27.5536.309.81e-4
100,000 add & poll76.2613.116.92e-4
stack
-
test nametime taken (ms)executions per secsample deviation
1,000,000 push37.1026.960.00
1,000,000 push & pop44.7222.360.01
+
test nametime taken (ms)executions per secsample deviation
1,000,000 push39.1925.520.00
1,000,000 push & pop44.9022.270.00
[//]: # (No deletion!!! End of Replace Section) diff --git a/package.json b/package.json index c6169ce..b19c699 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "data-structure-typed", - "version": "1.52.4", + "version": "1.52.5", "description": "Javascript Data Structure. Heap, Binary Tree, Red Black Tree, Linked List, Deque, Trie, HashMap, Directed Graph, Undirected Graph, Binary Search Tree(BST), AVL Tree, Priority Queue, Graph, Queue, Tree Multiset, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue, Stack. Benchmark compared with C++ STL. API aligned with ES6 and Java.util. Usability is comparable to Python", "main": "dist/cjs/index.js", "module": "dist/mjs/index.js", diff --git a/src/data-structures/base/iterable-element-base.ts b/src/data-structures/base/iterable-element-base.ts index fb1f9bf..8d22385 100644 --- a/src/data-structures/base/iterable-element-base.ts +++ b/src/data-structures/base/iterable-element-base.ts @@ -192,8 +192,8 @@ export abstract class IterableElementBase { * * The print function logs the elements of an array to the console. */ - print(): void { - console.log([...this]); + print(): E[] { + return [...this]; } abstract isEmpty(): boolean; diff --git a/src/data-structures/base/iterable-entry-base.ts b/src/data-structures/base/iterable-entry-base.ts index 96e4cd2..95e7af2 100644 --- a/src/data-structures/base/iterable-entry-base.ts +++ b/src/data-structures/base/iterable-entry-base.ts @@ -251,8 +251,8 @@ export abstract class IterableEntryBase { * * The print function logs the elements of an array to the console. */ - print(): void { - console.log([...this]); + print(): [K, V][] | string { + return [...this]; } abstract isEmpty(): boolean; diff --git a/src/data-structures/binary-tree/avl-tree-multi-map.ts b/src/data-structures/binary-tree/avl-tree-multi-map.ts index b1a7304..06d7371 100644 --- a/src/data-structures/binary-tree/avl-tree-multi-map.ts +++ b/src/data-structures/binary-tree/avl-tree-multi-map.ts @@ -182,15 +182,15 @@ export class AVLTreeMultiMap< if (keyOrNodeOrEntryOrRawElement === undefined || keyOrNodeOrEntryOrRawElement === null) return; if (this.isNode(keyOrNodeOrEntryOrRawElement)) return keyOrNodeOrEntryOrRawElement; - if (this.toEntryFn) { - const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); - if (key) return this.createNode(key, entryValue ?? value, count); + if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { + const [key, entryValue] = keyOrNodeOrEntryOrRawElement; + if (key === undefined || key === null) return; + if (this.isKey(key)) return this.createNode(key, value ?? entryValue, count); } - if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { - const [key, value] = keyOrNodeOrEntryOrRawElement; - if (key === undefined || key === null) return; - else return this.createNode(key, value, count); + if (this.toEntryFn) { + const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); + if (this.isKey(key)) return this.createNode(key, value ?? entryValue, count); } if (this.isKey(keyOrNodeOrEntryOrRawElement)) return this.createNode(keyOrNodeOrEntryOrRawElement, value, count); @@ -279,7 +279,7 @@ export class AVLTreeMultiMap< needBalanced = parent; } } else { - const leftSubTreeRightMost = curr.left ? this.getRightMost(curr.left) : undefined; + const leftSubTreeRightMost = curr.left ? this.getRightMost(node => node, curr.left) : undefined; if (leftSubTreeRightMost) { const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; orgCurrent = this._swapProperties(curr, leftSubTreeRightMost); diff --git a/src/data-structures/binary-tree/binary-tree.ts b/src/data-structures/binary-tree/binary-tree.ts index 4d26642..80160db 100644 --- a/src/data-structures/binary-tree/binary-tree.ts +++ b/src/data-structures/binary-tree/binary-tree.ts @@ -24,7 +24,7 @@ import type { } from '../../types'; import { DFSOperation, DFSStackItem } from '../../types'; import { IBinaryTree } from '../../interfaces'; -import { trampoline } from '../../utils'; +import { isComparable, trampoline } from '../../utils'; import { Queue } from '../queue'; import { IterableEntryBase } from '../base'; @@ -56,7 +56,7 @@ export class BinaryTreeNode< this.value = value; } - protected _left?: NODE | null; + protected _left?: OptBTNOrNull; /** * The function returns the value of the `_left` property, which can be of type `NODE`, `null`, or @@ -80,7 +80,7 @@ export class BinaryTreeNode< this._left = v; } - protected _right?: NODE | null; + protected _right?: OptBTNOrNull; /** * The function returns the right node of a binary tree or null if it doesn't exist. @@ -249,17 +249,17 @@ export class BinaryTree< if (this.isNode(keyOrNodeOrEntryOrRawElement)) return keyOrNodeOrEntryOrRawElement; - if (this.toEntryFn) { - const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); - if (key) return this.createNode(key, entryValue ?? value); - else return; - } - if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { - const [key, value] = keyOrNodeOrEntryOrRawElement; + const [key, entryValue] = keyOrNodeOrEntryOrRawElement; if (key === undefined) return; else if (key === null) return null; - else return this.createNode(key, value); + if (this.isKey(key)) return this.createNode(key, value ?? entryValue); + } + + if (this.toEntryFn) { + const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); + if (this.isKey(key)) return this.createNode(key, value ?? entryValue); + else return; } if (this.isKey(keyOrNodeOrEntryOrRawElement)) return this.createNode(keyOrNodeOrEntryOrRawElement, value); @@ -292,7 +292,7 @@ export class BinaryTree< if (this.toEntryFn) { const [key] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); - if (key) return this.getNodeByKey(key); + if (this.isKey(key)) return this.getNodeByKey(key); } if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { @@ -378,31 +378,19 @@ export class BinaryTree< } /** - * The function checks if a given value is a valid key by evaluating its type and value. - * @param {any} key - The `key` parameter can be of any type. It is the value that we want to check - * if it is a valid key. - * @param [isCheckValueOf=true] - The `isCheckValueOf` parameter is a boolean flag that determines - * whether the function should check the valueOf() method of an object when the key is of type - * 'object'. If `isCheckValueOf` is true, the function will recursively call itself with the value - * returned by key.valueOf(). - * @returns a boolean value. + * 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, isCheckValueOf = true): key is K { + isKey(key: any): key is K { if (key === null) return true; - const keyType = typeof key; - if (keyType === 'string' || keyType === 'bigint' || keyType === 'boolean') return true; - if (keyType === 'number') return !isNaN(key); - if (keyType === 'symbol' || keyType === 'undefined') return false; - if (keyType === 'function') return this.isKey(key()); - if (keyType === 'object') { - if (typeof key.toString === 'function') return true; - if (isCheckValueOf && typeof key.valueOf === 'function') { - this.isKey(key.valueOf(), false); - } - return false; - } - - return false; + return isComparable(key); } /** @@ -577,7 +565,7 @@ export class BinaryTree< if (!curr.left && !curr.right && !parent) { this._setRoot(undefined); } else if (curr.left) { - const leftSubTreeRightMost = this.getRightMost(curr.left); + const leftSubTreeRightMost = this.getRightMost(node => node, curr.left); if (leftSubTreeRightMost) { const parentOfLeftSubTreeMax = leftSubTreeRightMost.parent; orgCurrent = this._swapProperties(curr, leftSubTreeRightMost); @@ -662,6 +650,8 @@ export class BinaryTree< beginRoot: R | BTNKeyOrNodeOrEntry = this.root, iterationType: IterationType = this.iterationType ): NODE[] { + if (identifier === undefined) return []; + if (identifier === null) return []; beginRoot = this.ensureNode(beginRoot); if (!beginRoot) return []; callback = this._ensureCallback(identifier, callback); @@ -1132,23 +1122,32 @@ export class BinaryTree< * Time Complexity: O(log n) * Space Complexity: O(1) * - * The `getLeftMost` function returns the leftmost node in a binary tree, either using recursive or - * iterative traversal. - * @param {R | BTNKeyOrNodeOrEntry} beginRoot - The `beginRoot` parameter represents the - * starting point for finding the leftmost node in a binary tree. It can be either a root node (`R`), - * a key or node or entry (`BTNKeyOrNodeOrEntry`), or `null` or `undefined`. - * @param {IterationType} iterationType - The `iterationType` parameter is used to specify the type - * of iteration to be performed. It can have two possible values: - * @returns The function `getLeftMost` returns the leftmost node in a binary tree. + * 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 null if the tree is empty. It has a default value of `_DEFAULT_CALLBACK` + * if not provided explicitly. + * @param {R | BTNKeyOrNodeOrEntry} beginRoot - The `beginRoot` parameter in the + * `getLeftMost` function represents the starting point for finding the leftmost node in a binary + * tree. It can be either a reference to the root node of the tree (`R`), or a key, node, or entry in + * the binary tree structure (` + * @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 `beginRoot` node. If the `beginRoot` is `NIL`, + * it returns the result of the callback function applied to `undefined`. If the `beginRoot` is not a + * real node, it returns the result of the callback function applied */ - getLeftMost( + getLeftMost>>( + callback: C = this._DEFAULT_CALLBACK as C, beginRoot: R | BTNKeyOrNodeOrEntry = this.root, iterationType: IterationType = this.iterationType - ): OptBTNOrNull { - if (this.isNIL(beginRoot)) return beginRoot as NODE; + ): ReturnType { + if (this.isNIL(beginRoot)) return callback(undefined); beginRoot = this.ensureNode(beginRoot); - if (!this.isRealNode(beginRoot)) return beginRoot; + if (!this.isRealNode(beginRoot)) return callback(beginRoot); if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE): NODE => { @@ -1156,15 +1155,15 @@ export class BinaryTree< return dfs(cur.left); }; - return dfs(beginRoot); + return callback(dfs(beginRoot)); } else { // Indirect implementation of iteration using tail recursion optimization - const dfs = trampoline((cur: NODE) => { + const dfs = trampoline((cur: NODE): NODE => { if (!this.isRealNode(cur.left)) return cur; return dfs.cont(cur.left); }); - return dfs(beginRoot); + return callback(dfs(beginRoot)); } } @@ -1172,24 +1171,33 @@ export class BinaryTree< * Time Complexity: O(log n) * Space Complexity: O(1) * - * The `getRightMost` function returns the rightmost node in a binary tree, either recursively or - * iteratively. - * @param {R | BTNKeyOrNodeOrEntry} beginRoot - The `beginRoot` parameter represents the - * starting point for finding the rightmost node in a binary tree. It can be either a root node - * (`R`), a key or node or entry (`BTNKeyOrNodeOrEntry`), or `null` or `undefined`. - * @param {IterationType} iterationType - The `iterationType` parameter is used to specify the type - * of iteration to be performed when finding the rightmost node in a binary tree. It can have two - * possible values: - * @returns The function `getRightMost` returns a NODE object, `null`, or `undefined`. + * 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 the operation. It has a generic type `C` which extends `BTNCallback>`. The + * default value for `callback` is `this._DEFAULT_CALLBACK` if it is not provided. + * @param {R | BTNKeyOrNodeOrEntry} beginRoot - The `beginRoot` parameter in the + * `getRightMost` function represents the starting point for finding the rightmost node in a binary + * tree. It can be either a reference to the root node of the tree (`this.root`) or a specific key, + * node, or entry in the tree. If + * @param {IterationType} iterationType - The `iterationType` parameter in the `getRightMost` + * function specifies the type of iteration to be used when finding the rightmost node in a binary + * tree. It can have two possible values: + * @returns The `getRightMost` function returns the result of the callback function `C` applied to + * the rightmost node in the binary tree. The rightmost node is found either through a recursive + * depth-first search (if `iterationType` is 'RECURSIVE') or through an indirect implementation of + * iteration using tail recursion optimization. The result of the callback function applied to the + * rightmost node is returned */ - getRightMost( + getRightMost>>( + callback: C = this._DEFAULT_CALLBACK as C, beginRoot: R | BTNKeyOrNodeOrEntry = this.root, iterationType: IterationType = this.iterationType - ): OptBTNOrNull { - if (this.isNIL(beginRoot)) return beginRoot as NODE; + ): ReturnType { + if (this.isNIL(beginRoot)) return callback(undefined); // TODO support get right most by passing key in beginRoot = this.ensureNode(beginRoot); - if (!beginRoot) return beginRoot; + if (!beginRoot) return callback(beginRoot); if (iterationType === 'RECURSIVE') { const dfs = (cur: NODE): NODE => { @@ -1197,7 +1205,7 @@ export class BinaryTree< return dfs(cur.right); }; - return dfs(beginRoot); + return callback(dfs(beginRoot)); } else { // Indirect implementation of iteration using tail recursion optimization const dfs = trampoline((cur: NODE) => { @@ -1205,7 +1213,7 @@ export class BinaryTree< return dfs.cont(cur.right); }); - return dfs(beginRoot); + return callback(dfs(beginRoot)); } } @@ -1246,7 +1254,7 @@ export class BinaryTree< if (!this.isRealNode(x)) return undefined; if (this.isRealNode(x.right)) { - return this.getLeftMost(x.right); + return this.getLeftMost(node => node, x.right); } let y: OptBTNOrNull = x.parent; @@ -1736,20 +1744,21 @@ export class BinaryTree< * @returns Nothing is being returned. The function has a return type of `void`, which means it does * not return any value. */ - override print(beginRoot: R | BTNKeyOrNodeOrEntry = this.root, options?: BinaryTreePrintOptions): void { + override print(beginRoot: R | BTNKeyOrNodeOrEntry = this.root, options?: BinaryTreePrintOptions): string { const opts = { isShowUndefined: false, isShowNull: false, isShowRedBlackNIL: false, ...options }; beginRoot = this.ensureNode(beginRoot); - if (!beginRoot) return; + let output = ''; + if (!beginRoot) return output; if (opts.isShowUndefined) - console.log(`U for undefined - `); + output += `U for undefined + `; if (opts.isShowNull) - console.log(`N for null - `); + output += `N for null + `; if (opts.isShowRedBlackNIL) - console.log(`S for Sentinel Node(NIL) - `); + output += `S for Sentinel Node(NIL) + `; const display = (root: OptBTNOrNull): void => { const [lines, , ,] = this._displayAux(root, opts); @@ -1757,10 +1766,11 @@ export class BinaryTree< for (const line of lines) { paragraph += line + '\n'; } - console.log(paragraph); + output += paragraph; }; display(beginRoot); + return output; } protected _dfs>( diff --git a/src/data-structures/binary-tree/bst.ts b/src/data-structures/binary-tree/bst.ts index 74f7a02..bb79762 100644 --- a/src/data-structures/binary-tree/bst.ts +++ b/src/data-structures/binary-tree/bst.ts @@ -23,6 +23,7 @@ import { BTNEntry } from '../../types'; import { BinaryTree, BinaryTreeNode } from './binary-tree'; import { IBinaryTree } from '../../interfaces'; import { Queue } from '../queue'; +import { isComparable } from '../../utils'; export class BSTNode = BSTNodeNested> extends BinaryTreeNode< K, @@ -213,6 +214,10 @@ export class BST< return keyOrNodeOrEntryOrRawElement instanceof BSTNode; } + override isKey(key: any): key is K { + return isComparable(key, this.comparator !== this._DEFAULT_COMPARATOR); + } + /** * Time Complexity: O(log n) * Space Complexity: O(1) @@ -408,6 +413,8 @@ export class BST< beginRoot: R | BTNKeyOrNodeOrEntry = this.root, iterationType: IterationType = this.iterationType ): NODE[] { + if (identifier === undefined) return []; + if (identifier === null) return []; beginRoot = this.ensureNode(beginRoot); if (!beginRoot) return []; callback = this._ensureCallback(identifier, callback); diff --git a/src/data-structures/binary-tree/rb-tree.ts b/src/data-structures/binary-tree/rb-tree.ts index f3bf084..d662f05 100644 --- a/src/data-structures/binary-tree/rb-tree.ts +++ b/src/data-structures/binary-tree/rb-tree.ts @@ -164,7 +164,7 @@ export class RedBlackTree< // // if (this.toEntryFn) { // const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); - // if (key) return this.createNode(key, entryValue ?? value, 'RED'); + // if (this.isKey(key)) return this.createNode(key, entryValue ?? value, 'RED'); // } // // if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { @@ -262,7 +262,7 @@ export class RedBlackTree< replacementNode = nodeToDelete.left; this._transplant(nodeToDelete, nodeToDelete.left); } else { - const successor = this.getLeftMost(nodeToDelete.right); + const successor = this.getLeftMost(node => node, nodeToDelete.right); if (successor) { originalColor = successor.color; replacementNode = successor.right; diff --git a/src/data-structures/binary-tree/tree-multi-map.ts b/src/data-structures/binary-tree/tree-multi-map.ts index 9abf70e..d7388e2 100644 --- a/src/data-structures/binary-tree/tree-multi-map.ts +++ b/src/data-structures/binary-tree/tree-multi-map.ts @@ -167,15 +167,15 @@ export class TreeMultiMap< if (this.isNode(keyOrNodeOrEntryOrRawElement)) return keyOrNodeOrEntryOrRawElement; - if (this.toEntryFn) { - const [key] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); - if (key) return this.getNodeByKey(key); + if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { + const [key, entryValue] = keyOrNodeOrEntryOrRawElement; + if (key === undefined || key === null) return; + if (this.isKey(key)) return this.createNode(key, value ?? entryValue, 'BLACK', count); } - if (this.isEntry(keyOrNodeOrEntryOrRawElement)) { - const [key, value] = keyOrNodeOrEntryOrRawElement; - if (key === undefined || key === null) return; - else return this.createNode(key, value, 'BLACK', count); + if (this.toEntryFn) { + const [key, entryValue] = this.toEntryFn(keyOrNodeOrEntryOrRawElement as R); + if (this.isKey(key)) return this.createNode(key, value ?? entryValue, 'BLACK', count); } if (this.isKey(keyOrNodeOrEntryOrRawElement)) @@ -285,7 +285,7 @@ export class TreeMultiMap< return results; } } else { - const successor = this.getLeftMost(nodeToDelete.right); + const successor = this.getLeftMost(node => node, nodeToDelete.right); if (successor) { originalColor = successor.color; replacementNode = successor.right; diff --git a/src/types/utils/utils.ts b/src/types/utils/utils.ts index 91c84e5..8896b20 100644 --- a/src/types/utils/utils.ts +++ b/src/types/utils/utils.ts @@ -1,21 +1,23 @@ -export type ToThunkFn = () => ReturnType; -export type Thunk = () => ReturnType & { __THUNK__: symbol }; -export type TrlFn = (...args: any[]) => any; +export type ToThunkFn = () => R; +export type Thunk = ToThunkFn & { __THUNK__?: symbol }; +export type TrlFn = (...args: A) => R; export type TrlAsyncFn = (...args: any[]) => any; export type SpecifyOptional = Omit & Partial>; export type Any = string | number | bigint | boolean | symbol | undefined | object; -export type Comparable = - | number - | string - | bigint - | boolean - | ({ [key in string]: any } & { - valueOf(): Comparable; - }) - | ({ [key in string]: any } & { - toString(): Comparable; - }) - | (() => Comparable); +export type ComparablePrimitive = number | bigint | string | boolean; + +// TODO type safety looks not strict +export type ComparableObject = { [key in string]: any } & ( + | { + valueOf: () => ComparablePrimitive | ComparableObject; + toString?: () => string; + } + | { + toString: () => string; + } +); + +export type Comparable = ComparablePrimitive | Date | ComparableObject; diff --git a/src/utils/number.ts b/src/utils/number.ts index 11a4793..81222d9 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -1,3 +1,16 @@ +/** + * The function `toBinaryString` converts a number to a binary string representation with a specified + * number of digits. + * @param {number} num - The `num` parameter in the `toBinaryString` function represents the number + * that you want to convert to a binary string. + * @param [digit=32] - The `digit` parameter in the `toBinaryString` function represents the number of + * digits the binary string should have. By default, it is set to 32, meaning that the binary string + * will be padded with zeros at the beginning to ensure it is 32 bits long. You can provide a + * @returns The function `toBinaryString` takes a number as input and converts it to a binary string + * representation with a specified number of digits (default is 32). The binary string is padded with + * zeros at the beginning to ensure it has the specified number of digits. The function returns the + * binary string representation of the input number. + */ export function toBinaryString(num: number, digit = 32) { // Convert number to binary string let binaryString = (num >>> 0).toString(2); // Use the unsigned right shift operator to ensure you get a binary representation of a 32-bit unsigned integer diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 32ddf0b..cc6f5e5 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -5,8 +5,14 @@ * @copyright Copyright (c) 2022 Tyler Zeng * @license MIT License */ -import type { Comparable, Thunk, ToThunkFn, TrlAsyncFn, TrlFn } from '../types'; +import type { Comparable, ComparablePrimitive, Thunk, ToThunkFn, TrlAsyncFn, TrlFn } from '../types'; +/** + * The function generates a random UUID (Universally Unique Identifier) in TypeScript. + * @returns A randomly generated UUID (Universally Unique Identifier) in the format + * 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' where each 'x' is replaced with a random hexadecimal + * character. + */ export const uuidV4 = function () { return 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'.replace(/[x]/g, function (c) { const r = (Math.random() * 16) | 0, @@ -15,6 +21,15 @@ export const uuidV4 = function () { }); }; +/** + * The `arrayRemove` function removes elements from an array based on a specified predicate function + * and returns the removed elements. + * @param {T[]} array - An array of elements that you want to filter based on the provided predicate + * function. + * @param predicate - The `predicate` parameter is a function that takes three arguments: + * @returns The `arrayRemove` function returns an array containing the elements that satisfy the given + * `predicate` function. + */ export const arrayRemove = function (array: T[], predicate: (item: T, index: number, array: T[]) => boolean): T[] { let i = -1, len = array ? array.length : 0; @@ -34,18 +49,45 @@ export const arrayRemove = function (array: T[], predicate: (item: T, index: export const THUNK_SYMBOL = Symbol('thunk'); +/** + * The function `isThunk` checks if a given value is a function with a specific symbol property. + * @param {any} fnOrValue - The `fnOrValue` parameter in the `isThunk` function can be either a + * function or a value that you want to check if it is a thunk. Thunks are functions that are wrapped + * around a value or computation for lazy evaluation. The function checks if the `fnOrValue` is + * @returns The function `isThunk` is checking if the input `fnOrValue` is a function and if it has a + * property `__THUNK__` equal to `THUNK_SYMBOL`. The return value will be `true` if both conditions are + * met, otherwise it will be `false`. + */ export const isThunk = (fnOrValue: any) => { return typeof fnOrValue === 'function' && fnOrValue.__THUNK__ === THUNK_SYMBOL; }; +/** + * The `toThunk` function in TypeScript converts a function into a thunk by wrapping it in a closure. + * @param {ToThunkFn} fn - `fn` is a function that will be converted into a thunk. + * @returns A thunk function is being returned. Thunk functions are functions that delay the evaluation + * of an expression or operation until it is explicitly called or invoked. In this case, the `toThunk` + * function takes a function `fn` as an argument and returns a thunk function that, when called, will + * execute the `fn` function provided as an argument. + */ export const toThunk = (fn: ToThunkFn): Thunk => { const thunk = () => fn(); thunk.__THUNK__ = THUNK_SYMBOL; return thunk; }; +/** + * The `trampoline` function in TypeScript enables tail call optimization by using thunks to avoid + * stack overflow. + * @param {TrlFn} fn - The `fn` parameter in the `trampoline` function is a function that takes any + * number of arguments and returns a value. + * @returns The `trampoline` function returns an object with two properties: + * 1. A function that executes the provided function `fn` and continues to execute any thunks returned + * by `fn` until a non-thunk value is returned. + * 2. A `cont` property that is a function which creates a thunk for the provided function `fn`. + */ export const trampoline = (fn: TrlFn) => { - const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)); + const cont = (...args: [...Parameters]): ReturnType => toThunk(() => fn(...args)); return Object.assign( (...args: [...Parameters]) => { @@ -61,8 +103,20 @@ export const trampoline = (fn: TrlFn) => { ); }; +/** + * The `trampolineAsync` function in TypeScript allows for asynchronous trampolining of a given + * function. + * @param {TrlAsyncFn} fn - The `fn` parameter in the `trampolineAsync` function is expected to be a + * function that returns a Promise. This function will be called recursively until a non-thunk value is + * returned. + * @returns The `trampolineAsync` function returns an object with two properties: + * 1. An async function that executes the provided `TrlAsyncFn` function and continues to execute any + * thunks returned by the function until a non-thunk value is returned. + * 2. A `cont` property that is a function which wraps the provided `TrlAsyncFn` function in a thunk + * and returns it. + */ export const trampolineAsync = (fn: TrlAsyncFn) => { - const cont = (...args: [...Parameters]) => toThunk(() => fn(...args)); + const cont = (...args: [...Parameters]): ReturnType => toThunk(() => fn(...args)); return Object.assign( async (...args: [...Parameters]) => { @@ -78,6 +132,15 @@ export const trampolineAsync = (fn: TrlAsyncFn) => { ); }; +/** + * The function `getMSB` returns the most significant bit of a given number. + * @param {number} value - The `value` parameter is a number for which we want to find the position of + * the Most Significant Bit (MSB). The function `getMSB` takes this number as input and calculates the + * position of the MSB in its binary representation. + * @returns The function `getMSB` returns the most significant bit (MSB) of the input `value`. If the + * input value is less than or equal to 0, it returns 0. Otherwise, it calculates the position of the + * MSB using the `Math.clz32` function and bitwise left shifts 1 to that position. + */ export const getMSB = (value: number): number => { if (value <= 0) { return 0; @@ -85,42 +148,135 @@ export const getMSB = (value: number): number => { return 1 << (31 - Math.clz32(value)); }; +/** + * The `rangeCheck` function in TypeScript is used to validate if an index is within a specified range + * and throws a `RangeError` with a custom message if it is out of bounds. + * @param {number} index - The `index` parameter represents the value that you want to check if it + * falls within a specified range. + * @param {number} min - The `min` parameter represents the minimum value that the `index` should be + * compared against in the `rangeCheck` function. + * @param {number} max - The `max` parameter in the `rangeCheck` function represents the maximum value + * that the `index` parameter is allowed to have. If the `index` is greater than this `max` value, a + * `RangeError` will be thrown. + * @param [message=Index out of bounds.] - The `message` parameter is a string that represents the + * error message to be thrown if the index is out of bounds. By default, if no message is provided when + * calling the `rangeCheck` function, the message "Index out of bounds." will be used. + */ export const rangeCheck = (index: number, min: number, max: number, message = 'Index out of bounds.'): void => { if (index < min || index > max) throw new RangeError(message); }; +/** + * The function `throwRangeError` throws a RangeError with a custom message if called. + * @param [message=The value is off-limits.] - The `message` parameter is a string that represents the + * error message to be displayed when a `RangeError` is thrown. If no message is provided, the default + * message is 'The value is off-limits.'. + */ export const throwRangeError = (message = 'The value is off-limits.'): void => { throw new RangeError(message); }; +/** + * The function `isWeakKey` checks if the input is an object or a function in TypeScript. + * @param {unknown} input - The `input` parameter in the `isWeakKey` function is of type `unknown`, + * which means it can be any type. The function checks if the `input` is an object (excluding `null`) + * or a function, and returns a boolean indicating whether the `input` is a weak + * @returns The function `isWeakKey` returns a boolean value indicating whether the input is an object + * or a function. + */ export const isWeakKey = (input: unknown): input is object => { const inputType = typeof input; return (inputType === 'object' && input !== null) || inputType === 'function'; }; +/** + * The function `calcMinUnitsRequired` calculates the minimum number of units required to accommodate a + * given total quantity based on a specified unit size. + * @param {number} totalQuantity - The `totalQuantity` parameter represents the total quantity of items + * that need to be processed or handled. + * @param {number} unitSize - The `unitSize` parameter represents the size of each unit or package. It + * is used in the `calcMinUnitsRequired` function to calculate the minimum number of units required to + * accommodate a total quantity of items. + */ export const calcMinUnitsRequired = (totalQuantity: number, unitSize: number) => Math.floor((totalQuantity + unitSize - 1) / unitSize); +/** + * The `roundFixed` function in TypeScript rounds a number to a specified number of decimal places. + * @param {number} num - The `num` parameter is a number that you want to round to a certain number of + * decimal places. + * @param {number} [digit=10] - The `digit` parameter in the `roundFixed` function specifies the number + * of decimal places to round the number to. By default, it is set to 10 if not provided explicitly. + * @returns The function `roundFixed` returns a number that is rounded to the specified number of + * decimal places (default is 10 decimal places). + */ export const roundFixed = (num: number, digit: number = 10) => { const multiplier = Math.pow(10, digit); return Math.round(num * multiplier) / multiplier; }; -export function isComparable(key: any): key is Comparable { - const keyType = typeof key; - if (keyType === 'number') return !isNaN(key); - if (keyType === 'string') return true; - if (keyType === 'bigint') return true; - if (keyType === 'boolean') return true; - if (keyType === 'symbol') return false; - if (keyType === 'undefined') return false; - if (keyType === 'function') return isComparable(key()); - if (keyType === 'object') { - if (key === null) return true; - // if (typeof key.valueOf === 'function') return isComparable(key.valueOf()); // This will keep recursing because every object has a valueOf method. - // if (typeof key.toString === 'function') return isComparable(key.toString()); // This will also keep recursing because every string type has a toString method. - return false; - } - - return false; +/** + * The function `isPrimitiveComparable` checks if a value is a primitive type that can be compared. + * @param {unknown} value - The `value` parameter in the `isPrimitiveComparable` function is of type + * `unknown`, which means it can be any type. The function checks if the `value` is a primitive type + * that can be compared, such as number, bigint, string, or boolean. + * @returns The function `isPrimitiveComparable` returns a boolean value indicating whether the input + * `value` is a primitive value that can be compared using standard comparison operators (<, >, <=, + * >=). + */ +function isPrimitiveComparable(value: unknown): value is ComparablePrimitive { + const valueType = typeof value; + if (valueType === 'number') return !Number.isNaN(value); + return valueType === 'bigint' || valueType === 'string' || valueType === 'boolean'; +} + +/** + * The function `tryObjectToPrimitive` attempts to convert an object to a comparable primitive value by + * first checking the `valueOf` method and then the `toString` method. + * @param {object} obj - The `obj` parameter in the `tryObjectToPrimitive` function is an object that + * you want to convert to a primitive value. The function attempts to convert the object to a primitive + * value by first checking if the object has a `valueOf` method. If the `valueOf` method exists, it + * @returns The function `tryObjectToPrimitive` returns a value of type `ComparablePrimitive` if a + * primitive comparable value is found within the object, or a string value if the object has a custom + * `toString` method that does not return `'[object Object]'`. If neither condition is met, the + * function returns `null`. + */ +function tryObjectToPrimitive(obj: object): ComparablePrimitive | null { + if (typeof obj.valueOf === 'function') { + const valueOfResult = obj.valueOf(); + if (valueOfResult !== obj) { + if (isPrimitiveComparable(valueOfResult)) return valueOfResult; + if (typeof valueOfResult === 'object' && valueOfResult !== null) return tryObjectToPrimitive(valueOfResult); + } + } + if (typeof obj.toString === 'function') { + const stringResult = obj.toString(); + if (stringResult !== '[object Object]') return stringResult; + } + return null; +} + +/** + * The function `isComparable` in TypeScript checks if a value is comparable, handling primitive values + * and objects with optional force comparison. + * @param {unknown} value - The `value` parameter in the `isComparable` function represents the value + * that you want to check if it is comparable. It can be of any type (`unknown`), and the function will + * determine if it is comparable based on certain conditions. + * @param [isForceObjectComparable=false] - The `isForceObjectComparable` parameter in the + * `isComparable` function is a boolean flag that determines whether to treat non-primitive values as + * comparable objects. When set to `true`, it forces the function to consider non-primitive values as + * comparable objects, regardless of their type. + * @returns The function `isComparable` returns a boolean value indicating whether the `value` is + * considered comparable or not. + */ +export function isComparable(value: unknown, isForceObjectComparable = false): value is Comparable { + if (value === null || value === undefined) return false; + if (isPrimitiveComparable(value)) return true; + + if (typeof value !== 'object') return false; + if (value instanceof Date) return !Number.isNaN(value.getTime()); + if (isForceObjectComparable) return true; + const comparableValue = tryObjectToPrimitive(value); + if (comparableValue === null || comparableValue === undefined) return false; + return isPrimitiveComparable(comparableValue); } diff --git a/test/config.ts b/test/config.ts index 57a271d..30e6245 100644 --- a/test/config.ts +++ b/test/config.ts @@ -1,2 +1,10 @@ export const isDebugTest: boolean = false; export const isCompetitor: boolean = false; +export const isTestStackOverflow = false; +export const SYSTEM_MAX_CALL_STACK = (function getMaxStackDepth(depth = 0) { + try { + return getMaxStackDepth(depth + 1); + } catch (e) { + return depth + 3000; + } +})(); diff --git a/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts index 6dc1e38..e69f3a3 100644 --- a/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts @@ -84,10 +84,10 @@ describe('AVLTreeMultiMap operations test1', () => { const nodesByCount2 = treeMultimap.getNodes(2, node => node.count); expect(nodesByCount2.length).toBe(2); const leftMost = treeMultimap.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = treeMultimap.getNode(15); - const minNodeBySpecificNode = node15 && treeMultimap.getLeftMost(node15); + const minNodeBySpecificNode = node15 && treeMultimap.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(15); let subTreeSum = 0; @@ -340,10 +340,10 @@ describe('AVLTreeMultiMap operations test recursively1', () => { const nodesByCount2 = treeMultimap.getNodes(2, node => node.count); expect(nodesByCount2.length).toBe(2); const leftMost = treeMultimap.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = treeMultimap.getNode(15); - const minNodeBySpecificNode = node15 && treeMultimap.getLeftMost(node15); + const minNodeBySpecificNode = node15 && treeMultimap.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(15); let subTreeSum = 0; 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 5e3d5e3..b6b2b07 100644 --- a/test/unit/data-structures/binary-tree/avl-tree.test.ts +++ b/test/unit/data-structures/binary-tree/avl-tree.test.ts @@ -17,10 +17,10 @@ describe('AVL Tree Test', () => { expect(getNodeById?.key).toBe(10); const getMinNodeByRoot = tree.getLeftMost(); - expect(getMinNodeByRoot?.key).toBe(1); + expect(getMinNodeByRoot).toBe(1); const node15 = tree.getNode(15); - const getMinNodeBySpecificNode = node15 && tree.getLeftMost(node15); + const getMinNodeBySpecificNode = node15 && tree.getLeftMost(node => node, node15); expect(getMinNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; @@ -125,10 +125,10 @@ describe('AVL Tree Test recursively', () => { expect(getNodeById?.key).toBe(10); const getMinNodeByRoot = tree.getLeftMost(); - expect(getMinNodeByRoot?.key).toBe(1); + expect(getMinNodeByRoot).toBe(1); const node15 = tree.getNode(15); - const getMinNodeBySpecificNode = node15 && tree.getLeftMost(node15); + const getMinNodeBySpecificNode = node15 && tree.getLeftMost(node => node, node15); expect(getMinNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; 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 423f1dc..273b6bf 100644 --- a/test/unit/data-structures/binary-tree/binary-tree.test.ts +++ b/test/unit/data-structures/binary-tree/binary-tree.test.ts @@ -1,4 +1,4 @@ -import { BinaryTree, BinaryTreeNode } from '../../../../src'; +import { BinaryTree, BinaryTreeNode, BTNEntry } from '../../../../src'; import { getRandomIntArray } from '../../../utils'; // import { isDebugTest } from '../../../config'; @@ -86,6 +86,27 @@ describe('BinaryTreeNode', () => { }); }); +describe('BinaryTree addMany', () => { + it('should addMany', () => { + const tree = new BinaryTree([], { + toEntryFn: ({ id, name }) => [id, name] + }); + tree.addMany( + [ + { id: 1, name: 1 }, + { id: 2, name: 2 }, + { id: 4, name: 4 }, + { id: 3, name: 3 } + ], + [undefined, 22, 44, 33] + ); + expect(tree.getNodeByKey(2)?.value).toBe(22); + expect(tree.getNodeByKey(3)?.value).toBe(33); + expect(tree.getNodeByKey(4)?.value).toBe(44); + expect(tree.getNodeByKey(1)?.value).toBe(1); + }); +}); + describe('BinaryTree', () => { let tree: BinaryTree; @@ -258,15 +279,6 @@ describe('BinaryTree', () => { expect(inOrder).toEqual([1, 2, 3, 4, 5, 6, 7]); }); - it('should getLeftMost', () => { - tree.addMany([4, 2, 6, 1, 3, 5, 7]); - - const leftMost = tree.getLeftMost(tree.root, 'RECURSIVE'); - expect(leftMost?.key).toEqual(1); - const rightMost = tree.getRightMost(tree.root, 'RECURSIVE'); - expect(rightMost?.key).toEqual(7); - }); - it('should isSubtreeBST', () => { tree.addMany([ new BinaryTreeNode(4, 4), @@ -285,13 +297,187 @@ describe('BinaryTree', () => { it('should isSubtreeBST', () => { tree.addMany([4, 2, 6, 1, 3, 5, 7, 4]); + expect(tree.print()).toBe( + ' ___4___ \n' + + ' / \\ \n' + + ' _2_ _6_ \n' + + ' / \\ / \\ \n' + + ' 1 3 5 7 \n' + + ' \n' + ); expect(tree.isBST(tree.getNode(4), 'RECURSIVE')).toBe(true); expect(tree.isBST(tree.getNode(4), 'ITERATIVE')).toBe(true); expect(tree.getNodes(2, undefined, false, null)).toEqual([]); + expect(tree.getNodes(undefined)).toEqual([]); expect(tree.getNodes(tree.getNodeByKey(2), undefined, false, tree.root)).toEqual([tree.getNodeByKey(2)]); }); + describe('should isKey', () => { + describe('primitive types', () => { + it('numbers should be a key', () => { + expect(tree.isKey(42)).toBe(true); + expect(tree.isKey(0)).toBe(true); + expect(tree.isKey(-1)).toBe(true); + expect(tree.isKey(Infinity)).toBe(true); + expect(tree.isKey(-Infinity)).toBe(true); + }); + + it('NaN should not be a key', () => { + expect(tree.isKey(NaN)).toBe(false); + }); + + it('strings should be a key', () => { + expect(tree.isKey('hello')).toBe(true); + expect(tree.isKey('')).toBe(true); + expect(tree.isKey('123')).toBe(true); + }); + + it('BigInt should be a key', () => { + expect(tree.isKey(BigInt(42))).toBe(true); + expect(tree.isKey(BigInt(0))).toBe(true); + expect(tree.isKey(BigInt(-1))).toBe(true); + }); + + it('boolean should not be a key', () => { + expect(tree.isKey(true)).toBe(true); + expect(tree.isKey(false)).toBe(true); + }); + + it('null and undefined should not be a key', () => { + expect(tree.isKey(null)).toBe(true); + expect(tree.isKey(undefined)).toBe(false); + }); + + it('symbols should not be a key', () => { + expect(tree.isKey(Symbol('test'))).toBe(false); + expect(tree.isKey(Symbol.for('test'))).toBe(false); + }); + }); + + describe('Date objects', () => { + it('valid Date objects should be a key', () => { + expect(tree.isKey(new Date())).toBe(true); + expect(tree.isKey(new Date('2024-01-01'))).toBe(true); + }); + + it('invalid Date objects should not be a key', () => { + expect(tree.isKey(new Date('invalid'))).toBe(false); + }); + }); + + describe('arrays', () => { + it('arrays should be a key as they convert to string', () => { + expect(tree.isKey([])).toBe(true); + expect(tree.isKey([1, 2, 3])).toBe(true); + expect(tree.isKey(['a', 'b', 'c'])).toBe(true); + }); + }); + + describe('plain objects', () => { + it('plain objects should not be a key', () => { + expect(tree.isKey({})).toBe(false); + expect(tree.isKey({ a: 1 })).toBe(false); + }); + }); + + describe('custom objects', () => { + it('objects with numeric valueOf should be a key', () => { + expect(tree.isKey({ valueOf: () => 42 })).toBe(true); + }); + + it('objects with string valueOf should be a key', () => { + expect(tree.isKey({ valueOf: () => 'test' })).toBe(true); + }); + + it('objects with boolean valueOf should not be a key', () => { + expect(tree.isKey({ valueOf: () => true })).toBe(true); + }); + + it('objects with nested valueOf/toString should be a key', () => { + expect( + tree.isKey({ + valueOf: () => ({ toString: () => '42' }) + }) + ).toBe(true); + }); + }); + + describe('deeply nested objects', () => { + it('objects with deeply nested valueOf should be a key', () => { + const deeplyNested = { + valueOf: () => ({ + valueOf: () => 42 + }) + }; + expect(tree.isKey(deeplyNested)).toBe(true); + }); + + it('objects with very deeply nested conversion should be a key', () => { + const veryDeeplyNested = { + valueOf: () => ({ + valueOf: () => ({ + toString: () => '42' + }) + }) + }; + expect(tree.isKey(veryDeeplyNested)).toBe(true); + }); + + it('objects with circular references should not be a key', () => { + const circular: any = { + valueOf: () => circular + }; + expect(tree.isKey(circular)).toBe(false); + }); + }); + + describe('edge cases', () => { + it('objects returning non-primitive values should be handled correctly', () => { + const complexObject = { + valueOf: () => ({ + toString: () => ({ + valueOf: () => 'valid' + }) + }) + }; + expect(tree.isKey(complexObject)).toBe(false); + }); + + it('objects returning primitive values should be handled correctly', () => { + const complexObject = { + valueOf: () => ({ + valueOf: () => ({ + valueOf: () => ({ + valueOf: () => ({ + toString: () => `{ + valueOf: () => 'valid' + }` + }) + }) + }) + }) + }; + expect(tree.isKey(complexObject)).toBe(true); + }); + }); + + describe('type checking', () => { + it('should work with type guard in array methods', () => { + const values: unknown[] = [42, 'test', true, null, undefined, new Date()]; + const comparableValues = values.filter(item => tree.isKey(item)); + expect(comparableValues.length).toBe(5); + }); + }); + }); + + it('should isLeaf', () => { + tree.addMany([4, 2, 6, 1, 3, 5, 7, 4]); + const leftMost = tree.getLeftMost(); + expect(tree.isLeaf(leftMost)).toBe(true); + expect(tree.isLeaf(null)).toBe(true); + }); + it('should tree traverse', () => { tree.addMany([4, 2, 6, null, 1, 3, null, 5, null, 7]); expect(tree.dfs(node => node.key, 'PRE', undefined, 'ITERATIVE')).toEqual([4, 2, 1, 5, 6, 3, 7]); @@ -499,6 +685,56 @@ describe('BinaryTree', () => { null ]); }); + + it('should keyValueOrEntryOrRawElementToNode', () => { + const tree = new BinaryTree(); + const node0 = tree.keyValueOrEntryOrRawElementToNode(0); + expect(node0).toEqual({ + _left: undefined, + _right: undefined, + key: 0, + parent: undefined, + value: undefined + }); + + const nodeUndefined = tree.keyValueOrEntryOrRawElementToNode(undefined); + expect(nodeUndefined).toBe(undefined); + + const nodeNull = tree.keyValueOrEntryOrRawElementToNode(null); + expect(nodeNull).toBe(null); + + const nodeWithSeparateValue = tree.keyValueOrEntryOrRawElementToNode(7, 77); + expect(nodeWithSeparateValue?.value).toBe(77); + + expect(tree.keyValueOrEntryOrRawElementToNode([undefined, 2])).toBe(undefined); + + expect(tree.keyValueOrEntryOrRawElementToNode(Symbol('test') as unknown as number)).toBe(undefined); + + const bTree = new BinaryTree([], { + toEntryFn: (ele: { obj: { id: number } }) => [Symbol('test') as unknown as number, ele.obj.id] + }); + expect(bTree.keyValueOrEntryOrRawElementToNode({ obj: { id: 1 } })).toBe(undefined); + }); +}); + +describe('BinaryTree ensureNode', () => { + it('should ensureNode with toEntryFn', () => { + const tree = new BinaryTree< + number, + string, + { + id: number; + name: string; + } + >([], { toEntryFn: rawElement => [rawElement.id, rawElement.name] }); + tree.add({ id: 1, name: 'Pablo' }); + const node = tree.getNode(1); + expect(tree.ensureNode({ id: 1, name: 'Pablo' })).toBe(node); + expect(tree.ensureNode([1, 'Pablo'])).toBe(node); + expect(tree.ensureNode([null, 'Pablo'])).toBe(null); + expect(tree.ensureNode([undefined, 'Pablo'])).toBe(undefined); + expect(tree.ensureNode(Symbol('test') as unknown as number)).toBe(undefined); + }); }); describe('BinaryTree Morris Traversal', () => { @@ -553,25 +789,35 @@ describe('BinaryTree Morris Traversal', () => { }); describe('BinaryTree toEntryFn', () => { - it('should toEntryFn 1', () => { - const tree = new BinaryTree([], { + it('should toEntryFn throw', () => { + expect(() => { + new BinaryTree([], { + toEntryFn: `ele => [ele.obj.id, ele.obj.id]` as unknown as (rawElement: { + obj: { id: number }; + }) => BTNEntry + }); + }).toThrow('toEntryFn must be a function type'); + }); + + it('should toEntryFn with add', () => { + const binTree = new BinaryTree([], { toEntryFn: ele => [ele.obj.id, ele.obj.id] }); - tree.add({ obj: { id: 1 } }); - tree.add({ obj: { id: 2 } }); - tree.add({ obj: { id: 3 } }); - tree.add({ obj: { id: 4 } }); - tree.add({ obj: { id: 5 } }); + binTree.add({ obj: { id: 1 } }); + binTree.add({ obj: { id: 2 } }); + binTree.add({ obj: { id: 3 } }); + binTree.add({ obj: { id: 4 } }); + binTree.add({ obj: { id: 5 } }); const expected = [4, 2, 5, 1, 3]; - expect(tree.morris(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN', tree.root, 'RECURSIVE')).toEqual(expected); + expect(binTree.morris(node => node.key, 'IN')).toEqual(expected); + expect(binTree.dfs(node => node.key, 'IN')).toEqual(expected); + expect(binTree.dfs(node => node.key, 'IN', binTree.root, 'RECURSIVE')).toEqual(expected); }); - it('should toEntryFn 2', () => { - const tree = new BinaryTree( + it('should toEntryFn with initial', () => { + const binTree = new BinaryTree( [{ obj: { id: 1 } }, { obj: { id: 2 } }, { obj: { id: 3 } }, { obj: { id: 4 } }, { obj: { id: 5 } }], { toEntryFn: ele => [ele.obj.id, ele.obj.id] @@ -580,31 +826,24 @@ describe('BinaryTree toEntryFn', () => { const expected = [4, 2, 5, 1, 3]; - expect(tree.morris(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN', tree.root, 'RECURSIVE')).toEqual(expected); + expect(binTree.morris(node => node.key, 'IN')).toEqual(expected); + expect(binTree.dfs(node => node.key, 'IN')).toEqual(expected); + expect(binTree.dfs(node => node.key, 'IN', binTree.root, 'RECURSIVE')).toEqual(expected); }); - it('should toEntryFn 3', () => { - const tree = new BinaryTree<{ obj: { id: number } }, number>([ - { obj: { id: 1 } }, - { obj: { id: 2 } }, - { obj: { id: 3 } }, - { obj: { id: 4 } }, - { obj: { id: 5 } } - ]); - - const expected = [ - { obj: { id: 4 } }, - { obj: { id: 2 } }, - { obj: { id: 5 } }, - { obj: { id: 1 } }, - { obj: { id: 3 } } + it('should no toEntryFn', () => { + const data = [ + { obj: { id: 4 }, valueOf: () => 4 }, + { obj: { id: 2 }, valueOf: () => 2 }, + { obj: { id: 5 }, valueOf: () => 5 }, + { obj: { id: 1 }, valueOf: () => 1 }, + { obj: { id: 3 }, valueOf: () => 3 } ]; + const tree = new BinaryTree<{ obj: { id: number }; valueOf: () => number }, number>(data); - expect(tree.morris(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN')).toEqual(expected); - expect(tree.dfs(node => node.key, 'IN', tree.root, 'RECURSIVE')).toEqual(expected); + expect(tree.morris(node => node.key, 'IN')).toEqual(data.sort((a, b) => a.obj.id - b.obj.id)); + expect(tree.dfs(node => node.key, 'IN')).toEqual(data); + expect(tree.dfs(node => node.key, 'IN', tree.root, 'RECURSIVE')).toEqual(data); }); }); @@ -855,19 +1094,33 @@ describe('BinaryTree', () => { }); it('should get nodes by a custom callback', () => { - tree.add([5, 'A']); - tree.add([3, 'B']); - tree.add([7, 'C']); + tree.add([5, 'E']); + tree.add([4, 'D']); + tree.add([3, 'C']); + tree.add([7, 'G']); + tree.add([null, 'null']); + tree.add([1, 'A']); + tree.add([6, 'F']); + tree.add([null, 'null']); + tree.add([2, 'B']); + tree.add([null, 'null']); const nodes = tree.getNodes('B', node => node.value); expect(nodes.length).toBe(1); - expect(nodes[0].key).toBe(3); + expect(nodes[0].key).toBe(2); const nodesRec = tree.getNodes('B', node => node.value, false, tree.root, 'RECURSIVE'); expect(nodesRec.length).toBe(1); - expect(nodesRec[0].key).toBe(3); + expect(nodesRec[0].key).toBe(2); + + const nodesItr = tree.getNodes('B', node => node.value, false, tree.root, 'ITERATIVE'); + + expect(nodesItr.length).toBe(1); + expect(nodesItr[0].key).toBe(2); + + expect(nodesItr).toEqual(nodesRec); }); it('should perform Morris traversal', () => { diff --git a/test/unit/data-structures/binary-tree/bst.test.ts b/test/unit/data-structures/binary-tree/bst.test.ts index 4cbe25b..83f43ea 100644 --- a/test/unit/data-structures/binary-tree/bst.test.ts +++ b/test/unit/data-structures/binary-tree/bst.test.ts @@ -1,9 +1,37 @@ import { BinaryTreeNode, BST, BSTNode } from '../../../../src'; -import { isDebugTest } from '../../../config'; +import { isDebugTest, SYSTEM_MAX_CALL_STACK, isTestStackOverflow } from '../../../config'; const isDebug = isDebugTest; describe('BST operations test', () => { + it('should add undefined and null', () => { + const bst = new BST(); + const isAddUndefined = bst.add(undefined); + expect(isAddUndefined).toBe(false); + expect(bst.get(undefined)).toBe(undefined); + const isAddNull = bst.add(null); + expect(isAddNull).toBe(false); + expect(bst.get(null)).toBe(undefined); + const isAdd0 = bst.add(0, '0'); + expect(isAdd0).toBe(true); + expect(bst.get(0)).toBe('0'); + }); + it('should addMany undefined and null', () => { + const bst = new BST(); + const addManyWithUndefined = bst.addMany([1, undefined, 3]); + // TODO + // expect(addManyWithUndefined).toEqual([true, false, true]); + expect(addManyWithUndefined).toEqual([true, true]); + expect(bst.get(undefined)).toBe(undefined); + const addManyWithNull = bst.addMany([1, null, 3, 4]); + // TODO + // expect(addManyWithNull).toEqual([false, false, false, true]); + expect(addManyWithNull).toEqual([true, true, true]); + expect(bst.get(null)).toBe(undefined); + const node0 = bst.add(0, '0'); + expect(node0).toBe(true); + expect(bst.get(0)).toBe('0'); + }); it('should perform various operations on a Binary Search Tree with numeric values', () => { const bst = new BST(); expect(bst).toBeInstanceOf(BST); @@ -45,12 +73,12 @@ describe('BST operations test', () => { expect(nodeVal9?.key).toBe(9); const leftMost = bst.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); expect(bst.isBST()).toBe(true); const node15 = bst.getNode(15); - const minNodeBySpecificNode = node15 && bst.getLeftMost(node15); + const minNodeBySpecificNode = node15 && bst.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; @@ -249,14 +277,14 @@ describe('BST operations test', () => { expect(nodeVal9?.key).toBe(9); const leftMost = objBST.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = objBST.getNode(15); expect(node15?.value).toEqual({ name: 'Alice', age: 15 }); - const minNodeBySpecificNode = node15 && objBST.getLeftMost(node15); + const minNodeBySpecificNode = node15 && objBST.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; @@ -410,6 +438,24 @@ describe('BST operations test', () => { expect(bfsNodes[1].key).toBe(12); expect(bfsNodes[2].key).toBe(16); }); + + it('should keyValueOrEntryOrRawElementToNode', () => { + const bst = new BST(); + const node0 = bst.keyValueOrEntryOrRawElementToNode(0); + expect(node0).toEqual({ + _left: undefined, + _right: undefined, + key: 0, + parent: undefined, + value: undefined + }); + + const nodeUndefined = bst.keyValueOrEntryOrRawElementToNode(undefined); + expect(nodeUndefined).toBe(undefined); + + const nodeNull = bst.keyValueOrEntryOrRawElementToNode(null); + expect(nodeNull).toBe(undefined); + }); }); describe('BST operations test recursively', () => { @@ -442,10 +488,10 @@ describe('BST operations test recursively', () => { expect(nodeVal9?.key).toBe(undefined); const leftMost = bst.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = bst.getNode(15); - const minNodeBySpecificNode = node15 && bst.getLeftMost(node15); + const minNodeBySpecificNode = node15 && bst.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; @@ -645,14 +691,14 @@ describe('BST operations test recursively', () => { expect(nodeVal9?.key).toBe(9); const leftMost = objBST.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = objBST.getNode(15); expect(node15?.value).toEqual({ key: 15, keyA: 15 }); - const minNodeBySpecificNode = node15 && objBST.getLeftMost(node15); + const minNodeBySpecificNode = node15 && objBST.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(12); let subTreeSum = 0; @@ -870,6 +916,33 @@ describe('BST operations test recursively', () => { cloned.delete('5'); expect(cloned.size).toBe(0); }); + + if (isTestStackOverflow) { + it('should getLeftMost', () => { + const bst = new BST([], { comparator: (a, b) => b - a }); + for (let i = 1; i <= SYSTEM_MAX_CALL_STACK; i++) bst.add(i); + + expect(() => { + const leftMost = bst.getLeftMost(node => node, bst.root, 'RECURSIVE'); + expect(leftMost?.key).toEqual(SYSTEM_MAX_CALL_STACK); + }).toThrow('Maximum call stack size exceeded'); + + const leftMost = bst.getLeftMost(node => node, bst.root, 'ITERATIVE'); + expect(leftMost?.key).toEqual(SYSTEM_MAX_CALL_STACK); + }); + + it('should getRightMost', () => { + const bst = new BST(); + for (let i = 1; i <= SYSTEM_MAX_CALL_STACK; i++) bst.add(i); + + expect(() => { + const rightMost = bst.getRightMost(node => node, bst.root, 'RECURSIVE'); + expect(rightMost?.key).toEqual(SYSTEM_MAX_CALL_STACK); + }).toThrow('Maximum call stack size exceeded'); + const rightMost = bst.getRightMost(node => node, bst.root, 'ITERATIVE'); + expect(rightMost?.key).toEqual(SYSTEM_MAX_CALL_STACK); + }); + } }); describe('BST isBST', function () { diff --git a/test/unit/data-structures/binary-tree/overall.test.ts b/test/unit/data-structures/binary-tree/overall.test.ts index d21f1ef..e2b810b 100644 --- a/test/unit/data-structures/binary-tree/overall.test.ts +++ b/test/unit/data-structures/binary-tree/overall.test.ts @@ -18,7 +18,7 @@ describe('Overall BinaryTree Test', () => { expect(bst.getDepth(6)).toBe(3); // true const leftMost = bst.getLeftMost(); leftMost?.key === 1; // true - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); bst.delete(6); bst.getNode(6); // undefined expect(bst.getNode(6)).toBe(undefined); 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 3aea976..1155de4 100644 --- a/test/unit/data-structures/binary-tree/rb-tree.test.ts +++ b/test/unit/data-structures/binary-tree/rb-tree.test.ts @@ -68,13 +68,13 @@ describe('RedBlackTree 1', () => { rbTree.add(15); rbTree.add(3); - const minNode = rbTree.getLeftMost(rbTree.root); + const minNode = rbTree.getLeftMost(node => node, rbTree.root); expect(minNode?.key).toBe(3); }); it('should handle an empty rbTree', () => { - const minNode = rbTree.getLeftMost(rbTree.root); - expect(minNode).toBe(rbTree.NIL); + const minNode = rbTree.getLeftMost(node => node, rbTree.root); + expect(minNode).toBe(undefined); }); }); @@ -86,13 +86,13 @@ describe('RedBlackTree 1', () => { rbTree.add(15); rbTree.add(25); - const maxNode = rbTree.getRightMost(rbTree.root); + const maxNode = rbTree.getRightMost(node => node, rbTree.root); expect(maxNode?.key).toBe(25); }); it('should handle an empty rbTree', () => { - const maxNode = rbTree.getRightMost(rbTree.root); - expect(maxNode).toBe(rbTree.NIL); + const maxNode = rbTree.getRightMost(node => node, rbTree.root); + expect(maxNode).toBe(undefined); }); }); @@ -433,7 +433,7 @@ describe('RedBlackTree 2', () => { rbTree.add(15); const nodeLM = rbTree.getLeftMost(); - expect(nodeLM?.key).toBe(1); + expect(nodeLM).toBe(1); const node50 = rbTree.getNode(50); expect(node50?.key).toBe(50); diff --git a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts index a333efb..2a45456 100644 --- a/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +++ b/test/unit/data-structures/binary-tree/tree-multi-map.test.ts @@ -142,10 +142,10 @@ describe('TreeMultiMap operations test1', () => { const nodesByCount2 = tmm.getNodes(2, node => node.count); expect(nodesByCount2.length).toBe(2); const leftMost = tmm.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = tmm.getNode(15); - const minNodeBySpecificNode = node15 && tmm.getLeftMost(node15); + const minNodeBySpecificNode = node15 && tmm.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(14); let subTreeSum = 0; @@ -400,10 +400,10 @@ describe('TreeMultiMap operations test recursively1', () => { const nodesByCount2 = tmm.getNodes(2, node => node.count); expect(nodesByCount2.length).toBe(2); const leftMost = tmm.getLeftMost(); - expect(leftMost?.key).toBe(1); + expect(leftMost).toBe(1); const node15 = tmm.getNode(15); - const minNodeBySpecificNode = node15 && tmm.getLeftMost(node15); + const minNodeBySpecificNode = node15 && tmm.getLeftMost(node => node, node15); expect(minNodeBySpecificNode?.key).toBe(14); let subTreeSum = 0; diff --git a/test/unit/data-structures/hash/hash-map.test.ts b/test/unit/data-structures/hash/hash-map.test.ts index 7269577..9528e87 100644 --- a/test/unit/data-structures/hash/hash-map.test.ts +++ b/test/unit/data-structures/hash/hash-map.test.ts @@ -301,6 +301,16 @@ describe('HashMap', () => { it('values', () => { expect([...hm.values()]).toEqual([2, 3, 4, 5, 6]); }); + + it('print', () => { + expect(hm.print()).toEqual([ + [2, 2], + [3, 3], + [4, 4], + [5, 5], + [6, 6] + ]); + }); }); describe('HashMap HOF', () => { diff --git a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts index dd8fbc9..4a9a900 100644 --- a/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +++ b/test/unit/data-structures/linked-list/doubly-linked-list.test.ts @@ -65,6 +65,7 @@ describe('DoublyLinkedList Operation Test', () => { dList.delete('5'); expect([...dList]).toEqual(['1', '6', '0', '9']); expect([...cloned]).toEqual(['1', '6', '0', '5', '9']); + expect(cloned.print()).toEqual(['1', '6', '0', '5', '9']); }); it('should find undefined', () => { diff --git a/test/unit/data-structures/priority-queue/min-priority-queue.test.ts b/test/unit/data-structures/priority-queue/min-priority-queue.test.ts index 1698e76..3c81ddc 100644 --- a/test/unit/data-structures/priority-queue/min-priority-queue.test.ts +++ b/test/unit/data-structures/priority-queue/min-priority-queue.test.ts @@ -76,5 +76,6 @@ describe('MinPriorityQueue Operation Test', () => { ); expect(mapped instanceof MinPriorityQueue).toBe(true); expect([...mapped]).toEqual([{ key: 1 }, { key: 5 }, { key: 7 }]); + expect(mapped.print()).toEqual([{ key: 1 }, { key: 5 }, { key: 7 }]); }); }); diff --git a/test/unit/utils/utils.test.ts b/test/unit/utils/utils.test.ts index 4f1e137..a0319e0 100644 --- a/test/unit/utils/utils.test.ts +++ b/test/unit/utils/utils.test.ts @@ -3,3 +3,172 @@ describe('isNaN', () => { expect(isNaN('string' as unknown as number)).toBe(true); }); }); + +import { isComparable } from '../../../src'; + +describe('isComparable', () => { + describe('primitive types', () => { + it('numbers should be comparable', () => { + expect(isComparable(42)).toBe(true); + expect(isComparable(0)).toBe(true); + expect(isComparable(-1)).toBe(true); + expect(isComparable(Infinity)).toBe(true); + expect(isComparable(-Infinity)).toBe(true); + }); + + it('NaN should not be comparable', () => { + expect(isComparable(NaN)).toBe(false); + }); + + it('strings should be comparable', () => { + expect(isComparable('hello')).toBe(true); + expect(isComparable('')).toBe(true); + expect(isComparable('123')).toBe(true); + }); + + it('BigInt should be comparable', () => { + expect(isComparable(BigInt(42))).toBe(true); + expect(isComparable(BigInt(0))).toBe(true); + expect(isComparable(BigInt(-1))).toBe(true); + }); + + it('boolean should not be comparable', () => { + expect(isComparable(true)).toBe(true); + expect(isComparable(false)).toBe(true); + }); + + it('null and undefined should not be comparable', () => { + expect(isComparable(null)).toBe(false); + expect(isComparable(undefined)).toBe(false); + }); + + it('symbols should not be comparable', () => { + expect(isComparable(Symbol('test'))).toBe(false); + expect(isComparable(Symbol.for('test'))).toBe(false); + }); + }); + + describe('Date objects', () => { + it('valid Date objects should be comparable', () => { + expect(isComparable(new Date())).toBe(true); + expect(isComparable(new Date('2024-01-01'))).toBe(true); + }); + + it('invalid Date objects should not be comparable', () => { + expect(isComparable(new Date('invalid'))).toBe(false); + }); + }); + + describe('arrays', () => { + it('arrays should be comparable as they convert to string', () => { + expect(isComparable([])).toBe(true); + expect(isComparable([1, 2, 3])).toBe(true); + expect(isComparable(['a', 'b', 'c'])).toBe(true); + }); + }); + + describe('plain objects', () => { + it('plain objects should not be comparable', () => { + expect(isComparable({})).toBe(false); + expect(isComparable({ a: 1 })).toBe(false); + }); + }); + + describe('custom objects', () => { + it('objects with numeric valueOf should be comparable', () => { + expect(isComparable({ valueOf: () => 42 })).toBe(true); + }); + + it('objects with string valueOf should be comparable', () => { + expect(isComparable({ valueOf: () => 'test' })).toBe(true); + }); + + it('objects with boolean valueOf should not be comparable', () => { + expect(isComparable({ valueOf: () => true })).toBe(true); + }); + + it('objects with nested valueOf/toString should be comparable', () => { + expect( + isComparable({ + valueOf: () => ({ toString: () => '42' }) + }) + ).toBe(true); + }); + }); + + describe('deeply nested objects', () => { + it('objects with deeply nested valueOf should be comparable', () => { + const deeplyNested = { + valueOf: () => ({ + valueOf: () => 42 + }) + }; + expect(isComparable(deeplyNested)).toBe(true); + }); + + it('objects with very deeply nested conversion should be comparable', () => { + const veryDeeplyNested = { + valueOf: () => ({ + valueOf: () => ({ + toString: () => '42' + }) + }) + }; + expect(isComparable(veryDeeplyNested)).toBe(true); + }); + + it('objects with circular references should not be comparable', () => { + const circular: any = { + valueOf: () => circular + }; + expect(isComparable(circular)).toBe(false); + }); + }); + + describe('edge cases', () => { + it('objects returning non-primitive values should be handled correctly', () => { + const complexObject = { + valueOf: () => ({ + toString: () => ({ + valueOf: () => 'valid' + }) + }) + }; + expect(isComparable(complexObject)).toBe(false); + }); + + it('objects returning primitive values should be handled correctly', () => { + const complexObject = { + valueOf: () => ({ + valueOf: () => ({ + valueOf: () => ({ + valueOf: () => ({ + toString: () => `{ + valueOf: () => 'valid' + }` + }) + }) + }) + }) + }; + expect(isComparable(complexObject)).toBe(true); + }); + }); + + describe('type checking', () => { + // it('should narrow types correctly', () => { + // const value: unknown = 42; + // if (isComparable(value)) { + // // Type narrowing here should succeed + // const result = value > 0; + // expect(result).toBe(true); + // } + // }); + + it('should work with type guard in array methods', () => { + const values: unknown[] = [42, 'test', true, null, undefined, new Date()]; + const comparableValues = values.filter(item => isComparable(item)); + expect(comparableValues.length).toBe(4); + }); + }); +});