data-structure-typed 2.0.5 → 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 (260) hide show
  1. package/CHANGELOG.md +1 -1
  2. package/COMMANDS.md +17 -0
  3. package/benchmark/report.html +13 -77
  4. package/benchmark/report.json +145 -177
  5. package/dist/cjs/data-structures/base/iterable-element-base.d.ts +186 -83
  6. package/dist/cjs/data-structures/base/iterable-element-base.js +149 -107
  7. package/dist/cjs/data-structures/base/iterable-element-base.js.map +1 -1
  8. package/dist/cjs/data-structures/base/iterable-entry-base.d.ts +95 -119
  9. package/dist/cjs/data-structures/base/iterable-entry-base.js +59 -116
  10. package/dist/cjs/data-structures/base/iterable-entry-base.js.map +1 -1
  11. package/dist/cjs/data-structures/base/linear-base.d.ts +250 -192
  12. package/dist/cjs/data-structures/base/linear-base.js +137 -274
  13. package/dist/cjs/data-structures/base/linear-base.js.map +1 -1
  14. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.d.ts +126 -158
  15. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.js +171 -205
  16. package/dist/cjs/data-structures/binary-tree/avl-tree-counter.js.map +1 -1
  17. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.d.ts +100 -69
  18. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.js +135 -87
  19. package/dist/cjs/data-structures/binary-tree/avl-tree-multi-map.js.map +1 -1
  20. package/dist/cjs/data-structures/binary-tree/avl-tree.d.ts +138 -149
  21. package/dist/cjs/data-structures/binary-tree/avl-tree.js +208 -195
  22. package/dist/cjs/data-structures/binary-tree/avl-tree.js.map +1 -1
  23. package/dist/cjs/data-structures/binary-tree/binary-tree.d.ts +476 -632
  24. package/dist/cjs/data-structures/binary-tree/binary-tree.js +594 -865
  25. package/dist/cjs/data-structures/binary-tree/binary-tree.js.map +1 -1
  26. package/dist/cjs/data-structures/binary-tree/bst.d.ts +258 -306
  27. package/dist/cjs/data-structures/binary-tree/bst.js +505 -481
  28. package/dist/cjs/data-structures/binary-tree/bst.js.map +1 -1
  29. package/dist/cjs/data-structures/binary-tree/red-black-tree.d.ts +107 -179
  30. package/dist/cjs/data-structures/binary-tree/red-black-tree.js +114 -209
  31. package/dist/cjs/data-structures/binary-tree/red-black-tree.js.map +1 -1
  32. package/dist/cjs/data-structures/binary-tree/tree-counter.d.ts +132 -154
  33. package/dist/cjs/data-structures/binary-tree/tree-counter.js +172 -203
  34. package/dist/cjs/data-structures/binary-tree/tree-counter.js.map +1 -1
  35. package/dist/cjs/data-structures/binary-tree/tree-multi-map.d.ts +72 -69
  36. package/dist/cjs/data-structures/binary-tree/tree-multi-map.js +105 -85
  37. package/dist/cjs/data-structures/binary-tree/tree-multi-map.js.map +1 -1
  38. package/dist/cjs/data-structures/graph/abstract-graph.d.ts +238 -233
  39. package/dist/cjs/data-structures/graph/abstract-graph.js +267 -237
  40. package/dist/cjs/data-structures/graph/abstract-graph.js.map +1 -1
  41. package/dist/cjs/data-structures/graph/directed-graph.d.ts +108 -224
  42. package/dist/cjs/data-structures/graph/directed-graph.js +146 -233
  43. package/dist/cjs/data-structures/graph/directed-graph.js.map +1 -1
  44. package/dist/cjs/data-structures/graph/map-graph.d.ts +49 -55
  45. package/dist/cjs/data-structures/graph/map-graph.js +56 -59
  46. package/dist/cjs/data-structures/graph/map-graph.js.map +1 -1
  47. package/dist/cjs/data-structures/graph/undirected-graph.d.ts +103 -146
  48. package/dist/cjs/data-structures/graph/undirected-graph.js +129 -149
  49. package/dist/cjs/data-structures/graph/undirected-graph.js.map +1 -1
  50. package/dist/cjs/data-structures/hash/hash-map.d.ts +164 -338
  51. package/dist/cjs/data-structures/hash/hash-map.js +270 -457
  52. package/dist/cjs/data-structures/hash/hash-map.js.map +1 -1
  53. package/dist/cjs/data-structures/heap/heap.d.ts +214 -289
  54. package/dist/cjs/data-structures/heap/heap.js +340 -349
  55. package/dist/cjs/data-structures/heap/heap.js.map +1 -1
  56. package/dist/cjs/data-structures/heap/max-heap.d.ts +11 -47
  57. package/dist/cjs/data-structures/heap/max-heap.js +11 -66
  58. package/dist/cjs/data-structures/heap/max-heap.js.map +1 -1
  59. package/dist/cjs/data-structures/heap/min-heap.d.ts +12 -47
  60. package/dist/cjs/data-structures/heap/min-heap.js +11 -66
  61. package/dist/cjs/data-structures/heap/min-heap.js.map +1 -1
  62. package/dist/cjs/data-structures/linked-list/doubly-linked-list.d.ts +231 -347
  63. package/dist/cjs/data-structures/linked-list/doubly-linked-list.js +368 -494
  64. package/dist/cjs/data-structures/linked-list/doubly-linked-list.js.map +1 -1
  65. package/dist/cjs/data-structures/linked-list/singly-linked-list.d.ts +261 -310
  66. package/dist/cjs/data-structures/linked-list/singly-linked-list.js +447 -466
  67. package/dist/cjs/data-structures/linked-list/singly-linked-list.js.map +1 -1
  68. package/dist/cjs/data-structures/linked-list/skip-linked-list.d.ts +0 -107
  69. package/dist/cjs/data-structures/linked-list/skip-linked-list.js +0 -100
  70. package/dist/cjs/data-structures/linked-list/skip-linked-list.js.map +1 -1
  71. package/dist/cjs/data-structures/priority-queue/max-priority-queue.d.ts +12 -56
  72. package/dist/cjs/data-structures/priority-queue/max-priority-queue.js +11 -78
  73. package/dist/cjs/data-structures/priority-queue/max-priority-queue.js.map +1 -1
  74. package/dist/cjs/data-structures/priority-queue/min-priority-queue.d.ts +11 -57
  75. package/dist/cjs/data-structures/priority-queue/min-priority-queue.js +10 -79
  76. package/dist/cjs/data-structures/priority-queue/min-priority-queue.js.map +1 -1
  77. package/dist/cjs/data-structures/priority-queue/priority-queue.d.ts +2 -61
  78. package/dist/cjs/data-structures/priority-queue/priority-queue.js +8 -83
  79. package/dist/cjs/data-structures/priority-queue/priority-queue.js.map +1 -1
  80. package/dist/cjs/data-structures/queue/deque.d.ts +227 -254
  81. package/dist/cjs/data-structures/queue/deque.js +309 -348
  82. package/dist/cjs/data-structures/queue/deque.js.map +1 -1
  83. package/dist/cjs/data-structures/queue/queue.d.ts +180 -201
  84. package/dist/cjs/data-structures/queue/queue.js +265 -248
  85. package/dist/cjs/data-structures/queue/queue.js.map +1 -1
  86. package/dist/cjs/data-structures/stack/stack.d.ts +124 -102
  87. package/dist/cjs/data-structures/stack/stack.js +181 -125
  88. package/dist/cjs/data-structures/stack/stack.js.map +1 -1
  89. package/dist/cjs/data-structures/trie/trie.d.ts +164 -165
  90. package/dist/cjs/data-structures/trie/trie.js +189 -172
  91. package/dist/cjs/data-structures/trie/trie.js.map +1 -1
  92. package/dist/cjs/interfaces/binary-tree.d.ts +56 -6
  93. package/dist/cjs/interfaces/graph.d.ts +16 -0
  94. package/dist/cjs/types/data-structures/base/base.d.ts +1 -1
  95. package/dist/cjs/types/data-structures/graph/abstract-graph.d.ts +4 -0
  96. package/dist/cjs/types/utils/utils.d.ts +1 -0
  97. package/dist/cjs/utils/utils.d.ts +1 -1
  98. package/dist/cjs/utils/utils.js +2 -1
  99. package/dist/cjs/utils/utils.js.map +1 -1
  100. package/dist/esm/data-structures/base/iterable-element-base.d.ts +186 -83
  101. package/dist/esm/data-structures/base/iterable-element-base.js +155 -107
  102. package/dist/esm/data-structures/base/iterable-element-base.js.map +1 -1
  103. package/dist/esm/data-structures/base/iterable-entry-base.d.ts +95 -119
  104. package/dist/esm/data-structures/base/iterable-entry-base.js +59 -116
  105. package/dist/esm/data-structures/base/iterable-entry-base.js.map +1 -1
  106. package/dist/esm/data-structures/base/linear-base.d.ts +250 -192
  107. package/dist/esm/data-structures/base/linear-base.js +137 -274
  108. package/dist/esm/data-structures/base/linear-base.js.map +1 -1
  109. package/dist/esm/data-structures/binary-tree/avl-tree-counter.d.ts +126 -158
  110. package/dist/esm/data-structures/binary-tree/avl-tree-counter.js +171 -212
  111. package/dist/esm/data-structures/binary-tree/avl-tree-counter.js.map +1 -1
  112. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.d.ts +100 -69
  113. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.js +133 -94
  114. package/dist/esm/data-structures/binary-tree/avl-tree-multi-map.js.map +1 -1
  115. package/dist/esm/data-structures/binary-tree/avl-tree.d.ts +138 -149
  116. package/dist/esm/data-structures/binary-tree/avl-tree.js +206 -200
  117. package/dist/esm/data-structures/binary-tree/avl-tree.js.map +1 -1
  118. package/dist/esm/data-structures/binary-tree/binary-tree.d.ts +476 -632
  119. package/dist/esm/data-structures/binary-tree/binary-tree.js +598 -874
  120. package/dist/esm/data-structures/binary-tree/binary-tree.js.map +1 -1
  121. package/dist/esm/data-structures/binary-tree/bst.d.ts +258 -306
  122. package/dist/esm/data-structures/binary-tree/bst.js +507 -487
  123. package/dist/esm/data-structures/binary-tree/bst.js.map +1 -1
  124. package/dist/esm/data-structures/binary-tree/red-black-tree.d.ts +107 -179
  125. package/dist/esm/data-structures/binary-tree/red-black-tree.js +114 -215
  126. package/dist/esm/data-structures/binary-tree/red-black-tree.js.map +1 -1
  127. package/dist/esm/data-structures/binary-tree/tree-counter.d.ts +132 -154
  128. package/dist/esm/data-structures/binary-tree/tree-counter.js +175 -209
  129. package/dist/esm/data-structures/binary-tree/tree-counter.js.map +1 -1
  130. package/dist/esm/data-structures/binary-tree/tree-multi-map.d.ts +72 -69
  131. package/dist/esm/data-structures/binary-tree/tree-multi-map.js +103 -92
  132. package/dist/esm/data-structures/binary-tree/tree-multi-map.js.map +1 -1
  133. package/dist/esm/data-structures/graph/abstract-graph.d.ts +238 -233
  134. package/dist/esm/data-structures/graph/abstract-graph.js +267 -237
  135. package/dist/esm/data-structures/graph/abstract-graph.js.map +1 -1
  136. package/dist/esm/data-structures/graph/directed-graph.d.ts +108 -224
  137. package/dist/esm/data-structures/graph/directed-graph.js +145 -233
  138. package/dist/esm/data-structures/graph/directed-graph.js.map +1 -1
  139. package/dist/esm/data-structures/graph/map-graph.d.ts +49 -55
  140. package/dist/esm/data-structures/graph/map-graph.js +56 -59
  141. package/dist/esm/data-structures/graph/map-graph.js.map +1 -1
  142. package/dist/esm/data-structures/graph/undirected-graph.d.ts +103 -146
  143. package/dist/esm/data-structures/graph/undirected-graph.js +128 -149
  144. package/dist/esm/data-structures/graph/undirected-graph.js.map +1 -1
  145. package/dist/esm/data-structures/hash/hash-map.d.ts +164 -338
  146. package/dist/esm/data-structures/hash/hash-map.js +270 -457
  147. package/dist/esm/data-structures/hash/hash-map.js.map +1 -1
  148. package/dist/esm/data-structures/heap/heap.d.ts +214 -289
  149. package/dist/esm/data-structures/heap/heap.js +329 -349
  150. package/dist/esm/data-structures/heap/heap.js.map +1 -1
  151. package/dist/esm/data-structures/heap/max-heap.d.ts +11 -47
  152. package/dist/esm/data-structures/heap/max-heap.js +11 -66
  153. package/dist/esm/data-structures/heap/max-heap.js.map +1 -1
  154. package/dist/esm/data-structures/heap/min-heap.d.ts +12 -47
  155. package/dist/esm/data-structures/heap/min-heap.js +11 -66
  156. package/dist/esm/data-structures/heap/min-heap.js.map +1 -1
  157. package/dist/esm/data-structures/linked-list/doubly-linked-list.d.ts +231 -347
  158. package/dist/esm/data-structures/linked-list/doubly-linked-list.js +368 -495
  159. package/dist/esm/data-structures/linked-list/doubly-linked-list.js.map +1 -1
  160. package/dist/esm/data-structures/linked-list/singly-linked-list.d.ts +261 -310
  161. package/dist/esm/data-structures/linked-list/singly-linked-list.js +448 -467
  162. package/dist/esm/data-structures/linked-list/singly-linked-list.js.map +1 -1
  163. package/dist/esm/data-structures/linked-list/skip-linked-list.d.ts +0 -107
  164. package/dist/esm/data-structures/linked-list/skip-linked-list.js +0 -100
  165. package/dist/esm/data-structures/linked-list/skip-linked-list.js.map +1 -1
  166. package/dist/esm/data-structures/priority-queue/max-priority-queue.d.ts +12 -56
  167. package/dist/esm/data-structures/priority-queue/max-priority-queue.js +11 -78
  168. package/dist/esm/data-structures/priority-queue/max-priority-queue.js.map +1 -1
  169. package/dist/esm/data-structures/priority-queue/min-priority-queue.d.ts +11 -57
  170. package/dist/esm/data-structures/priority-queue/min-priority-queue.js +10 -79
  171. package/dist/esm/data-structures/priority-queue/min-priority-queue.js.map +1 -1
  172. package/dist/esm/data-structures/priority-queue/priority-queue.d.ts +2 -61
  173. package/dist/esm/data-structures/priority-queue/priority-queue.js +8 -83
  174. package/dist/esm/data-structures/priority-queue/priority-queue.js.map +1 -1
  175. package/dist/esm/data-structures/queue/deque.d.ts +227 -254
  176. package/dist/esm/data-structures/queue/deque.js +313 -348
  177. package/dist/esm/data-structures/queue/deque.js.map +1 -1
  178. package/dist/esm/data-structures/queue/queue.d.ts +180 -201
  179. package/dist/esm/data-structures/queue/queue.js +263 -248
  180. package/dist/esm/data-structures/queue/queue.js.map +1 -1
  181. package/dist/esm/data-structures/stack/stack.d.ts +124 -102
  182. package/dist/esm/data-structures/stack/stack.js +181 -125
  183. package/dist/esm/data-structures/stack/stack.js.map +1 -1
  184. package/dist/esm/data-structures/trie/trie.d.ts +164 -165
  185. package/dist/esm/data-structures/trie/trie.js +193 -172
  186. package/dist/esm/data-structures/trie/trie.js.map +1 -1
  187. package/dist/esm/interfaces/binary-tree.d.ts +56 -6
  188. package/dist/esm/interfaces/graph.d.ts +16 -0
  189. package/dist/esm/types/data-structures/base/base.d.ts +1 -1
  190. package/dist/esm/types/data-structures/graph/abstract-graph.d.ts +4 -0
  191. package/dist/esm/types/utils/utils.d.ts +1 -0
  192. package/dist/esm/utils/utils.d.ts +1 -1
  193. package/dist/esm/utils/utils.js +2 -1
  194. package/dist/esm/utils/utils.js.map +1 -1
  195. package/dist/umd/data-structure-typed.js +4685 -6477
  196. package/dist/umd/data-structure-typed.min.js +8 -6
  197. package/dist/umd/data-structure-typed.min.js.map +1 -1
  198. package/package.json +3 -4
  199. package/src/data-structures/base/iterable-element-base.ts +238 -115
  200. package/src/data-structures/base/iterable-entry-base.ts +96 -120
  201. package/src/data-structures/base/linear-base.ts +271 -277
  202. package/src/data-structures/binary-tree/avl-tree-counter.ts +198 -216
  203. package/src/data-structures/binary-tree/avl-tree-multi-map.ts +192 -101
  204. package/src/data-structures/binary-tree/avl-tree.ts +239 -206
  205. package/src/data-structures/binary-tree/binary-tree.ts +660 -889
  206. package/src/data-structures/binary-tree/bst.ts +568 -570
  207. package/src/data-structures/binary-tree/red-black-tree.ts +161 -222
  208. package/src/data-structures/binary-tree/tree-counter.ts +199 -218
  209. package/src/data-structures/binary-tree/tree-multi-map.ts +131 -97
  210. package/src/data-structures/graph/abstract-graph.ts +339 -264
  211. package/src/data-structures/graph/directed-graph.ts +146 -236
  212. package/src/data-structures/graph/map-graph.ts +63 -60
  213. package/src/data-structures/graph/undirected-graph.ts +129 -152
  214. package/src/data-structures/hash/hash-map.ts +274 -496
  215. package/src/data-structures/heap/heap.ts +389 -402
  216. package/src/data-structures/heap/max-heap.ts +12 -76
  217. package/src/data-structures/heap/min-heap.ts +13 -76
  218. package/src/data-structures/linked-list/doubly-linked-list.ts +426 -530
  219. package/src/data-structures/linked-list/singly-linked-list.ts +495 -517
  220. package/src/data-structures/linked-list/skip-linked-list.ts +1 -108
  221. package/src/data-structures/priority-queue/max-priority-queue.ts +12 -87
  222. package/src/data-structures/priority-queue/min-priority-queue.ts +11 -88
  223. package/src/data-structures/priority-queue/priority-queue.ts +3 -92
  224. package/src/data-structures/queue/deque.ts +381 -357
  225. package/src/data-structures/queue/queue.ts +310 -264
  226. package/src/data-structures/stack/stack.ts +217 -131
  227. package/src/data-structures/trie/trie.ts +240 -175
  228. package/src/interfaces/binary-tree.ts +240 -6
  229. package/src/interfaces/graph.ts +37 -0
  230. package/src/types/data-structures/base/base.ts +5 -5
  231. package/src/types/data-structures/graph/abstract-graph.ts +5 -0
  232. package/src/types/utils/utils.ts +2 -0
  233. package/src/utils/utils.ts +9 -14
  234. package/test/integration/index.html +1 -1
  235. package/test/performance/benchmark-runner.ts +528 -0
  236. package/test/performance/reportor.mjs +43 -43
  237. package/test/performance/runner-config.json +39 -0
  238. package/test/performance/single-suite-runner.ts +69 -0
  239. package/test/unit/data-structures/binary-tree/avl-tree-counter.test.ts +3 -3
  240. package/test/unit/data-structures/binary-tree/avl-tree-multi-map.test.ts +5 -5
  241. package/test/unit/data-structures/binary-tree/avl-tree.test.ts +4 -4
  242. package/test/unit/data-structures/binary-tree/binary-tree.test.ts +350 -90
  243. package/test/unit/data-structures/binary-tree/bst.test.ts +12 -9
  244. package/test/unit/data-structures/binary-tree/red-black-tree.test.ts +2 -2
  245. package/test/unit/data-structures/binary-tree/tree-counter.test.ts +25 -24
  246. package/test/unit/data-structures/binary-tree/tree-multi-map.test.ts +3 -3
  247. package/test/unit/data-structures/graph/abstract-graph.test.ts +0 -4
  248. package/test/unit/data-structures/graph/directed-graph.test.ts +1 -1
  249. package/test/unit/data-structures/heap/heap.test.ts +14 -21
  250. package/test/unit/data-structures/heap/max-heap.test.ts +5 -9
  251. package/test/unit/data-structures/heap/min-heap.test.ts +1 -4
  252. package/test/unit/data-structures/linked-list/doubly-linked-list.test.ts +14 -14
  253. package/test/unit/data-structures/linked-list/singly-linked-list.test.ts +0 -7
  254. package/test/unit/data-structures/priority-queue/max-priority-queue.test.ts +8 -11
  255. package/test/unit/data-structures/priority-queue/min-priority-queue.test.ts +1 -4
  256. package/test/unit/data-structures/priority-queue/priority-queue.test.ts +1 -4
  257. package/test/unit/data-structures/queue/queue.test.ts +4 -5
  258. package/test/unit/utils/utils.test.ts +0 -1
  259. package/test/performance/data-structures/binary-tree/avl-tree.test.mjs +0 -71
  260. 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
+ })();