@calcit/ternary-tree 0.0.24 → 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.
- package/.yarn/install-state.gz +0 -0
- package/.yarnrc.yml +6 -0
- package/README.md +24 -4
- package/TESTING.md +155 -0
- package/lib/bench.d.mts +11 -0
- package/lib/bench.mjs +99 -0
- package/lib/benchmark.d.mts +1 -0
- package/lib/benchmark.mjs +67 -0
- package/lib/list.d.mts +1 -0
- package/lib/list.mjs +265 -188
- package/lib/map.mjs +223 -161
- package/lib/test-list-detailed-perf.d.mts +1 -0
- package/lib/test-list-detailed-perf.mjs +165 -0
- package/lib/test-list-perf.d.mts +1 -0
- package/lib/test-list-perf.mjs +153 -0
- package/lib/test-list.mjs +202 -203
- package/lib/test-map-perf.d.mts +1 -0
- package/lib/test-map-perf.mjs +70 -0
- package/lib/test-map.mjs +201 -208
- package/lib/test-utils.d.mts +9 -5
- package/lib/test-utils.mjs +131 -18
- package/lib/{main.mjs → test.mjs} +3 -0
- package/lib/types.d.mts +1 -0
- package/package.json +14 -5
- /package/lib/{main.d.mts → test.d.mts} +0 -0
|
Binary file
|
package/.yarnrc.yml
ADDED
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ function sameMapShape<K, T>(xs: TernaryTreeMap<K, T>, ys: TernaryTreeMap<K, T>):
|
|
|
40
40
|
|
|
41
41
|
List functions:
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
````ts
|
|
44
44
|
function makeTernaryTreeList<T>(size: number, offset: number, xs: /* var */ Array<TernaryTreeList<T>>): TernaryTreeList<T>;
|
|
45
45
|
function initTernaryTreeList<T>(xs: Array<T>): TernaryTreeList<T>;
|
|
46
46
|
function initEmptyTernaryTreeList<T>(): TernaryTreeList<T>;
|
|
@@ -75,9 +75,29 @@ function sameListShape<T>(xs: TernaryTreeList<T>, ys: TernaryTreeList<T>): boole
|
|
|
75
75
|
function getDepth<T>(tree: TernaryTreeList<T>): number;
|
|
76
76
|
function listToString<T>(tree: TernaryTreeList<T>): string;
|
|
77
77
|
function formatListInline<T>(tree: TernaryTreeList<T>): string;
|
|
78
|
-
function
|
|
78
|
+
function checkList
|
|
79
|
+
|
|
80
|
+
### Development
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Install dependencies
|
|
84
|
+
yarn install
|
|
85
|
+
|
|
86
|
+
# Build TypeScript
|
|
87
|
+
yarn build
|
|
88
|
+
|
|
89
|
+
# Run all tests
|
|
90
|
+
yarn test
|
|
91
|
+
|
|
92
|
+
# Run specific test suites
|
|
93
|
+
yarn test:list # List-related tests only
|
|
94
|
+
yarn test:map # Map-related tests only
|
|
95
|
+
````
|
|
96
|
+
|
|
97
|
+
For detailed testing information, see [TESTING.md](./TESTING.md).Structure<T>(tree: TernaryTreeList<T>): boolean;
|
|
79
98
|
function forceListInplaceBalancing<T>(tree: TernaryTreeList<T>): void;
|
|
80
|
-
|
|
99
|
+
|
|
100
|
+
````
|
|
81
101
|
|
|
82
102
|
To overwrite internals behaviors:
|
|
83
103
|
|
|
@@ -85,7 +105,7 @@ To overwrite internals behaviors:
|
|
|
85
105
|
overwriteHashGenerator(f);
|
|
86
106
|
|
|
87
107
|
overwriteComparator(f);
|
|
88
|
-
|
|
108
|
+
````
|
|
89
109
|
|
|
90
110
|
### License
|
|
91
111
|
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Testing Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This project uses a custom lightweight testing framework that provides colored output, test organization, and detailed error reporting.
|
|
6
|
+
|
|
7
|
+
## Running Tests
|
|
8
|
+
|
|
9
|
+
### Basic Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Build and run all tests
|
|
13
|
+
yarn test
|
|
14
|
+
|
|
15
|
+
# Only compile (useful for checking TypeScript errors)
|
|
16
|
+
yarn build
|
|
17
|
+
|
|
18
|
+
# Run tests manually after building
|
|
19
|
+
node lib/test.mjs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Selective Testing
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# Run only list tests (any test with "list" in the name)
|
|
26
|
+
yarn test:list
|
|
27
|
+
|
|
28
|
+
# Run only map tests (any test with "map" in the name)
|
|
29
|
+
yarn test:map
|
|
30
|
+
|
|
31
|
+
# Use environment variable for custom filtering
|
|
32
|
+
TARGET=specific-test-name yarn test
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Test Framework Features
|
|
36
|
+
|
|
37
|
+
### Test Organization
|
|
38
|
+
|
|
39
|
+
- **Test Suites**: Use `describe()` to group related tests
|
|
40
|
+
- **Individual Tests**: Use `test()` for specific test cases
|
|
41
|
+
- **Colored Output**: Green ✓ for passing tests, red ✗ for failing tests
|
|
42
|
+
- **Timing**: Shows execution time for each test
|
|
43
|
+
|
|
44
|
+
### Assertion Functions
|
|
45
|
+
|
|
46
|
+
#### Basic Assertions
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
check(condition: boolean, message?: string)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### Equality Checks
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
checkEqual<T>(actual: T, expected: T, message?: string)
|
|
56
|
+
checkDeepEqual<T>(actual: T, expected: T, message?: string)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
#### Array Comparisons
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
checkArrayEqual<T>(actual: Array<T>, expected: Array<T>, message?: string)
|
|
63
|
+
arrayEqual<T>(xs: Array<T>, ys: Array<T>): boolean
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
#### Exception Testing
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
checkThrows(fn: () => void, message?: string)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### Debug Utilities
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
justDisplay(actual: any, expected: any) // For comparing values visually
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Example Test Structure
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { describe, test, check, checkEqual, checkArrayEqual } from "./test-utils.mjs";
|
|
82
|
+
|
|
83
|
+
export function runMyTests() {
|
|
84
|
+
describe("My Component Tests", () => {
|
|
85
|
+
test("should do something basic", () => {
|
|
86
|
+
const result = myFunction();
|
|
87
|
+
check(result !== null, "Result should not be null");
|
|
88
|
+
checkEqual(result.status, "success");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("should handle arrays correctly", () => {
|
|
92
|
+
const actual = [1, 2, 3];
|
|
93
|
+
const expected = [1, 2, 3];
|
|
94
|
+
checkArrayEqual(actual, expected);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("should throw on invalid input", () => {
|
|
98
|
+
checkThrows(() => {
|
|
99
|
+
myFunction(null);
|
|
100
|
+
}, "Should throw on null input");
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Test Output
|
|
107
|
+
|
|
108
|
+
The framework provides:
|
|
109
|
+
|
|
110
|
+
- **Suite Organization**: Clear grouping with cyan headers
|
|
111
|
+
- **Individual Results**: Green checkmarks for passing tests
|
|
112
|
+
- **Timing Information**: Execution time for each test
|
|
113
|
+
- **Error Details**: Clear error messages with stack traces
|
|
114
|
+
- **Summary Statistics**: Total tests, passed, failed counts
|
|
115
|
+
- **Exit Codes**: Non-zero exit code on test failures
|
|
116
|
+
|
|
117
|
+
## Current Test Coverage
|
|
118
|
+
|
|
119
|
+
### TernaryTreeList Tests (15 tests)
|
|
120
|
+
|
|
121
|
+
- Initialization and basic operations
|
|
122
|
+
- List insertions and modifications
|
|
123
|
+
- Concatenation and merging
|
|
124
|
+
- Equality checking and comparison
|
|
125
|
+
- Balancing and structure integrity
|
|
126
|
+
- Iteration and traversal
|
|
127
|
+
- Slicing and reversal
|
|
128
|
+
- Index finding and mapping
|
|
129
|
+
- Stress testing
|
|
130
|
+
|
|
131
|
+
### TernaryTreeMap Tests (12 tests)
|
|
132
|
+
|
|
133
|
+
- Map initialization and creation
|
|
134
|
+
- Association and containment checking
|
|
135
|
+
- Structure integrity validation
|
|
136
|
+
- Dissociation operations
|
|
137
|
+
- Array conversion and iteration
|
|
138
|
+
- Equality and shape comparison
|
|
139
|
+
- Map merging with various strategies
|
|
140
|
+
- Value mapping transformations
|
|
141
|
+
- Large dataset handling
|
|
142
|
+
|
|
143
|
+
## Adding New Tests
|
|
144
|
+
|
|
145
|
+
1. Create test functions in existing or new test files
|
|
146
|
+
2. Use the `describe()` and `test()` structure
|
|
147
|
+
3. Import and call your test function in `test.mts`
|
|
148
|
+
4. Use appropriate assertion functions for validation
|
|
149
|
+
5. Run `yarn test` to verify everything works
|
|
150
|
+
|
|
151
|
+
## Performance Considerations
|
|
152
|
+
|
|
153
|
+
- Tests include timing information to identify slow operations
|
|
154
|
+
- Stress tests (like concat loop test) help identify performance regressions
|
|
155
|
+
- Structure integrity checks validate the internal tree consistency
|
package/lib/bench.d.mts
ADDED
|
@@ -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.");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Performance comparison script
|
|
2
|
+
import { initTernaryTreeMap, initTernaryTreeMapFromArray, contains, mapGetDefault, toPairsArray, } from "./map.mjs";
|
|
3
|
+
function createTestData(size) {
|
|
4
|
+
const data = [];
|
|
5
|
+
for (let i = 0; i < size; i++) {
|
|
6
|
+
data.push([i, `value_${i}`]);
|
|
7
|
+
}
|
|
8
|
+
return data;
|
|
9
|
+
}
|
|
10
|
+
function measureTime(fn, iterations = 1) {
|
|
11
|
+
const start = performance.now();
|
|
12
|
+
for (let i = 0; i < iterations; i++) {
|
|
13
|
+
fn();
|
|
14
|
+
}
|
|
15
|
+
const end = performance.now();
|
|
16
|
+
return (end - start) / iterations;
|
|
17
|
+
}
|
|
18
|
+
function runComparison() {
|
|
19
|
+
console.log("=== Performance Optimization Results ===\n");
|
|
20
|
+
const sizes = [1000, 5000, 10000];
|
|
21
|
+
const iterations = 5; // Run multiple times for more accurate results
|
|
22
|
+
for (const size of sizes) {
|
|
23
|
+
console.log(`--- ${size} elements (averaged over ${iterations} runs) ---`);
|
|
24
|
+
const testData = createTestData(size);
|
|
25
|
+
const mapData = new Map(testData);
|
|
26
|
+
// Test initTernaryTreeMapFromArray performance
|
|
27
|
+
const arrayTime = measureTime(() => {
|
|
28
|
+
return initTernaryTreeMapFromArray(testData);
|
|
29
|
+
}, iterations);
|
|
30
|
+
const mapTime = measureTime(() => {
|
|
31
|
+
return initTernaryTreeMap(mapData);
|
|
32
|
+
}, iterations);
|
|
33
|
+
console.log(`initTernaryTreeMapFromArray: ${arrayTime.toFixed(2)}ms`);
|
|
34
|
+
console.log(`initTernaryTreeMap: ${mapTime.toFixed(2)}ms`);
|
|
35
|
+
// Create tree for lookup tests
|
|
36
|
+
const tree = initTernaryTreeMapFromArray(testData);
|
|
37
|
+
// Test lookup performance
|
|
38
|
+
const lookupKeys = testData.slice(0, 100).map(([k]) => k);
|
|
39
|
+
const lookupTime = measureTime(() => {
|
|
40
|
+
for (const key of lookupKeys) {
|
|
41
|
+
contains(tree, key);
|
|
42
|
+
mapGetDefault(tree, key, "default");
|
|
43
|
+
}
|
|
44
|
+
}, iterations);
|
|
45
|
+
console.log(`Lookups (100 operations): ${lookupTime.toFixed(2)}ms`);
|
|
46
|
+
// Test toPairsArray performance
|
|
47
|
+
const toPairsTime = measureTime(() => {
|
|
48
|
+
return toPairsArray(tree);
|
|
49
|
+
}, iterations);
|
|
50
|
+
console.log(`toPairsArray: ${toPairsTime.toFixed(2)}ms`);
|
|
51
|
+
console.log(`Array/Map init ratio: ${(arrayTime / mapTime).toFixed(2)}x`);
|
|
52
|
+
console.log("");
|
|
53
|
+
}
|
|
54
|
+
// Stress test
|
|
55
|
+
console.log("=== Stress Test ===");
|
|
56
|
+
const largeData = createTestData(50000);
|
|
57
|
+
const stressTime = measureTime(() => {
|
|
58
|
+
const tree = initTernaryTreeMapFromArray(largeData);
|
|
59
|
+
// Test some operations
|
|
60
|
+
for (let i = 0; i < 1000; i++) {
|
|
61
|
+
contains(tree, i);
|
|
62
|
+
}
|
|
63
|
+
return tree;
|
|
64
|
+
});
|
|
65
|
+
console.log(`Stress test (50k elements + 1k lookups): ${stressTime.toFixed(2)}ms`);
|
|
66
|
+
}
|
|
67
|
+
runComparison();
|
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>;
|