recursive-set 7.0.0 → 8.0.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/README.md +87 -116
- package/dist/cjs/index.js +612 -443
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +140 -114
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +611 -443
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,18 +3,18 @@
|
|
|
3
3
|
[](LICENSE)
|
|
4
4
|
[](https://www.npmjs.com/package/recursive-set)
|
|
5
5
|
|
|
6
|
-
High-performance
|
|
6
|
+
High-performance collection library for TypeScript supporting **value semantics** (deep equality) and recursive structures. Version 8 introduces a new hash-based architecture optimized for high-throughput workloads like SAT solvers and graph algorithms.
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
`RecursiveSet`
|
|
10
|
+
`RecursiveSet` provides mathematical sets and maps where equality is determined by structure/content rather than object reference (e.g., `{1, 2}` is equal to `{2, 1}`).
|
|
11
11
|
|
|
12
|
-
Key
|
|
12
|
+
**Key Architectural Features:**
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
14
|
+
- **Open Addressing:** Uses linear probing with a load factor of 0.75 for cache-efficient lookups.
|
|
15
|
+
- **Backshift Deletion:** Maintains probe chain integrity without "tombstones," preventing performance degradation over time.
|
|
16
|
+
- **Zero-Allocation Hashing:** Uses static buffers and raw bitwise operations to hash numbers without triggering the Garbage Collector.
|
|
17
|
+
- **Structure of Arrays (SoA):** Data is stored in flat arrays to maximize CPU cache locality.
|
|
18
18
|
|
|
19
19
|
## Installation
|
|
20
20
|
|
|
@@ -22,186 +22,157 @@ Key design points:
|
|
|
22
22
|
npm install recursive-set
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
26
25
|
## Quickstart
|
|
27
26
|
|
|
28
|
-
###
|
|
27
|
+
### Working with Sets
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
Sets automatically deduplicate elements based on their value or structure.
|
|
31
30
|
|
|
32
31
|
```ts
|
|
33
|
-
import { RecursiveSet
|
|
32
|
+
import { RecursiveSet } from "recursive-set";
|
|
33
|
+
|
|
34
|
+
// Primitive values
|
|
35
|
+
const numbers = new RecursiveSet(1, 2, 3);
|
|
36
|
+
numbers.add(1); // No effect, 1 is already present
|
|
34
37
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
38
|
+
// Recursive structures (Sets of Sets)
|
|
39
|
+
const setA = new RecursiveSet(1, 2);
|
|
40
|
+
const setB = new RecursiveSet(2, 1);
|
|
41
|
+
const metaSet = new RecursiveSet<RecursiveSet<number>>();
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
partition.add(states); // {{q0, q1, q2}}
|
|
43
|
+
metaSet.add(setA);
|
|
44
|
+
metaSet.add(setB);
|
|
41
45
|
|
|
42
|
-
console.log(
|
|
46
|
+
console.log(metaSet.size); // 1, because setA equals setB
|
|
43
47
|
```
|
|
44
48
|
|
|
49
|
+
### Working with Maps
|
|
45
50
|
|
|
46
|
-
|
|
51
|
+
`RecursiveMap` allows using complex objects (like Tuples or Sets) as keys.
|
|
47
52
|
|
|
48
53
|
```ts
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
import { RecursiveMap, Tuple } from "recursive-set";
|
|
55
|
+
|
|
56
|
+
const transitions = new RecursiveMap<Tuple<[string, string]>, number>();
|
|
51
57
|
const edge = new Tuple("q0", "q1");
|
|
52
58
|
|
|
53
|
-
|
|
54
|
-
transitions.add(edge);
|
|
55
|
-
```
|
|
59
|
+
transitions.set(edge, 1);
|
|
56
60
|
|
|
61
|
+
// Retrieval using a new, structurally identical key
|
|
62
|
+
console.log(transitions.get(new Tuple("q0", "q1"))); // 1
|
|
63
|
+
```
|
|
57
64
|
|
|
58
|
-
### Lifecycle (
|
|
65
|
+
### Lifecycle (Mutable → Frozen)
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
To guarantee hash stability, collections become **immutable** (frozen) once their hash code is computed or they are inserted into another collection.
|
|
61
68
|
|
|
62
69
|
```ts
|
|
63
|
-
const A = new RecursiveSet(1
|
|
64
|
-
const B = new RecursiveSet(A); //
|
|
65
|
-
|
|
66
|
-
console.log(B.has(A)); // true
|
|
70
|
+
const A = new RecursiveSet(1);
|
|
71
|
+
const B = new RecursiveSet(A); // Accessing A's hash to store it in B freezes A.
|
|
67
72
|
|
|
68
73
|
try {
|
|
69
|
-
A.add(
|
|
70
|
-
} catch {
|
|
71
|
-
|
|
74
|
+
A.add(2); // Throws Error: Frozen Set modified.
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// Expected behavior
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
//
|
|
79
|
+
// Use mutableCopy to continue editing
|
|
75
80
|
const C = A.mutableCopy();
|
|
76
|
-
C.add(
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
## Contracts
|
|
81
|
-
|
|
82
|
-
This library optimizes for raw throughput. Using it correctly requires strict adherence to these rules:
|
|
83
|
-
|
|
84
|
-
1. **Finite numbers only:** Do not insert `NaN`, `Infinity`, or `-Infinity`. Comparison logic uses fast arithmetic (`a - b`).
|
|
85
|
-
2. **No mutation:** Do not mutate arrays/tuples/objects after insertion.
|
|
86
|
-
3. **Type consistency:** Avoid mixing distinct structure types (e.g., `Array` vs `Tuple`) in the same set for the same logical role, as hash-collision edge cases may treat them as equal for performance reasons.
|
|
87
|
-
|
|
88
|
-
Violating the contract can break sorted order invariants, hashing assumptions, and equality semantics (garbage in → garbage out).
|
|
89
|
-
|
|
90
|
-
### Freeze-on-hash rule
|
|
91
|
-
|
|
92
|
-
- A set is mutable until `hashCode` is accessed.
|
|
93
|
-
- After hashing, mutation methods throw; use `mutableCopy()` to continue editing.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
### Tuple vs Array
|
|
97
|
-
|
|
98
|
-
- `Tuple` is an immutable container: it makes a defensive copy and freezes its internal storage via `Object.freeze()` (shallow immutability).
|
|
99
|
-
- Plain `Array` values are supported as ordered sequences, but they are not frozen by the library.
|
|
100
|
-
|
|
101
|
-
**Recommendation:** For hot loops (like SAT solvers), represent frequently compared “small composite values” as `Tuple` to benefit from cached hashing and immutability.
|
|
102
|
-
|
|
103
|
-
## API
|
|
104
|
-
|
|
105
|
-
### Types
|
|
106
|
-
|
|
107
|
-
```ts
|
|
108
|
-
export type Primitive = number | string;
|
|
109
|
-
export type Value =
|
|
110
|
-
| Primitive
|
|
111
|
-
| RecursiveSet<any>
|
|
112
|
-
| Tuple<any>
|
|
113
|
-
| ReadonlyArray<Value>;
|
|
81
|
+
C.add(2);
|
|
114
82
|
```
|
|
115
83
|
|
|
84
|
+
## Contracts & Invariants
|
|
116
85
|
|
|
117
|
-
|
|
86
|
+
This library optimizes for raw speed and assumes strict adherence to the following contracts. Violating them leads to undefined behavior.
|
|
118
87
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
88
|
+
1. **Finite Numbers Only:** `NaN` and `Infinity` are **strictly forbidden**. They break strict equality checks and integer optimization paths.
|
|
89
|
+
2. **Strict Value Semantics:** Plain JavaScript objects (`{}`) are **not supported**. Keys must implement the `Structural` interface (provide `equals`, `hashCode`, and `toString`).
|
|
90
|
+
3. **Hash Quality:** The $O(1)$ performance guarantee relies on a good distribution. Returning a constant `hashCode` (e.g., `42`) forces all elements into a single bucket, degrading performance to $O(N)$.
|
|
91
|
+
4. **Deterministic Visualization:** Custom `toString()` implementations **must** utilize `compareVisualLogic` for nested structures. Failing to do so results in unstable string output.
|
|
92
|
+
5. **Immutability:** Once an object is added to a collection, its `hashCode` **must not change**.
|
|
93
|
+
6. **No Circular Dependencies:** A `RecursiveSet` cannot contain itself, directly or indirectly. Runtime checks are omitted for performance; creating a cycle will cause a Stack Overflow during hashing.
|
|
122
94
|
|
|
123
|
-
|
|
95
|
+
## API Reference
|
|
124
96
|
|
|
125
|
-
###
|
|
97
|
+
### Core Types
|
|
126
98
|
|
|
127
99
|
```ts
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
Sorts once and deduplicates (typically much faster than many `.add()` calls).
|
|
100
|
+
type Primitive = number | string;
|
|
101
|
+
type Value = Primitive | Structural;
|
|
132
102
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
103
|
+
interface Structural {
|
|
104
|
+
readonly hashCode: number;
|
|
105
|
+
equals(other: unknown): boolean;
|
|
106
|
+
toString(): string;
|
|
107
|
+
}
|
|
137
108
|
```
|
|
138
109
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
### Mutation (only while unfrozen)
|
|
110
|
+
### RecursiveSet
|
|
142
111
|
|
|
143
|
-
|
|
144
|
-
- `remove(element: T): this`
|
|
145
|
-
- `clear(): this`
|
|
112
|
+
#### Construction
|
|
146
113
|
|
|
114
|
+
- `new RecursiveSet<T>(...elements: T[])`: Creates a set from the given arguments.
|
|
147
115
|
|
|
148
|
-
|
|
116
|
+
#### Mutation (Unfrozen state only)
|
|
149
117
|
|
|
150
|
-
- `
|
|
151
|
-
- `
|
|
118
|
+
- `add(element: T): void`: Adds an element ($O(1)$ amortized).
|
|
119
|
+
- `remove(element: T): void`: Removes an element ($O(1)$ amortized).
|
|
152
120
|
|
|
121
|
+
#### Set Operations
|
|
153
122
|
|
|
154
|
-
|
|
123
|
+
All operations return a new `RecursiveSet` instance.
|
|
155
124
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
- `union(other): RecursiveSet<T | U>`
|
|
125
|
+
- `union(other): RecursiveSet<T>`
|
|
159
126
|
- `intersection(other): RecursiveSet<T>`
|
|
160
|
-
- `difference(other): RecursiveSet<T>`
|
|
127
|
+
- `difference(other): RecursiveSet<T>` ($A \setminus B$)
|
|
161
128
|
- `symmetricDifference(other): RecursiveSet<T>`
|
|
162
|
-
- `powerset(): RecursiveSet<RecursiveSet<T>>` (guarded; throws if too large)
|
|
163
129
|
- `cartesianProduct<U>(other): RecursiveSet<Tuple<[T, U]>>`
|
|
130
|
+
- `powerset(): RecursiveSet<RecursiveSet<T>>` (Throws if size > 20)
|
|
164
131
|
|
|
165
|
-
|
|
166
|
-
### Predicates \& properties
|
|
132
|
+
#### Properties
|
|
167
133
|
|
|
168
134
|
- `has(element: T): boolean`
|
|
169
|
-
- `equals(other:
|
|
170
|
-
- `compare(other: RecursiveSet<Value>): number`
|
|
135
|
+
- `equals(other: unknown): boolean`
|
|
171
136
|
- `isSubset(other): boolean`
|
|
172
137
|
- `isSuperset(other): boolean`
|
|
173
|
-
- `
|
|
138
|
+
- `mutableCopy(): RecursiveSet<T>`: Returns a shallow mutable clone.
|
|
174
139
|
- `size: number`
|
|
175
|
-
- `hashCode: number
|
|
176
|
-
- `isFrozen: boolean`
|
|
140
|
+
- `hashCode: number`: Computes hash and freezes the set.
|
|
177
141
|
|
|
142
|
+
### RecursiveMap
|
|
178
143
|
|
|
179
|
-
|
|
144
|
+
A hash map supporting `Value` keys.
|
|
180
145
|
|
|
181
|
-
|
|
146
|
+
- `set(key: K, value: V): void`
|
|
147
|
+
- `get(key: K): V | undefined`
|
|
148
|
+
- `delete(key: K): boolean`
|
|
149
|
+
- `has(key: K): boolean`
|
|
150
|
+
- `mutableCopy(): RecursiveMap<K, V>`
|
|
182
151
|
|
|
183
|
-
|
|
184
|
-
- Sequences compare by length first, then lexicographically element-by-element.
|
|
185
|
-
- Sets compare by cached hash first, then by structural comparison on collision.
|
|
152
|
+
### Tuple
|
|
186
153
|
|
|
154
|
+
An immutable, hashable sequence of values. Useful for composite keys.
|
|
155
|
+
|
|
156
|
+
- `new Tuple(...elements: T[])`
|
|
157
|
+
- `get(index: number): T[index]`
|
|
158
|
+
- `length: number`
|
|
187
159
|
|
|
188
160
|
## Credits
|
|
189
161
|
|
|
190
162
|
This library was developed as a student research project under the supervision of **[Karl Stroetmann](https://github.com/karlstroetmann/)**.
|
|
191
163
|
|
|
192
|
-
Special thanks for his architectural guidance
|
|
164
|
+
Special thanks for his architectural guidance on homogeneous sets and the theoretical foundations required for high-performance set engines.
|
|
193
165
|
|
|
194
166
|
## Contributing
|
|
195
167
|
|
|
196
168
|
```bash
|
|
197
|
-
git clone https://github.com/cstrerath/recursive-set.git
|
|
169
|
+
git clone [https://github.com/cstrerath/recursive-set.git](https://github.com/cstrerath/recursive-set.git)
|
|
198
170
|
npm install
|
|
199
171
|
npm run build
|
|
200
172
|
npx tsx test/test.ts
|
|
201
173
|
npx tsx test/nqueens.ts
|
|
202
174
|
```
|
|
203
175
|
|
|
204
|
-
|
|
205
176
|
## License
|
|
206
177
|
|
|
207
|
-
MIT License © 2025 Christian Strerath. See `LICENSE
|
|
178
|
+
MIT License © 2025 Christian Strerath. See `LICENSE`.
|