@calcit/ternary-tree 0.0.25 → 0.0.26

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.
Binary file
package/.yarnrc.yml ADDED
@@ -0,0 +1,6 @@
1
+ approvedGitRepositories:
2
+ - "**"
3
+
4
+ enableScripts: true
5
+
6
+ nodeLinker: node-modules
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Micro-benchmark for ts-ternary-tree hot paths.
3
+ *
4
+ * Usage:
5
+ * yarn bench
6
+ *
7
+ * Each bench() call runs the task for ~1 second and reports ops/sec.
8
+ * A higher number is better. Compare before/after code changes to judge
9
+ * whether an optimisation has real impact.
10
+ */
11
+ export {};
package/lib/bench.mjs ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Micro-benchmark for ts-ternary-tree hot paths.
3
+ *
4
+ * Usage:
5
+ * yarn bench
6
+ *
7
+ * Each bench() call runs the task for ~1 second and reports ops/sec.
8
+ * A higher number is better. Compare before/after code changes to judge
9
+ * whether an optimisation has real impact.
10
+ */
11
+ import { initTernaryTreeList, listGet, assocList, dissocList, listToItems, concat, } from "./list.mjs";
12
+ import { initTernaryTreeMapFromArray, assocMap, dissocMap, mapGetDefault, } from "./map.mjs";
13
+ import { deepEqual, overwriteComparator } from "./utils.mjs";
14
+ import { mergeValueHash, overwriteHashGenerator, valueHash } from "./types.mjs";
15
+ overwriteComparator(deepEqual);
16
+ overwriteHashGenerator((x) => mergeValueHash(10, valueHash(x)));
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+ function bench(label, fn, durationMs = 1000) {
21
+ // Warmup
22
+ for (let i = 0; i < 100; i++)
23
+ fn();
24
+ let ops = 0;
25
+ const start = Date.now();
26
+ while (Date.now() - start < durationMs) {
27
+ fn();
28
+ ops++;
29
+ }
30
+ const elapsed = Date.now() - start;
31
+ const opsPerSec = Math.round((ops / elapsed) * 1000);
32
+ console.log(` ${label.padEnd(40)} ${opsPerSec.toLocaleString()} ops/sec`);
33
+ }
34
+ function section(title) {
35
+ console.log(`\n--- ${title} ---`);
36
+ }
37
+ // ---------------------------------------------------------------------------
38
+ // Fixtures
39
+ // ---------------------------------------------------------------------------
40
+ const SIZE_SMALL = 100;
41
+ const SIZE_MEDIUM = 1000;
42
+ const SIZE_LARGE = 10000;
43
+ const arrSmall = Array.from({ length: SIZE_SMALL }, (_, i) => i);
44
+ const arrMedium = Array.from({ length: SIZE_MEDIUM }, (_, i) => i);
45
+ const arrLarge = Array.from({ length: SIZE_LARGE }, (_, i) => i);
46
+ const listSmall = initTernaryTreeList(arrSmall);
47
+ const listMedium = initTernaryTreeList(arrMedium);
48
+ const listLarge = initTernaryTreeList(arrLarge);
49
+ const mapMediumPairs = arrMedium.map((i) => [`k${i}`, i]);
50
+ const mapMedium = initTernaryTreeMapFromArray(mapMediumPairs);
51
+ // ---------------------------------------------------------------------------
52
+ // List benchmarks
53
+ // ---------------------------------------------------------------------------
54
+ section("listGet (random access)");
55
+ bench("listGet small (n=100) middle idx", () => listGet(listSmall, 50));
56
+ bench("listGet medium (n=1k) middle idx", () => listGet(listMedium, 500));
57
+ bench("listGet large (n=10k) middle idx", () => listGet(listLarge, 5000));
58
+ section("listToItems (full iteration)");
59
+ bench("listToItems small (n=100)", () => {
60
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
61
+ for (const _ of listToItems(listSmall)) { /* consume */ }
62
+ });
63
+ bench("listToItems medium (n=1k)", () => {
64
+ for (const _ of listToItems(listMedium)) { /* consume */ }
65
+ });
66
+ bench("listToItems large (n=10k)", () => {
67
+ for (const _ of listToItems(listLarge)) { /* consume */ }
68
+ });
69
+ section("assocList (element update)");
70
+ bench("assocList small (n=100) idx=50", () => assocList(listSmall, 50, 999));
71
+ bench("assocList medium (n=1k) idx=500", () => assocList(listMedium, 500, 999));
72
+ bench("assocList large (n=10k) idx=5000", () => assocList(listLarge, 5000, 999));
73
+ section("dissocList (element remove)");
74
+ bench("dissocList small (n=100) idx=50", () => dissocList(listSmall, 50));
75
+ bench("dissocList medium (n=1k) idx=500", () => dissocList(listMedium, 500));
76
+ bench("dissocList large (n=10k) idx=5000", () => dissocList(listLarge, 5000));
77
+ section("initTernaryTreeList (construction)");
78
+ bench("init list small (n=100)", () => initTernaryTreeList(arrSmall));
79
+ bench("init list medium (n=1k)", () => initTernaryTreeList(arrMedium));
80
+ bench("init list large (n=10k)", () => initTernaryTreeList(arrLarge));
81
+ section("concat");
82
+ const half1 = initTernaryTreeList(arrMedium.slice(0, 500));
83
+ const half2 = initTernaryTreeList(arrMedium.slice(500));
84
+ bench("concat two halves (n=500 each)", () => concat(half1, half2));
85
+ // ---------------------------------------------------------------------------
86
+ // Map benchmarks
87
+ // ---------------------------------------------------------------------------
88
+ section("assocMap (key update/insert)");
89
+ bench("assocMap medium (n=1k) existing key", () => assocMap(mapMedium, "k500", 9999));
90
+ bench("assocMap medium (n=1k) new key", () => assocMap(mapMedium, "new-key", 9999));
91
+ section("dissocMap (key remove)");
92
+ bench("dissocMap medium (n=1k)", () => dissocMap(mapMedium, "k500"));
93
+ section("mapGetDefault (lookup)");
94
+ bench("mapGetDefault medium (n=1k) hit", () => mapGetDefault(mapMedium, "k500", -1));
95
+ bench("mapGetDefault medium (n=1k) miss", () => mapGetDefault(mapMedium, "no-such-key", -1));
96
+ section("initTernaryTreeMap (construction)");
97
+ bench("init map small (n=100)", () => initTernaryTreeMapFromArray(mapMediumPairs.slice(0, 100)));
98
+ bench("init map medium (n=1k)", () => initTernaryTreeMapFromArray(mapMediumPairs));
99
+ console.log("\nDone.");
package/lib/list.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { TernaryTreeList } from "./types.mjs";
2
+ export declare function enableStructureCheck(enabled?: boolean): void;
2
3
  export declare function getDepth<T>(tree: TernaryTreeList<T>): number;
3
4
  export declare function makeTernaryTreeList<T>(size: number, offset: number, xs: Array<TernaryTreeList<T>>): TernaryTreeList<T>;
4
5
  export declare function initTernaryTreeList<T>(xs: Array<T>): TernaryTreeList<T>;
package/lib/list.mjs CHANGED
@@ -1,5 +1,11 @@
1
1
  import { TernaryTreeKind } from "./types.mjs";
2
- import { dataEqual, divideTernarySizes, roughIntPow } from "./utils.mjs";
2
+ import { dataEqual, roughIntPow } from "./utils.mjs";
3
+ // When false (default), internal structure checks are skipped for performance.
4
+ // Set to true in development/testing to catch structural bugs early.
5
+ let _devMode = false;
6
+ export function enableStructureCheck(enabled = true) {
7
+ _devMode = enabled;
8
+ }
3
9
  // just get, will not compute recursively
4
10
  export function getDepth(tree) {
5
11
  if (tree == null)
@@ -18,16 +24,18 @@ let isEmptyBranch = (x) => {
18
24
  }
19
25
  return x.size == 0;
20
26
  };
21
- function decideParentDepth(...xs) {
22
- let depth = 0;
23
- for (let i = 0; i < xs.length; i++) {
24
- let x = xs[i];
25
- let y = getDepth(x);
26
- if (y > depth) {
27
- depth = y;
28
- }
29
- }
30
- return depth + 1;
27
+ // Explicit 2/3-arg versions avoid rest-parameter array allocation on every call.
28
+ // Inlining getDepth avoids indirect function calls in V8's lower JIT tiers.
29
+ function decideParentDepth(a, b, c) {
30
+ let da = a == null ? 0 : a.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : a.depth;
31
+ let db = b == null ? 0 : b.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : b.depth;
32
+ let d = da > db ? da : db;
33
+ if (c != null) {
34
+ let dc = c.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : c.depth;
35
+ if (dc > d)
36
+ d = dc;
37
+ }
38
+ return d + 1;
31
39
  }
32
40
  export function makeTernaryTreeList(size, offset, xs) {
33
41
  switch (size) {
@@ -47,7 +55,8 @@ export function makeTernaryTreeList(size, offset, xs) {
47
55
  right: emptyBranch,
48
56
  depth: decideParentDepth(left, middle),
49
57
  };
50
- checkListStructure(result);
58
+ if (_devMode)
59
+ checkListStructure(result);
51
60
  return result;
52
61
  }
53
62
  case 3: {
@@ -62,14 +71,20 @@ export function makeTernaryTreeList(size, offset, xs) {
62
71
  right: right,
63
72
  depth: decideParentDepth(left, middle, right),
64
73
  };
65
- checkListStructure(result);
74
+ if (_devMode)
75
+ checkListStructure(result);
66
76
  return result;
67
77
  }
68
78
  default: {
69
- let divided = divideTernarySizes(size);
70
- let left = makeTernaryTreeList(divided.left, offset, xs);
71
- let middle = makeTernaryTreeList(divided.middle, offset + divided.left, xs);
72
- let right = makeTernaryTreeList(divided.right, offset + divided.left + divided.middle, xs);
79
+ // Inline divideTernarySizes to avoid heap object allocation per recursive call
80
+ let extra = size % 3;
81
+ let groupSize = (size / 3) | 0;
82
+ let leftSize = groupSize + (extra === 2 ? 1 : 0);
83
+ let middleSize = groupSize + (extra === 1 ? 1 : 0);
84
+ let rightSize = groupSize + (extra === 2 ? 1 : 0);
85
+ let left = makeTernaryTreeList(leftSize, offset, xs);
86
+ let middle = makeTernaryTreeList(middleSize, offset + leftSize, xs);
87
+ let right = makeTernaryTreeList(rightSize, offset + leftSize + middleSize, xs);
73
88
  let result = {
74
89
  kind: TernaryTreeKind.ternaryTreeBranch,
75
90
  size: left.size + middle.size + right.size,
@@ -78,7 +93,8 @@ export function makeTernaryTreeList(size, offset, xs) {
78
93
  middle: middle,
79
94
  right: right,
80
95
  };
81
- checkListStructure(result);
96
+ if (_devMode)
97
+ checkListStructure(result);
82
98
  return result;
83
99
  }
84
100
  }
@@ -134,34 +150,26 @@ export function formatListInline(tree) {
134
150
  }
135
151
  }
136
152
  export function* listToItems(tree) {
137
- if (tree != null) {
138
- switch (tree.kind) {
139
- case TernaryTreeKind.ternaryTreeLeaf: {
140
- yield tree.value;
141
- break;
142
- }
143
- case TernaryTreeKind.ternaryTreeBranch: {
144
- // Cache children to avoid repeated property access
145
- const left = tree.left;
146
- const middle = tree.middle;
147
- const right = tree.right;
148
- if (left != null) {
149
- for (let x of listToItems(left)) {
150
- yield x;
151
- }
152
- }
153
- if (middle != null) {
154
- for (let x of listToItems(middle)) {
155
- yield x;
156
- }
157
- }
158
- if (right != null) {
159
- for (let x of listToItems(right)) {
160
- yield x;
161
- }
162
- }
163
- break;
164
- }
153
+ if (tree == null)
154
+ return;
155
+ // Iterative pre-order traversal using an explicit stack — avoids
156
+ // O(n) nested generator delegation overhead of the recursive version.
157
+ let stack = [tree];
158
+ while (stack.length > 0) {
159
+ let node = stack.pop();
160
+ if (node == null || node.size === 0)
161
+ continue;
162
+ if (node.kind === TernaryTreeKind.ternaryTreeLeaf) {
163
+ yield node.value;
164
+ }
165
+ else {
166
+ // Push right first so left is popped (processed) first (LIFO order).
167
+ if (node.right != null && node.right.size > 0)
168
+ stack.push(node.right);
169
+ if (node.middle != null && node.middle.size > 0)
170
+ stack.push(node.middle);
171
+ if (node.left != null && node.left.size > 0)
172
+ stack.push(node.left);
165
173
  }
166
174
  }
167
175
  }
@@ -307,7 +315,7 @@ export function listGet(originalTree, originalIdx) {
307
315
  const leftSize = left == null ? 0 : left.size;
308
316
  const middleSize = middle == null ? 0 : middle.size;
309
317
  const rightSize = right == null ? 0 : right.size;
310
- if (leftSize + middleSize + rightSize !== tree.size) {
318
+ if (_devMode && leftSize + middleSize + rightSize !== tree.size) {
311
319
  throw new Error("tree.size does not match sum case branch sizes");
312
320
  }
313
321
  if (idx <= leftSize - 1) {
@@ -362,7 +370,7 @@ export function assocList(tree, idx, item) {
362
370
  const leftSize = left == null ? 0 : left.size;
363
371
  const middleSize = middle == null ? 0 : middle.size;
364
372
  const rightSize = right == null ? 0 : right.size;
365
- if (leftSize + middleSize + rightSize !== tree.size)
373
+ if (_devMode && leftSize + middleSize + rightSize !== tree.size)
366
374
  throw new Error("tree.size does not match sum case branch sizes");
367
375
  if (idx <= leftSize - 1) {
368
376
  let changedBranch = assocList(left, idx, item);
@@ -374,7 +382,8 @@ export function assocList(tree, idx, item) {
374
382
  middle: middle,
375
383
  right: right,
376
384
  };
377
- checkListStructure(result);
385
+ if (_devMode)
386
+ checkListStructure(result);
378
387
  return result;
379
388
  }
380
389
  else if (idx <= leftSize + middleSize - 1) {
@@ -387,7 +396,8 @@ export function assocList(tree, idx, item) {
387
396
  middle: changedBranch,
388
397
  right: right,
389
398
  };
390
- checkListStructure(result);
399
+ if (_devMode)
400
+ checkListStructure(result);
391
401
  return result;
392
402
  }
393
403
  else {
@@ -400,7 +410,8 @@ export function assocList(tree, idx, item) {
400
410
  middle: middle,
401
411
  right: changedBranch,
402
412
  };
403
- checkListStructure(result);
413
+ if (_devMode)
414
+ checkListStructure(result);
404
415
  return result;
405
416
  }
406
417
  }
@@ -430,7 +441,7 @@ export function dissocList(tree, idx) {
430
441
  const leftSize = left == null ? 0 : left.size;
431
442
  const middleSize = middle == null ? 0 : middle.size;
432
443
  const rightSize = right == null ? 0 : right.size;
433
- if (leftSize + middleSize + rightSize !== tree.size) {
444
+ if (_devMode && leftSize + middleSize + rightSize !== tree.size) {
434
445
  throw new Error("tree.size does not match sum from branch sizes");
435
446
  }
436
447
  let result = emptyBranch;
@@ -463,7 +474,7 @@ export function dissocList(tree, idx) {
463
474
  result = {
464
475
  kind: TernaryTreeKind.ternaryTreeBranch,
465
476
  size: tree.size - 1,
466
- depth: decideParentDepth(left, changedBranch, right),
477
+ depth: decideParentDepth(left, right, emptyBranch),
467
478
  left: left,
468
479
  middle: right,
469
480
  right: emptyBranch,
@@ -497,7 +508,8 @@ export function dissocList(tree, idx) {
497
508
  if (result.middle == null) {
498
509
  return result.left;
499
510
  }
500
- checkListStructure(result);
511
+ if (_devMode)
512
+ checkListStructure(result);
501
513
  return result;
502
514
  }
503
515
  export function rest(tree) {
@@ -535,7 +547,8 @@ export function insert(tree, idx, item, after = false) {
535
547
  middle: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
536
548
  right: emptyBranch,
537
549
  };
538
- checkListStructure(result);
550
+ if (_devMode)
551
+ checkListStructure(result);
539
552
  return result;
540
553
  }
541
554
  else {
@@ -547,11 +560,13 @@ export function insert(tree, idx, item, after = false) {
547
560
  middle: tree,
548
561
  right: emptyBranch,
549
562
  };
550
- checkListStructure(result);
563
+ if (_devMode)
564
+ checkListStructure(result);
551
565
  return result;
552
566
  }
553
567
  }
554
- checkListStructure(tree);
568
+ if (_devMode)
569
+ checkListStructure(tree);
555
570
  if (listLen(tree) === 1) {
556
571
  if (after) {
557
572
  // in compact mode, values placed at left
@@ -563,7 +578,8 @@ export function insert(tree, idx, item, after = false) {
563
578
  middle: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
564
579
  right: emptyBranch,
565
580
  };
566
- checkListStructure(result);
581
+ if (_devMode)
582
+ checkListStructure(result);
567
583
  return result;
568
584
  }
569
585
  else {
@@ -575,7 +591,8 @@ export function insert(tree, idx, item, after = false) {
575
591
  middle: tree.left,
576
592
  right: emptyBranch,
577
593
  };
578
- checkListStructure(result);
594
+ if (_devMode)
595
+ checkListStructure(result);
579
596
  return result;
580
597
  }
581
598
  }
@@ -590,7 +607,8 @@ export function insert(tree, idx, item, after = false) {
590
607
  middle: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
591
608
  right: tree.middle,
592
609
  };
593
- checkListStructure(result);
610
+ if (_devMode)
611
+ checkListStructure(result);
594
612
  return result;
595
613
  }
596
614
  if (idx === 1) {
@@ -602,7 +620,8 @@ export function insert(tree, idx, item, after = false) {
602
620
  middle: tree.middle,
603
621
  right: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
604
622
  };
605
- checkListStructure(result);
623
+ if (_devMode)
624
+ checkListStructure(result);
606
625
  return result;
607
626
  }
608
627
  else {
@@ -619,7 +638,8 @@ export function insert(tree, idx, item, after = false) {
619
638
  middle: tree.left,
620
639
  right: tree.middle,
621
640
  };
622
- checkListStructure(result);
641
+ if (_devMode)
642
+ checkListStructure(result);
623
643
  return result;
624
644
  }
625
645
  else if (idx === 1) {
@@ -631,7 +651,8 @@ export function insert(tree, idx, item, after = false) {
631
651
  middle: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
632
652
  right: tree.middle,
633
653
  };
634
- checkListStructure(result);
654
+ if (_devMode)
655
+ checkListStructure(result);
635
656
  return result;
636
657
  }
637
658
  else {
@@ -646,7 +667,7 @@ export function insert(tree, idx, item, after = false) {
646
667
  const leftSize = left == null ? 0 : left.size;
647
668
  const middleSize = middle == null ? 0 : middle.size;
648
669
  const rightSize = right == null ? 0 : right.size;
649
- if (leftSize + middleSize + rightSize !== tree.size) {
670
+ if (_devMode && leftSize + middleSize + rightSize !== tree.size) {
650
671
  throw new Error("tree.size does not match sum case branch sizes");
651
672
  }
652
673
  // echo "picking: ", idx, " ", leftSize, " ", middleSize, " ", rightSize
@@ -660,7 +681,8 @@ export function insert(tree, idx, item, after = false) {
660
681
  middle: tree,
661
682
  right: emptyBranch,
662
683
  };
663
- checkListStructure(result);
684
+ if (_devMode)
685
+ checkListStructure(result);
664
686
  return result;
665
687
  }
666
688
  }
@@ -674,7 +696,8 @@ export function insert(tree, idx, item, after = false) {
674
696
  middle: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
675
697
  right: emptyBranch,
676
698
  };
677
- checkListStructure(result);
699
+ if (_devMode)
700
+ checkListStructure(result);
678
701
  return result;
679
702
  }
680
703
  }
@@ -687,10 +710,11 @@ export function insert(tree, idx, item, after = false) {
687
710
  middle: tree.middle,
688
711
  right: { kind: TernaryTreeKind.ternaryTreeLeaf, size: 1, value: item },
689
712
  };
690
- checkListStructure(result);
713
+ if (_devMode)
714
+ checkListStructure(result);
691
715
  return result;
692
716
  }
693
- if (!after && idx === 0 && rightSize === 0 && middleSize >= rightSize) {
717
+ if (!after && idx === 0 && rightSize === 0 && middleSize >= leftSize) {
694
718
  let result = {
695
719
  kind: TernaryTreeKind.ternaryTreeBranch,
696
720
  size: tree.size + 1,
@@ -699,7 +723,8 @@ export function insert(tree, idx, item, after = false) {
699
723
  middle: left,
700
724
  right: middle,
701
725
  };
702
- checkListStructure(result);
726
+ if (_devMode)
727
+ checkListStructure(result);
703
728
  return result;
704
729
  }
705
730
  if (idx <= leftSize - 1) {
@@ -712,7 +737,8 @@ export function insert(tree, idx, item, after = false) {
712
737
  middle: middle,
713
738
  right: right,
714
739
  };
715
- checkListStructure(result);
740
+ if (_devMode)
741
+ checkListStructure(result);
716
742
  return result;
717
743
  }
718
744
  else if (idx <= leftSize + middleSize - 1) {
@@ -725,7 +751,8 @@ export function insert(tree, idx, item, after = false) {
725
751
  middle: changedBranch,
726
752
  right: right,
727
753
  };
728
- checkListStructure(result);
754
+ if (_devMode)
755
+ checkListStructure(result);
729
756
  return result;
730
757
  }
731
758
  else {
@@ -738,7 +765,8 @@ export function insert(tree, idx, item, after = false) {
738
765
  middle: middle,
739
766
  right: changedBranch,
740
767
  };
741
- checkListStructure(result);
768
+ if (_devMode)
769
+ checkListStructure(result);
742
770
  return result;
743
771
  }
744
772
  }
@@ -806,7 +834,8 @@ export function concat(...xsGroups) {
806
834
  }
807
835
  let result = makeTernaryTreeList(xsGroups.length, 0, xsGroups);
808
836
  maybeReblance(result);
809
- checkListStructure(result);
837
+ if (_devMode)
838
+ checkListStructure(result);
810
839
  return result;
811
840
  }
812
841
  export function concat2(left, middle) {
@@ -844,7 +873,8 @@ export function concat2(left, middle) {
844
873
  middle: middle,
845
874
  right: emptyBranch,
846
875
  };
847
- checkListStructure(ret);
876
+ if (_devMode)
877
+ checkListStructure(ret);
848
878
  return ret;
849
879
  }
850
880
  export function concat3(left, middle, right) {
@@ -856,7 +886,8 @@ export function concat3(left, middle, right) {
856
886
  middle,
857
887
  right,
858
888
  };
859
- checkListStructure(ret);
889
+ if (_devMode)
890
+ checkListStructure(ret);
860
891
  return ret;
861
892
  }
862
893
  export function sameListShape(xs, ys) {
package/lib/map.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TernaryTreeKind, hashGenerator, } from "./types.mjs";
2
- import { divideTernarySizes, cmp, dataEqual } from "./utils.mjs";
2
+ import { cmp, dataEqual } from "./utils.mjs";
3
3
  let emptyBranch = null;
4
4
  let nilResult = null;
5
5
  function getMax(tree) {
@@ -29,7 +29,6 @@ function getMin(tree) {
29
29
  }
30
30
  }
31
31
  export function getMapDepth(tree) {
32
- // console.log( "calling...", tree)
33
32
  if (tree == null) {
34
33
  return 0;
35
34
  }
@@ -37,11 +36,21 @@ export function getMapDepth(tree) {
37
36
  case TernaryTreeKind.ternaryTreeLeaf:
38
37
  return 1;
39
38
  case TernaryTreeKind.ternaryTreeBranch:
40
- return Math.max(getMapDepth(tree.left), getMapDepth(tree.middle), getMapDepth(tree.right)) + 1;
39
+ return tree.depth; // use cached depth — O(1)
41
40
  default:
42
41
  throw new Error("Unknown");
43
42
  }
44
43
  }
44
+ function decideMapBranchDepth(left, middle, right) {
45
+ // Inline getMapDepth to avoid 3 indirect function calls per invocation
46
+ let dl = left == null ? 0 : left.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : left.depth;
47
+ let dm = middle == null ? 0 : middle.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : middle.depth;
48
+ let dr = right == null ? 0 : right.kind === TernaryTreeKind.ternaryTreeLeaf ? 1 : right.depth;
49
+ let d = dl > dm ? dl : dm;
50
+ if (dr > d)
51
+ d = dr;
52
+ return d + 1;
53
+ }
45
54
  function createLeaf(k, v) {
46
55
  let result = {
47
56
  kind: TernaryTreeKind.ternaryTreeLeaf,
@@ -81,7 +90,8 @@ function makeTernaryTreeMap(size, offset, xs) {
81
90
  left: createLeafFromHashEntry(leftPair),
82
91
  middle: createLeafFromHashEntry(middlePair),
83
92
  right: emptyBranch,
84
- depth: 1,
93
+ depth: 2,
94
+ size: leftPair.pairs.length + middlePair.pairs.length,
85
95
  };
86
96
  return result;
87
97
  }
@@ -96,15 +106,21 @@ function makeTernaryTreeMap(size, offset, xs) {
96
106
  left: createLeafFromHashEntry(leftPair),
97
107
  middle: createLeafFromHashEntry(middlePair),
98
108
  right: createLeafFromHashEntry(rightPair),
99
- depth: 1,
109
+ depth: 2,
110
+ size: leftPair.pairs.length + middlePair.pairs.length + rightPair.pairs.length,
100
111
  };
101
112
  return result;
102
113
  }
103
114
  default: {
104
- let divided = divideTernarySizes(size);
105
- let left = makeTernaryTreeMap(divided.left, offset, xs);
106
- let middle = makeTernaryTreeMap(divided.middle, offset + divided.left, xs);
107
- let right = makeTernaryTreeMap(divided.right, offset + divided.left + divided.middle, xs);
115
+ // Inline divideTernarySizes to avoid heap object allocation per recursive call
116
+ let extra = size % 3;
117
+ let groupSize = (size / 3) | 0;
118
+ let leftSize = groupSize + (extra === 2 ? 1 : 0);
119
+ let middleSize = groupSize + (extra === 1 ? 1 : 0);
120
+ let rightSize = groupSize + (extra === 2 ? 1 : 0);
121
+ let left = makeTernaryTreeMap(leftSize, offset, xs);
122
+ let middle = makeTernaryTreeMap(middleSize, offset + leftSize, xs);
123
+ let right = makeTernaryTreeMap(rightSize, offset + leftSize + middleSize, xs);
108
124
  let result = {
109
125
  kind: TernaryTreeKind.ternaryTreeBranch,
110
126
  maxHash: getMax(right),
@@ -112,7 +128,8 @@ function makeTernaryTreeMap(size, offset, xs) {
112
128
  left: left,
113
129
  middle: middle,
114
130
  right: right,
115
- depth: Math.max(getMapDepth(left), getMapDepth(middle), getMapDepth(right)) + 1,
131
+ depth: decideMapBranchDepth(left, middle, right),
132
+ size: mapLen(left) + mapLen(middle) + mapLen(right),
116
133
  };
117
134
  return result;
118
135
  }
@@ -202,7 +219,7 @@ export function mapLen(tree) {
202
219
  case TernaryTreeKind.ternaryTreeLeaf:
203
220
  return tree.elements.length;
204
221
  case TernaryTreeKind.ternaryTreeBranch:
205
- return mapLen(tree.left) + mapLen(tree.middle) + mapLen(tree.right); // TODO
222
+ return tree.size; // O(1) via cached size field
206
223
  default:
207
224
  throw new Error("Unknown");
208
225
  }
@@ -264,13 +281,13 @@ export function isMapEmpty(tree) {
264
281
  }
265
282
  export function isMapOfOne(tree, counted = 0) {
266
283
  if (tree == null) {
267
- return true;
284
+ return false;
268
285
  }
269
286
  switch (tree.kind) {
270
287
  case TernaryTreeKind.ternaryTreeLeaf:
271
- return false;
288
+ return tree.elements.length === 1;
272
289
  case TernaryTreeKind.ternaryTreeBranch:
273
- return tree.left == null && tree.middle == null && tree.right == null;
290
+ return mapLenBound(tree, 2) === 1;
274
291
  default:
275
292
  throw new Error("Unknown");
276
293
  }
@@ -534,7 +551,7 @@ export function checkMapStructure(tree) {
534
551
  throw new Error(`Bad hash at leaf node ${tree}`);
535
552
  }
536
553
  }
537
- if (mapLenBound(tree, 2) !== 1) {
554
+ if (mapLenBound(tree, 2) < 1) {
538
555
  throw new Error(`Bad len at leaf node ${tree}`);
539
556
  }
540
557
  }
@@ -629,46 +646,49 @@ function assocExisted(tree, key, item, thisHash = null) {
629
646
  throw new Error("Unexpected missing hash in assoc, found not branch");
630
647
  }
631
648
  if (rangeContainsHash(tree.left, thisHash)) {
632
- let result = {
649
+ let newLeft = assocExisted(tree.left, key, item, thisHash);
650
+ return {
633
651
  kind: TernaryTreeKind.ternaryTreeBranch,
634
652
  maxHash: tree.maxHash,
635
653
  minHash: tree.minHash,
636
- left: assocExisted(tree.left, key, item, thisHash),
654
+ left: newLeft,
637
655
  middle: tree.middle,
638
656
  right: tree.right,
639
- depth: 0, // TODO
657
+ depth: decideMapBranchDepth(newLeft, tree.middle, tree.right),
658
+ size: tree.size,
640
659
  };
641
- return result;
642
660
  }
643
661
  if (tree.middle == null) {
644
662
  throw new Error("Unexpected missing hash in assoc, found not branch");
645
663
  }
646
664
  if (rangeContainsHash(tree.middle, thisHash)) {
647
- let result = {
665
+ let newMiddle = assocExisted(tree.middle, key, item, thisHash);
666
+ return {
648
667
  kind: TernaryTreeKind.ternaryTreeBranch,
649
668
  maxHash: tree.maxHash,
650
669
  minHash: tree.minHash,
651
670
  left: tree.left,
652
- middle: assocExisted(tree.middle, key, item, thisHash),
671
+ middle: newMiddle,
653
672
  right: tree.right,
654
- depth: 0, // TODO
673
+ depth: decideMapBranchDepth(tree.left, newMiddle, tree.right),
674
+ size: tree.size,
655
675
  };
656
- return result;
657
676
  }
658
677
  if (tree.right == null) {
659
678
  throw new Error("Unexpected missing hash in assoc, found not branch");
660
679
  }
661
680
  if (rangeContainsHash(tree.right, thisHash)) {
662
- let result = {
681
+ let newRight = assocExisted(tree.right, key, item, thisHash);
682
+ return {
663
683
  kind: TernaryTreeKind.ternaryTreeBranch,
664
684
  maxHash: tree.maxHash,
665
685
  minHash: tree.minHash,
666
686
  left: tree.left,
667
687
  middle: tree.middle,
668
- right: assocExisted(tree.right, key, item, thisHash),
669
- depth: 0, // TODO
688
+ right: newRight,
689
+ depth: decideMapBranchDepth(tree.left, tree.middle, newRight),
690
+ size: tree.size,
670
691
  };
671
- return result;
672
692
  }
673
693
  throw new Error("Unexpected missing hash in assoc, found not branch");
674
694
  }
@@ -692,7 +712,8 @@ function assocNew(tree, key, item, thisHash = null) {
692
712
  left: tree,
693
713
  middle: childBranch,
694
714
  right: emptyBranch,
695
- depth: 0, // TODO
715
+ depth: decideMapBranchDepth(tree, childBranch, emptyBranch),
716
+ size: mapLen(tree) + 1,
696
717
  };
697
718
  return result;
698
719
  }
@@ -709,7 +730,8 @@ function assocNew(tree, key, item, thisHash = null) {
709
730
  left: childBranch,
710
731
  middle: tree,
711
732
  right: emptyBranch,
712
- depth: 0, // TODO
733
+ depth: decideMapBranchDepth(childBranch, tree, emptyBranch),
734
+ size: mapLen(tree) + 1,
713
735
  };
714
736
  return result;
715
737
  }
@@ -750,7 +772,8 @@ function assocNew(tree, key, item, thisHash = null) {
750
772
  left: childBranch,
751
773
  middle: tree.left,
752
774
  right: tree.middle,
753
- depth: 0, // TODO
775
+ depth: decideMapBranchDepth(childBranch, tree.left, tree.middle),
776
+ size: tree.size + 1,
754
777
  };
755
778
  return result;
756
779
  }
@@ -767,7 +790,8 @@ function assocNew(tree, key, item, thisHash = null) {
767
790
  left: childBranch,
768
791
  middle: tree,
769
792
  right: emptyBranch,
770
- depth: 0, // TODO
793
+ depth: decideMapBranchDepth(childBranch, tree, emptyBranch),
794
+ size: tree.size + 1,
771
795
  };
772
796
  return result;
773
797
  }
@@ -787,7 +811,8 @@ function assocNew(tree, key, item, thisHash = null) {
787
811
  left: tree.left,
788
812
  middle: childBranch,
789
813
  right: emptyBranch,
790
- depth: 0, // TODO
814
+ depth: decideMapBranchDepth(tree.left, childBranch, emptyBranch),
815
+ size: tree.size + 1,
791
816
  };
792
817
  return result;
793
818
  }
@@ -804,7 +829,8 @@ function assocNew(tree, key, item, thisHash = null) {
804
829
  left: tree.left,
805
830
  middle: tree.middle,
806
831
  right: childBranch,
807
- depth: 0, // TODO
832
+ depth: decideMapBranchDepth(tree.left, tree.middle, childBranch),
833
+ size: tree.size + 1,
808
834
  };
809
835
  return result;
810
836
  }
@@ -821,73 +847,79 @@ function assocNew(tree, key, item, thisHash = null) {
821
847
  left: tree,
822
848
  middle: childBranch,
823
849
  right: emptyBranch,
824
- depth: 0, // TODO
850
+ depth: decideMapBranchDepth(tree, childBranch, emptyBranch),
851
+ size: tree.size + 1,
825
852
  };
826
853
  return result;
827
854
  }
828
855
  }
829
856
  if (rangeContainsHash(tree.left, thisHash)) {
830
- let result = {
857
+ let newLeft = assocNew(tree.left, key, item, thisHash);
858
+ return {
831
859
  kind: TernaryTreeKind.ternaryTreeBranch,
832
860
  maxHash: tree.maxHash,
833
861
  minHash: tree.minHash,
834
- left: assocNew(tree.left, key, item, thisHash),
862
+ left: newLeft,
835
863
  middle: tree.middle,
836
864
  right: tree.right,
837
- depth: 0, // TODO
865
+ depth: decideMapBranchDepth(newLeft, tree.middle, tree.right),
866
+ size: tree.size + 1,
838
867
  };
839
- return result;
840
868
  }
841
869
  if (rangeContainsHash(tree.middle, thisHash)) {
842
- let result = {
870
+ let newMiddle = assocNew(tree.middle, key, item, thisHash);
871
+ return {
843
872
  kind: TernaryTreeKind.ternaryTreeBranch,
844
873
  maxHash: tree.maxHash,
845
874
  minHash: tree.minHash,
846
875
  left: tree.left,
847
- middle: assocNew(tree.middle, key, item, thisHash),
876
+ middle: newMiddle,
848
877
  right: tree.right,
849
- depth: 0, // TODO
878
+ depth: decideMapBranchDepth(tree.left, newMiddle, tree.right),
879
+ size: tree.size + 1,
850
880
  };
851
- return result;
852
881
  }
853
882
  if (rangeContainsHash(tree.right, thisHash)) {
854
- let result = {
883
+ let newRight = assocNew(tree.right, key, item, thisHash);
884
+ return {
855
885
  kind: TernaryTreeKind.ternaryTreeBranch,
856
886
  maxHash: tree.maxHash,
857
887
  minHash: tree.minHash,
858
888
  left: tree.left,
859
889
  middle: tree.middle,
860
- right: assocNew(tree.right, key, item, thisHash),
861
- depth: 0, // TODO
890
+ right: newRight,
891
+ depth: decideMapBranchDepth(tree.left, tree.middle, newRight),
892
+ size: tree.size + 1,
862
893
  };
863
- return result;
864
894
  }
865
895
  if (tree.middle == null) {
866
896
  throw new Error("unreachable. if inside range, then middle should be here");
867
897
  }
868
898
  if (thisHash < getMin(tree.middle)) {
869
- let result = {
899
+ let newLeft = assocNew(tree.left, key, item, thisHash);
900
+ return {
870
901
  kind: TernaryTreeKind.ternaryTreeBranch,
871
902
  maxHash: tree.maxHash,
872
903
  minHash: tree.minHash,
873
- left: assocNew(tree.left, key, item, thisHash),
904
+ left: newLeft,
874
905
  middle: tree.middle,
875
906
  right: tree.right,
876
- depth: 0, // TODO
907
+ depth: decideMapBranchDepth(newLeft, tree.middle, tree.right),
908
+ size: tree.size + 1,
877
909
  };
878
- return result;
879
910
  }
880
911
  else {
881
- let result = {
912
+ let newRight = assocNew(tree.right, key, item, thisHash);
913
+ return {
882
914
  kind: TernaryTreeKind.ternaryTreeBranch,
883
915
  maxHash: tree.maxHash,
884
916
  minHash: tree.minHash,
885
917
  left: tree.left,
886
918
  middle: tree.middle,
887
- right: assocNew(tree.right, key, item, thisHash),
888
- depth: 0, // TODO
919
+ right: newRight,
920
+ depth: decideMapBranchDepth(tree.left, tree.middle, newRight),
921
+ size: tree.size + 1,
889
922
  };
890
- return result;
891
923
  }
892
924
  }
893
925
  }
@@ -948,7 +980,8 @@ function dissocExisted(tree, key) {
948
980
  left: tree.middle,
949
981
  middle: tree.right,
950
982
  right: emptyBranch,
951
- depth: 0, // TODO
983
+ depth: decideMapBranchDepth(tree.middle, tree.right, emptyBranch),
984
+ size: tree.size - 1,
952
985
  };
953
986
  return result;
954
987
  }
@@ -960,7 +993,8 @@ function dissocExisted(tree, key) {
960
993
  left: changedBranch,
961
994
  middle: tree.middle,
962
995
  right: tree.right,
963
- depth: 0, // TODO
996
+ depth: decideMapBranchDepth(changedBranch, tree.middle, tree.right),
997
+ size: tree.size - 1,
964
998
  };
965
999
  return result;
966
1000
  }
@@ -978,7 +1012,8 @@ function dissocExisted(tree, key) {
978
1012
  left: tree.left,
979
1013
  middle: tree.right,
980
1014
  right: emptyBranch,
981
- depth: 0, // TODO
1015
+ depth: decideMapBranchDepth(tree.left, tree.right, emptyBranch),
1016
+ size: tree.size - 1,
982
1017
  };
983
1018
  return result;
984
1019
  }
@@ -990,7 +1025,8 @@ function dissocExisted(tree, key) {
990
1025
  left: tree.left,
991
1026
  middle: changedBranch,
992
1027
  right: tree.right,
993
- depth: 0, // TODO
1028
+ depth: decideMapBranchDepth(tree.left, changedBranch, tree.right),
1029
+ size: tree.size - 1,
994
1030
  };
995
1031
  return result;
996
1032
  }
@@ -1005,7 +1041,8 @@ function dissocExisted(tree, key) {
1005
1041
  left: tree.left,
1006
1042
  middle: tree.middle,
1007
1043
  right: emptyBranch,
1008
- depth: 0, // TODO
1044
+ depth: decideMapBranchDepth(tree.left, tree.middle, emptyBranch),
1045
+ size: tree.size - 1,
1009
1046
  };
1010
1047
  return result;
1011
1048
  }
@@ -1017,7 +1054,8 @@ function dissocExisted(tree, key) {
1017
1054
  left: tree.left,
1018
1055
  middle: tree.middle,
1019
1056
  right: changedBranch,
1020
- depth: 0, // TODO
1057
+ depth: decideMapBranchDepth(tree.left, tree.middle, changedBranch),
1058
+ size: tree.size - 1,
1021
1059
  };
1022
1060
  return result;
1023
1061
  }
@@ -1298,6 +1336,7 @@ export function mapMapValues(tree, f) {
1298
1336
  left: mapMapValues(tree.left, f),
1299
1337
  middle: mapMapValues(tree.middle, f),
1300
1338
  right: mapMapValues(tree.right, f),
1339
+ size: tree.size,
1301
1340
  };
1302
1341
  return result;
1303
1342
  }
package/lib/types.d.mts CHANGED
@@ -22,6 +22,7 @@ export type TernaryTreeMapHashEntry<K, V> = {
22
22
  };
23
23
  export type TernaryTreeMapTheBranch<K, T> = {
24
24
  kind: TernaryTreeKind.ternaryTreeBranch;
25
+ size: number;
25
26
  depth: number;
26
27
  maxHash: number;
27
28
  minHash: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@calcit/ternary-tree",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "main": "./lib/index.mjs",
5
5
  "scripts": {
6
6
  "build": "tsc",
@@ -9,11 +9,12 @@
9
9
  "test:list": "TARGET=list yarn test",
10
10
  "test:map": "TARGET=map yarn test",
11
11
  "test:list-perf": "yarn build && node lib/test-list-perf.mjs",
12
- "test:list-detailed-perf": "yarn build && node lib/test-list-detailed-perf.mjs"
12
+ "test:list-detailed-perf": "yarn build && node lib/test-list-detailed-perf.mjs",
13
+ "bench": "tsc && node lib/bench.mjs"
13
14
  },
14
15
  "devDependencies": {
15
- "@types/node": "^20.12.5",
16
- "prettier": "^3.2.5",
17
- "typescript": "^5.4.4"
16
+ "@types/node": "*",
17
+ "prettier": "*",
18
+ "typescript": "*"
18
19
  }
19
20
  }