data-structure-typed 2.0.4 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/CHANGELOG.md +3 -1
  2. package/COMMANDS.md +17 -0
  3. package/README.md +11 -11
  4. package/benchmark/report.html +13 -77
  5. package/benchmark/report.json +158 -190
  6. package/dist/cjs/data-structures/base/iterable-element-base.d.ts +186 -83
  7. package/dist/cjs/data-structures/base/iterable-element-base.js +149 -107
  8. package/dist/cjs/data-structures/base/iterable-element-base.js.map +1 -1
  9. package/dist/cjs/data-structures/base/iterable-entry-base.d.ts +95 -119
  10. package/dist/cjs/data-structures/base/iterable-entry-base.js +59 -116
  11. package/dist/cjs/data-structures/base/iterable-entry-base.js.map +1 -1
  12. package/dist/cjs/data-structures/base/linear-base.d.ts +250 -192
  13. package/dist/cjs/data-structures/base/linear-base.js +137 -274
  14. package/dist/cjs/data-structures/base/linear-base.js.map +1 -1
  15. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.d.ts +126 -158
  16. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.js +171 -205
  17. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.js.map +1 -1
  18. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.d.ts +100 -69
  19. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.js +135 -87
  20. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.js.map +1 -1
  21. package/dist/cjs/data-structures/binary-tree/avl-tree.d.ts +138 -149
  22. package/dist/cjs/data-structures/binary-tree/avl-tree.js +208 -195
  23. package/dist/cjs/data-structures/binary-tree/avl-tree.js.map +1 -1
  24. package/dist/cjs/data-structures/binary-tree/binary-tree.d.ts +476 -632
  25. package/dist/cjs/data-structures/binary-tree/binary-tree.js +608 -875
  26. package/dist/cjs/data-structures/binary-tree/binary-tree.js.map +1 -1
  27. package/dist/cjs/data-structures/binary-tree/bst.d.ts +258 -306
  28. package/dist/cjs/data-structures/binary-tree/bst.js +505 -481
  29. package/dist/cjs/data-structures/binary-tree/bst.js.map +1 -1
  30. package/dist/cjs/data-structures/binary-tree/red-black-tree.d.ts +107 -179
  31. package/dist/cjs/data-structures/binary-tree/red-black-tree.js +114 -209
  32. package/dist/cjs/data-structures/binary-tree/red-black-tree.js.map +1 -1
  33. package/dist/cjs/data-structures/binary-tree/tree-counter.d.ts +132 -154
  34. package/dist/cjs/data-structures/binary-tree/tree-counter.js +172 -203
  35. package/dist/cjs/data-structures/binary-tree/tree-counter.js.map +1 -1
  36. package/dist/cjs/data-structures/binary-tree/tree-multi-map.d.ts +72 -69
  37. package/dist/cjs/data-structures/binary-tree/tree-multi-map.js +105 -85
  38. package/dist/cjs/data-structures/binary-tree/tree-multi-map.js.map +1 -1
  39. package/dist/cjs/data-structures/graph/abstract-graph.d.ts +238 -233
  40. package/dist/cjs/data-structures/graph/abstract-graph.js +267 -237
  41. package/dist/cjs/data-structures/graph/abstract-graph.js.map +1 -1
  42. package/dist/cjs/data-structures/graph/directed-graph.d.ts +108 -224
  43. package/dist/cjs/data-structures/graph/directed-graph.js +146 -233
  44. package/dist/cjs/data-structures/graph/directed-graph.js.map +1 -1
  45. package/dist/cjs/data-structures/graph/map-graph.d.ts +49 -55
  46. package/dist/cjs/data-structures/graph/map-graph.js +56 -59
  47. package/dist/cjs/data-structures/graph/map-graph.js.map +1 -1
  48. package/dist/cjs/data-structures/graph/undirected-graph.d.ts +103 -146
  49. package/dist/cjs/data-structures/graph/undirected-graph.js +129 -149
  50. package/dist/cjs/data-structures/graph/undirected-graph.js.map +1 -1
  51. package/dist/cjs/data-structures/hash/hash-map.d.ts +164 -338
  52. package/dist/cjs/data-structures/hash/hash-map.js +270 -457
  53. package/dist/cjs/data-structures/hash/hash-map.js.map +1 -1
  54. package/dist/cjs/data-structures/heap/heap.d.ts +214 -289
  55. package/dist/cjs/data-structures/heap/heap.js +340 -349
  56. package/dist/cjs/data-structures/heap/heap.js.map +1 -1
  57. package/dist/cjs/data-structures/heap/max-heap.d.ts +11 -47
  58. package/dist/cjs/data-structures/heap/max-heap.js +11 -66
  59. package/dist/cjs/data-structures/heap/max-heap.js.map +1 -1
  60. package/dist/cjs/data-structures/heap/min-heap.d.ts +12 -47
  61. package/dist/cjs/data-structures/heap/min-heap.js +11 -66
  62. package/dist/cjs/data-structures/heap/min-heap.js.map +1 -1
  63. package/dist/cjs/data-structures/linked-list/doubly-linked-list.d.ts +231 -347
  64. package/dist/cjs/data-structures/linked-list/doubly-linked-list.js +368 -494
  65. package/dist/cjs/data-structures/linked-list/doubly-linked-list.js.map +1 -1
  66. package/dist/cjs/data-structures/linked-list/singly-linked-list.d.ts +261 -310
  67. package/dist/cjs/data-structures/linked-list/singly-linked-list.js +447 -466
  68. package/dist/cjs/data-structures/linked-list/singly-linked-list.js.map +1 -1
  69. package/dist/cjs/data-structures/linked-list/skip-linked-list.d.ts +0 -107
  70. package/dist/cjs/data-structures/linked-list/skip-linked-list.js +0 -100
  71. package/dist/cjs/data-structures/linked-list/skip-linked-list.js.map +1 -1
  72. package/dist/cjs/data-structures/priority-queue/max-priority-queue.d.ts +12 -56
  73. package/dist/cjs/data-structures/priority-queue/max-priority-queue.js +11 -78
  74. package/dist/cjs/data-structures/priority-queue/max-priority-queue.js.map +1 -1
  75. package/dist/cjs/data-structures/priority-queue/min-priority-queue.d.ts +11 -57
  76. package/dist/cjs/data-structures/priority-queue/min-priority-queue.js +10 -79
  77. package/dist/cjs/data-structures/priority-queue/min-priority-queue.js.map +1 -1
  78. package/dist/cjs/data-structures/priority-queue/priority-queue.d.ts +2 -61
  79. package/dist/cjs/data-structures/priority-queue/priority-queue.js +8 -83
  80. package/dist/cjs/data-structures/priority-queue/priority-queue.js.map +1 -1
  81. package/dist/cjs/data-structures/queue/deque.d.ts +227 -254
  82. package/dist/cjs/data-structures/queue/deque.js +309 -348
  83. package/dist/cjs/data-structures/queue/deque.js.map +1 -1
  84. package/dist/cjs/data-structures/queue/queue.d.ts +180 -201
  85. package/dist/cjs/data-structures/queue/queue.js +265 -248
  86. package/dist/cjs/data-structures/queue/queue.js.map +1 -1
  87. package/dist/cjs/data-structures/stack/stack.d.ts +124 -102
  88. package/dist/cjs/data-structures/stack/stack.js +181 -125
  89. package/dist/cjs/data-structures/stack/stack.js.map +1 -1
  90. package/dist/cjs/data-structures/trie/trie.d.ts +164 -165
  91. package/dist/cjs/data-structures/trie/trie.js +189 -172
  92. package/dist/cjs/data-structures/trie/trie.js.map +1 -1
  93. package/dist/cjs/interfaces/binary-tree.d.ts +56 -6
  94. package/dist/cjs/interfaces/graph.d.ts +16 -0
  95. package/dist/cjs/types/data-structures/base/base.d.ts +1 -1
  96. package/dist/cjs/types/data-structures/graph/abstract-graph.d.ts +4 -0
  97. package/dist/cjs/types/utils/utils.d.ts +6 -6
  98. package/dist/cjs/utils/utils.d.ts +110 -49
  99. package/dist/cjs/utils/utils.js +148 -73
  100. package/dist/cjs/utils/utils.js.map +1 -1
  101. package/dist/esm/data-structures/base/iterable-element-base.d.ts +186 -83
  102. package/dist/esm/data-structures/base/iterable-element-base.js +155 -107
  103. package/dist/esm/data-structures/base/iterable-element-base.js.map +1 -1
  104. package/dist/esm/data-structures/base/iterable-entry-base.d.ts +95 -119
  105. package/dist/esm/data-structures/base/iterable-entry-base.js +59 -116
  106. package/dist/esm/data-structures/base/iterable-entry-base.js.map +1 -1
  107. package/dist/esm/data-structures/base/linear-base.d.ts +250 -192
  108. package/dist/esm/data-structures/base/linear-base.js +137 -274
  109. package/dist/esm/data-structures/base/linear-base.js.map +1 -1
  110. package/dist/esm/data-structures/binary-tree/avl-tree-counter.d.ts +126 -158
  111. package/dist/esm/data-structures/binary-tree/avl-tree-counter.js +171 -212
  112. package/dist/esm/data-structures/binary-tree/avl-tree-counter.js.map +1 -1
  113. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.d.ts +100 -69
  114. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.js +133 -94
  115. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.js.map +1 -1
  116. package/dist/esm/data-structures/binary-tree/avl-tree.d.ts +138 -149
  117. package/dist/esm/data-structures/binary-tree/avl-tree.js +206 -200
  118. package/dist/esm/data-structures/binary-tree/avl-tree.js.map +1 -1
  119. package/dist/esm/data-structures/binary-tree/binary-tree.d.ts +476 -632
  120. package/dist/esm/data-structures/binary-tree/binary-tree.js +613 -885
  121. package/dist/esm/data-structures/binary-tree/binary-tree.js.map +1 -1
  122. package/dist/esm/data-structures/binary-tree/bst.d.ts +258 -306
  123. package/dist/esm/data-structures/binary-tree/bst.js +507 -487
  124. package/dist/esm/data-structures/binary-tree/bst.js.map +1 -1
  125. package/dist/esm/data-structures/binary-tree/red-black-tree.d.ts +107 -179
  126. package/dist/esm/data-structures/binary-tree/red-black-tree.js +114 -215
  127. package/dist/esm/data-structures/binary-tree/red-black-tree.js.map +1 -1
  128. package/dist/esm/data-structures/binary-tree/tree-counter.d.ts +132 -154
  129. package/dist/esm/data-structures/binary-tree/tree-counter.js +175 -209
  130. package/dist/esm/data-structures/binary-tree/tree-counter.js.map +1 -1
  131. package/dist/esm/data-structures/binary-tree/tree-multi-map.d.ts +72 -69
  132. package/dist/esm/data-structures/binary-tree/tree-multi-map.js +103 -92
  133. package/dist/esm/data-structures/binary-tree/tree-multi-map.js.map +1 -1
  134. package/dist/esm/data-structures/graph/abstract-graph.d.ts +238 -233
  135. package/dist/esm/data-structures/graph/abstract-graph.js +267 -237
  136. package/dist/esm/data-structures/graph/abstract-graph.js.map +1 -1
  137. package/dist/esm/data-structures/graph/directed-graph.d.ts +108 -224
  138. package/dist/esm/data-structures/graph/directed-graph.js +145 -233
  139. package/dist/esm/data-structures/graph/directed-graph.js.map +1 -1
  140. package/dist/esm/data-structures/graph/map-graph.d.ts +49 -55
  141. package/dist/esm/data-structures/graph/map-graph.js +56 -59
  142. package/dist/esm/data-structures/graph/map-graph.js.map +1 -1
  143. package/dist/esm/data-structures/graph/undirected-graph.d.ts +103 -146
  144. package/dist/esm/data-structures/graph/undirected-graph.js +128 -149
  145. package/dist/esm/data-structures/graph/undirected-graph.js.map +1 -1
  146. package/dist/esm/data-structures/hash/hash-map.d.ts +164 -338
  147. package/dist/esm/data-structures/hash/hash-map.js +270 -457
  148. package/dist/esm/data-structures/hash/hash-map.js.map +1 -1
  149. package/dist/esm/data-structures/heap/heap.d.ts +214 -289
  150. package/dist/esm/data-structures/heap/heap.js +329 -349
  151. package/dist/esm/data-structures/heap/heap.js.map +1 -1
  152. package/dist/esm/data-structures/heap/max-heap.d.ts +11 -47
  153. package/dist/esm/data-structures/heap/max-heap.js +11 -66
  154. package/dist/esm/data-structures/heap/max-heap.js.map +1 -1
  155. package/dist/esm/data-structures/heap/min-heap.d.ts +12 -47
  156. package/dist/esm/data-structures/heap/min-heap.js +11 -66
  157. package/dist/esm/data-structures/heap/min-heap.js.map +1 -1
  158. package/dist/esm/data-structures/linked-list/doubly-linked-list.d.ts +231 -347
  159. package/dist/esm/data-structures/linked-list/doubly-linked-list.js +368 -495
  160. package/dist/esm/data-structures/linked-list/doubly-linked-list.js.map +1 -1
  161. package/dist/esm/data-structures/linked-list/singly-linked-list.d.ts +261 -310
  162. package/dist/esm/data-structures/linked-list/singly-linked-list.js +448 -467
  163. package/dist/esm/data-structures/linked-list/singly-linked-list.js.map +1 -1
  164. package/dist/esm/data-structures/linked-list/skip-linked-list.d.ts +0 -107
  165. package/dist/esm/data-structures/linked-list/skip-linked-list.js +0 -100
  166. package/dist/esm/data-structures/linked-list/skip-linked-list.js.map +1 -1
  167. package/dist/esm/data-structures/priority-queue/max-priority-queue.d.ts +12 -56
  168. package/dist/esm/data-structures/priority-queue/max-priority-queue.js +11 -78
  169. package/dist/esm/data-structures/priority-queue/max-priority-queue.js.map +1 -1
  170. package/dist/esm/data-structures/priority-queue/min-priority-queue.d.ts +11 -57
  171. package/dist/esm/data-structures/priority-queue/min-priority-queue.js +10 -79
  172. package/dist/esm/data-structures/priority-queue/min-priority-queue.js.map +1 -1
  173. package/dist/esm/data-structures/priority-queue/priority-queue.d.ts +2 -61
  174. package/dist/esm/data-structures/priority-queue/priority-queue.js +8 -83
  175. package/dist/esm/data-structures/priority-queue/priority-queue.js.map +1 -1
  176. package/dist/esm/data-structures/queue/deque.d.ts +227 -254
  177. package/dist/esm/data-structures/queue/deque.js +313 -348
  178. package/dist/esm/data-structures/queue/deque.js.map +1 -1
  179. package/dist/esm/data-structures/queue/queue.d.ts +180 -201
  180. package/dist/esm/data-structures/queue/queue.js +263 -248
  181. package/dist/esm/data-structures/queue/queue.js.map +1 -1
  182. package/dist/esm/data-structures/stack/stack.d.ts +124 -102
  183. package/dist/esm/data-structures/stack/stack.js +181 -125
  184. package/dist/esm/data-structures/stack/stack.js.map +1 -1
  185. package/dist/esm/data-structures/trie/trie.d.ts +164 -165
  186. package/dist/esm/data-structures/trie/trie.js +193 -172
  187. package/dist/esm/data-structures/trie/trie.js.map +1 -1
  188. package/dist/esm/interfaces/binary-tree.d.ts +56 -6
  189. package/dist/esm/interfaces/graph.d.ts +16 -0
  190. package/dist/esm/types/data-structures/base/base.d.ts +1 -1
  191. package/dist/esm/types/data-structures/graph/abstract-graph.d.ts +4 -0
  192. package/dist/esm/types/utils/utils.d.ts +6 -6
  193. package/dist/esm/utils/utils.d.ts +110 -49
  194. package/dist/esm/utils/utils.js +139 -68
  195. package/dist/esm/utils/utils.js.map +1 -1
  196. package/dist/umd/data-structure-typed.js +4737 -6525
  197. package/dist/umd/data-structure-typed.min.js +8 -6
  198. package/dist/umd/data-structure-typed.min.js.map +1 -1
  199. package/package.json +3 -4
  200. package/src/data-structures/base/iterable-element-base.ts +238 -115
  201. package/src/data-structures/base/iterable-entry-base.ts +96 -120
  202. package/src/data-structures/base/linear-base.ts +271 -277
  203. package/src/data-structures/binary-tree/avl-tree-counter.ts +198 -216
  204. package/src/data-structures/binary-tree/avl-tree-multi-map.ts +192 -101
  205. package/src/data-structures/binary-tree/avl-tree.ts +239 -206
  206. package/src/data-structures/binary-tree/binary-tree.ts +677 -901
  207. package/src/data-structures/binary-tree/bst.ts +568 -570
  208. package/src/data-structures/binary-tree/red-black-tree.ts +161 -222
  209. package/src/data-structures/binary-tree/tree-counter.ts +199 -218
  210. package/src/data-structures/binary-tree/tree-multi-map.ts +131 -97
  211. package/src/data-structures/graph/abstract-graph.ts +339 -264
  212. package/src/data-structures/graph/directed-graph.ts +146 -236
  213. package/src/data-structures/graph/map-graph.ts +63 -60
  214. package/src/data-structures/graph/undirected-graph.ts +129 -152
  215. package/src/data-structures/hash/hash-map.ts +274 -496
  216. package/src/data-structures/heap/heap.ts +389 -402
  217. package/src/data-structures/heap/max-heap.ts +12 -76
  218. package/src/data-structures/heap/min-heap.ts +13 -76
  219. package/src/data-structures/linked-list/doubly-linked-list.ts +426 -530
  220. package/src/data-structures/linked-list/singly-linked-list.ts +495 -517
  221. package/src/data-structures/linked-list/skip-linked-list.ts +1 -108
  222. package/src/data-structures/priority-queue/max-priority-queue.ts +12 -87
  223. package/src/data-structures/priority-queue/min-priority-queue.ts +11 -88
  224. package/src/data-structures/priority-queue/priority-queue.ts +3 -92
  225. package/src/data-structures/queue/deque.ts +381 -357
  226. package/src/data-structures/queue/queue.ts +310 -264
  227. package/src/data-structures/stack/stack.ts +217 -131
  228. package/src/data-structures/trie/trie.ts +240 -175
  229. package/src/interfaces/binary-tree.ts +240 -6
  230. package/src/interfaces/graph.ts +37 -0
  231. package/src/types/data-structures/base/base.ts +5 -5
  232. package/src/types/data-structures/graph/abstract-graph.ts +5 -0
  233. package/src/types/utils/utils.ts +9 -5
  234. package/src/utils/utils.ts +152 -86
  235. package/test/integration/index.html +1 -1
  236. package/test/performance/benchmark-runner.ts +528 -0
  237. package/test/performance/reportor.mjs +43 -43
  238. package/test/performance/runner-config.json +39 -0
  239. package/test/performance/single-suite-runner.ts +69 -0
  240. package/test/unit/data-structures/binary-tree/avl-tree-counter.test.ts +3 -3
  241. package/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +5 -5
  242. package/test/unit/data-structures/binary-tree/avl-tree.test.ts +4 -4
  243. package/test/unit/data-structures/binary-tree/binary-tree.test.ts +350 -90
  244. package/test/unit/data-structures/binary-tree/bst.test.ts +84 -5
  245. package/test/unit/data-structures/binary-tree/red-black-tree.test.ts +2 -2
  246. package/test/unit/data-structures/binary-tree/tree-counter.test.ts +25 -24
  247. package/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +3 -3
  248. package/test/unit/data-structures/graph/abstract-graph.test.ts +0 -4
  249. package/test/unit/data-structures/graph/directed-graph.test.ts +1 -1
  250. package/test/unit/data-structures/heap/heap.test.ts +14 -21
  251. package/test/unit/data-structures/heap/max-heap.test.ts +5 -9
  252. package/test/unit/data-structures/heap/min-heap.test.ts +1 -4
  253. package/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +14 -14
  254. package/test/unit/data-structures/linked-list/singly-linked-list.test.ts +0 -7
  255. package/test/unit/data-structures/priority-queue/max-priority-queue.test.ts +8 -11
  256. package/test/unit/data-structures/priority-queue/min-priority-queue.test.ts +1 -4
  257. package/test/unit/data-structures/priority-queue/priority-queue.test.ts +1 -4
  258. package/test/unit/data-structures/queue/queue.test.ts +4 -5
  259. package/test/unit/utils/utils.test.ts +0 -1
  260. package/test/performance/data-structures/binary-tree/avl-tree.test.mjs +0 -71
  261. package/test/performance/data-structures/binary-tree/red-black-tree.test.mjs +0 -81
@@ -0,0 +1,528 @@
1
+ import * as Benchmark from 'benchmark';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs';
4
+ import * as fastGlob from 'fast-glob';
5
+ import { fork } from 'child_process';
6
+ import { ConsoleColor, numberFix } from '../utils';
7
+
8
+ /**
9
+ * Optimized benchmark runner
10
+ * Features:
11
+ * - Optional per-suite isolation via child_process (--isolate)
12
+ * - GC + cooldown between suites (--gc, --cooldown-ms=50)
13
+ * - Shuffle or custom order (--shuffle, --order=heap,avl-tree,...)
14
+ * - Arg tokens still filter test files like before
15
+ * - Maintains JSON/HTML report + README injection
16
+ *
17
+ * Example:
18
+ * ts-node benchmark-runner.optimized.ts --isolate --gc --cooldown-ms=80 heap set get
19
+ */
20
+
21
+ // ---- CLI parsing (lightweight) ----
22
+ type Flags = {
23
+ isolate: boolean;
24
+ gc: boolean;
25
+ cooldownMs: number;
26
+ shuffle: boolean;
27
+ order?: string[];
28
+ orderFile?: string;
29
+ include?: string[];
30
+ exclude?: string[];
31
+ label?: string;
32
+ };
33
+
34
+ function parseArgs(argv: string[]) {
35
+ const flags: Flags = {
36
+ isolate: false,
37
+ gc: false,
38
+ cooldownMs: 50,
39
+ shuffle: false,
40
+ order: undefined,
41
+ orderFile: undefined,
42
+ include: undefined,
43
+ exclude: undefined,
44
+ label: undefined
45
+ };
46
+ const filters: string[] = [];
47
+ argv.forEach(raw => {
48
+ if (!raw.startsWith('--')) {
49
+ filters.push(raw);
50
+ return;
51
+ }
52
+ const [k, v] = raw.replace(/^--/, '').split('=');
53
+ switch (k) {
54
+ case 'isolate':
55
+ flags.isolate = true;
56
+ break;
57
+ case 'gc':
58
+ flags.gc = true;
59
+ break;
60
+ case 'shuffle':
61
+ flags.shuffle = true;
62
+ break;
63
+ case 'cooldown-ms':
64
+ flags.cooldownMs = v ? Number(v) : flags.cooldownMs;
65
+ break;
66
+ case 'order':
67
+ flags.order = (v ?? '')
68
+ .split(',')
69
+ .map(s => s.trim())
70
+ .filter(Boolean);
71
+ break;
72
+ case 'order-file':
73
+ flags.orderFile = v || '';
74
+ break;
75
+ case 'include':
76
+ flags.include = (v ?? '')
77
+ .split(',')
78
+ .map(s => s.trim())
79
+ .filter(Boolean);
80
+ break;
81
+ case 'exclude':
82
+ flags.exclude = (v ?? '')
83
+ .split(',')
84
+ .map(s => s.trim())
85
+ .filter(Boolean);
86
+ break;
87
+ case 'label':
88
+ flags.label = v || '';
89
+ break;
90
+ default:
91
+ break;
92
+ }
93
+ });
94
+ return { flags, filters };
95
+ }
96
+
97
+ const argv = process.argv.slice(2);
98
+ const { flags, filters } = parseArgs(argv);
99
+
100
+ const { GREEN, BOLD, END, YELLOW, GRAY, CYAN, BG_YELLOW } = ConsoleColor;
101
+
102
+ // ---- Optional runOrder config support (order/include/exclude/label) ----
103
+ type RunConfig = { order?: string[]; include?: string[]; exclude?: string[]; label?: string };
104
+
105
+ function loadRunConfigFromFile(filePath?: string): RunConfig | null {
106
+ const p1 = filePath ? path.resolve(process.cwd(), filePath) : path.resolve(__dirname, 'runner-config.json');
107
+ const p2 = filePath ? undefined : path.resolve(__dirname, 'run-order.json');
108
+ const candidates = [p1, p2].filter(Boolean) as string[];
109
+ for (const p of candidates) {
110
+ try {
111
+ if (fs.existsSync(p)) {
112
+ const data = JSON.parse(fs.readFileSync(p, 'utf8'));
113
+ if (Array.isArray(data)) {
114
+ console.log(`${YELLOW}Using run config from file:${END} ${p}`);
115
+ return { order: data as string[] };
116
+ } else if (data && typeof data === 'object') {
117
+ const cfg: RunConfig = {};
118
+ if (Array.isArray((data as any).order))
119
+ cfg.order = (data as any).order.filter((x: any) => typeof x === 'string');
120
+ if (Array.isArray((data as any).include))
121
+ cfg.include = (data as any).include.filter((x: any) => typeof x === 'string');
122
+ if (Array.isArray((data as any).exclude))
123
+ cfg.exclude = (data as any).exclude.filter((x: any) => typeof x === 'string');
124
+ if (typeof (data as any).label === 'string') cfg.label = (data as any).label;
125
+ console.log(`${YELLOW}Using run config from file:${END} ${p}`);
126
+ return cfg;
127
+ }
128
+ }
129
+ } catch (e) {
130
+ console.warn(`Failed to load run config from ${p}:`, e);
131
+ }
132
+ }
133
+ return null;
134
+ }
135
+
136
+ function loadRunOrderFromFile(filePath?: string): string[] | null {
137
+ const p1 = filePath ? path.resolve(process.cwd(), filePath) : path.resolve(__dirname, 'runner-config.json');
138
+ const p2 = filePath ? undefined : path.resolve(__dirname, 'run-order.json');
139
+ const candidates = [p1, p2].filter(Boolean) as string[];
140
+ for (const p of candidates) {
141
+ try {
142
+ if (fs.existsSync(p)) {
143
+ const arr = JSON.parse(fs.readFileSync(p, 'utf8'));
144
+ if (Array.isArray(arr) && arr.every((x: any) => typeof x === 'string')) {
145
+ console.log(`${YELLOW}Using runOrder from file:${END} ${p}`);
146
+ return arr;
147
+ }
148
+ }
149
+ } catch (e) {
150
+ console.warn(`Failed to load order from ${p}:`, e);
151
+ }
152
+ }
153
+ return null;
154
+ }
155
+
156
+ const defaultRunOrder = [
157
+ 'heap',
158
+ 'avl-tree',
159
+ 'red-black-tree',
160
+ 'doubly-linked-list',
161
+ 'linked-hash-map',
162
+ 'hash-map',
163
+ 'map-graph',
164
+ 'graph',
165
+ 'directed-graph',
166
+ 'undirected-graph',
167
+ 'queue',
168
+ 'deque',
169
+ 'priority-queue',
170
+ 'singly-linked-list',
171
+ 'binary-tree-overall',
172
+ 'bst',
173
+ 'trie',
174
+ 'stack'
175
+ ];
176
+
177
+ const cfg = loadRunConfigFromFile(flags.orderFile);
178
+ const fileOrder = cfg?.order || loadRunOrderFromFile(flags.orderFile);
179
+ const runOrder = flags.order && flags.order.length ? flags.order : fileOrder || defaultRunOrder;
180
+
181
+ const getRelativePath = (file: string) => path.relative(__dirname, file);
182
+
183
+ // ---- Selection helpers (include/exclude) ----
184
+ function fileRel(file: string) {
185
+ return path.relative(testDir, file).replace(/\\/g, '/');
186
+ }
187
+
188
+ function makeMatcher(rule: string): (s: string) => boolean {
189
+ // If there is no wildcard, the substring matches and is compatible with your original behavior
190
+ if (!/[?*]/.test(rule)) {
191
+ return (s: string) => s.includes(rule);
192
+ }
193
+
194
+ // Use POSIX delimiters uniformly
195
+ const norm = rule.replace(/\\/g, '/');
196
+
197
+ // Escape regular special characters first, but keep * and ?
198
+ const esc = norm.replace(/[.+^${}()|[\]\\]/g, '\\$&');
199
+
200
+ // To process ** (across multi-level directories), use sentinel placeholders first to avoid conflicts with the subsequent replacement of *
201
+ const GLOBSTAR = '<<GLOBSTAR>>';
202
+ const withSentinel = esc.replace(/\*\*/g, GLOBSTAR);
203
+
204
+ // * => Multiple characters that do not cross directories, ? => Single characters that do not cross directories
205
+ const seg = withSentinel.replace(/\*/g, '[^/]*').replace(/\?/g, '[^/]');
206
+
207
+ // Restore ** => any character (can cross directories)
208
+ const regexSource = '^' + seg.split(GLOBSTAR).join('.*') + '$';
209
+ const re = new RegExp(regexSource);
210
+
211
+ return (s: string) => re.test(s.replace(/\\/g, '/'));
212
+ }
213
+
214
+ function applyIncludeExclude(files: string[], include?: string[], exclude?: string[]) {
215
+ let res = [...files];
216
+ if (include && include.length) {
217
+ const matchers = include.map(makeMatcher);
218
+ res = res.filter(f => matchers.some(m => m(fileRel(f))));
219
+ }
220
+ if (exclude && exclude.length) {
221
+ const matchers = exclude.map(makeMatcher);
222
+ res = res.filter(f => !matchers.some(m => m(fileRel(f))));
223
+ }
224
+ return res;
225
+ }
226
+
227
+ const coloredLabeled = (label: string, file: string) => {
228
+ const relativeFilePath = getRelativePath(file);
229
+ const directory = path.dirname(relativeFilePath);
230
+ const fileName = path.basename(relativeFilePath);
231
+ return `${BG_YELLOW} ${label} ${END} ${GRAY}${directory}/${END}${CYAN}${fileName}${END}`;
232
+ };
233
+
234
+ const parentDirectory = path.resolve(__dirname, '../..');
235
+ const reportDistPath = path.join(parentDirectory, 'benchmark');
236
+
237
+ const testDir = path.join(__dirname, 'data-structures');
238
+ const allFiles = fastGlob.sync([path.join(testDir, '**', '*.test.ts'), path.join(testDir, '**', '*.test.mjs')]);
239
+ let testFiles: string[] = [];
240
+
241
+ // Filters: same semantics as your original runner (non -- args are match substrings)
242
+ if (filters.length > 0) {
243
+ console.log(`arguments: ${filters.join(' ')}`);
244
+ testFiles = allFiles.filter(file => filters.every(word => file.includes(word)));
245
+ } else {
246
+ testFiles = [...allFiles];
247
+ }
248
+
249
+ // Apply include/exclude from config and CLI
250
+ const includeRules = [...(cfg?.include || []), ...(flags.include || [])];
251
+ const excludeRules = [...(cfg?.exclude || []), ...(flags.exclude || [])];
252
+ if (includeRules.length || excludeRules.length) {
253
+ testFiles = applyIncludeExclude(testFiles, includeRules, excludeRules);
254
+ }
255
+
256
+ // sort by runOrder, optionally shuffle
257
+ function sortByOrder(files: string[]): string[] {
258
+ type Item = { file: string; name: string; idx: number };
259
+ const items: Item[] = files.map(file => {
260
+ const name = path.basename(file, '.test.ts');
261
+ const idx = runOrder.indexOf(name);
262
+ return { file, name, idx: idx === -1 ? Number.MAX_SAFE_INTEGER : idx };
263
+ });
264
+ items.sort((a, b) => a.idx - b.idx || a.name.localeCompare(b.name));
265
+ return items.map(i => i.file);
266
+ }
267
+
268
+ function shuffle<T>(arr: T[]): T[] {
269
+ for (let i = arr.length - 1; i > 0; i--) {
270
+ const j = Math.floor(Math.random() * (i + 1));
271
+ [arr[i], arr[j]] = [arr[j], arr[i]];
272
+ }
273
+ return arr;
274
+ }
275
+
276
+ const discoveredTotal = allFiles.length;
277
+
278
+ testFiles = sortByOrder(testFiles);
279
+ if (flags.shuffle) shuffle(testFiles);
280
+
281
+ const plannedCount = testFiles.length;
282
+ const isIndividual = filters.length > 0;
283
+
284
+ // ---------------- report utils (kept from original) ----------------
285
+ const report: { [key: string]: any } = {};
286
+ let completedCount = 0;
287
+
288
+ function ensureDist() {
289
+ if (!fs.existsSync(reportDistPath)) fs.mkdirSync(reportDistPath, { recursive: true });
290
+ }
291
+
292
+ function writeReportHTMLAndJSON(htmlTables: string) {
293
+ ensureDist();
294
+ const filePath = path.join(reportDistPath, 'report.json');
295
+ const htmlFilePath = path.join(reportDistPath, 'report.html');
296
+ fs.writeFileSync(filePath, JSON.stringify(report, null, 2));
297
+ const html = `<!DOCTYPE html>
298
+ <html lang="en"><head><meta charset="UTF-8"/><title>Benchmark Report</title>
299
+ <style>
300
+ body { margin:0; padding:0; font-family: ui-sans-serif, system-ui, -apple-system; }
301
+ .json-to-html-title { font-size: 3rem; font-weight: bold; }
302
+ .content { padding: 2rem; }
303
+ .content table { width:100%; table-layout:fixed; border-collapse:collapse; margin-top:10px; font-size:16px; }
304
+ .content table th, .content table td { padding: 8px 12px; text-align:left; border:1px solid #ddd; }
305
+ .content table th { background:#f2f2f2; font-weight:bold; }
306
+ .content table tr:nth-child(odd) { background:#fff; }
307
+ </style></head><body><div class="content">
308
+ <div class="json-to-html-title">Benchmark Report</div>
309
+ ${htmlTables}
310
+ </div></body></html>`;
311
+
312
+ if (!isIndividual) {
313
+ replaceMarkdownContent(
314
+ '[//]: # (No deletion!!! Start of Replace Section)',
315
+ '[//]: # (No deletion!!! End of Replace Section)',
316
+ htmlTables
317
+ );
318
+ }
319
+ fs.writeFileSync(htmlFilePath, html);
320
+ console.log(`Performance ${BOLD}${GREEN}report${END} file generated in file://${BOLD}${GREEN}${htmlFilePath}${END}`);
321
+ }
322
+
323
+ function replaceMarkdownContent(startMarker: string, endMarker: string, newText: string) {
324
+ const filePath = path.join(parentDirectory, 'README.md');
325
+ fs.readFile(filePath, 'utf8', (err, data) => {
326
+ if (err) {
327
+ console.error(`Unable to read ${filePath}:`, err);
328
+ return;
329
+ }
330
+ const startIndex = data.indexOf(startMarker);
331
+ const endIndex = data.indexOf(endMarker);
332
+ if (startIndex === -1 || endIndex === -1 || startIndex >= endIndex) {
333
+ console.warn('Markers not found or invalid range; skip README injection.');
334
+ return;
335
+ }
336
+ const updatedMarkdown = data.slice(0, startIndex + startMarker.length) + '\n' + newText + data.slice(endIndex);
337
+ fs.writeFile(filePath, updatedMarkdown, 'utf8', err2 => {
338
+ if (err2) console.error(`Unable to write to ${filePath}:`, err2);
339
+ else console.log(`The content has been successfully replaced in file://${BOLD}${GREEN}${filePath}${END}`);
340
+ });
341
+ });
342
+ }
343
+
344
+ function toHtmlTables() {
345
+ let htmlTables = '';
346
+ for (const key of Object.keys(report)) {
347
+ const block = report[key];
348
+ const rows = block.benchmarks as Array<Record<string, any>>;
349
+ if (!rows || !rows.length) continue;
350
+ const headers = Object.keys(rows[0]);
351
+ const table = [
352
+ '<table>',
353
+ '<thead>',
354
+ '<tr>' + headers.map(h => `<th>${h}</th>`).join('') + '</tr>',
355
+ '</thead>',
356
+ '<tbody>',
357
+ ...rows.map(r => '<tr>' + headers.map(h => `<td>${r[h]}</td>`).join('') + '</tr>'),
358
+ '</tbody>',
359
+ '</table>'
360
+ ].join('');
361
+ htmlTables += `<h2>${key}</h2>` + table;
362
+ }
363
+ return htmlTables;
364
+ }
365
+
366
+ // ---------------- in-process mode (improved hygiene) ----------------
367
+
368
+ async function runInProcess(files: string[]) {
369
+ const durations: number[] = [];
370
+ let index = 0;
371
+ for (const file of files) {
372
+ index++;
373
+ const base = path.basename(file);
374
+ const testName = base.replace(/\.test\.[^.]+$/, '');
375
+ console.log(`${BOLD}${GREEN}[${index}/${plannedCount}]${END} ${coloredLabeled('Run', file)}`);
376
+
377
+ const mod = require(file);
378
+ const suite: Benchmark.Suite | undefined = mod?.suite ?? mod?.default?.suite;
379
+ if (!suite) {
380
+ report[testName] = { benchmarks: [] };
381
+ completedCount++;
382
+ console.log(
383
+ `Progress: ${BOLD}${GREEN}${completedCount}${END}/${BOLD}${GREEN}${plannedCount}${END} Last ${testName}: ${YELLOW}SKIP${END}`
384
+ );
385
+ continue;
386
+ }
387
+
388
+ try {
389
+ suite.forEach((b: any) => {
390
+ if (typeof b.fn === 'function') b.fn.call(b);
391
+ });
392
+ } catch {}
393
+
394
+ if (flags.gc && global.gc) global.gc();
395
+ await new Promise(r => setTimeout(r, flags.cooldownMs));
396
+
397
+ const t0 = Date.now();
398
+ await new Promise<void>(resolve => {
399
+ suite
400
+ .on('complete', function (this: Benchmark.Suite) {
401
+ const rows = this.map((benchmark: any) => ({
402
+ 'test name': benchmark.name,
403
+ 'time taken (ms)': numberFix(benchmark.times.period * 1000, 2),
404
+ 'sample mean (secs)': numberFix(benchmark.stats.mean, 2),
405
+ 'sample deviation': numberFix(benchmark.stats.deviation, 2)
406
+ }));
407
+ report[testName] = { benchmarks: rows };
408
+ resolve();
409
+ })
410
+ .run({ async: false });
411
+ });
412
+
413
+ const dt = (Date.now() - t0) / 1000;
414
+ durations.push(dt);
415
+ const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
416
+ const remaining = plannedCount - ++completedCount;
417
+ const etaSec = Math.max(0, Math.round(remaining * avg));
418
+ const mins = Math.floor(etaSec / 60)
419
+ .toString()
420
+ .padStart(2, '0');
421
+ const secs = (etaSec % 60).toString().padStart(2, '0');
422
+ console.log(
423
+ `Progress: ${BOLD}${GREEN}${completedCount}${END}/${BOLD}${GREEN}${plannedCount}${END} Last ${testName}: ${GREEN}OK${END} in ${numberFix(dt, 2)}s | ETA ~ ${mins}:${secs}`
424
+ );
425
+ }
426
+ }
427
+
428
+ // ---------------- isolated-per-suite mode
429
+ type ChildMessage = {
430
+ testName: string;
431
+ benchmarks: Array<Record<string, any>>;
432
+ runTime: number;
433
+ file: string;
434
+ };
435
+
436
+ async function runIsolated(files: string[]) {
437
+ const durations: number[] = [];
438
+ let index = 0;
439
+
440
+ for (const file of files) {
441
+ index++;
442
+ const testName = path.basename(file).replace(/\.test\.[^.]+$/, '');
443
+ console.log(`${BOLD}${GREEN}[${index}/${plannedCount}]${END} ${coloredLabeled('Fork', file)}`);
444
+
445
+ await new Promise<void>((resolve, reject) => {
446
+ const childEntry = path.resolve(__dirname, './single-suite-runner.ts'); // ensure .ts
447
+
448
+ // Always run with Node + ts-node/register in child.
449
+ const execArgv = ['-r', 'ts-node/register/transpile-only'];
450
+
451
+ // If user wants GC, put it into NODE_OPTIONS (so Node consumes it, not ts-node).
452
+ const env = {
453
+ ...process.env,
454
+ NODE_OPTIONS: ((process.env.NODE_OPTIONS || '') + (flags.gc ? ' --expose-gc' : '')).trim()
455
+ };
456
+
457
+ const t0 = Date.now();
458
+ const cp = fork(childEntry, [file], {
459
+ stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
460
+ execArgv,
461
+ execPath: process.execPath,
462
+ env
463
+ });
464
+
465
+ let got = false;
466
+ cp.on('message', (m: ChildMessage) => {
467
+ got = true;
468
+ report[m.testName] = { benchmarks: m.benchmarks };
469
+ });
470
+ cp.on('exit', code => {
471
+ completedCount++;
472
+ const dt = (Date.now() - t0) / 1000;
473
+ durations.push(dt);
474
+
475
+ const avg = durations.reduce((a, b) => a + b, 0) / durations.length;
476
+ const remaining = files.length - completedCount;
477
+ const etaSec = Math.max(0, Math.round(remaining * avg));
478
+ const mins = Math.floor(etaSec / 60)
479
+ .toString()
480
+ .padStart(2, '0');
481
+ const secs = (etaSec % 60).toString().padStart(2, '0');
482
+ const label =
483
+ code === 0 && got ? `${GREEN}OK${END}` : code === 0 && !got ? `${YELLOW}SKIP${END}` : `${YELLOW}ERR${END}`;
484
+
485
+ console.log(
486
+ `Progress: ${BOLD}${GREEN}${completedCount}${END}/${BOLD}${GREEN}${plannedCount}${END}`,
487
+ `Last ${testName}: ${label} in ${numberFix(dt, 2)}s | ETA ~ ${mins}:${secs}`
488
+ );
489
+
490
+ if (code !== 0) reject(new Error(`Child failed: ${code}`));
491
+ else resolve();
492
+ });
493
+ });
494
+
495
+ await new Promise(r => setTimeout(r, flags.cooldownMs));
496
+ }
497
+ }
498
+
499
+ // ---------------- entry ----------------
500
+ (async function main() {
501
+ if (!testFiles.length) {
502
+ console.log(`${YELLOW}No test files matched.${END}`);
503
+ process.exit(0);
504
+ }
505
+ console.log(
506
+ `${BOLD}${GREEN}Running ${plannedCount} planned suite(s)${END} out of ${discoveredTotal} discovered ${flags.isolate ? '(isolated)' : '(in-process)'} ${flags.shuffle ? '[shuffled]' : ''} ${flags.label ? '[' + flags.label + ']' : ''}`
507
+ );
508
+
509
+ if (flags.isolate) await runIsolated(testFiles);
510
+ else await runInProcess(testFiles);
511
+
512
+ // Render report (same as original)
513
+ const htmlTables = toHtmlTables();
514
+ writeReportHTMLAndJSON(htmlTables);
515
+
516
+ // Summary
517
+ const counts = { ok: 0, skip: 0, err: 0 };
518
+ for (const key of Object.keys(report)) {
519
+ const arr = report[key]?.benchmarks || [];
520
+ if (!Array.isArray(arr)) continue;
521
+ if (arr.length === 0) counts.skip++;
522
+ else counts.ok++;
523
+ }
524
+ // We don't track 'err' explicitly here because child errors stop the run; when needed, you can extend message payload.
525
+ console.log(
526
+ `${BOLD}${GREEN}Summary:${END} planned=${plannedCount}, discovered=${discoveredTotal}, ok=${counts.ok}, skipped=${counts.skip}`
527
+ );
528
+ })();