recursive-set 7.0.1 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -20
- package/README.md +201 -207
- package/dist/cjs/index.js +664 -436
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +156 -108
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +663 -436
- package/dist/esm/index.js.map +1 -1
- package/package.json +51 -51
package/dist/cjs/index.js
CHANGED
|
@@ -1,554 +1,782 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* @module recursive-set
|
|
4
|
-
* @version
|
|
4
|
+
* @version 8.1.0
|
|
5
5
|
* @description
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* * **
|
|
9
|
-
* 1. **Finite Numbers Only:**
|
|
10
|
-
* 2. **
|
|
11
|
-
* 3. **
|
|
6
|
+
* High-Performance collection library supporting **Value Semantics** (Deep Equality)
|
|
7
|
+
* and recursive structures.
|
|
8
|
+
* * **CONTRACTS & INVARIANTS:**
|
|
9
|
+
* 1. **Finite Numbers Only:** `NaN` and `Infinity` are **strictly forbidden**. They break strict equality checks and integer optimization paths.
|
|
10
|
+
* 2. **Strict Value Semantics:** Plain JavaScript objects (`{}`) are **not supported**. Objects must implement the `Structural` interface.
|
|
11
|
+
* 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)$.
|
|
12
|
+
* 4. **Deterministic Visualization:** Custom `toString()` implementations **must** utilize `compareVisualLogic` for nested structures. Failing to do so results in cut/unstable string output.
|
|
13
|
+
* 5. **Immutability:** Once an object is added to a collection, its `hashCode` **must not change**.
|
|
14
|
+
* 6. **No Circular Dependencies:** A `RecursiveSet` cannot contain itself, directly or indirectly (e.g. A ∈ B ∈ A). Runtime checks are omitted for performance. Creating a cycle will cause a **Stack Overflow** during hashing or visualization.
|
|
15
|
+
* * **Architecture:**
|
|
16
|
+
* - **Storage:** Open Addressing with Linear Probing.
|
|
17
|
+
* - **Hashing:** Zero-allocation FNV-1a / Murmur-inspired hybrid.
|
|
18
|
+
* - **Memory:** Flattened arrays for cache locality (SoA - Structure of Arrays).
|
|
12
19
|
*/
|
|
13
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.
|
|
15
|
-
exports.compare = compare;
|
|
21
|
+
exports.hashValue = exports.Tuple = exports.RecursiveMap = exports.RecursiveSet = void 0;
|
|
16
22
|
exports.emptySet = emptySet;
|
|
17
23
|
exports.singleton = singleton;
|
|
24
|
+
exports.getHashCode = getHashCode;
|
|
18
25
|
// ============================================================================
|
|
19
|
-
//
|
|
26
|
+
// 2. VISUAL HELPERS
|
|
20
27
|
// ============================================================================
|
|
21
|
-
const FNV_PRIME = 16777619;
|
|
22
|
-
const FNV_OFFSET = 2166136261;
|
|
23
|
-
const floatBuffer = new ArrayBuffer(8);
|
|
24
|
-
const view = new DataView(floatBuffer);
|
|
25
28
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
29
|
+
* Visual Comparator used strictly for deterministic .toString() output.
|
|
30
|
+
* * @remarks
|
|
31
|
+
* This does NOT define the logical order for the Set/Map (which are unordered).
|
|
32
|
+
* It ensures that `{a, b}` and `{b, a}` produce the same string output.
|
|
28
33
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
h ^= str.charCodeAt(i);
|
|
46
|
-
h = Math.imul(h, FNV_PRIME);
|
|
47
|
-
}
|
|
48
|
-
return h >>> 0;
|
|
34
|
+
function compareVisualLogic(a, b) {
|
|
35
|
+
if (a === b)
|
|
36
|
+
return 0;
|
|
37
|
+
// 1. Numbers: Mathematical sort
|
|
38
|
+
if (typeof a === 'number' && typeof b === 'number')
|
|
39
|
+
return a - b;
|
|
40
|
+
// 2. Strings: Lexicographic sort
|
|
41
|
+
if (typeof a === 'string' && typeof b === 'string')
|
|
42
|
+
return a < b ? -1 : 1;
|
|
43
|
+
// 3. Type separation (Numbers < Strings < Objects)
|
|
44
|
+
const typeA = getTypeId(a);
|
|
45
|
+
const typeB = getTypeId(b);
|
|
46
|
+
if (typeA !== typeB)
|
|
47
|
+
return typeA - typeB;
|
|
48
|
+
// 4. Recursive Fallback: Compare string representations
|
|
49
|
+
return String(a) < String(b) ? -1 : 1;
|
|
49
50
|
}
|
|
50
|
-
function
|
|
51
|
+
function getTypeId(v) {
|
|
51
52
|
if (typeof v === 'number')
|
|
52
|
-
return
|
|
53
|
+
return 1;
|
|
53
54
|
if (typeof v === 'string')
|
|
54
|
-
return
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
return 2;
|
|
56
|
+
return 3; // Objects / Structures
|
|
57
|
+
}
|
|
58
|
+
// ============================================================================
|
|
59
|
+
// 3. HASH ENGINE (Optimized)
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Static allocation to prevent GC pauses during number hashing.
|
|
62
|
+
// We treat numbers as raw bits to avoid FPU overhead.
|
|
63
|
+
const _buffer = new ArrayBuffer(8);
|
|
64
|
+
const _f64 = new Float64Array(_buffer);
|
|
65
|
+
const _i32 = new Int32Array(_buffer);
|
|
66
|
+
// Mixing constants (derived from MurmurHash3)
|
|
67
|
+
const HASH_C1 = 0xcc9e2d51;
|
|
68
|
+
const HASH_C2 = 0x1b873593;
|
|
69
|
+
/**
|
|
70
|
+
* Computes a high-quality 32-bit hash code for any supported Value.
|
|
71
|
+
* Optimized for V8's internal number representation (Smis vs Doubles).
|
|
72
|
+
*/
|
|
73
|
+
function getHashCode(val) {
|
|
74
|
+
// --- CASE A: NUMBER ---
|
|
75
|
+
if (typeof val === 'number') {
|
|
76
|
+
// Fast Path: 32-Bit Integers (Smis)
|
|
77
|
+
// (val | 0) === val checks if it's a safe integer.
|
|
78
|
+
// This handles -0 correctly (converts to 0).
|
|
79
|
+
if ((val | 0) === val) {
|
|
80
|
+
let h = val | 0;
|
|
81
|
+
// Avalanche Mixer: Spreads bits to prevent collisions on sequential numbers
|
|
82
|
+
h = Math.imul(h ^ (h >>> 16), 0x85ebca6b);
|
|
83
|
+
h = Math.imul(h ^ (h >>> 13), 0xc2b2ae35);
|
|
84
|
+
return (h ^ (h >>> 16)) >>> 0;
|
|
85
|
+
}
|
|
86
|
+
// Slow Path: Floats (Doubles)
|
|
87
|
+
// Interpret the double's IEEE 754 bit pattern as two 32-bit integers.
|
|
88
|
+
_f64[0] = val;
|
|
89
|
+
const low = _i32[0];
|
|
90
|
+
const high = _i32[1];
|
|
91
|
+
return (Math.imul(low, HASH_C1) ^ Math.imul(high, HASH_C2)) >>> 0;
|
|
92
|
+
}
|
|
93
|
+
// --- CASE B: STRING ---
|
|
94
|
+
// Jenkins One-at-a-Time variant
|
|
95
|
+
if (typeof val === 'string') {
|
|
96
|
+
let h = 0x811c9dc5;
|
|
97
|
+
const len = val.length;
|
|
98
|
+
for (let i = 0; i < len; i++) {
|
|
99
|
+
h ^= val.charCodeAt(i);
|
|
100
|
+
h = Math.imul(h, 0x01000193);
|
|
62
101
|
}
|
|
63
102
|
return h >>> 0;
|
|
64
103
|
}
|
|
104
|
+
// --- CASE C: STRUCTURAL ---
|
|
105
|
+
if (val && typeof val === 'object') {
|
|
106
|
+
return val.hashCode;
|
|
107
|
+
}
|
|
65
108
|
return 0;
|
|
66
109
|
}
|
|
67
|
-
// ============================================================================
|
|
68
|
-
// COMPARATOR (Optimized)
|
|
69
|
-
// ============================================================================
|
|
70
110
|
/**
|
|
71
|
-
*
|
|
72
|
-
* * **WARNING: UNSAFE OPTIMIZATIONS**
|
|
73
|
-
* - Uses `a - b` for numbers. **Precondition:** Only finite numbers allowed.
|
|
74
|
-
* Inputting `NaN` or `Infinity` results in undefined sorting behavior.
|
|
75
|
-
* - **Hash Collisions:** If two distinct object types (e.g. Array vs Tuple) have the
|
|
76
|
-
* same hash, they may be treated as equal. Avoid mixing structure types in the same set.
|
|
111
|
+
* Deep equality check dispatcher.
|
|
77
112
|
*/
|
|
78
|
-
function
|
|
113
|
+
function areEqual(a, b) {
|
|
79
114
|
if (a === b)
|
|
80
|
-
return
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const typeB = typeof b;
|
|
88
|
-
// Type Score: Number(1) < String(2) < Array/Tuple(3) < Set(4)
|
|
89
|
-
if (typeA !== typeB) {
|
|
90
|
-
const scoreA = (typeA === 'number') ? 1 : (typeA === 'string' ? 2 : 3);
|
|
91
|
-
const scoreB = (typeB === 'number') ? 1 : (typeB === 'string' ? 2 : 3);
|
|
92
|
-
return scoreA - scoreB;
|
|
93
|
-
}
|
|
94
|
-
// Hash Short-Circuit
|
|
95
|
-
const h1 = hashValue(a);
|
|
96
|
-
const h2 = hashValue(b);
|
|
97
|
-
if (h1 !== h2)
|
|
98
|
-
return h1 < h2 ? -1 : 1;
|
|
99
|
-
// Deep Compare
|
|
100
|
-
if (a instanceof RecursiveSet && b instanceof RecursiveSet)
|
|
101
|
-
return a.compare(b);
|
|
102
|
-
if (a instanceof Tuple && b instanceof Tuple)
|
|
103
|
-
return compareSequences(a.raw, b.raw);
|
|
104
|
-
if (Array.isArray(a) && Array.isArray(b))
|
|
105
|
-
return compareSequences(a, b);
|
|
106
|
-
return 0;
|
|
107
|
-
}
|
|
108
|
-
function compareSequences(a, b) {
|
|
109
|
-
const len = a.length;
|
|
110
|
-
if (len !== b.length)
|
|
111
|
-
return len - b.length;
|
|
112
|
-
for (let i = 0; i < len; i++) {
|
|
113
|
-
const diff = compare(a[i], b[i]);
|
|
114
|
-
if (diff !== 0)
|
|
115
|
-
return diff;
|
|
115
|
+
return true;
|
|
116
|
+
if (typeof a !== typeof b)
|
|
117
|
+
return false;
|
|
118
|
+
if (typeof a === 'number' || typeof a === 'string')
|
|
119
|
+
return a === b;
|
|
120
|
+
if (typeof a === 'object' && a !== null && b !== null) {
|
|
121
|
+
return a.equals(b);
|
|
116
122
|
}
|
|
117
|
-
return
|
|
123
|
+
return false;
|
|
118
124
|
}
|
|
119
125
|
// ============================================================================
|
|
120
|
-
//
|
|
126
|
+
// 4. TUPLE
|
|
121
127
|
// ============================================================================
|
|
122
128
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* - Creates a defensive copy of the input array.
|
|
126
|
-
* - Freezes the internal storage (`Object.freeze`).
|
|
127
|
-
* - **Note:** Freezing is **shallow**. Do not mutate nested elements.
|
|
128
|
-
* @template T - Array type of the tuple elements.
|
|
129
|
+
* An immutable, hashable sequence of values.
|
|
130
|
+
* Useful as composite keys in Maps.
|
|
129
131
|
*/
|
|
130
132
|
class Tuple {
|
|
131
|
-
#
|
|
132
|
-
hashCode;
|
|
133
|
-
constructor(...
|
|
134
|
-
this.#
|
|
135
|
-
Object.freeze(this.#
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
get length() { return this.#
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
133
|
+
#elements;
|
|
134
|
+
#hashCode;
|
|
135
|
+
constructor(...elements) {
|
|
136
|
+
this.#elements = [...elements]; // Defensive copy
|
|
137
|
+
Object.freeze(this.#elements);
|
|
138
|
+
// Compute hash immediately (tuples are immutable)
|
|
139
|
+
let h = 1;
|
|
140
|
+
for (const e of this.#elements) {
|
|
141
|
+
h = Math.imul(h, 31) + getHashCode(e);
|
|
142
|
+
}
|
|
143
|
+
this.#hashCode = h;
|
|
144
|
+
}
|
|
145
|
+
get length() { return this.#elements.length; }
|
|
146
|
+
get hashCode() { return this.#hashCode; }
|
|
147
|
+
/** Typesafe access to elements */
|
|
148
|
+
get(index) {
|
|
149
|
+
return this.#elements[index];
|
|
150
|
+
}
|
|
151
|
+
equals(other) {
|
|
152
|
+
if (this === other)
|
|
153
|
+
return true;
|
|
154
|
+
if (!(other instanceof Tuple))
|
|
155
|
+
return false;
|
|
156
|
+
if (this.hashCode !== other.hashCode)
|
|
157
|
+
return false;
|
|
158
|
+
if (this.length !== other.length)
|
|
159
|
+
return false;
|
|
160
|
+
for (let i = 0; i < this.length; i++) {
|
|
161
|
+
if (!areEqual(this.#elements[i], other.#elements[i]))
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
[Symbol.iterator]() {
|
|
167
|
+
return this.#elements[Symbol.iterator]();
|
|
168
|
+
}
|
|
169
|
+
toString() {
|
|
170
|
+
return `(${this.#elements.map(e => String(e)).join(', ')})`;
|
|
171
|
+
}
|
|
151
172
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
152
173
|
}
|
|
153
174
|
exports.Tuple = Tuple;
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// 5. RECURSIVE MAP
|
|
177
|
+
// ============================================================================
|
|
154
178
|
/**
|
|
155
|
-
*
|
|
156
|
-
* *
|
|
157
|
-
*
|
|
158
|
-
* (or it is added to another set/map).
|
|
159
|
-
* - **Runtime Checks:** Mutation methods verify frozen state via a fast boolean check.
|
|
160
|
-
* * @template T - Type of elements.
|
|
179
|
+
* A Hash Map implementation supporting complex keys (Value Semantics).
|
|
180
|
+
* * @template K Key Type
|
|
181
|
+
* @template V Value Type
|
|
161
182
|
*/
|
|
162
|
-
class
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
183
|
+
class RecursiveMap {
|
|
184
|
+
// Structure of Arrays (SoA) for cache locality
|
|
185
|
+
_keys = [];
|
|
186
|
+
_values = [];
|
|
187
|
+
_hashes = [];
|
|
188
|
+
// Open Addressing Index Table
|
|
189
|
+
// Maps (Hash & Mask) -> Index in _keys/_values + 1 (0 means empty)
|
|
190
|
+
_indices;
|
|
191
|
+
_bucketCount = 16;
|
|
192
|
+
_mask = 15;
|
|
193
|
+
_isFrozen = false;
|
|
194
|
+
_cachedHash = null;
|
|
195
|
+
constructor() {
|
|
196
|
+
this._indices = new Uint32Array(16);
|
|
197
|
+
}
|
|
198
|
+
get size() { return this._keys.length; }
|
|
199
|
+
get isFrozen() { return this._isFrozen; }
|
|
200
|
+
isEmpty() { return this._keys.length === 0; }
|
|
168
201
|
/**
|
|
169
|
-
* Creates a
|
|
170
|
-
*
|
|
171
|
-
* @param elements Initial elements.
|
|
202
|
+
* Creates a shallow mutable copy of the map.
|
|
203
|
+
* Useful when the current map is frozen.
|
|
172
204
|
*/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
else {
|
|
179
|
-
this.#elements = elements;
|
|
205
|
+
mutableCopy() {
|
|
206
|
+
const copy = new RecursiveMap();
|
|
207
|
+
copy.ensureCapacity(this.size);
|
|
208
|
+
for (let i = 0; i < this._keys.length; i++) {
|
|
209
|
+
copy.set(this._keys[i], this._values[i]);
|
|
180
210
|
}
|
|
211
|
+
return copy;
|
|
181
212
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
*
|
|
213
|
+
/**
|
|
214
|
+
* Computes the hash code of the map.
|
|
215
|
+
* Order-independent (XOR sum).
|
|
216
|
+
* @remarks ACCESSING THIS FREEZES THE MAP.
|
|
185
217
|
*/
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
s
|
|
218
|
+
get hashCode() {
|
|
219
|
+
if (this._cachedHash !== null)
|
|
220
|
+
return this._cachedHash;
|
|
221
|
+
let h = 0;
|
|
222
|
+
for (let i = 0; i < this._keys.length; i++) {
|
|
223
|
+
const k = this._keys[i];
|
|
224
|
+
const hk = this._hashes[i]; // Use cached hash for speed
|
|
225
|
+
// Force freeze on the key if it's structural (nested freeze)
|
|
226
|
+
if (k && typeof k === 'object') {
|
|
227
|
+
k.hashCode;
|
|
228
|
+
}
|
|
229
|
+
// Calculate value hash (freezes value if structural)
|
|
230
|
+
const hv = getHashCode(this._values[i]);
|
|
231
|
+
// Mix key hash and value hash
|
|
232
|
+
h ^= Math.imul(hk, 31) ^ hv;
|
|
194
233
|
}
|
|
195
|
-
|
|
234
|
+
this._cachedHash = h;
|
|
235
|
+
this._isFrozen = true;
|
|
236
|
+
return h;
|
|
196
237
|
}
|
|
197
|
-
/**
|
|
198
|
-
*
|
|
199
|
-
* Use only if you strictly guarantee invariants.
|
|
238
|
+
/**
|
|
239
|
+
* Resizes the internal lookup table if load factor > 0.75.
|
|
200
240
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
241
|
+
ensureCapacity(capacity) {
|
|
242
|
+
if (capacity * 1.33 > this._bucketCount) {
|
|
243
|
+
let target = this._bucketCount;
|
|
244
|
+
while (target * 0.75 < capacity)
|
|
245
|
+
target *= 2;
|
|
246
|
+
this._bucketCount = target;
|
|
247
|
+
this._mask = this._bucketCount - 1;
|
|
248
|
+
// Rehash all entries into new index table
|
|
249
|
+
const oldKeys = this._keys;
|
|
250
|
+
const oldHashes = this._hashes;
|
|
251
|
+
this._indices = new Uint32Array(this._bucketCount);
|
|
252
|
+
for (let i = 0; i < oldKeys.length; i++) {
|
|
253
|
+
const h = oldHashes[i];
|
|
254
|
+
let idx = h & this._mask;
|
|
255
|
+
// Linear Probing
|
|
256
|
+
while (this._indices[idx] !== 0)
|
|
257
|
+
idx = (idx + 1) & this._mask;
|
|
258
|
+
this._indices[idx] = i + 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
resize() {
|
|
263
|
+
this.ensureCapacity(this._keys.length + 1);
|
|
205
264
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
265
|
+
/**
|
|
266
|
+
* Associates the specified value with the specified key.
|
|
267
|
+
* If the map previously contained a mapping for the key, the old value is replaced.
|
|
268
|
+
*/
|
|
269
|
+
set(key, value) {
|
|
270
|
+
if (this._isFrozen)
|
|
271
|
+
throw new Error("Frozen Map");
|
|
272
|
+
if (this._keys.length >= this._bucketCount * 0.75)
|
|
273
|
+
this.resize();
|
|
274
|
+
const h = getHashCode(key);
|
|
275
|
+
let idx = h & this._mask;
|
|
276
|
+
while (true) {
|
|
277
|
+
const entry = this._indices[idx];
|
|
278
|
+
// Found empty slot -> Insert new
|
|
279
|
+
if (entry === 0) {
|
|
280
|
+
this._hashes.push(h);
|
|
281
|
+
this._keys.push(key);
|
|
282
|
+
this._values.push(value);
|
|
283
|
+
this._indices[idx] = this._keys.length; // Store 1-based index
|
|
284
|
+
this._cachedHash = null;
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
// Found existing key -> Update value
|
|
288
|
+
const ptr = entry - 1;
|
|
289
|
+
if (this._hashes[ptr] === h && areEqual(this._keys[ptr], key)) {
|
|
290
|
+
this._values[ptr] = value;
|
|
291
|
+
this._cachedHash = null;
|
|
292
|
+
return;
|
|
217
293
|
}
|
|
294
|
+
// Collision -> Next slot
|
|
295
|
+
idx = (idx + 1) & this._mask;
|
|
218
296
|
}
|
|
219
|
-
return out;
|
|
220
297
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
298
|
+
get(key) {
|
|
299
|
+
const h = getHashCode(key);
|
|
300
|
+
let idx = h & this._mask;
|
|
301
|
+
while (true) {
|
|
302
|
+
const entry = this._indices[idx];
|
|
303
|
+
if (entry === 0)
|
|
304
|
+
return undefined;
|
|
305
|
+
const ptr = entry - 1;
|
|
306
|
+
if (this._hashes[ptr] === h && areEqual(this._keys[ptr], key))
|
|
307
|
+
return this._values[ptr];
|
|
308
|
+
idx = (idx + 1) & this._mask;
|
|
224
309
|
}
|
|
225
310
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
311
|
+
delete(key) {
|
|
312
|
+
if (this._isFrozen)
|
|
313
|
+
throw new Error("Frozen Map");
|
|
314
|
+
if (this.size === 0)
|
|
315
|
+
return false;
|
|
316
|
+
const h = getHashCode(key);
|
|
317
|
+
let idx = h & this._mask;
|
|
318
|
+
while (true) {
|
|
319
|
+
const entry = this._indices[idx];
|
|
320
|
+
if (entry === 0)
|
|
321
|
+
return false;
|
|
322
|
+
const ptr = entry - 1;
|
|
323
|
+
if (this._hashes[ptr] === h && areEqual(this._keys[ptr], key)) {
|
|
324
|
+
this._cachedHash = null;
|
|
325
|
+
// 1. Remove from index table (Backshift Deletion)
|
|
326
|
+
this.removeIndex(idx);
|
|
327
|
+
// 2. Remove from dense arrays (Swap with last element to keep arrays packed)
|
|
328
|
+
const lastKey = this._keys.pop();
|
|
329
|
+
const lastVal = this._values.pop();
|
|
330
|
+
const lastHash = this._hashes.pop();
|
|
331
|
+
if (ptr < this._keys.length) {
|
|
332
|
+
this._keys[ptr] = lastKey;
|
|
333
|
+
this._values[ptr] = lastVal;
|
|
334
|
+
this._hashes[ptr] = lastHash;
|
|
335
|
+
// Update index table to point to new location of the swapped element
|
|
336
|
+
this.updateIndexForKey(lastHash, this._keys.length + 1, ptr + 1);
|
|
337
|
+
}
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
idx = (idx + 1) & this._mask;
|
|
242
341
|
}
|
|
243
|
-
this.#hashCode = h;
|
|
244
|
-
this.#isFrozen = true; // Freeze on hash access
|
|
245
|
-
return h;
|
|
246
342
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
return 0;
|
|
257
|
-
const h1 = this.hashCode;
|
|
258
|
-
const h2 = other.hashCode;
|
|
259
|
-
if (h1 !== h2)
|
|
260
|
-
return h1 < h2 ? -1 : 1;
|
|
261
|
-
const arrA = this.#elements;
|
|
262
|
-
const arrB = other.#elements;
|
|
263
|
-
const len = arrA.length;
|
|
264
|
-
if (len !== arrB.length)
|
|
265
|
-
return len - arrB.length;
|
|
266
|
-
for (let i = 0; i < len; i++) {
|
|
267
|
-
const diff = compare(arrA[i], arrB[i]);
|
|
268
|
-
if (diff !== 0)
|
|
269
|
-
return diff;
|
|
343
|
+
/** Updates the index table when an element is moved in the dense arrays. */
|
|
344
|
+
updateIndexForKey(hash, oldLoc, newLoc) {
|
|
345
|
+
let idx = hash & this._mask;
|
|
346
|
+
while (true) {
|
|
347
|
+
if (this._indices[idx] === oldLoc) {
|
|
348
|
+
this._indices[idx] = newLoc;
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
idx = (idx + 1) & this._mask;
|
|
270
352
|
}
|
|
271
|
-
return 0;
|
|
272
353
|
}
|
|
273
|
-
equals(other) { return this.compare(other) === 0; }
|
|
274
354
|
/**
|
|
275
|
-
*
|
|
276
|
-
* Uses
|
|
355
|
+
* Removes an entry from the hash table and repairs the probe chain.
|
|
356
|
+
* Uses "Backshift Deletion" (Robin Hood style) to fill the hole.
|
|
277
357
|
*/
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
358
|
+
removeIndex(holeIdx) {
|
|
359
|
+
let i = (holeIdx + 1) & this._mask;
|
|
360
|
+
while (this._indices[i] !== 0) {
|
|
361
|
+
const entry = this._indices[i];
|
|
362
|
+
const ptr = entry - 1;
|
|
363
|
+
const h = this._hashes[ptr];
|
|
364
|
+
const ideal = h & this._mask;
|
|
365
|
+
const distHole = (holeIdx - ideal + this._bucketCount) & this._mask;
|
|
366
|
+
const distI = (i - ideal + this._bucketCount) & this._mask;
|
|
367
|
+
// If the element at 'i' belongs to a bucket logically before the hole,
|
|
368
|
+
// or is further away from its ideal slot than the hole is, move it back.
|
|
369
|
+
if (distHole < distI) {
|
|
370
|
+
this._indices[holeIdx] = entry;
|
|
371
|
+
holeIdx = i;
|
|
285
372
|
}
|
|
373
|
+
i = (i + 1) & this._mask;
|
|
374
|
+
}
|
|
375
|
+
this._indices[holeIdx] = 0;
|
|
376
|
+
}
|
|
377
|
+
equals(other) {
|
|
378
|
+
if (this === other)
|
|
379
|
+
return true;
|
|
380
|
+
if (!(other instanceof RecursiveMap))
|
|
286
381
|
return false;
|
|
382
|
+
if (this.size !== other.size)
|
|
383
|
+
return false;
|
|
384
|
+
if (this.hashCode !== other.hashCode)
|
|
385
|
+
return false;
|
|
386
|
+
for (let i = 0; i < this._keys.length; i++) {
|
|
387
|
+
const val = other.get(this._keys[i]);
|
|
388
|
+
if (val === undefined || !areEqual(this._values[i], val))
|
|
389
|
+
return false;
|
|
287
390
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
*[Symbol.iterator]() {
|
|
394
|
+
for (let i = 0; i < this._keys.length; i++)
|
|
395
|
+
yield [this._keys[i], this._values[i]];
|
|
396
|
+
}
|
|
397
|
+
toString() {
|
|
398
|
+
if (this.isEmpty())
|
|
399
|
+
return `RecursiveMap(0) {}`;
|
|
400
|
+
// Sort only for visual output stability
|
|
401
|
+
const entries = this._keys.map((k, i) => ({ key: k, value: this._values[i] }));
|
|
402
|
+
entries.sort((a, b) => compareVisualLogic(a.key, b.key));
|
|
403
|
+
const body = entries
|
|
404
|
+
.map(e => ` ${String(e.key)} => ${String(e.value)}`)
|
|
405
|
+
.join(',\n');
|
|
406
|
+
return `RecursiveMap(${this.size}) {\n${body}\n}`;
|
|
407
|
+
}
|
|
408
|
+
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
409
|
+
}
|
|
410
|
+
exports.RecursiveMap = RecursiveMap;
|
|
411
|
+
// ============================================================================
|
|
412
|
+
// 6. RECURSIVE SET
|
|
413
|
+
// ============================================================================
|
|
414
|
+
/**
|
|
415
|
+
* A Hash Set implementation supporting Value Semantics.
|
|
416
|
+
* * Features:
|
|
417
|
+
* - O(1) amortized Add/Has/Remove.
|
|
418
|
+
* - Deep equality for recursive structures.
|
|
419
|
+
* - Frozen state protection after hashing.
|
|
420
|
+
*/
|
|
421
|
+
class RecursiveSet {
|
|
422
|
+
_values = [];
|
|
423
|
+
_hashes = [];
|
|
424
|
+
_indices;
|
|
425
|
+
_bucketCount;
|
|
426
|
+
_mask;
|
|
427
|
+
_xorHash = 0;
|
|
428
|
+
_isFrozen = false;
|
|
429
|
+
LOAD_FACTOR = 0.75;
|
|
430
|
+
MIN_BUCKETS = 16;
|
|
431
|
+
constructor(...initialData) {
|
|
432
|
+
this._bucketCount = this.MIN_BUCKETS;
|
|
433
|
+
if (initialData.length > 0) {
|
|
434
|
+
const target = Math.ceil(initialData.length / this.LOAD_FACTOR);
|
|
435
|
+
while (this._bucketCount < target)
|
|
436
|
+
this._bucketCount <<= 1;
|
|
298
437
|
}
|
|
299
|
-
|
|
438
|
+
this._mask = this._bucketCount - 1;
|
|
439
|
+
this._indices = new Uint32Array(this._bucketCount);
|
|
440
|
+
for (const item of initialData)
|
|
441
|
+
this.add(item);
|
|
442
|
+
}
|
|
443
|
+
get size() { return this._values.length; }
|
|
444
|
+
get isFrozen() { return this._isFrozen; }
|
|
445
|
+
isEmpty() { return this._values.length === 0; }
|
|
446
|
+
get hashCode() {
|
|
447
|
+
if (this._isFrozen)
|
|
448
|
+
return this._xorHash;
|
|
449
|
+
// Deep-freeze trigger: touch child hashCodes once
|
|
450
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
451
|
+
const v = this._values[i];
|
|
452
|
+
if (v && typeof v === 'object') {
|
|
453
|
+
v.hashCode; // triggers freeze of nested RecursiveSet/Map/Tuple/etc.
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
this._isFrozen = true;
|
|
457
|
+
return this._xorHash;
|
|
300
458
|
}
|
|
301
459
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
304
|
-
* Complexity: $O(N)$ (Array splice).
|
|
460
|
+
* Ensures the hash table has enough buckets to hold `capacity` elements
|
|
461
|
+
* without exceeding the load factor.
|
|
305
462
|
*/
|
|
306
|
-
|
|
307
|
-
this
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
463
|
+
ensureCapacity(capacity) {
|
|
464
|
+
if (capacity * 1.33 > this._bucketCount) {
|
|
465
|
+
let target = this._bucketCount;
|
|
466
|
+
while (target * 0.75 < capacity)
|
|
467
|
+
target *= 2;
|
|
468
|
+
this._bucketCount = target;
|
|
469
|
+
this._mask = this._bucketCount - 1;
|
|
470
|
+
const oldValues = this._values;
|
|
471
|
+
const oldHashes = this._hashes;
|
|
472
|
+
this._indices = new Uint32Array(this._bucketCount);
|
|
473
|
+
for (let i = 0; i < oldValues.length; i++) {
|
|
474
|
+
const h = oldHashes[i];
|
|
475
|
+
let idx = h & this._mask;
|
|
476
|
+
while (this._indices[idx] !== 0)
|
|
477
|
+
idx = (idx + 1) & this._mask;
|
|
478
|
+
this._indices[idx] = i + 1;
|
|
316
479
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
resize() {
|
|
483
|
+
this.ensureCapacity(this._values.length + 1);
|
|
484
|
+
}
|
|
485
|
+
/** Updates the index table when a value moves in the dense array */
|
|
486
|
+
updateIndexForValue(hash, oldLoc, newLoc) {
|
|
487
|
+
let idx = hash & this._mask;
|
|
488
|
+
while (true) {
|
|
489
|
+
if (this._indices[idx] === oldLoc) {
|
|
490
|
+
this._indices[idx] = newLoc;
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
idx = (idx + 1) & this._mask;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
/** Repairs the probe chain after deletion (Backshifting) */
|
|
497
|
+
removeIndex(holeIdx) {
|
|
498
|
+
let i = (holeIdx + 1) & this._mask;
|
|
499
|
+
while (this._indices[i] !== 0) {
|
|
500
|
+
const entry = this._indices[i];
|
|
501
|
+
const valPtr = entry - 1;
|
|
502
|
+
const h = this._hashes[valPtr];
|
|
503
|
+
const idealIdx = h & this._mask;
|
|
504
|
+
const distToHole = (holeIdx - idealIdx + this._bucketCount) & this._mask;
|
|
505
|
+
const distToI = (i - idealIdx + this._bucketCount) & this._mask;
|
|
506
|
+
if (distToHole < distToI) {
|
|
507
|
+
this._indices[holeIdx] = entry;
|
|
508
|
+
holeIdx = i;
|
|
331
509
|
}
|
|
510
|
+
i = (i + 1) & this._mask;
|
|
332
511
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
512
|
+
this._indices[holeIdx] = 0;
|
|
513
|
+
}
|
|
514
|
+
static compareVisual(a, b) {
|
|
515
|
+
return compareVisualLogic(a, b);
|
|
336
516
|
}
|
|
337
517
|
/**
|
|
338
|
-
*
|
|
518
|
+
* Adds an element to the set.
|
|
519
|
+
* @returns void to signal no chaining (performance).
|
|
339
520
|
* @throws if set is frozen.
|
|
340
|
-
* Complexity: $O(N)$.
|
|
341
521
|
*/
|
|
342
|
-
|
|
343
|
-
this
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
522
|
+
add(e) {
|
|
523
|
+
if (this._isFrozen)
|
|
524
|
+
throw new Error("Frozen Set modified.");
|
|
525
|
+
if (this._values.length >= this._bucketCount * this.LOAD_FACTOR)
|
|
526
|
+
this.resize();
|
|
527
|
+
const h = getHashCode(e);
|
|
528
|
+
let idx = h & this._mask;
|
|
529
|
+
while (true) {
|
|
530
|
+
const entry = this._indices[idx];
|
|
531
|
+
if (entry === 0) {
|
|
532
|
+
this._hashes.push(h);
|
|
533
|
+
this._values.push(e);
|
|
534
|
+
this._indices[idx] = this._values.length;
|
|
535
|
+
this._xorHash ^= h;
|
|
536
|
+
return;
|
|
353
537
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
538
|
+
const valIndex = entry - 1;
|
|
539
|
+
if (this._hashes[valIndex] === h && areEqual(this._values[valIndex], e))
|
|
540
|
+
return;
|
|
541
|
+
idx = (idx + 1) & this._mask;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
has(element) {
|
|
545
|
+
const h = getHashCode(element);
|
|
546
|
+
let idx = h & this._mask;
|
|
547
|
+
while (true) {
|
|
548
|
+
const entry = this._indices[idx];
|
|
549
|
+
if (entry === 0)
|
|
550
|
+
return false;
|
|
551
|
+
const valIndex = entry - 1;
|
|
552
|
+
if (this._hashes[valIndex] === h && areEqual(this._values[valIndex], element))
|
|
553
|
+
return true;
|
|
554
|
+
idx = (idx + 1) & this._mask;
|
|
358
555
|
}
|
|
359
|
-
return this;
|
|
360
556
|
}
|
|
361
|
-
|
|
362
|
-
this
|
|
363
|
-
|
|
364
|
-
this
|
|
365
|
-
|
|
557
|
+
remove(e) {
|
|
558
|
+
if (this._isFrozen)
|
|
559
|
+
throw new Error("Frozen Set modified.");
|
|
560
|
+
if (this._values.length === 0)
|
|
561
|
+
return;
|
|
562
|
+
const h = getHashCode(e);
|
|
563
|
+
let idx = h & this._mask;
|
|
564
|
+
while (true) {
|
|
565
|
+
const entry = this._indices[idx];
|
|
566
|
+
if (entry === 0)
|
|
567
|
+
return;
|
|
568
|
+
const valIndex = entry - 1;
|
|
569
|
+
if (this._hashes[valIndex] === h && areEqual(this._values[valIndex], e)) {
|
|
570
|
+
this._xorHash ^= h;
|
|
571
|
+
this.removeIndex(idx);
|
|
572
|
+
const lastVal = this._values.pop();
|
|
573
|
+
const lastHash = this._hashes.pop();
|
|
574
|
+
if (valIndex < this._values.length) {
|
|
575
|
+
this._values[valIndex] = lastVal;
|
|
576
|
+
this._hashes[valIndex] = lastHash;
|
|
577
|
+
this.updateIndexForValue(lastHash, this._values.length + 1, valIndex + 1);
|
|
578
|
+
}
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
idx = (idx + 1) & this._mask;
|
|
582
|
+
}
|
|
366
583
|
}
|
|
367
|
-
|
|
584
|
+
clone() {
|
|
368
585
|
const s = new RecursiveSet();
|
|
369
|
-
s
|
|
586
|
+
if (s._bucketCount !== this._bucketCount)
|
|
587
|
+
s.ensureCapacity(this.size);
|
|
588
|
+
if (s._bucketCount === this._bucketCount) {
|
|
589
|
+
s._indices.set(this._indices);
|
|
590
|
+
s._values = this._values.slice();
|
|
591
|
+
s._hashes = this._hashes.slice();
|
|
592
|
+
s._xorHash = this._xorHash;
|
|
593
|
+
}
|
|
594
|
+
else {
|
|
595
|
+
for (const v of this._values)
|
|
596
|
+
s.add(v);
|
|
597
|
+
}
|
|
370
598
|
return s;
|
|
371
599
|
}
|
|
372
|
-
|
|
373
|
-
// ===
|
|
600
|
+
// ========================================================================
|
|
601
|
+
// === FUNCTIONAL METHODS (Optimized) ===
|
|
602
|
+
// ========================================================================
|
|
374
603
|
/**
|
|
375
|
-
*
|
|
376
|
-
*
|
|
377
|
-
* Complexity: $O(|A| + |B|)$.
|
|
604
|
+
* Tests whether all elements in the set pass the test implemented by the provided function.
|
|
605
|
+
* Uses internal iteration for max speed.
|
|
378
606
|
*/
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const lenA = A.length, lenB = B.length;
|
|
385
|
-
while (i < lenA && j < lenB) {
|
|
386
|
-
const cmp = compare(A[i], B[j]);
|
|
387
|
-
if (cmp < 0)
|
|
388
|
-
res.push(A[i++]);
|
|
389
|
-
else if (cmp > 0)
|
|
390
|
-
res.push(B[j++]);
|
|
391
|
-
else {
|
|
392
|
-
res.push(A[i++]);
|
|
393
|
-
j++;
|
|
394
|
-
}
|
|
607
|
+
every(predicate) {
|
|
608
|
+
// Direct array access avoids iterator generator overhead
|
|
609
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
610
|
+
if (!predicate(this._values[i]))
|
|
611
|
+
return false;
|
|
395
612
|
}
|
|
396
|
-
|
|
397
|
-
res.push(A[i++]);
|
|
398
|
-
while (j < lenB)
|
|
399
|
-
res.push(B[j++]);
|
|
400
|
-
return RecursiveSet.fromSortedUnsafe(res);
|
|
613
|
+
return true;
|
|
401
614
|
}
|
|
402
615
|
/**
|
|
403
|
-
*
|
|
404
|
-
* Implementation: Synchronous Scan.
|
|
405
|
-
* Complexity: $O(|A| + |B|)$.
|
|
616
|
+
* Tests whether at least one element in the set passes the test.
|
|
406
617
|
*/
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
let i = 0, j = 0;
|
|
412
|
-
const lenA = A.length, lenB = B.length;
|
|
413
|
-
while (i < lenA && j < lenB) {
|
|
414
|
-
const cmp = compare(A[i], B[j]);
|
|
415
|
-
if (cmp < 0)
|
|
416
|
-
i++;
|
|
417
|
-
else if (cmp > 0)
|
|
418
|
-
j++;
|
|
419
|
-
else {
|
|
420
|
-
res.push(A[i++]);
|
|
421
|
-
j++;
|
|
422
|
-
}
|
|
618
|
+
some(predicate) {
|
|
619
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
620
|
+
if (predicate(this._values[i]))
|
|
621
|
+
return true;
|
|
423
622
|
}
|
|
424
|
-
return
|
|
623
|
+
return false;
|
|
425
624
|
}
|
|
426
625
|
/**
|
|
427
|
-
*
|
|
428
|
-
*
|
|
626
|
+
* Creates a new RecursiveSet populated with the results of calling a provided function.
|
|
627
|
+
* Pre-allocates storage to prevent resizing.
|
|
429
628
|
*/
|
|
430
|
-
|
|
431
|
-
const
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const lenA = A.length, lenB = B.length;
|
|
436
|
-
while (i < lenA && j < lenB) {
|
|
437
|
-
const cmp = compare(A[i], B[j]);
|
|
438
|
-
if (cmp < 0)
|
|
439
|
-
res.push(A[i++]);
|
|
440
|
-
else if (cmp > 0)
|
|
441
|
-
j++;
|
|
442
|
-
else {
|
|
443
|
-
i++;
|
|
444
|
-
j++;
|
|
445
|
-
}
|
|
629
|
+
map(callback) {
|
|
630
|
+
const result = new RecursiveSet();
|
|
631
|
+
result.ensureCapacity(this.size);
|
|
632
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
633
|
+
result.add(callback(this._values[i]));
|
|
446
634
|
}
|
|
447
|
-
|
|
448
|
-
res.push(A[i++]);
|
|
449
|
-
return RecursiveSet.fromSortedUnsafe(res);
|
|
635
|
+
return result;
|
|
450
636
|
}
|
|
451
637
|
/**
|
|
452
|
-
*
|
|
453
|
-
* Complexity: $O(|A| + |B|)$.
|
|
638
|
+
* Creates a new RecursiveSet with all elements that pass the test.
|
|
454
639
|
*/
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
while (i < lenA && j < lenB) {
|
|
462
|
-
const cmp = compare(A[i], B[j]);
|
|
463
|
-
if (cmp < 0)
|
|
464
|
-
res.push(A[i++]);
|
|
465
|
-
else if (cmp > 0)
|
|
466
|
-
res.push(B[j++]);
|
|
467
|
-
else {
|
|
468
|
-
i++;
|
|
469
|
-
j++;
|
|
470
|
-
}
|
|
640
|
+
filter(predicate) {
|
|
641
|
+
const result = new RecursiveSet();
|
|
642
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
643
|
+
const v = this._values[i];
|
|
644
|
+
if (predicate(v))
|
|
645
|
+
result.add(v);
|
|
471
646
|
}
|
|
472
|
-
|
|
473
|
-
res.push(A[i++]);
|
|
474
|
-
while (j < lenB)
|
|
475
|
-
res.push(B[j++]);
|
|
476
|
-
return RecursiveSet.fromSortedUnsafe(res);
|
|
647
|
+
return result;
|
|
477
648
|
}
|
|
478
649
|
/**
|
|
479
|
-
*
|
|
480
|
-
* Complexity: $O(|A| \cdot |B| \cdot \log(|A| \cdot |B|))$ (due to sorting).
|
|
650
|
+
* Executes a reducer function on each element.
|
|
481
651
|
*/
|
|
652
|
+
reduce(callback, initialValue) {
|
|
653
|
+
let accumulator = initialValue;
|
|
654
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
655
|
+
accumulator = callback(accumulator, this._values[i]);
|
|
656
|
+
}
|
|
657
|
+
return accumulator;
|
|
658
|
+
}
|
|
659
|
+
// === SET OPERATIONS ===
|
|
660
|
+
union(other) {
|
|
661
|
+
const res = this.clone();
|
|
662
|
+
for (const v of other._values)
|
|
663
|
+
res.add(v);
|
|
664
|
+
return res;
|
|
665
|
+
}
|
|
666
|
+
intersection(other) {
|
|
667
|
+
const res = new RecursiveSet();
|
|
668
|
+
// Iterate over smaller set for O(min(N, M))
|
|
669
|
+
const [small, large] = this.size < other.size ? [this, other] : [other, this];
|
|
670
|
+
for (const v of small._values) {
|
|
671
|
+
if (large.has(v))
|
|
672
|
+
res.add(v);
|
|
673
|
+
}
|
|
674
|
+
return res;
|
|
675
|
+
}
|
|
676
|
+
difference(other) {
|
|
677
|
+
if (other.isEmpty())
|
|
678
|
+
return this.clone();
|
|
679
|
+
const res = new RecursiveSet();
|
|
680
|
+
for (const v of this._values) {
|
|
681
|
+
if (!other.has(v))
|
|
682
|
+
res.add(v);
|
|
683
|
+
}
|
|
684
|
+
return res;
|
|
685
|
+
}
|
|
686
|
+
symmetricDifference(other) {
|
|
687
|
+
const res = new RecursiveSet();
|
|
688
|
+
for (const v of this._values) {
|
|
689
|
+
if (!other.has(v))
|
|
690
|
+
res.add(v);
|
|
691
|
+
}
|
|
692
|
+
for (const v of other._values) {
|
|
693
|
+
if (!this.has(v))
|
|
694
|
+
res.add(v);
|
|
695
|
+
}
|
|
696
|
+
return res;
|
|
697
|
+
}
|
|
482
698
|
cartesianProduct(other) {
|
|
483
|
-
const result =
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
for (let i = 0; i < lenA; i++) {
|
|
489
|
-
const a = arrA[i];
|
|
490
|
-
for (let j = 0; j < lenB; j++) {
|
|
491
|
-
result.push(new Tuple(a, arrB[j]));
|
|
699
|
+
const result = new RecursiveSet();
|
|
700
|
+
result.ensureCapacity(this.size * other.size);
|
|
701
|
+
for (const a of this) {
|
|
702
|
+
for (const b of other) {
|
|
703
|
+
result.add(new Tuple(a, b));
|
|
492
704
|
}
|
|
493
705
|
}
|
|
494
|
-
|
|
495
|
-
result.sort(compare);
|
|
496
|
-
// But uniqueness is guaranteed mathematically, so use unsafe create
|
|
497
|
-
return RecursiveSet.fromSortedUnsafe(result);
|
|
706
|
+
return result;
|
|
498
707
|
}
|
|
499
708
|
/**
|
|
500
|
-
* Computes the Power Set
|
|
501
|
-
*
|
|
502
|
-
* @throws if size > 20.
|
|
709
|
+
* Computes the Power Set.
|
|
710
|
+
* Warning: Complexity is O(2^N). Use only for small N.
|
|
711
|
+
* @throws Error if size > 20 to prevent memory exhaustion and 32-bit shift overflow.
|
|
503
712
|
*/
|
|
504
713
|
powerset() {
|
|
505
|
-
const n = this.
|
|
506
|
-
if (n > 20)
|
|
507
|
-
throw new Error(
|
|
508
|
-
|
|
714
|
+
const n = this._values.length;
|
|
715
|
+
if (n > 20) {
|
|
716
|
+
throw new Error(`Powerset size too large (N=${n}). Max supported is 20.`);
|
|
717
|
+
}
|
|
509
718
|
const max = 1 << n;
|
|
719
|
+
const result = new RecursiveSet();
|
|
720
|
+
result.ensureCapacity(max);
|
|
510
721
|
for (let i = 0; i < max; i++) {
|
|
511
|
-
const
|
|
722
|
+
const subset = new RecursiveSet();
|
|
512
723
|
for (let j = 0; j < n; j++) {
|
|
513
|
-
if (i & (1 << j))
|
|
514
|
-
|
|
724
|
+
if ((i & (1 << j)) !== 0) {
|
|
725
|
+
subset.add(this._values[j]);
|
|
726
|
+
}
|
|
515
727
|
}
|
|
516
|
-
|
|
517
|
-
subsets.push(RecursiveSet.fromSortedUnsafe(subsetElements));
|
|
728
|
+
result.add(subset);
|
|
518
729
|
}
|
|
519
|
-
|
|
520
|
-
|
|
730
|
+
return result;
|
|
731
|
+
}
|
|
732
|
+
pickRandom() {
|
|
733
|
+
if (this._values.length === 0)
|
|
734
|
+
return undefined;
|
|
735
|
+
return this._values[Math.floor(Math.random() * this._values.length)];
|
|
521
736
|
}
|
|
522
737
|
isSubset(other) {
|
|
523
738
|
if (this.size > other.size)
|
|
524
739
|
return false;
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
if (cmp < 0)
|
|
740
|
+
if (this === other)
|
|
741
|
+
return true;
|
|
742
|
+
for (const item of this._values) {
|
|
743
|
+
if (!other.has(item))
|
|
530
744
|
return false;
|
|
531
|
-
if (cmp > 0)
|
|
532
|
-
j++;
|
|
533
|
-
else {
|
|
534
|
-
i++;
|
|
535
|
-
j++;
|
|
536
|
-
}
|
|
537
745
|
}
|
|
538
|
-
return
|
|
746
|
+
return true;
|
|
747
|
+
}
|
|
748
|
+
isSuperset(other) {
|
|
749
|
+
return other.isSubset(this);
|
|
750
|
+
}
|
|
751
|
+
[Symbol.iterator]() { return this._values[Symbol.iterator](); }
|
|
752
|
+
equals(other) {
|
|
753
|
+
if (this === other)
|
|
754
|
+
return true;
|
|
755
|
+
if (!(other instanceof RecursiveSet))
|
|
756
|
+
return false;
|
|
757
|
+
if (this.size !== other.size)
|
|
758
|
+
return false;
|
|
759
|
+
if (this.hashCode !== other.hashCode)
|
|
760
|
+
return false;
|
|
761
|
+
for (const v of this._values) {
|
|
762
|
+
if (!other.has(v))
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
toString() {
|
|
768
|
+
// Use compareVisual exclusively for user-facing output
|
|
769
|
+
const sorted = [...this._values].sort(compareVisualLogic);
|
|
770
|
+
return `{${sorted.map(String).join(', ')}}`;
|
|
539
771
|
}
|
|
540
|
-
isSuperset(other) { return other.isSubset(this); }
|
|
541
|
-
/** Iterates over set elements in sorted order. */
|
|
542
|
-
*[Symbol.iterator]() { yield* this.#elements; }
|
|
543
|
-
/** Returns string representation e.g. "{1, 2, 3}". */
|
|
544
|
-
toString() { return `{${this.#elements.join(', ')}}`; }
|
|
545
|
-
/** Custom inspection for Node.js console to avoid printing internal state. */
|
|
546
772
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
547
773
|
}
|
|
548
774
|
exports.RecursiveSet = RecursiveSet;
|
|
549
|
-
//
|
|
550
|
-
|
|
775
|
+
// ============================================================================
|
|
776
|
+
// 7. PUBLIC EXPORTS
|
|
777
|
+
// ============================================================================
|
|
551
778
|
function emptySet() { return new RecursiveSet(); }
|
|
552
|
-
|
|
553
|
-
|
|
779
|
+
function singleton(el) { return new RecursiveSet(el); }
|
|
780
|
+
const hashValue = getHashCode;
|
|
781
|
+
exports.hashValue = hashValue;
|
|
554
782
|
//# sourceMappingURL=index.js.map
|