recursive-set 2.2.1 → 4.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 +83 -137
- package/dist/cjs/index.js +427 -209
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +41 -23
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +424 -204
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -1,284 +1,502 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
/**
|
|
3
|
+
* @module recursive-set
|
|
4
|
+
* High-Performance Mutable Recursive Set backed by Sorted Arrays.
|
|
5
|
+
* Optimized for small sets, structural equality, and deterministic hashing.
|
|
6
|
+
*/
|
|
5
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.RecursiveSet = void 0;
|
|
8
|
+
exports.RecursiveSet = exports.Tuple = void 0;
|
|
7
9
|
exports.emptySet = emptySet;
|
|
8
10
|
exports.singleton = singleton;
|
|
9
11
|
exports.fromIterable = fromIterable;
|
|
10
|
-
|
|
12
|
+
// === HASHING ENGINE ===
|
|
11
13
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
|
|
14
|
+
* FNV-1a Hash implementation for strings.
|
|
15
|
+
* Constants inlined for V8 optimization.
|
|
16
|
+
*/
|
|
17
|
+
function hashString(str) {
|
|
18
|
+
let hash = 0x811c9dc5;
|
|
19
|
+
for (let i = 0; i < str.length; i++) {
|
|
20
|
+
hash ^= str.charCodeAt(i);
|
|
21
|
+
hash = Math.imul(hash, 0x01000193);
|
|
22
|
+
}
|
|
23
|
+
return hash >>> 0;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Universal Hash Function.
|
|
27
|
+
* Calculates deterministic hashes for Primitives, Sequences (Order Dependent), and Sets.
|
|
28
|
+
*/
|
|
29
|
+
function hashValue(val) {
|
|
30
|
+
if (typeof val === 'string')
|
|
31
|
+
return hashString(val);
|
|
32
|
+
if (typeof val === 'number')
|
|
33
|
+
return val | 0;
|
|
34
|
+
// Fast Path: Objects with cached hash
|
|
35
|
+
if (val instanceof RecursiveSet)
|
|
36
|
+
return val.hashCode;
|
|
37
|
+
if (val instanceof Tuple)
|
|
38
|
+
return val.hashCode;
|
|
39
|
+
// Arrays: Treated as sequences (Rolling Hash)
|
|
40
|
+
if (Array.isArray(val)) {
|
|
41
|
+
let h = 0;
|
|
42
|
+
for (let i = 0; i < val.length; i++) {
|
|
43
|
+
let v = val[i];
|
|
44
|
+
let vh = 0;
|
|
45
|
+
if (typeof v === 'string')
|
|
46
|
+
vh = hashString(v);
|
|
47
|
+
else
|
|
48
|
+
vh = hashValue(v);
|
|
49
|
+
h = Math.imul(31, h) + vh;
|
|
50
|
+
}
|
|
51
|
+
return h >>> 0;
|
|
52
|
+
}
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
// === COMPARATOR ===
|
|
56
|
+
/**
|
|
57
|
+
* High-performance comparator with hash short-circuiting.
|
|
58
|
+
* Order: Primitives (0) < Sequences (1) < Sets (2)
|
|
59
|
+
*/
|
|
60
|
+
function compare(a, b) {
|
|
61
|
+
if (a === b)
|
|
62
|
+
return 0;
|
|
63
|
+
// 1. Hash Short-Circuit
|
|
64
|
+
// Using interface casting avoids runtime overhead of 'in' operator checks
|
|
65
|
+
const aH = a?.hashCode;
|
|
66
|
+
const bH = b?.hashCode;
|
|
67
|
+
const ha = (aH !== undefined) ? aH : hashValue(a);
|
|
68
|
+
const hb = (bH !== undefined) ? bH : hashValue(b);
|
|
69
|
+
if (ha !== hb)
|
|
70
|
+
return ha < hb ? -1 : 1;
|
|
71
|
+
// 2. Primitive Value Check
|
|
72
|
+
const typeA = typeof a;
|
|
73
|
+
const typeB = typeof b;
|
|
74
|
+
if (typeA === 'string' && typeB === 'string')
|
|
75
|
+
return a < b ? -1 : 1;
|
|
76
|
+
if (typeA === 'number' && typeB === 'number')
|
|
77
|
+
return a < b ? -1 : 1;
|
|
78
|
+
// 3. Structural Type Check
|
|
79
|
+
const isSetA = a instanceof RecursiveSet;
|
|
80
|
+
const isSetB = b instanceof RecursiveSet;
|
|
81
|
+
if (isSetA && isSetB) {
|
|
82
|
+
return a.compare(b);
|
|
83
|
+
}
|
|
84
|
+
const isArrA = Array.isArray(a);
|
|
85
|
+
const isArrB = Array.isArray(b);
|
|
86
|
+
const isTupA = a instanceof Tuple;
|
|
87
|
+
const isTupB = b instanceof Tuple;
|
|
88
|
+
const isSeqA = isArrA || isTupA;
|
|
89
|
+
const isSeqB = isArrB || isTupB;
|
|
90
|
+
// Sort by Type Group if types differ
|
|
91
|
+
if (isSetA !== isSetB || isSeqA !== isSeqB) {
|
|
92
|
+
const scoreA = isSetA ? 2 : isSeqA ? 1 : 0;
|
|
93
|
+
const scoreB = isSetB ? 2 : isSeqB ? 1 : 0;
|
|
94
|
+
return scoreA - scoreB;
|
|
95
|
+
}
|
|
96
|
+
// 4. Sequence Comparison (Array/Tuple)
|
|
97
|
+
if (isSeqA && isSeqB) {
|
|
98
|
+
const valA = isTupA ? a.values : a;
|
|
99
|
+
const valB = isTupB ? b.values : b;
|
|
100
|
+
const len = valA.length;
|
|
101
|
+
if (len !== valB.length)
|
|
102
|
+
return len - valB.length;
|
|
103
|
+
for (let i = 0; i < len; i++) {
|
|
104
|
+
const diff = compare(valA[i], valB[i]);
|
|
105
|
+
if (diff !== 0)
|
|
106
|
+
return diff;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
// Fallback for safe types
|
|
111
|
+
return a < b ? -1 : 1;
|
|
112
|
+
}
|
|
113
|
+
// === CLASSES ===
|
|
114
|
+
/**
|
|
115
|
+
* Immutable wrapper for sequence values.
|
|
116
|
+
* Useful when strict typing for sequences is required.
|
|
117
|
+
*/
|
|
118
|
+
class Tuple {
|
|
119
|
+
values;
|
|
120
|
+
hashCode;
|
|
121
|
+
constructor(...values) {
|
|
122
|
+
this.values = values;
|
|
123
|
+
this.hashCode = hashValue(values);
|
|
124
|
+
}
|
|
125
|
+
get length() { return this.values.length; }
|
|
126
|
+
get(i) { return this.values[i]; }
|
|
127
|
+
*[Symbol.iterator]() { yield* this.values; }
|
|
128
|
+
toString() { return `(${this.values.join(', ')})`; }
|
|
129
|
+
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
130
|
+
}
|
|
131
|
+
exports.Tuple = Tuple;
|
|
132
|
+
/**
|
|
133
|
+
* A Set implementation that supports deep structural equality and efficient hashing.
|
|
134
|
+
* Internally backed by a sorted array for optimal CPU cache locality on small sets.
|
|
15
135
|
*/
|
|
16
136
|
class RecursiveSet {
|
|
17
|
-
// Underlying persistent data structure
|
|
18
|
-
_tree;
|
|
19
|
-
// Internal XOR-based hash for O(1) inequality checks
|
|
20
|
-
_hash = 0;
|
|
21
137
|
/**
|
|
22
|
-
*
|
|
23
|
-
* Handles primitives, RecursiveSets, and deep structural equality.
|
|
138
|
+
* Internal storage. Public for inlining access within the module, but treated as private API.
|
|
24
139
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (isSetA !== isSetB)
|
|
33
|
-
return isSetA ? 1 : -1;
|
|
34
|
-
// 3. Primitives
|
|
35
|
-
if (!isSetA) {
|
|
36
|
-
if (typeof a !== typeof b)
|
|
37
|
-
return typeof a > typeof b ? 1 : -1;
|
|
38
|
-
if (a < b)
|
|
39
|
-
return -1;
|
|
40
|
-
if (a > b)
|
|
41
|
-
return 1;
|
|
42
|
-
return 0;
|
|
140
|
+
_elements;
|
|
141
|
+
_hashCode = null;
|
|
142
|
+
static compare(a, b) { return compare(a, b); }
|
|
143
|
+
constructor(...elements) {
|
|
144
|
+
if (elements.length === 0) {
|
|
145
|
+
this._elements = [];
|
|
146
|
+
this._hashCode = 0;
|
|
43
147
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
return sizeA < sizeB ? -1 : 1;
|
|
49
|
-
// Hash mismatch implies inequality (O(1))
|
|
50
|
-
if (a._hash !== b._hash)
|
|
51
|
-
return a._hash < b._hash ? -1 : 1;
|
|
52
|
-
// Deep structural comparison using internal iterators (low-level optimization)
|
|
53
|
-
let iterA = a._tree.begin;
|
|
54
|
-
let iterB = b._tree.begin;
|
|
55
|
-
while (iterA.valid && iterB.valid) {
|
|
56
|
-
const cmp = RecursiveSet.compare(iterA.key, iterB.key);
|
|
57
|
-
if (cmp !== 0)
|
|
58
|
-
return cmp;
|
|
59
|
-
iterA.next();
|
|
60
|
-
iterB.next();
|
|
148
|
+
else {
|
|
149
|
+
this._elements = elements;
|
|
150
|
+
this._elements.sort(compare);
|
|
151
|
+
this._unique();
|
|
61
152
|
}
|
|
62
|
-
return 0;
|
|
63
153
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
154
|
+
_unique() {
|
|
155
|
+
const arr = this._elements;
|
|
156
|
+
const len = arr.length;
|
|
157
|
+
if (len < 2)
|
|
158
|
+
return;
|
|
159
|
+
let write = 1;
|
|
160
|
+
for (let read = 1; read < len; read++) {
|
|
161
|
+
if (compare(arr[read], arr[read - 1]) !== 0) {
|
|
162
|
+
arr[write++] = arr[read];
|
|
163
|
+
}
|
|
68
164
|
}
|
|
165
|
+
arr.length = write;
|
|
69
166
|
}
|
|
70
|
-
// === Copy-on-Write Support ===
|
|
71
167
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
168
|
+
* Calculates the hash code for the set.
|
|
169
|
+
* Uses a rolling hash over sorted elements, ensuring determinstic results for equal sets.
|
|
74
170
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
171
|
+
get hashCode() {
|
|
172
|
+
if (this._hashCode !== null)
|
|
173
|
+
return this._hashCode;
|
|
174
|
+
let h = 0;
|
|
175
|
+
const arr = this._elements;
|
|
176
|
+
const len = arr.length;
|
|
177
|
+
for (let i = 0; i < len; i++) {
|
|
178
|
+
h = Math.imul(31, h) + hashValue(arr[i]);
|
|
179
|
+
}
|
|
180
|
+
this._hashCode = h | 0;
|
|
181
|
+
return this._hashCode;
|
|
182
|
+
}
|
|
183
|
+
// Backward compatibility alias
|
|
184
|
+
getHashCode() { return this.hashCode; }
|
|
185
|
+
compare(other) {
|
|
186
|
+
if (this === other)
|
|
187
|
+
return 0;
|
|
188
|
+
const h1 = this.hashCode;
|
|
189
|
+
const h2 = other.hashCode;
|
|
190
|
+
if (h1 !== h2)
|
|
191
|
+
return h1 < h2 ? -1 : 1;
|
|
192
|
+
const arrA = this._elements;
|
|
193
|
+
const arrB = other._elements;
|
|
194
|
+
const len = arrA.length;
|
|
195
|
+
if (len !== arrB.length)
|
|
196
|
+
return len - arrB.length;
|
|
197
|
+
for (let i = 0; i < len; i++) {
|
|
198
|
+
const cmp = compare(arrA[i], arrB[i]);
|
|
199
|
+
if (cmp !== 0)
|
|
200
|
+
return cmp;
|
|
201
|
+
}
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
get size() { return this._elements.length; }
|
|
205
|
+
isEmpty() { return this._elements.length === 0; }
|
|
206
|
+
has(element) {
|
|
207
|
+
const arr = this._elements;
|
|
208
|
+
const len = arr.length;
|
|
209
|
+
// Small Array Optimization: Linear Scan is faster than Binary Search overhead for N < 16
|
|
210
|
+
if (len < 16) {
|
|
211
|
+
for (let i = 0; i < len; i++) {
|
|
212
|
+
if (compare(arr[i], element) === 0)
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
let low = 0, high = len - 1;
|
|
218
|
+
while (low <= high) {
|
|
219
|
+
const mid = (low + high) >>> 1;
|
|
220
|
+
const cmp = compare(arr[mid], element);
|
|
221
|
+
if (cmp === 0)
|
|
222
|
+
return true;
|
|
223
|
+
if (cmp < 0)
|
|
224
|
+
low = mid + 1;
|
|
225
|
+
else
|
|
226
|
+
high = mid - 1;
|
|
227
|
+
}
|
|
228
|
+
return false;
|
|
80
229
|
}
|
|
81
|
-
// === Mutable Operations ===
|
|
82
230
|
add(element) {
|
|
83
|
-
|
|
84
|
-
|
|
231
|
+
// --- Validation Check ---
|
|
232
|
+
if (typeof element === 'object' && element !== null) {
|
|
233
|
+
const isSet = element instanceof RecursiveSet;
|
|
234
|
+
const isTup = element instanceof Tuple;
|
|
235
|
+
const isArr = Array.isArray(element);
|
|
236
|
+
if (!isSet && !isTup && !isArr) {
|
|
237
|
+
throw new Error("Plain Objects are not supported. Use Tuple, Array or RecursiveSet.");
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else if (typeof element === "number" && Number.isNaN(element)) {
|
|
241
|
+
throw new Error("NaN is not supported");
|
|
242
|
+
}
|
|
243
|
+
// --- End Validation ---
|
|
244
|
+
const arr = this._elements;
|
|
245
|
+
const len = arr.length;
|
|
246
|
+
// Common Case: Appending a larger element (during ordered construction)
|
|
247
|
+
if (len > 0) {
|
|
248
|
+
const lastCmp = compare(arr[len - 1], element);
|
|
249
|
+
if (lastCmp < 0) {
|
|
250
|
+
arr.push(element);
|
|
251
|
+
this._hashCode = null;
|
|
252
|
+
return this;
|
|
253
|
+
}
|
|
254
|
+
if (lastCmp === 0)
|
|
255
|
+
return this;
|
|
85
256
|
}
|
|
86
|
-
|
|
87
|
-
|
|
257
|
+
else {
|
|
258
|
+
arr.push(element);
|
|
259
|
+
this._hashCode = null;
|
|
88
260
|
return this;
|
|
89
261
|
}
|
|
90
|
-
//
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
262
|
+
// Small Array Strategy: Linear Scan + Splice
|
|
263
|
+
if (len < 16) {
|
|
264
|
+
for (let i = 0; i < len; i++) {
|
|
265
|
+
const cmp = compare(arr[i], element);
|
|
266
|
+
if (cmp === 0)
|
|
267
|
+
return this;
|
|
268
|
+
if (cmp > 0) {
|
|
269
|
+
arr.splice(i, 0, element);
|
|
270
|
+
this._hashCode = null;
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
94
273
|
}
|
|
274
|
+
return this;
|
|
95
275
|
}
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
276
|
+
// Large Array Strategy: Binary Search
|
|
277
|
+
let low = 0, high = len - 1, idx = 0;
|
|
278
|
+
while (low <= high) {
|
|
279
|
+
const mid = (low + high) >>> 1;
|
|
280
|
+
const cmp = compare(arr[mid], element);
|
|
281
|
+
if (cmp === 0)
|
|
282
|
+
return this;
|
|
283
|
+
if (cmp < 0) {
|
|
284
|
+
idx = mid + 1;
|
|
285
|
+
low = mid + 1;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
idx = mid;
|
|
289
|
+
high = mid - 1;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
arr.splice(idx, 0, element);
|
|
293
|
+
this._hashCode = null;
|
|
100
294
|
return this;
|
|
101
295
|
}
|
|
102
296
|
remove(element) {
|
|
103
|
-
|
|
297
|
+
const arr = this._elements;
|
|
298
|
+
const len = arr.length;
|
|
299
|
+
if (len < 16) {
|
|
300
|
+
for (let i = 0; i < len; i++) {
|
|
301
|
+
if (compare(arr[i], element) === 0) {
|
|
302
|
+
arr.splice(i, 1);
|
|
303
|
+
this._hashCode = null;
|
|
304
|
+
return this;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
104
307
|
return this;
|
|
105
308
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
309
|
+
let low = 0, high = len - 1;
|
|
310
|
+
while (low <= high) {
|
|
311
|
+
const mid = (low + high) >>> 1;
|
|
312
|
+
const cmp = compare(arr[mid], element);
|
|
313
|
+
if (cmp === 0) {
|
|
314
|
+
arr.splice(mid, 1);
|
|
315
|
+
this._hashCode = null;
|
|
316
|
+
return this;
|
|
317
|
+
}
|
|
318
|
+
if (cmp < 0)
|
|
319
|
+
low = mid + 1;
|
|
320
|
+
else
|
|
321
|
+
high = mid - 1;
|
|
322
|
+
}
|
|
109
323
|
return this;
|
|
110
324
|
}
|
|
111
325
|
clear() {
|
|
112
|
-
this.
|
|
113
|
-
this.
|
|
326
|
+
this._elements = [];
|
|
327
|
+
this._hashCode = 0;
|
|
114
328
|
return this;
|
|
115
329
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (typeof element === 'string') {
|
|
122
|
-
let h = 0;
|
|
123
|
-
for (let i = 0; i < element.length; i++)
|
|
124
|
-
h = Math.imul(31, h) + element.charCodeAt(i) | 0;
|
|
125
|
-
return h;
|
|
126
|
-
}
|
|
127
|
-
return 0;
|
|
330
|
+
clone() {
|
|
331
|
+
const s = new RecursiveSet();
|
|
332
|
+
s._elements = this._elements.slice();
|
|
333
|
+
s._hashCode = this._hashCode;
|
|
334
|
+
return s;
|
|
128
335
|
}
|
|
129
|
-
// === Immutable Operations ===
|
|
130
336
|
union(other) {
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
337
|
+
const s = new RecursiveSet();
|
|
338
|
+
const arrA = this._elements;
|
|
339
|
+
const arrB = other._elements;
|
|
340
|
+
if (arrA.length === 0)
|
|
341
|
+
return other.clone();
|
|
342
|
+
if (arrB.length === 0)
|
|
343
|
+
return this.clone();
|
|
344
|
+
const res = [];
|
|
345
|
+
let i = 0, j = 0;
|
|
346
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
347
|
+
// Merge Sort Algorithm O(N + M)
|
|
348
|
+
while (i < lenA && j < lenB) {
|
|
349
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
350
|
+
if (cmp < 0)
|
|
351
|
+
res.push(arrA[i++]);
|
|
352
|
+
else if (cmp > 0)
|
|
353
|
+
res.push(arrB[j++]);
|
|
354
|
+
else {
|
|
355
|
+
res.push(arrA[i++]);
|
|
356
|
+
j++;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
while (i < lenA)
|
|
360
|
+
res.push(arrA[i++]);
|
|
361
|
+
while (j < lenB)
|
|
362
|
+
res.push(arrB[j++]);
|
|
363
|
+
s._elements = res;
|
|
364
|
+
return s;
|
|
135
365
|
}
|
|
136
366
|
intersection(other) {
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
const
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
367
|
+
const s = new RecursiveSet();
|
|
368
|
+
const arrA = this._elements;
|
|
369
|
+
const arrB = other._elements;
|
|
370
|
+
const res = [];
|
|
371
|
+
let i = 0, j = 0;
|
|
372
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
373
|
+
while (i < lenA && j < lenB) {
|
|
374
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
375
|
+
if (cmp < 0)
|
|
376
|
+
i++;
|
|
377
|
+
else if (cmp > 0)
|
|
378
|
+
j++;
|
|
379
|
+
else {
|
|
380
|
+
res.push(arrA[i++]);
|
|
381
|
+
j++;
|
|
143
382
|
}
|
|
144
383
|
}
|
|
145
|
-
|
|
384
|
+
s._elements = res;
|
|
385
|
+
return s;
|
|
146
386
|
}
|
|
147
387
|
difference(other) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
388
|
+
const s = new RecursiveSet();
|
|
389
|
+
const arrA = this._elements;
|
|
390
|
+
const arrB = other._elements;
|
|
391
|
+
const res = [];
|
|
392
|
+
let i = 0, j = 0;
|
|
393
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
394
|
+
while (i < lenA && j < lenB) {
|
|
395
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
396
|
+
if (cmp < 0)
|
|
397
|
+
res.push(arrA[i++]);
|
|
398
|
+
else if (cmp > 0)
|
|
399
|
+
j++;
|
|
400
|
+
else {
|
|
401
|
+
i++;
|
|
402
|
+
j++;
|
|
152
403
|
}
|
|
153
404
|
}
|
|
154
|
-
|
|
405
|
+
while (i < lenA)
|
|
406
|
+
res.push(arrA[i++]);
|
|
407
|
+
s._elements = res;
|
|
408
|
+
return s;
|
|
155
409
|
}
|
|
156
410
|
symmetricDifference(other) {
|
|
157
|
-
|
|
158
|
-
|
|
411
|
+
const s = new RecursiveSet();
|
|
412
|
+
const arrA = this._elements;
|
|
413
|
+
const arrB = other._elements;
|
|
414
|
+
const res = [];
|
|
415
|
+
let i = 0, j = 0;
|
|
416
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
417
|
+
while (i < lenA && j < lenB) {
|
|
418
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
419
|
+
if (cmp < 0)
|
|
420
|
+
res.push(arrA[i++]);
|
|
421
|
+
else if (cmp > 0)
|
|
422
|
+
res.push(arrB[j++]);
|
|
423
|
+
else {
|
|
424
|
+
i++;
|
|
425
|
+
j++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
while (i < lenA)
|
|
429
|
+
res.push(arrA[i++]);
|
|
430
|
+
while (j < lenB)
|
|
431
|
+
res.push(arrB[j++]);
|
|
432
|
+
s._elements = res;
|
|
433
|
+
return s;
|
|
159
434
|
}
|
|
160
435
|
powerset() {
|
|
161
436
|
const n = this.size;
|
|
162
|
-
if (n >
|
|
163
|
-
throw new Error("Powerset
|
|
164
|
-
const elements = [];
|
|
165
|
-
this._tree.forEach((key) => { elements.push(key); return undefined; });
|
|
437
|
+
if (n > 20)
|
|
438
|
+
throw new Error("Powerset too large");
|
|
166
439
|
const subsets = [];
|
|
167
|
-
|
|
440
|
+
const max = 1 << n;
|
|
441
|
+
for (let i = 0; i < max; i++) {
|
|
168
442
|
const subset = new RecursiveSet();
|
|
169
443
|
for (let j = 0; j < n; j++) {
|
|
170
|
-
if (i & (1 << j))
|
|
171
|
-
subset.
|
|
172
|
-
}
|
|
444
|
+
if (i & (1 << j))
|
|
445
|
+
subset._elements.push(this._elements[j]);
|
|
173
446
|
}
|
|
174
447
|
subsets.push(subset);
|
|
175
448
|
}
|
|
176
449
|
return new RecursiveSet(...subsets);
|
|
177
450
|
}
|
|
178
451
|
cartesianProduct(other) {
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const pair = new RecursiveSet(new RecursiveSet(valX), new RecursiveSet(valX, valY));
|
|
186
|
-
pairs.push(pair);
|
|
452
|
+
const result = new RecursiveSet();
|
|
453
|
+
const arrA = this._elements;
|
|
454
|
+
const arrB = other._elements;
|
|
455
|
+
for (const x of arrA) {
|
|
456
|
+
for (const y of arrB) {
|
|
457
|
+
result._elements.push(new Tuple(x, y));
|
|
187
458
|
}
|
|
188
459
|
}
|
|
189
|
-
return
|
|
190
|
-
}
|
|
191
|
-
// === Predicates ===
|
|
192
|
-
has(element) {
|
|
193
|
-
return this._tree.get(element) !== undefined;
|
|
460
|
+
return result;
|
|
194
461
|
}
|
|
462
|
+
// Standard Set methods
|
|
195
463
|
isSubset(other) {
|
|
196
464
|
if (this.size > other.size)
|
|
197
465
|
return false;
|
|
198
|
-
|
|
199
|
-
|
|
466
|
+
let i = 0, j = 0;
|
|
467
|
+
const arrA = this._elements, arrB = other._elements;
|
|
468
|
+
while (i < arrA.length && j < arrB.length) {
|
|
469
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
470
|
+
if (cmp < 0)
|
|
200
471
|
return false;
|
|
472
|
+
if (cmp > 0)
|
|
473
|
+
j++;
|
|
474
|
+
else {
|
|
475
|
+
i++;
|
|
476
|
+
j++;
|
|
477
|
+
}
|
|
201
478
|
}
|
|
202
|
-
return
|
|
203
|
-
}
|
|
204
|
-
isSuperset(other) {
|
|
205
|
-
return other.isSubset(this);
|
|
206
|
-
}
|
|
207
|
-
isProperSubset(other) {
|
|
208
|
-
return this.isSubset(other) && !this.equals(other);
|
|
209
|
-
}
|
|
210
|
-
isEmpty() {
|
|
211
|
-
return this.size === 0;
|
|
212
|
-
}
|
|
213
|
-
equals(other) {
|
|
214
|
-
return RecursiveSet.compare(this, other) === 0;
|
|
215
|
-
}
|
|
216
|
-
// === Internals ===
|
|
217
|
-
_wouldCreateCycle(element) {
|
|
218
|
-
const visited = new Set();
|
|
219
|
-
const stack = [element];
|
|
220
|
-
while (stack.length > 0) {
|
|
221
|
-
const current = stack.pop();
|
|
222
|
-
if (current === this)
|
|
223
|
-
return true;
|
|
224
|
-
if (visited.has(current))
|
|
225
|
-
continue;
|
|
226
|
-
visited.add(current);
|
|
227
|
-
// Optimization: Direct internal tree traversal avoids iterator overhead
|
|
228
|
-
current._tree.forEach((key) => {
|
|
229
|
-
if (key instanceof RecursiveSet) {
|
|
230
|
-
stack.push(key);
|
|
231
|
-
}
|
|
232
|
-
return undefined;
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
// === Utility ===
|
|
238
|
-
get size() {
|
|
239
|
-
return this._tree.length;
|
|
240
|
-
}
|
|
241
|
-
toSet() {
|
|
242
|
-
const result = new Set();
|
|
243
|
-
this._tree.forEach((key) => { result.add(key); return undefined; });
|
|
244
|
-
return result;
|
|
245
|
-
}
|
|
246
|
-
// Lazy Iterator (Critical for performance)
|
|
247
|
-
*[Symbol.iterator]() {
|
|
248
|
-
let iter = this._tree.begin;
|
|
249
|
-
while (iter.valid) {
|
|
250
|
-
yield iter.key;
|
|
251
|
-
iter.next();
|
|
252
|
-
}
|
|
479
|
+
return i === arrA.length;
|
|
253
480
|
}
|
|
481
|
+
isSuperset(other) { return other.isSubset(this); }
|
|
482
|
+
isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
|
|
483
|
+
equals(other) { return this.compare(other) === 0; }
|
|
484
|
+
toSet() { return new Set(this._elements); }
|
|
485
|
+
*[Symbol.iterator]() { yield* this._elements; }
|
|
254
486
|
toString() {
|
|
255
487
|
if (this.isEmpty())
|
|
256
488
|
return "∅";
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
elements.push(String(key));
|
|
264
|
-
}
|
|
265
|
-
return undefined;
|
|
489
|
+
const elementsStr = this._elements.map(el => {
|
|
490
|
+
if (Array.isArray(el))
|
|
491
|
+
return `[${el.join(', ')}]`;
|
|
492
|
+
return String(el);
|
|
266
493
|
});
|
|
267
|
-
return `{${
|
|
268
|
-
}
|
|
269
|
-
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
270
|
-
return this.toString();
|
|
494
|
+
return `{${elementsStr.join(', ')}}`;
|
|
271
495
|
}
|
|
496
|
+
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
272
497
|
}
|
|
273
498
|
exports.RecursiveSet = RecursiveSet;
|
|
274
|
-
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
function singleton(element) {
|
|
279
|
-
return new RecursiveSet(element);
|
|
280
|
-
}
|
|
281
|
-
function fromIterable(iterable) {
|
|
282
|
-
return new RecursiveSet(...iterable);
|
|
283
|
-
}
|
|
499
|
+
function emptySet() { return new RecursiveSet(); }
|
|
500
|
+
function singleton(element) { return new RecursiveSet(element); }
|
|
501
|
+
function fromIterable(iterable) { return new RecursiveSet(...iterable); }
|
|
284
502
|
//# sourceMappingURL=index.js.map
|