data-structure-typed 2.2.8 → 2.3.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.
- package/.github/workflows/ci.yml +9 -0
- package/CHANGELOG.md +1 -1
- package/README.md +1 -1
- package/README_CN.md +1 -1
- package/dist/cjs/index.cjs +584 -76
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs-legacy/index.cjs +588 -79
- package/dist/cjs-legacy/index.cjs.map +1 -1
- package/dist/esm/index.mjs +584 -76
- package/dist/esm/index.mjs.map +1 -1
- package/dist/esm-legacy/index.mjs +588 -79
- package/dist/esm-legacy/index.mjs.map +1 -1
- package/dist/types/data-structures/base/linear-base.d.ts +6 -6
- package/dist/types/data-structures/binary-tree/binary-tree.d.ts +3 -4
- package/dist/types/data-structures/binary-tree/bst.d.ts +2 -1
- package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +150 -20
- package/dist/types/data-structures/binary-tree/tree-counter.d.ts +3 -3
- package/dist/types/interfaces/binary-tree.d.ts +1 -1
- package/dist/umd/data-structure-typed.js +588 -79
- package/dist/umd/data-structure-typed.js.map +1 -1
- package/dist/umd/data-structure-typed.min.js +3 -3
- package/dist/umd/data-structure-typed.min.js.map +1 -1
- package/package.json +4 -3
- package/src/data-structures/base/linear-base.ts +2 -12
- package/src/data-structures/binary-tree/binary-tree.ts +5 -6
- package/src/data-structures/binary-tree/bst.ts +79 -4
- package/src/data-structures/binary-tree/red-black-tree.ts +583 -73
- package/src/data-structures/binary-tree/tree-counter.ts +21 -9
- package/src/data-structures/queue/deque.ts +10 -0
- package/src/interfaces/binary-tree.ts +1 -1
- package/test/unit/data-structures/base/iterable-element-base.coverage.test.ts +106 -0
- package/test/unit/data-structures/base/iterable-element-base.more-branches.coverage.test.ts +61 -0
- package/test/unit/data-structures/base/linear-base.array.coverage.test.ts +168 -0
- package/test/unit/data-structures/base/linear-base.concat-else.coverage.test.ts +82 -0
- package/test/unit/data-structures/base/linear-base.coverage.test.ts +72 -0
- package/test/unit/data-structures/base/linear-base.more-branches.coverage.test.ts +417 -0
- package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches-3.coverage.test.ts +146 -0
- package/test/unit/data-structures/binary-tree/avl-tree-counter.more-branches.coverage.test.ts +93 -0
- package/test/unit/data-structures/binary-tree/avl-tree-multi-map.coverage.test.ts +108 -0
- package/test/unit/data-structures/binary-tree/avl-tree-multi-map.more-branches-2.coverage.test.ts +85 -0
- package/test/unit/data-structures/binary-tree/avl-tree-node.familyPosition-root-left.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/avl-tree.more-branches-2.coverage.test.ts +99 -0
- package/test/unit/data-structures/binary-tree/binary-indexed-tree.more-branches.coverage.test.ts +18 -0
- package/test/unit/data-structures/binary-tree/binary-tree.more-branches.coverage.test.ts +56 -0
- package/test/unit/data-structures/binary-tree/binary-tree.remaining-branches.coverage.test.ts +229 -0
- package/test/unit/data-structures/binary-tree/bst.bound-by-predicate.coverage.test.ts +33 -0
- package/test/unit/data-structures/binary-tree/bst.coverage.test.ts +94 -0
- package/test/unit/data-structures/binary-tree/bst.deletebykey.coverage.test.ts +70 -0
- package/test/unit/data-structures/binary-tree/bst.deletewhere.coverage.test.ts +37 -0
- package/test/unit/data-structures/binary-tree/bst.floor-lower-predicate.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/bst.floor-setmany.coverage.test.ts +72 -0
- package/test/unit/data-structures/binary-tree/bst.getnode.range-ensure.coverage.test.ts +22 -0
- package/test/unit/data-structures/binary-tree/bst.misc-branches.coverage.test.ts +100 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-2.coverage.test.ts +133 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-3.coverage.test.ts +45 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-4.coverage.test.ts +36 -0
- package/test/unit/data-structures/binary-tree/bst.more-branches-5.coverage.test.ts +40 -0
- package/test/unit/data-structures/binary-tree/bst.more.coverage.test.ts +39 -0
- package/test/unit/data-structures/binary-tree/bst.node-family.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/bst.range-pruning.coverage.test.ts +43 -0
- package/test/unit/data-structures/binary-tree/bst.search-fastpath.coverage.test.ts +30 -0
- package/test/unit/data-structures/binary-tree/bst.test.ts +25 -55
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-corruption-repair.coverage.test.ts +66 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-max-update.coverage.test.ts +18 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-null.coverage.test.ts +53 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-stale-cache.coverage.test.ts +25 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.boundary-update.coverage.test.ts +23 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-delete.coverage.test.ts +49 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-edge.coverage.test.ts +37 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.cache-stale-insert.coverage.test.ts +39 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.coverage.test.ts +334 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.delete-fixup.coverage.test.ts +68 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.delete-successor.coverage.test.ts +75 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.factories.coverage.test.ts +26 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-compare-update.coverage.test.ts +74 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-no-update.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-cache-nullish.coverage.test.ts +61 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-defined.coverage.test.ts +35 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-mapmode-undefined.coverage.test.ts +43 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint-more.coverage.test.ts +99 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.hint.coverage.test.ts +60 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.insert-cache-nullish.coverage.test.ts +29 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.insert-header-parent-nullish.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.internal-walk.coverage.test.ts +57 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.minmax-cache.test.ts +65 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.misc-inputs.coverage.test.ts +17 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-2.coverage.test.ts +121 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-3.coverage.test.ts +55 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.more-branches-4.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.predsucc.coverage.test.ts +40 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.remaining-branches.coverage.test.ts +123 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.set-inputs.coverage.test.ts +64 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-parent-cache.coverage.test.ts +79 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-remaining.coverage.test.ts +44 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.setkvnode-uncovered.coverage.test.ts +74 -0
- package/test/unit/data-structures/binary-tree/red-black-tree.update-branches.coverage.test.ts +30 -0
- package/test/unit/data-structures/binary-tree/segment-tree.more-branches.coverage.test.ts +31 -0
- package/test/unit/data-structures/binary-tree/tree-counter.coverage.test.ts +115 -0
- package/test/unit/data-structures/binary-tree/tree-counter.more-branches.coverage.test.ts +244 -0
- package/test/unit/data-structures/binary-tree/tree-counter.test.ts +4 -2
- package/test/unit/data-structures/binary-tree/tree-multi-map.coverage.test.ts +104 -0
- package/test/unit/data-structures/binary-tree/tree-multi-map.more-branches-2.coverage.test.ts +59 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-2.coverage.test.ts +40 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-3.coverage.test.ts +65 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-4.coverage.test.ts +98 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches-5.coverage.test.ts +51 -0
- package/test/unit/data-structures/graph/abstract-graph.more-branches.coverage.test.ts +62 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches-2.coverage.test.ts +38 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches-3.coverage.test.ts +25 -0
- package/test/unit/data-structures/graph/directed-graph.more-branches.coverage.test.ts +82 -0
- package/test/unit/data-structures/graph/map-graph.more-branches.coverage.test.ts +22 -0
- package/test/unit/data-structures/graph/undirected-graph.more-branches-2.coverage.test.ts +35 -0
- package/test/unit/data-structures/graph/undirected-graph.more-branches.coverage.test.ts +87 -0
- package/test/unit/data-structures/hash/hash-map.more-branches.coverage.test.ts +64 -0
- package/test/unit/data-structures/hash/hash-map.toEntryFn-branch.coverage.test.ts +9 -0
- package/test/unit/data-structures/heap/heap.misc-branches.coverage.test.ts +110 -0
- package/test/unit/data-structures/heap/heap.remaining-branches.coverage.test.ts +22 -0
- package/test/unit/data-structures/heap/max-heap.coverage.test.ts +29 -0
- package/test/unit/data-structures/linked-list/doubly-linked-list.more-branches.coverage.test.ts +72 -0
- package/test/unit/data-structures/linked-list/linked-list.unshiftMany-else.coverage.test.ts +15 -0
- package/test/unit/data-structures/linked-list/singly-linked-list.coverage.test.ts +221 -0
- package/test/unit/data-structures/linked-list/singly-linked-list.more-branches.coverage.test.ts +86 -0
- package/test/unit/data-structures/linked-list/skip-linked-list.more-branches.coverage.test.ts +31 -0
- package/test/unit/data-structures/matrix/matrix.more-branches.coverage.test.ts +81 -0
- package/test/unit/data-structures/matrix/matrix.pivotElement-nullish.coverage.test.ts +28 -0
- package/test/unit/data-structures/priority-queue/max-priority-queue.more-branches.coverage.test.ts +10 -0
- package/test/unit/data-structures/priority-queue/priority-queue.coverage.test.ts +21 -0
- package/test/unit/data-structures/queue/deque.coverage.test.ts +173 -0
- package/test/unit/data-structures/queue/deque.more-branches-2.coverage.test.ts +39 -0
- package/test/unit/data-structures/queue/deque.more-branches-3.coverage.test.ts +9 -0
- package/test/unit/data-structures/queue/deque.more-branches.coverage.test.ts +95 -0
- package/test/unit/data-structures/queue/queue.coverage.test.ts +138 -0
- package/test/unit/data-structures/queue/queue.more-branches-2.coverage.test.ts +27 -0
- package/test/unit/data-structures/stack/stack.coverage.test.ts +112 -0
- package/test/unit/data-structures/tree/tree.more-branches.coverage.test.ts +9 -0
- package/test/unit/data-structures/trie/trie.more-branches-2.coverage.test.ts +51 -0
- package/test/utils/patch.ts +33 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BST } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('BST additional reachable branch coverage (batch 5)', () => {
|
|
4
|
+
it('search(): forces shouldVisitRight key-pruning branch + covers && chain short-circuit variants', () => {
|
|
5
|
+
const t = new BST<number, number>();
|
|
6
|
+
t.setMany([
|
|
7
|
+
[10, 10],
|
|
8
|
+
[20, 20]
|
|
9
|
+
]);
|
|
10
|
+
|
|
11
|
+
const originalIsPredicate = (t as any)._isPredicate.bind(t);
|
|
12
|
+
|
|
13
|
+
const run = (keyNodeOrEntry: any) => {
|
|
14
|
+
let calls = 0;
|
|
15
|
+
(t as any)._isPredicate = (p: any) => {
|
|
16
|
+
// 1st call (isPred computation) => true, to bypass the fast-path.
|
|
17
|
+
// After that, behave like the real implementation so _ensurePredicate works
|
|
18
|
+
// and shouldVisitRight sees "not predicate".
|
|
19
|
+
calls++;
|
|
20
|
+
if (calls === 1) return true;
|
|
21
|
+
return originalIsPredicate(p);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
t.search(keyNodeOrEntry);
|
|
26
|
+
} finally {
|
|
27
|
+
(t as any)._isPredicate = originalIsPredicate;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// 1) benchmarkKey === null => hits first short-circuit (benchmarkKey !== null)
|
|
32
|
+
run([null, 0]);
|
|
33
|
+
|
|
34
|
+
// 2) benchmarkKey === undefined => passes first check, fails second
|
|
35
|
+
run([undefined, 0]);
|
|
36
|
+
|
|
37
|
+
// 3) compare(...) < 0 is false => passes both nullish checks
|
|
38
|
+
run([5, 0]);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { BST, BSTNode, Range } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('BST additional coverage', () => {
|
|
4
|
+
it('BSTNode.familyPosition covers MAL_NODE branch', () => {
|
|
5
|
+
const root = new BSTNode<number, number>(10);
|
|
6
|
+
const mal = new BSTNode<number, number>(5);
|
|
7
|
+
mal.parent = root;
|
|
8
|
+
// parent does not point to mal via left/right
|
|
9
|
+
expect(mal.familyPosition).toBe('MAL_NODE');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('getNode returns undefined for null/undefined, and handles runtime Range input', () => {
|
|
13
|
+
const bst = new BST<number, number>();
|
|
14
|
+
bst.set(2, 2);
|
|
15
|
+
|
|
16
|
+
expect(bst.getNode(undefined as any)).toBeUndefined();
|
|
17
|
+
expect(bst.getNode(null as any)).toBeUndefined();
|
|
18
|
+
|
|
19
|
+
const r = new Range<number>(2, 2, true, true);
|
|
20
|
+
expect(bst.getNode(r as any)?.key).toBe(2);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('getNodes returns [] for null/undefined and ignores entry with null key', () => {
|
|
24
|
+
const bst = new BST<number, number>([2, 1, 3]);
|
|
25
|
+
|
|
26
|
+
expect(bst.getNodes(undefined as any)).toEqual([]);
|
|
27
|
+
expect(bst.getNodes(null as any)).toEqual([]);
|
|
28
|
+
|
|
29
|
+
// entry with null key => targetKey undefined => []
|
|
30
|
+
expect(bst.getNodes([null as any, 123] as any)).toEqual([]);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('getNodes returns [] when startNode is not a real node', () => {
|
|
34
|
+
const bst = new BST<number, number>([2, 1, 3]);
|
|
35
|
+
|
|
36
|
+
// ensureNode(null) => undefined => []
|
|
37
|
+
expect(bst.getNodes(2, false, null as any)).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { BSTNode } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('BSTNode familyPosition coverage', () => {
|
|
4
|
+
it('covers ISOLATED/ROOT/LEFT/RIGHT/ROOT_LEFT/ROOT_RIGHT and MAL_NODE', () => {
|
|
5
|
+
const isolated = new BSTNode<number, number>(1);
|
|
6
|
+
expect(isolated.familyPosition).toBe('ISOLATED');
|
|
7
|
+
|
|
8
|
+
const root = new BSTNode<number, number>(10);
|
|
9
|
+
const left = new BSTNode<number, number>(5);
|
|
10
|
+
const right = new BSTNode<number, number>(15);
|
|
11
|
+
|
|
12
|
+
root.left = left;
|
|
13
|
+
root.right = right;
|
|
14
|
+
|
|
15
|
+
expect(root.familyPosition).toBe('ROOT');
|
|
16
|
+
expect(left.familyPosition).toBe('LEFT');
|
|
17
|
+
expect(right.familyPosition).toBe('RIGHT');
|
|
18
|
+
|
|
19
|
+
left.left = new BSTNode<number, number>(2);
|
|
20
|
+
right.right = new BSTNode<number, number>(20);
|
|
21
|
+
|
|
22
|
+
expect(left.familyPosition).toBe('ROOT_LEFT');
|
|
23
|
+
expect(right.familyPosition).toBe('ROOT_RIGHT');
|
|
24
|
+
|
|
25
|
+
const mal = new BSTNode<number, number>(999);
|
|
26
|
+
mal.parent = root;
|
|
27
|
+
expect(mal.familyPosition).toBe('MAL_NODE');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { BST, Range } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('BST range/pruning coverage', () => {
|
|
4
|
+
it('getNodes(range) covers includeLow/includeHigh pruning branches', () => {
|
|
5
|
+
const bst = new BST<number, number>();
|
|
6
|
+
for (const k of [10, 5, 15, 3, 7, 12, 18]) bst.set(k, k);
|
|
7
|
+
|
|
8
|
+
// includeLow/includeHigh true
|
|
9
|
+
const r1 = new Range<number>(7, 12, true, true);
|
|
10
|
+
expect(bst.getNodes(r1 as any).map(n => n.key)).toEqual([7, 10, 12]);
|
|
11
|
+
|
|
12
|
+
// includeLow false (exclude 7)
|
|
13
|
+
const r2 = new Range<number>(7, 12, false, true);
|
|
14
|
+
expect(bst.getNodes(r2 as any).map(n => n.key)).toEqual([10, 12]);
|
|
15
|
+
|
|
16
|
+
// includeHigh false (exclude 12)
|
|
17
|
+
const r3 = new Range<number>(7, 12, true, false);
|
|
18
|
+
expect(bst.getNodes(r3 as any).map(n => n.key)).toEqual([7, 10]);
|
|
19
|
+
|
|
20
|
+
// exclude both ends
|
|
21
|
+
const r4 = new Range<number>(7, 12, false, false);
|
|
22
|
+
expect(bst.getNodes(r4 as any).map(n => n.key)).toEqual([10]);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('getNodes(predicate) exercises predicate-search shouldVisitLeft/Right fallthrough', () => {
|
|
26
|
+
const bst = new BST<number, number>();
|
|
27
|
+
for (const k of [4, 2, 6, 1, 3, 5, 7]) bst.set(k, k);
|
|
28
|
+
|
|
29
|
+
// Predicate that matches only leaf nodes, to force traversal and filtering.
|
|
30
|
+
const nodes = bst.getNodes(node => !bst.isRealNode(node.left) && !bst.isRealNode(node.right));
|
|
31
|
+
expect(nodes.map(n => n.key).sort((a, b) => a - b)).toEqual([1, 3, 5, 7]);
|
|
32
|
+
|
|
33
|
+
// onlyOne=true should early-exit after first match (in-order => 1)
|
|
34
|
+
const one = bst.getNodes(node => !bst.isRealNode(node.left) && !bst.isRealNode(node.right), true);
|
|
35
|
+
expect(one).toHaveLength(1);
|
|
36
|
+
expect(one[0].key).toBe(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('rangeSearch overload [low, high] is accepted', () => {
|
|
40
|
+
const bst = new BST<number, number>([10, 5, 15, 3, 7, 12, 18]);
|
|
41
|
+
expect(bst.rangeSearch([7, 12])).toEqual([7, 10, 12]);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { BST } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('BST search() fast-path coverage', () => {
|
|
4
|
+
it('search(node) uses node.key extraction branch and returns callback result', () => {
|
|
5
|
+
const t = new BST<number, string>([], { isMapMode: false });
|
|
6
|
+
t.set(10, 'a');
|
|
7
|
+
t.set(5, 'b');
|
|
8
|
+
t.set(15, 'c');
|
|
9
|
+
|
|
10
|
+
const n5 = t.getNode(5)!;
|
|
11
|
+
const out = t.search(n5, false, (n: any) => n.value);
|
|
12
|
+
expect(out).toEqual(['b']);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('search(entry) with null key returns [] (covers entry null-key guard in fast path)', () => {
|
|
16
|
+
const t = new BST<number, number>([], { isMapMode: false });
|
|
17
|
+
for (const k of [10, 5, 15]) t.set(k, k);
|
|
18
|
+
|
|
19
|
+
const out = t.search([null as any, 1] as any);
|
|
20
|
+
expect(out).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('search by key returns [] when startNode ensureNode fails', () => {
|
|
24
|
+
const t = new BST<number, number>([], { isMapMode: false });
|
|
25
|
+
for (const k of [10, 5, 15]) t.set(k, k);
|
|
26
|
+
|
|
27
|
+
const out = t.search(10 as any, false, (n: any) => n.key, null as any);
|
|
28
|
+
expect(out).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { BST, BSTNode, IBinaryTree, Range } from '../../../../src';
|
|
2
2
|
import { isDebugTest, isTestStackOverflow, SYSTEM_MAX_CALL_STACK } from '../../../config';
|
|
3
|
+
import { withMutedConsole } from '../../../utils/patch';
|
|
3
4
|
|
|
4
5
|
const isDebug = isDebugTest;
|
|
5
6
|
|
|
7
|
+
function expectSingleDeleteResult<K, V>(removed: any, expectedKey: K) {
|
|
8
|
+
// Most tree delete APIs here return an array of { deleted, ... }.
|
|
9
|
+
expect(removed).toBeInstanceOf(Array);
|
|
10
|
+
expect(removed[0]).toBeDefined();
|
|
11
|
+
expect(removed[0].deleted).toBeDefined();
|
|
12
|
+
if (removed[0].deleted) expect(removed[0].deleted.key).toBe(expectedKey);
|
|
13
|
+
}
|
|
14
|
+
|
|
6
15
|
describe('BST operations test', () => {
|
|
7
16
|
it('should add undefined and null', () => {
|
|
8
17
|
const bst = new BST<number, string>();
|
|
@@ -115,114 +124,74 @@ describe('BST operations test', () => {
|
|
|
115
124
|
expect(bfsNodesAfterBalanced[bfsNodesAfterBalanced.length - 1].key).toBe(16);
|
|
116
125
|
|
|
117
126
|
const removed11 = bst.delete(11);
|
|
118
|
-
|
|
119
|
-
expect(removed11[0]).toBeDefined();
|
|
120
|
-
expect(removed11[0].deleted).toBeDefined();
|
|
121
|
-
|
|
122
|
-
if (removed11[0].deleted) expect(removed11[0].deleted.key).toBe(11);
|
|
127
|
+
expectSingleDeleteResult(removed11, 11);
|
|
123
128
|
|
|
124
129
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
125
130
|
|
|
126
131
|
expect(bst.getHeight(15)).toBe(1);
|
|
127
132
|
|
|
128
133
|
const removed1 = bst.delete(1);
|
|
129
|
-
|
|
130
|
-
expect(removed1[0]).toBeDefined();
|
|
131
|
-
expect(removed1[0].deleted).toBeDefined();
|
|
132
|
-
if (removed1[0].deleted) expect(removed1[0].deleted.key).toBe(1);
|
|
134
|
+
expectSingleDeleteResult(removed1, 1);
|
|
133
135
|
|
|
134
136
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
135
137
|
|
|
136
138
|
expect(bst.getHeight()).toBe(4);
|
|
137
139
|
|
|
138
140
|
const removed4 = bst.delete(4);
|
|
139
|
-
|
|
140
|
-
expect(removed4[0]).toBeDefined();
|
|
141
|
-
expect(removed4[0].deleted).toBeDefined();
|
|
142
|
-
if (removed4[0].deleted) expect(removed4[0].deleted.key).toBe(4);
|
|
141
|
+
expectSingleDeleteResult(removed4, 4);
|
|
143
142
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
144
143
|
expect(bst.getHeight()).toBe(4);
|
|
145
144
|
|
|
146
145
|
const removed10 = bst.delete(10);
|
|
147
|
-
|
|
148
|
-
expect(removed10[0]).toBeDefined();
|
|
149
|
-
expect(removed10[0].deleted).toBeDefined();
|
|
150
|
-
if (removed10[0].deleted) expect(removed10[0].deleted.key).toBe(10);
|
|
146
|
+
expectSingleDeleteResult(removed10, 10);
|
|
151
147
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
152
148
|
expect(bst.getHeight()).toBe(4);
|
|
153
149
|
|
|
154
150
|
const removed15 = bst.delete(15);
|
|
155
|
-
|
|
156
|
-
expect(removed15[0]).toBeDefined();
|
|
157
|
-
expect(removed15[0].deleted).toBeDefined();
|
|
158
|
-
if (removed15[0].deleted) expect(removed15[0].deleted.key).toBe(15);
|
|
151
|
+
expectSingleDeleteResult(removed15, 15);
|
|
159
152
|
|
|
160
153
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
161
154
|
expect(bst.getHeight()).toBe(3);
|
|
162
155
|
|
|
163
156
|
const removed5 = bst.delete(5);
|
|
164
|
-
|
|
165
|
-
expect(removed5[0]).toBeDefined();
|
|
166
|
-
expect(removed5[0].deleted).toBeDefined();
|
|
167
|
-
if (removed5[0].deleted) expect(removed5[0].deleted.key).toBe(5);
|
|
157
|
+
expectSingleDeleteResult(removed5, 5);
|
|
168
158
|
|
|
169
159
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
170
160
|
expect(bst.getHeight()).toBe(3);
|
|
171
161
|
|
|
172
162
|
const removed13 = bst.delete(13);
|
|
173
|
-
|
|
174
|
-
expect(removed13[0]).toBeDefined();
|
|
175
|
-
expect(removed13[0].deleted).toBeDefined();
|
|
176
|
-
if (removed13[0].deleted) expect(removed13[0].deleted.key).toBe(13);
|
|
163
|
+
expectSingleDeleteResult(removed13, 13);
|
|
177
164
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
178
165
|
expect(bst.getHeight()).toBe(3);
|
|
179
166
|
|
|
180
167
|
const removed3 = bst.delete(3);
|
|
181
|
-
|
|
182
|
-
expect(removed3[0]).toBeDefined();
|
|
183
|
-
expect(removed3[0].deleted).toBeDefined();
|
|
184
|
-
if (removed3[0].deleted) expect(removed3[0].deleted.key).toBe(3);
|
|
168
|
+
expectSingleDeleteResult(removed3, 3);
|
|
185
169
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
186
170
|
expect(bst.getHeight()).toBe(3);
|
|
187
171
|
|
|
188
172
|
const removed8 = bst.delete(8);
|
|
189
|
-
|
|
190
|
-
expect(removed8[0]).toBeDefined();
|
|
191
|
-
expect(removed8[0].deleted).toBeDefined();
|
|
192
|
-
if (removed8[0].deleted) expect(removed8[0].deleted.key).toBe(8);
|
|
173
|
+
expectSingleDeleteResult(removed8, 8);
|
|
193
174
|
expect(bst.isAVLBalanced()).toBe(true);
|
|
194
175
|
expect(bst.getHeight()).toBe(3);
|
|
195
176
|
|
|
196
177
|
const removed6 = bst.delete(6);
|
|
197
|
-
|
|
198
|
-
expect(removed6[0]).toBeDefined();
|
|
199
|
-
expect(removed6[0].deleted).toBeDefined();
|
|
200
|
-
if (removed6[0].deleted) expect(removed6[0].deleted.key).toBe(6);
|
|
178
|
+
expectSingleDeleteResult(removed6, 6);
|
|
201
179
|
expect(bst.delete(6).length).toBe(0);
|
|
202
180
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
203
181
|
expect(bst.getHeight()).toBe(3);
|
|
204
182
|
|
|
205
183
|
const removed7 = bst.delete(7);
|
|
206
|
-
|
|
207
|
-
expect(removed7[0]).toBeDefined();
|
|
208
|
-
expect(removed7[0].deleted).toBeDefined();
|
|
209
|
-
if (removed7[0].deleted) expect(removed7[0].deleted.key).toBe(7);
|
|
184
|
+
expectSingleDeleteResult(removed7, 7);
|
|
210
185
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
211
186
|
expect(bst.getHeight()).toBe(3);
|
|
212
187
|
|
|
213
188
|
const removed9 = bst.delete(9);
|
|
214
|
-
|
|
215
|
-
expect(removed9[0]).toBeDefined();
|
|
216
|
-
expect(removed9[0].deleted).toBeDefined();
|
|
217
|
-
if (removed9[0].deleted) expect(removed9[0].deleted.key).toBe(9);
|
|
189
|
+
expectSingleDeleteResult(removed9, 9);
|
|
218
190
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
219
191
|
expect(bst.getHeight()).toBe(3);
|
|
220
192
|
|
|
221
193
|
const removed14 = bst.delete(14);
|
|
222
|
-
|
|
223
|
-
expect(removed14[0]).toBeDefined();
|
|
224
|
-
expect(removed14[0].deleted).toBeDefined();
|
|
225
|
-
if (removed14[0].deleted) expect(removed14[0].deleted.key).toBe(14);
|
|
194
|
+
expectSingleDeleteResult(removed14, 14);
|
|
226
195
|
expect(bst.isAVLBalanced()).toBe(false);
|
|
227
196
|
expect(bst.getHeight()).toBe(2);
|
|
228
197
|
|
|
@@ -3300,11 +3269,12 @@ describe('BST Comparator Tests', () => {
|
|
|
3300
3269
|
});
|
|
3301
3270
|
|
|
3302
3271
|
describe('classic use', () => {
|
|
3303
|
-
it('@example basic BST creation and add operation', () => {
|
|
3272
|
+
it('@example basic BST creation and add operation', async () => {
|
|
3304
3273
|
// Create a simple BST with numeric keys
|
|
3305
3274
|
const bst = new BST<number>([11, 3, 15, 1, 8, 13, 16, 2, 6, 9, 12, 14, 4, 7, 10, 5]);
|
|
3306
3275
|
|
|
3307
|
-
|
|
3276
|
+
// Keep the example output in source comments but avoid noisy test logs.
|
|
3277
|
+
await withMutedConsole(() => bst.print());
|
|
3308
3278
|
// _______8__________
|
|
3309
3279
|
// / \
|
|
3310
3280
|
// ___4___ ____12_____
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree boundary corruption repair coverage', () => {
|
|
4
|
+
it('min-attach: when header._right is corrupted to NIL, branch mirrors max cache to new min', () => {
|
|
5
|
+
let tree: RedBlackTree<number, number> = new RedBlackTree<number>();
|
|
6
|
+
let active = false;
|
|
7
|
+
let flipped = false;
|
|
8
|
+
const comparator = (a: number, b: number) => {
|
|
9
|
+
// Flip once during the boundary pre-check, before the attach happens.
|
|
10
|
+
if (active && !flipped && tree) {
|
|
11
|
+
flipped = true;
|
|
12
|
+
(tree as any)._header._right = tree.NIL;
|
|
13
|
+
}
|
|
14
|
+
return a - b;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
tree = new RedBlackTree<number, number>([], { isMapMode: false, comparator });
|
|
18
|
+
tree.set(10, 10);
|
|
19
|
+
tree.set(5, 5);
|
|
20
|
+
tree.set(15, 15);
|
|
21
|
+
|
|
22
|
+
// Boundary insert smaller than current min.
|
|
23
|
+
active = true;
|
|
24
|
+
tree.set(1, 1);
|
|
25
|
+
active = false;
|
|
26
|
+
|
|
27
|
+
// Repair branch should have mirrored max cache when header._right was NIL.
|
|
28
|
+
expect((tree as any)._header._right.key).toBe(1);
|
|
29
|
+
|
|
30
|
+
// Restore correct max cache so subsequent tests/users aren't affected.
|
|
31
|
+
const root = (tree as any)._root;
|
|
32
|
+
(tree as any)._setMaxCache(tree.isRealNode(root) ? tree.getRightMost((n: any) => n, root) : undefined);
|
|
33
|
+
expect((tree as any)._header._right.key).toBe(15);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('max-attach: when header._left is corrupted to NIL, branch initializes min cache during max attach', () => {
|
|
37
|
+
let tree: RedBlackTree<number, number> = new RedBlackTree<number>();;
|
|
38
|
+
let active = false;
|
|
39
|
+
let flipped = false;
|
|
40
|
+
const comparator = (a: number, b: number) => {
|
|
41
|
+
if (active && !flipped && tree) {
|
|
42
|
+
flipped = true;
|
|
43
|
+
(tree as any)._header._left = tree.NIL;
|
|
44
|
+
}
|
|
45
|
+
return a - b;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
tree = new RedBlackTree<number, number>([], { isMapMode: false, comparator });
|
|
49
|
+
tree.set(10, 10);
|
|
50
|
+
tree.set(5, 5);
|
|
51
|
+
tree.set(15, 15);
|
|
52
|
+
|
|
53
|
+
// Boundary insert larger than current max.
|
|
54
|
+
active = true;
|
|
55
|
+
tree.set(100, 100);
|
|
56
|
+
active = false;
|
|
57
|
+
|
|
58
|
+
// Observe the branch effect before repairing (min cache got initialized).
|
|
59
|
+
expect((tree as any)._header._left.key).toBe(100);
|
|
60
|
+
|
|
61
|
+
// Restore correct min cache.
|
|
62
|
+
const root = (tree as any)._root;
|
|
63
|
+
(tree as any)._setMinCache(tree.isRealNode(root) ? tree.getLeftMost((n: any) => n, root) : undefined);
|
|
64
|
+
expect((tree as any)._header._left.key).toBe(5);
|
|
65
|
+
});
|
|
66
|
+
});
|
package/test/unit/data-structures/binary-tree/red-black-tree.boundary-max-update.coverage.test.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree boundary max update coverage', () => {
|
|
4
|
+
it('mapMode: updating existing max key with defined value hits store.set branch in cMax===0 fast-path', () => {
|
|
5
|
+
const t = new RedBlackTree<number, string>(); // mapMode default
|
|
6
|
+
|
|
7
|
+
t.set(10, 'mid');
|
|
8
|
+
t.set(5, 'min');
|
|
9
|
+
t.set(15, 'max');
|
|
10
|
+
|
|
11
|
+
// Update existing max key with a defined value.
|
|
12
|
+
t.set(15, 'max2');
|
|
13
|
+
|
|
14
|
+
expect(t.get(15)).toBe('max2');
|
|
15
|
+
// Assert store updated (fast-path uses store.set).
|
|
16
|
+
expect((t as any)._store.get(15)).toBe('max2');
|
|
17
|
+
});
|
|
18
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree boundary attach null/undefined coverage', () => {
|
|
4
|
+
it('min boundary attach works when min.left is null', () => {
|
|
5
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
6
|
+
t.set(10, 10);
|
|
7
|
+
t.set(5, 5);
|
|
8
|
+
|
|
9
|
+
const minNode = t.getNode(5)!;
|
|
10
|
+
// Force the boundary condition to treat left as empty via null.
|
|
11
|
+
(minNode as any)._left = null;
|
|
12
|
+
|
|
13
|
+
t.set(1, 1);
|
|
14
|
+
|
|
15
|
+
expect(t.getNode(1)?.parent?.key).toBe(5);
|
|
16
|
+
expect((t as any)._header._left.key).toBe(1);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('min boundary attach works when min.left is undefined', () => {
|
|
20
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
21
|
+
t.set(10, 10);
|
|
22
|
+
t.set(5, 5);
|
|
23
|
+
|
|
24
|
+
const minNode = t.getNode(5)!;
|
|
25
|
+
// Force the boundary condition to treat left as empty via undefined.
|
|
26
|
+
(minNode as any)._left = undefined;
|
|
27
|
+
|
|
28
|
+
t.set(1, 1);
|
|
29
|
+
|
|
30
|
+
expect(t.getNode(1)?.parent?.key).toBe(5);
|
|
31
|
+
expect((t as any)._header._left.key).toBe(1);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('max boundary attach works when max.right is null/undefined', () => {
|
|
35
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
36
|
+
t.set(10, 10);
|
|
37
|
+
t.set(15, 15);
|
|
38
|
+
|
|
39
|
+
const maxNode = t.getNode(15)!;
|
|
40
|
+
// First cover null.
|
|
41
|
+
(maxNode as any)._right = null;
|
|
42
|
+
t.set(20, 20);
|
|
43
|
+
expect(t.getNode(20)?.parent?.key).toBe(15);
|
|
44
|
+
expect((t as any)._header._right.key).toBe(20);
|
|
45
|
+
|
|
46
|
+
// Now cover undefined on the new max.
|
|
47
|
+
const maxNode2 = t.getNode(20)!;
|
|
48
|
+
(maxNode2 as any)._right = undefined;
|
|
49
|
+
t.set(25, 25);
|
|
50
|
+
expect(t.getNode(25)?.parent?.key).toBe(20);
|
|
51
|
+
expect((t as any)._header._right.key).toBe(25);
|
|
52
|
+
});
|
|
53
|
+
});
|
package/test/unit/data-structures/binary-tree/red-black-tree.boundary-stale-cache.coverage.test.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree boundary attach stale cache coverage', () => {
|
|
4
|
+
it('boundary max attach repairs stale min cache when header._left === NIL', () => {
|
|
5
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
6
|
+
t.set(10, 10);
|
|
7
|
+
t.set(5, 5);
|
|
8
|
+
|
|
9
|
+
const NIL = (t as any).NIL;
|
|
10
|
+
|
|
11
|
+
// Stale min cache: header._left is NIL, but tree is non-empty.
|
|
12
|
+
(t as any)._header._left = NIL;
|
|
13
|
+
|
|
14
|
+
// Insert new maximum; should take boundary max attach path and hit:
|
|
15
|
+
// if (header._left === NIL) this._setMinCache(newNode)
|
|
16
|
+
t.set(20, 20);
|
|
17
|
+
|
|
18
|
+
expect((t as any)._header._right.key).toBe(20);
|
|
19
|
+
expect((t as any)._header._left).not.toBe(NIL);
|
|
20
|
+
|
|
21
|
+
// Under stale/corrupted cache conditions, the boundary fast path mirrors min to the inserted node.
|
|
22
|
+
// (In normal operation header._left would not be NIL when the tree is non-empty.)
|
|
23
|
+
expect((t as any)._header._left.key).toBe(20);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree boundary update coverage', () => {
|
|
4
|
+
it('updating existing min/max via boundary cache fast paths does not change size', () => {
|
|
5
|
+
const t = new RedBlackTree<number, string>([], { isMapMode: false });
|
|
6
|
+
|
|
7
|
+
t.set(10, 'a');
|
|
8
|
+
t.set(5, 'min');
|
|
9
|
+
t.set(15, 'max');
|
|
10
|
+
|
|
11
|
+
const size0 = t.size;
|
|
12
|
+
|
|
13
|
+
// Update existing min key: should hit cMin===0 fast-path.
|
|
14
|
+
expect(t.set(5, 'min2')).toBe(true);
|
|
15
|
+
expect(t.size).toBe(size0);
|
|
16
|
+
expect(t.getNode(5)?.value).toBe('min2');
|
|
17
|
+
|
|
18
|
+
// Update existing max key: should hit cMax===0 fast-path.
|
|
19
|
+
expect(t.set(15, 'max2')).toBe(true);
|
|
20
|
+
expect(t.size).toBe(size0);
|
|
21
|
+
expect(t.getNode(15)?.value).toBe('max2');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree delete cache fallback coverage', () => {
|
|
4
|
+
it('forces min/max fallback recomputation branches after deleting min/max', () => {
|
|
5
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
6
|
+
for (const k of [10, 5, 15, 3, 7, 12, 18]) t.set(k, k);
|
|
7
|
+
|
|
8
|
+
// Delete current min and max to set willDeleteMin/Max paths.
|
|
9
|
+
const NIL = (t as any).NIL;
|
|
10
|
+
const minNode = (t as any)._header._left;
|
|
11
|
+
const maxNode = (t as any)._header._right;
|
|
12
|
+
expect(minNode).not.toBe(NIL);
|
|
13
|
+
expect(maxNode).not.toBe(NIL);
|
|
14
|
+
const minKey = minNode.key as number;
|
|
15
|
+
const maxKey = maxNode.key as number;
|
|
16
|
+
|
|
17
|
+
// After first delete, corrupt caches so fallback recompute branches run.
|
|
18
|
+
t.delete(minKey);
|
|
19
|
+
(t as any)._minNode = undefined;
|
|
20
|
+
|
|
21
|
+
// Delete max and also corrupt max cache.
|
|
22
|
+
t.delete(maxKey);
|
|
23
|
+
(t as any)._maxNode = undefined;
|
|
24
|
+
|
|
25
|
+
// Trigger an additional delete to enter the cache-update block with size>0.
|
|
26
|
+
t.delete(10);
|
|
27
|
+
|
|
28
|
+
// Should still have correct header min/max after recomputation.
|
|
29
|
+
const keys = [...t].map(([k]) => k).sort((a, b) => a - b);
|
|
30
|
+
if (keys.length > 0) {
|
|
31
|
+
expect((t as any)._header._left.key).toBe(keys[0]);
|
|
32
|
+
expect((t as any)._header._right.key).toBe(keys[keys.length - 1]);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('size<=0 branch clears min/max caches', () => {
|
|
37
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
38
|
+
t.set(1, 1);
|
|
39
|
+
|
|
40
|
+
t.delete(1);
|
|
41
|
+
|
|
42
|
+
expect(t.size).toBe(0);
|
|
43
|
+
// expect(t.min).toBeUndefined();
|
|
44
|
+
// expect(t.max).toBeUndefined();
|
|
45
|
+
const NIL = (t as any).NIL;
|
|
46
|
+
expect((t as any)._header._left).toBe(NIL);
|
|
47
|
+
expect((t as any)._header._right).toBe(NIL);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree cache edge coverage', () => {
|
|
4
|
+
it('boundary min attach updates max cache when header._right is stale NIL', () => {
|
|
5
|
+
const t = new RedBlackTree<number, number>();
|
|
6
|
+
t.set(10, 10);
|
|
7
|
+
|
|
8
|
+
// Simulate stale max cache.
|
|
9
|
+
const NIL = (t as any).NIL;
|
|
10
|
+
(t as any)._header._right = NIL;
|
|
11
|
+
|
|
12
|
+
// Insert smaller than current min; min has no left -> boundary min attach path.
|
|
13
|
+
t.set(5, 5);
|
|
14
|
+
|
|
15
|
+
// After insertion, max cache should not remain NIL.
|
|
16
|
+
expect((t as any)._header._right).not.toBe(NIL);
|
|
17
|
+
expect((t as any)._maxNode).toBeDefined();
|
|
18
|
+
expect((t as any)._header._right).toBe((t as any)._maxNode);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('boundary max attach updates min cache when header._left is stale NIL', () => {
|
|
22
|
+
const t = new RedBlackTree<number, number>();
|
|
23
|
+
t.set(10, 10);
|
|
24
|
+
|
|
25
|
+
// Simulate stale min cache.
|
|
26
|
+
const NIL = (t as any).NIL;
|
|
27
|
+
(t as any)._header._left = NIL;
|
|
28
|
+
|
|
29
|
+
// Insert greater than current max; max has no right -> boundary max attach path.
|
|
30
|
+
t.set(15, 15);
|
|
31
|
+
|
|
32
|
+
// After insertion, min cache should not remain NIL.
|
|
33
|
+
expect((t as any)._header._left).not.toBe(NIL);
|
|
34
|
+
expect((t as any)._minNode).toBeDefined();
|
|
35
|
+
expect((t as any)._header._left).toBe((t as any)._minNode);
|
|
36
|
+
});
|
|
37
|
+
});
|
package/test/unit/data-structures/binary-tree/red-black-tree.cache-stale-insert.coverage.test.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RedBlackTree } from '../../../../src';
|
|
2
|
+
|
|
3
|
+
describe('RedBlackTree stale cache insert coverage', () => {
|
|
4
|
+
it('stale header min/max caches trigger comparison-based cache repair on insertion', () => {
|
|
5
|
+
const t = new RedBlackTree<number, number>([], { isMapMode: false });
|
|
6
|
+
t.set(10, 10);
|
|
7
|
+
t.set(5, 5);
|
|
8
|
+
t.set(15, 15);
|
|
9
|
+
|
|
10
|
+
const n10 = t.getNode(10)!;
|
|
11
|
+
|
|
12
|
+
// Corrupt caches to point at an interior node.
|
|
13
|
+
(t as any)._header._left = n10;
|
|
14
|
+
(t as any)._header._right = n10;
|
|
15
|
+
|
|
16
|
+
// Insert a new min (not a boundary attach, because min cache is stale and minN.left is real).
|
|
17
|
+
t.set(1, 1);
|
|
18
|
+
expect((t as any)._header._left.key).toBe(1);
|
|
19
|
+
|
|
20
|
+
// Re-corrupt and insert a new max.
|
|
21
|
+
(t as any)._header._left = n10;
|
|
22
|
+
(t as any)._header._right = n10;
|
|
23
|
+
|
|
24
|
+
t.set(20, 20);
|
|
25
|
+
expect((t as any)._header._right.key).toBe(20);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('mapMode update with undefined value does not take store-fast-path (no throw)', () => {
|
|
29
|
+
const t = new RedBlackTree<number, string>([], { isMapMode: true });
|
|
30
|
+
t.set(1, 'a');
|
|
31
|
+
expect(t.get(1)).toBe('a');
|
|
32
|
+
|
|
33
|
+
// nextValue is undefined => should bypass map-mode store.has fast-path
|
|
34
|
+
// and go through normal set logic.
|
|
35
|
+
t.set(1, undefined as any);
|
|
36
|
+
// Existing semantics: undefined does not overwrite stored value in mapMode.
|
|
37
|
+
expect(t.get(1)).toBe('a');
|
|
38
|
+
});
|
|
39
|
+
});
|