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