Change the print method to return a value instead of directly calling console.log.Unify the logic of all keyValueOrEntryOrRawElementToNode methods in BinaryTree.

Use a generic isComparable method to determine if a value is comparable in size when using the isKey method in the BinaryTree.
Fix the boundary value bug in the getNodes method of BinaryTree.
Support callback functions in getLeftMost and getRightMost to support returning results with different properties.
If a comparator is specified for BST, the isKey method will force the object type to be comparable.
The isComparable method has been implemented based on JavaScript's comparison principles.
This commit is contained in:
Revone 2024-10-29 16:23:24 +13:00
parent 9f197d48b0
commit a29dc270e6
24 changed files with 927 additions and 224 deletions

View file

@ -823,43 +823,43 @@ Version 11.7.9
[//]: # (No deletion!!! Start of Replace Section)
<div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>heap</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>7.49</td><td>133.43</td><td>2.10e-4</td></tr><tr><td>100,000 add & poll</td><td>44.08</td><td>22.69</td><td>0.00</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>7.55</td><td>132.36</td><td>2.00e-4</td></tr><tr><td>100,000 add & poll</td><td>44.17</td><td>22.64</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>rb-tree</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>79.16</td><td>12.63</td><td>0.00</td></tr><tr><td>100,000 add randomly</td><td>84.35</td><td>11.85</td><td>0.00</td></tr><tr><td>100,000 get</td><td>111.74</td><td>8.95</td><td>0.00</td></tr><tr><td>100,000 iterator</td><td>26.61</td><td>37.58</td><td>0.00</td></tr><tr><td>100,000 add & delete orderly</td><td>160.05</td><td>6.25</td><td>0.02</td></tr><tr><td>100,000 add & delete randomly</td><td>234.55</td><td>4.26</td><td>0.01</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>77.03</td><td>12.98</td><td>0.00</td></tr><tr><td>100,000 add randomly</td><td>80.74</td><td>12.38</td><td>0.00</td></tr><tr><td>100,000 get</td><td>111.29</td><td>8.99</td><td>0.00</td></tr><tr><td>100,000 iterator</td><td>24.91</td><td>40.14</td><td>0.01</td></tr><tr><td>100,000 add & delete orderly</td><td>153.12</td><td>6.53</td><td>0.00</td></tr><tr><td>100,000 add & delete randomly</td><td>233.76</td><td>4.28</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>queue</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>40.70</td><td>24.57</td><td>0.01</td></tr><tr><td>100,000 push & shift</td><td>5.15</td><td>194.25</td><td>6.57e-4</td></tr><tr><td>Native JS Array 100,000 push & shift</td><td>2143.37</td><td>0.47</td><td>0.15</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>43.79</td><td>22.83</td><td>0.01</td></tr><tr><td>100,000 push & shift</td><td>5.09</td><td>196.54</td><td>5.29e-4</td></tr><tr><td>Native JS Array 100,000 push & shift</td><td>2134.49</td><td>0.47</td><td>0.18</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>deque</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>24.74</td><td>40.43</td><td>0.00</td></tr><tr><td>1,000,000 push & pop</td><td>31.38</td><td>31.87</td><td>0.00</td></tr><tr><td>1,000,000 push & shift</td><td>32.12</td><td>31.13</td><td>0.00</td></tr><tr><td>100,000 push & shift</td><td>3.39</td><td>295.24</td><td>3.62e-4</td></tr><tr><td>Native JS Array 100,000 push & shift</td><td>2348.52</td><td>0.43</td><td>0.21</td></tr><tr><td>100,000 unshift & shift</td><td>3.28</td><td>304.60</td><td>2.76e-4</td></tr><tr><td>Native JS Array 100,000 unshift & shift</td><td>4062.43</td><td>0.25</td><td>0.12</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>25.39</td><td>39.39</td><td>0.00</td></tr><tr><td>1,000,000 push & pop</td><td>32.93</td><td>30.37</td><td>0.01</td></tr><tr><td>1,000,000 push & shift</td><td>32.36</td><td>30.90</td><td>0.00</td></tr><tr><td>100,000 push & shift</td><td>3.41</td><td>293.26</td><td>3.14e-4</td></tr><tr><td>Native JS Array 100,000 push & shift</td><td>2309.81</td><td>0.43</td><td>0.46</td></tr><tr><td>100,000 unshift & shift</td><td>3.24</td><td>308.35</td><td>3.36e-4</td></tr><tr><td>Native JS Array 100,000 unshift & shift</td><td>4213.92</td><td>0.24</td><td>0.20</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>hash-map</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 set</td><td>106.65</td><td>9.38</td><td>0.02</td></tr><tr><td>Native JS Map 1,000,000 set</td><td>201.70</td><td>4.96</td><td>0.01</td></tr><tr><td>Native JS Set 1,000,000 add</td><td>163.55</td><td>6.11</td><td>0.01</td></tr><tr><td>1,000,000 set & get</td><td>115.56</td><td>8.65</td><td>0.02</td></tr><tr><td>Native JS Map 1,000,000 set & get</td><td>264.29</td><td>3.78</td><td>0.01</td></tr><tr><td>Native JS Set 1,000,000 add & has</td><td>171.96</td><td>5.82</td><td>0.01</td></tr><tr><td>1,000,000 ObjKey set & get</td><td>326.27</td><td>3.06</td><td>0.05</td></tr><tr><td>Native JS Map 1,000,000 ObjKey set & get</td><td>322.17</td><td>3.10</td><td>0.06</td></tr><tr><td>Native JS Set 1,000,000 ObjKey add & has</td><td>241.34</td><td>4.14</td><td>0.03</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 set</td><td>107.76</td><td>9.28</td><td>0.02</td></tr><tr><td>Native JS Map 1,000,000 set</td><td>204.81</td><td>4.88</td><td>0.02</td></tr><tr><td>Native JS Set 1,000,000 add</td><td>165.26</td><td>6.05</td><td>0.01</td></tr><tr><td>1,000,000 set & get</td><td>121.13</td><td>8.26</td><td>0.02</td></tr><tr><td>Native JS Map 1,000,000 set & get</td><td>265.52</td><td>3.77</td><td>0.01</td></tr><tr><td>Native JS Set 1,000,000 add & has</td><td>167.98</td><td>5.95</td><td>0.01</td></tr><tr><td>1,000,000 ObjKey set & get</td><td>331.86</td><td>3.01</td><td>0.04</td></tr><tr><td>Native JS Map 1,000,000 ObjKey set & get</td><td>322.12</td><td>3.10</td><td>0.05</td></tr><tr><td>Native JS Set 1,000,000 ObjKey add & has</td><td>267.74</td><td>3.73</td><td>0.03</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>trie</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 push</td><td>44.21</td><td>22.62</td><td>0.00</td></tr><tr><td>100,000 getWords</td><td>85.22</td><td>11.73</td><td>0.00</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 push</td><td>42.79</td><td>23.37</td><td>7.76e-4</td></tr><tr><td>100,000 getWords</td><td>81.82</td><td>12.22</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>avl-tree</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>269.92</td><td>3.70</td><td>0.01</td></tr><tr><td>100,000 add randomly</td><td>317.13</td><td>3.15</td><td>0.00</td></tr><tr><td>100,000 get</td><td>127.74</td><td>7.83</td><td>0.00</td></tr><tr><td>100,000 iterator</td><td>29.99</td><td>33.34</td><td>0.01</td></tr><tr><td>100,000 add & delete orderly</td><td>431.27</td><td>2.32</td><td>0.00</td></tr><tr><td>100,000 add & delete randomly</td><td>580.91</td><td>1.72</td><td>0.00</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>270.76</td><td>3.69</td><td>0.01</td></tr><tr><td>100,000 add randomly</td><td>326.94</td><td>3.06</td><td>0.00</td></tr><tr><td>100,000 get</td><td>129.13</td><td>7.74</td><td>0.00</td></tr><tr><td>100,000 iterator</td><td>30.96</td><td>32.30</td><td>0.00</td></tr><tr><td>100,000 add & delete orderly</td><td>440.62</td><td>2.27</td><td>0.00</td></tr><tr><td>100,000 add & delete randomly</td><td>589.06</td><td>1.70</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>binary-tree-overall</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>10,000 RBTree add randomly</td><td>6.73</td><td>148.59</td><td>1.04e-4</td></tr><tr><td>10,000 RBTree get randomly</td><td>9.48</td><td>105.50</td><td>1.07e-4</td></tr><tr><td>10,000 RBTree add & delete randomly</td><td>18.40</td><td>54.33</td><td>2.70e-4</td></tr><tr><td>10,000 AVLTree add randomly</td><td>23.57</td><td>42.43</td><td>1.73e-4</td></tr><tr><td>10,000 AVLTree get randomly</td><td>9.70</td><td>103.06</td><td>7.92e-5</td></tr><tr><td>10,000 AVLTree add & delete randomly</td><td>44.43</td><td>22.51</td><td>3.33e-4</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>10,000 RBTree add randomly</td><td>6.89</td><td>145.16</td><td>9.69e-5</td></tr><tr><td>10,000 RBTree get randomly</td><td>9.22</td><td>108.43</td><td>1.55e-4</td></tr><tr><td>10,000 RBTree add & delete randomly</td><td>18.70</td><td>53.46</td><td>1.16e-4</td></tr><tr><td>10,000 AVLTree add randomly</td><td>24.45</td><td>40.91</td><td>2.06e-4</td></tr><tr><td>10,000 AVLTree get randomly</td><td>9.77</td><td>102.34</td><td>1.09e-4</td></tr><tr><td>10,000 AVLTree add & delete randomly</td><td>45.71</td><td>21.88</td><td>4.07e-4</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>directed-graph</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000 addVertex</td><td>0.10</td><td>9723.27</td><td>2.46e-6</td></tr><tr><td>1,000 addEdge</td><td>6.26</td><td>159.78</td><td>7.28e-4</td></tr><tr><td>1,000 getVertex</td><td>0.04</td><td>2.54e+4</td><td>4.17e-7</td></tr><tr><td>1,000 getEdge</td><td>22.64</td><td>44.16</td><td>0.00</td></tr><tr><td>tarjan</td><td>200.14</td><td>5.00</td><td>0.01</td></tr><tr><td>topologicalSort</td><td>175.91</td><td>5.68</td><td>0.01</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000 addVertex</td><td>0.10</td><td>9817.83</td><td>9.67e-7</td></tr><tr><td>1,000 addEdge</td><td>6.18</td><td>161.72</td><td>1.55e-4</td></tr><tr><td>1,000 getVertex</td><td>0.04</td><td>2.51e+4</td><td>5.08e-7</td></tr><tr><td>1,000 getEdge</td><td>22.82</td><td>43.82</td><td>0.00</td></tr><tr><td>tarjan</td><td>211.73</td><td>4.72</td><td>0.02</td></tr><tr><td>topologicalSort</td><td>188.71</td><td>5.30</td><td>0.02</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>doubly-linked-list</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>196.17</td><td>5.10</td><td>0.03</td></tr><tr><td>1,000,000 unshift</td><td>203.73</td><td>4.91</td><td>0.04</td></tr><tr><td>1,000,000 unshift & shift</td><td>186.32</td><td>5.37</td><td>0.04</td></tr><tr><td>1,000,000 addBefore</td><td>298.35</td><td>3.35</td><td>0.05</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>212.17</td><td>4.71</td><td>0.03</td></tr><tr><td>1,000,000 unshift</td><td>210.09</td><td>4.76</td><td>0.03</td></tr><tr><td>1,000,000 unshift & shift</td><td>213.41</td><td>4.69</td><td>0.07</td></tr><tr><td>1,000,000 addBefore</td><td>320.83</td><td>3.12</td><td>0.07</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>singly-linked-list</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push & shift</td><td>210.62</td><td>4.75</td><td>0.09</td></tr><tr><td>10,000 push & pop</td><td>226.02</td><td>4.42</td><td>0.02</td></tr><tr><td>10,000 addBefore</td><td>249.35</td><td>4.01</td><td>0.01</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push & shift</td><td>205.40</td><td>4.87</td><td>0.05</td></tr><tr><td>10,000 push & pop</td><td>220.93</td><td>4.53</td><td>0.00</td></tr><tr><td>10,000 addBefore</td><td>248.19</td><td>4.03</td><td>0.01</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>priority-queue</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>27.23</td><td>36.72</td><td>7.64e-4</td></tr><tr><td>100,000 add & poll</td><td>76.66</td><td>13.04</td><td>0.00</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>100,000 add</td><td>27.55</td><td>36.30</td><td>9.81e-4</td></tr><tr><td>100,000 add & poll</td><td>76.26</td><td>13.11</td><td>6.92e-4</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>stack</span></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>37.10</td><td>26.96</td><td>0.00</td></tr><tr><td>1,000,000 push & pop</td><td>44.72</td><td>22.36</td><td>0.01</td></tr></table></div>
<div class="content"><table style="display: table; width:100%; table-layout: fixed;"><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>sample deviation</th></tr><tr><td>1,000,000 push</td><td>39.19</td><td>25.52</td><td>0.00</td></tr><tr><td>1,000,000 push & pop</td><td>44.90</td><td>22.27</td><td>0.00</td></tr></table></div>
</div>
[//]: # (No deletion!!! End of Replace Section)

View file

@ -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",

View file

@ -192,8 +192,8 @@ export abstract class IterableElementBase<E, R, C> {
*
* The print function logs the elements of an array to the console.
*/
print(): void {
console.log([...this]);
print(): E[] {
return [...this];
}
abstract isEmpty(): boolean;

View file

@ -251,8 +251,8 @@ export abstract class IterableEntryBase<K = any, V = any> {
*
* 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;

View file

@ -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);

View file

@ -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<NODE>;
/**
* 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<NODE>;
/**
* 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<K, V, NODE> = 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<K, V, NODE>} 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<K, V, NODE>`), 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<K, V, NODE>} 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<C extends BTNCallback<OptBTNOrNull<NODE>>>(
callback: C = this._DEFAULT_CALLBACK as C,
beginRoot: R | BTNKeyOrNodeOrEntry<K, V, NODE> = this.root,
iterationType: IterationType = this.iterationType
): OptBTNOrNull<NODE> {
if (this.isNIL(beginRoot)) return beginRoot as NODE;
): ReturnType<C> {
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<K, V, NODE>} 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<K, V, NODE>`), 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<OptBTNOrNull<NODE>>`. The
* default value for `callback` is `this._DEFAULT_CALLBACK` if it is not provided.
* @param {R | BTNKeyOrNodeOrEntry<K, V, NODE>} 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<C extends BTNCallback<OptBTNOrNull<NODE>>>(
callback: C = this._DEFAULT_CALLBACK as C,
beginRoot: R | BTNKeyOrNodeOrEntry<K, V, NODE> = this.root,
iterationType: IterationType = this.iterationType
): OptBTNOrNull<NODE> {
if (this.isNIL(beginRoot)) return beginRoot as NODE;
): ReturnType<C> {
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<NODE> = 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<K, V, NODE> = this.root, options?: BinaryTreePrintOptions): void {
override print(beginRoot: R | BTNKeyOrNodeOrEntry<K, V, NODE> = 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<NODE>): 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<C extends BTNCallback<NODE>>(

View file

@ -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<K = any, V = any, NODE extends BSTNode<K, V, NODE> = BSTNodeNested<K, V>> 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<K, V, NODE> = 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);

View file

@ -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;

View file

@ -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;

View file

@ -1,21 +1,23 @@
export type ToThunkFn = () => ReturnType<TrlFn>;
export type Thunk = () => ReturnType<ToThunkFn> & { __THUNK__: symbol };
export type TrlFn = (...args: any[]) => any;
export type ToThunkFn<R = any> = () => R;
export type Thunk<R = any> = ToThunkFn<R> & { __THUNK__?: symbol };
export type TrlFn<A extends any[] = any[], R = any> = (...args: A) => R;
export type TrlAsyncFn = (...args: any[]) => any;
export type SpecifyOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
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;

View file

@ -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

View file

@ -5,8 +5,14 @@
* @copyright Copyright (c) 2022 Tyler Zeng <zrwusa@gmail.com>
* @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 <T>(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 <T>(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<TrlFn>]) => toThunk(() => fn(...args));
const cont = (...args: [...Parameters<TrlFn>]): ReturnType<TrlFn> => toThunk(() => fn(...args));
return Object.assign(
(...args: [...Parameters<TrlFn>]) => {
@ -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<TrlAsyncFn>]) => toThunk(() => fn(...args));
const cont = (...args: [...Parameters<TrlAsyncFn>]): ReturnType<TrlAsyncFn> => toThunk(() => fn(...args));
return Object.assign(
async (...args: [...Parameters<TrlAsyncFn>]) => {
@ -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);
}

View file

@ -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;
}
})();

View file

@ -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;

View file

@ -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;

View file

@ -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<number, number, { id: number; name: number }>([], {
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<number>;
@ -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<number>();
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<number, number, { obj: { id: number } }>([], {
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<number, number, { obj: { id: number } }>([], {
it('should toEntryFn throw', () => {
expect(() => {
new BinaryTree<number, number, { obj: { id: number } }>([], {
toEntryFn: `ele => [ele.obj.id, ele.obj.id]` as unknown as (rawElement: {
obj: { id: number };
}) => BTNEntry<number, number>
});
}).toThrow('toEntryFn must be a function type');
});
it('should toEntryFn with add', () => {
const binTree = new BinaryTree<number, number, { obj: { id: number } }>([], {
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<number, number, { obj: { id: number } }>(
it('should toEntryFn with initial', () => {
const binTree = new BinaryTree<number, number, { obj: { id: number } }>(
[{ 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', () => {

View file

@ -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<number, string>();
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<number, string>();
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<number, number>();
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<number>();
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<number>([], { 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<number>();
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 () {

View file

@ -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);

View file

@ -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);

View file

@ -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;

View file

@ -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', () => {

View file

@ -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', () => {

View file

@ -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 }]);
});
});

View file

@ -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);
});
});
});