recursive-set 3.0.0 → 5.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 +93 -132
- package/dist/cjs/index.js +398 -209
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +24 -29
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +395 -203
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,278 +1,470 @@
|
|
|
1
|
-
import createTree from 'functional-red-black-tree';
|
|
2
1
|
/**
|
|
3
2
|
* @module recursive-set
|
|
4
|
-
*
|
|
5
|
-
* Powered by functional Red-Black Trees for O(log n) operations and O(1) cloning.
|
|
3
|
+
* High-Performance Recursive Set with "Freeze-on-Hash" semantics.
|
|
6
4
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
5
|
+
// === HASHING ENGINE (FNV-1a) ===
|
|
6
|
+
const FNV_PRIME = 16777619;
|
|
7
|
+
const FNV_OFFSET = 2166136261;
|
|
8
|
+
function hashString(str) {
|
|
9
|
+
let hash = FNV_OFFSET;
|
|
10
|
+
const len = str.length;
|
|
11
|
+
for (let i = 0; i < len; i++) {
|
|
12
|
+
hash ^= str.charCodeAt(i);
|
|
13
|
+
hash = Math.imul(hash, FNV_PRIME);
|
|
15
14
|
}
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
return hash >>> 0;
|
|
16
|
+
}
|
|
17
|
+
function hashValue(val) {
|
|
18
|
+
if (typeof val === 'string')
|
|
19
|
+
return hashString(val);
|
|
20
|
+
if (typeof val === 'number') {
|
|
21
|
+
let hash = FNV_OFFSET;
|
|
22
|
+
hash ^= (val | 0);
|
|
23
|
+
hash = Math.imul(hash, FNV_PRIME);
|
|
24
|
+
return hash >>> 0;
|
|
18
25
|
}
|
|
19
|
-
|
|
20
|
-
|
|
26
|
+
// Fast Path: Objects with cached hash
|
|
27
|
+
if (val && typeof val === 'object' && 'hashCode' in val) {
|
|
28
|
+
return val.hashCode;
|
|
21
29
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
30
|
+
if (Array.isArray(val)) {
|
|
31
|
+
let h = FNV_OFFSET;
|
|
32
|
+
const len = val.length;
|
|
33
|
+
for (let i = 0; i < len; i++) {
|
|
34
|
+
h ^= hashValue(val[i]);
|
|
35
|
+
h = Math.imul(h, FNV_PRIME);
|
|
25
36
|
}
|
|
37
|
+
return h >>> 0;
|
|
26
38
|
}
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
return 0;
|
|
40
|
+
}
|
|
41
|
+
// === COMPARATOR ===
|
|
42
|
+
function compare(a, b) {
|
|
43
|
+
if (a === b)
|
|
44
|
+
return 0;
|
|
45
|
+
// 1. Hash Short-Circuit (Optimization)
|
|
46
|
+
const aH = a?.hashCode;
|
|
47
|
+
const bH = b?.hashCode;
|
|
48
|
+
const ha = (aH !== undefined) ? aH : hashValue(a);
|
|
49
|
+
const hb = (bH !== undefined) ? bH : hashValue(b);
|
|
50
|
+
if (ha !== hb)
|
|
51
|
+
return ha < hb ? -1 : 1;
|
|
52
|
+
// 2. Structural Type Check
|
|
53
|
+
const typeA = typeof a;
|
|
54
|
+
const typeB = typeof b;
|
|
55
|
+
if (typeA === 'string' && typeB === 'string')
|
|
56
|
+
return a < b ? -1 : 1;
|
|
57
|
+
if (typeA === 'number' && typeB === 'number')
|
|
58
|
+
return a < b ? -1 : 1;
|
|
59
|
+
const isSetA = a instanceof RecursiveSet;
|
|
60
|
+
const isSetB = b instanceof RecursiveSet;
|
|
61
|
+
if (isSetA && isSetB) {
|
|
62
|
+
return a.compare(b);
|
|
29
63
|
}
|
|
30
|
-
|
|
31
|
-
|
|
64
|
+
const isSeqA = Array.isArray(a) || a instanceof Tuple;
|
|
65
|
+
const isSeqB = Array.isArray(b) || b instanceof Tuple;
|
|
66
|
+
// Sort Order: Primitives (0) < Sequences (1) < Sets (2)
|
|
67
|
+
if (isSetA !== isSetB || isSeqA !== isSeqB) {
|
|
68
|
+
const scoreA = isSetA ? 2 : isSeqA ? 1 : 0;
|
|
69
|
+
const scoreB = isSetB ? 2 : isSeqB ? 1 : 0;
|
|
70
|
+
return scoreA - scoreB;
|
|
71
|
+
}
|
|
72
|
+
// 3. Sequence Comparison
|
|
73
|
+
if (isSeqA && isSeqB) {
|
|
74
|
+
const valA = (a instanceof Tuple) ? a.values : a;
|
|
75
|
+
const valB = (b instanceof Tuple) ? b.values : b;
|
|
76
|
+
const len = valA.length;
|
|
77
|
+
if (len !== valB.length)
|
|
78
|
+
return len - valB.length;
|
|
79
|
+
for (let i = 0; i < len; i++) {
|
|
80
|
+
const diff = compare(valA[i], valB[i]);
|
|
81
|
+
if (diff !== 0)
|
|
82
|
+
return diff;
|
|
83
|
+
}
|
|
84
|
+
return 0;
|
|
32
85
|
}
|
|
86
|
+
return a < b ? -1 : 1;
|
|
87
|
+
}
|
|
88
|
+
// === CLASSES ===
|
|
89
|
+
export class Tuple {
|
|
90
|
+
values;
|
|
91
|
+
hashCode;
|
|
92
|
+
constructor(...values) {
|
|
93
|
+
this.values = values;
|
|
94
|
+
this.hashCode = hashValue(values);
|
|
95
|
+
}
|
|
96
|
+
get length() { return this.values.length; }
|
|
97
|
+
get(i) { return this.values[i]; }
|
|
98
|
+
*[Symbol.iterator]() { yield* this.values; }
|
|
99
|
+
toString() { return `(${this.values.join(', ')})`; }
|
|
100
|
+
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
33
101
|
}
|
|
34
102
|
export class RecursiveSet {
|
|
35
|
-
|
|
103
|
+
_elements;
|
|
104
|
+
_hashCode = null;
|
|
105
|
+
_isFrozen = false;
|
|
106
|
+
static compare(a, b) { return compare(a, b); }
|
|
107
|
+
constructor(...elements) {
|
|
108
|
+
if (elements.length === 0) {
|
|
109
|
+
this._elements = [];
|
|
110
|
+
this._hashCode = 0;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this._elements = elements;
|
|
114
|
+
this._elements.sort(compare);
|
|
115
|
+
this._unique();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
_checkFrozen(op) {
|
|
119
|
+
if (this._isFrozen) {
|
|
120
|
+
throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet.\n` +
|
|
121
|
+
`This Set has been hashed or used in a collection (Value Semantics).\n` +
|
|
122
|
+
`Use .mutableCopy() to create a modifiable copy.`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
_unique() {
|
|
126
|
+
const arr = this._elements;
|
|
127
|
+
const len = arr.length;
|
|
128
|
+
if (len < 2)
|
|
129
|
+
return;
|
|
130
|
+
let write = 1;
|
|
131
|
+
for (let read = 1; read < len; read++) {
|
|
132
|
+
if (compare(arr[read], arr[read - 1]) !== 0) {
|
|
133
|
+
arr[write++] = arr[read];
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
arr.length = write;
|
|
137
|
+
}
|
|
36
138
|
/**
|
|
37
|
-
*
|
|
38
|
-
* Supports Primitives, RecursiveSets and Tuples.
|
|
39
|
-
* REJECTS plain JS Objects and Arrays to enforce strict semantics.
|
|
139
|
+
* Calculates/Caches hash code and FREEZES the set.
|
|
40
140
|
*/
|
|
41
|
-
|
|
42
|
-
if (
|
|
43
|
-
return
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const getTypeScore = (isSet, isTup) => {
|
|
50
|
-
if (isSet)
|
|
51
|
-
return 2;
|
|
52
|
-
if (isTup)
|
|
53
|
-
return 1;
|
|
54
|
-
return 0;
|
|
55
|
-
};
|
|
56
|
-
const scoreA = getTypeScore(isSetA, isTupA);
|
|
57
|
-
const scoreB = getTypeScore(isSetB, isTupB);
|
|
58
|
-
if (scoreA !== scoreB)
|
|
59
|
-
return scoreA < scoreB ? -1 : 1;
|
|
60
|
-
// 1. Sets
|
|
61
|
-
if (isSetA && isSetB) {
|
|
62
|
-
const setA = a;
|
|
63
|
-
const setB = b;
|
|
64
|
-
if (setA.size !== setB.size)
|
|
65
|
-
return setA.size < setB.size ? -1 : 1;
|
|
66
|
-
let iterA = setA._tree.begin;
|
|
67
|
-
let iterB = setB._tree.begin;
|
|
68
|
-
while (iterA.valid && iterB.valid) {
|
|
69
|
-
const cmp = RecursiveSet.compare(iterA.key, iterB.key);
|
|
70
|
-
if (cmp !== 0)
|
|
71
|
-
return cmp;
|
|
72
|
-
iterA.next();
|
|
73
|
-
iterB.next();
|
|
74
|
-
}
|
|
75
|
-
return 0;
|
|
141
|
+
get hashCode() {
|
|
142
|
+
if (this._hashCode !== null)
|
|
143
|
+
return this._hashCode;
|
|
144
|
+
let h = 0;
|
|
145
|
+
const arr = this._elements;
|
|
146
|
+
const len = arr.length;
|
|
147
|
+
for (let i = 0; i < len; i++) {
|
|
148
|
+
h = Math.imul(31, h) + hashValue(arr[i]);
|
|
76
149
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const cmp = RecursiveSet.compare(tupA.get(i), tupB.get(i));
|
|
85
|
-
if (cmp !== 0)
|
|
86
|
-
return cmp;
|
|
87
|
-
}
|
|
150
|
+
this._hashCode = h | 0;
|
|
151
|
+
this._isFrozen = true;
|
|
152
|
+
return this._hashCode;
|
|
153
|
+
}
|
|
154
|
+
get isFrozen() { return this._isFrozen; }
|
|
155
|
+
compare(other) {
|
|
156
|
+
if (this === other)
|
|
88
157
|
return 0;
|
|
158
|
+
const h1 = this.hashCode;
|
|
159
|
+
const h2 = other.hashCode;
|
|
160
|
+
if (h1 !== h2)
|
|
161
|
+
return h1 < h2 ? -1 : 1;
|
|
162
|
+
const arrA = this._elements;
|
|
163
|
+
const arrB = other._elements;
|
|
164
|
+
const len = arrA.length;
|
|
165
|
+
if (len !== arrB.length)
|
|
166
|
+
return len - arrB.length;
|
|
167
|
+
for (let i = 0; i < len; i++) {
|
|
168
|
+
const cmp = compare(arrA[i], arrB[i]);
|
|
169
|
+
if (cmp !== 0)
|
|
170
|
+
return cmp;
|
|
89
171
|
}
|
|
90
|
-
// 3. Primitives (guaranteed by add() validation)
|
|
91
|
-
const tA = typeof a;
|
|
92
|
-
const tB = typeof b;
|
|
93
|
-
if (tA !== tB)
|
|
94
|
-
return tA > tB ? 1 : -1;
|
|
95
|
-
// @ts-ignore
|
|
96
|
-
if (a < b)
|
|
97
|
-
return -1;
|
|
98
|
-
// @ts-ignore
|
|
99
|
-
if (a > b)
|
|
100
|
-
return 1;
|
|
101
172
|
return 0;
|
|
102
173
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
174
|
+
get size() { return this._elements.length; }
|
|
175
|
+
isEmpty() { return this._elements.length === 0; }
|
|
176
|
+
has(element) {
|
|
177
|
+
const arr = this._elements;
|
|
178
|
+
const len = arr.length;
|
|
179
|
+
// Linear Scan (Prefetch-friendly for small sets)
|
|
180
|
+
if (len < 16) {
|
|
181
|
+
for (let i = 0; i < len; i++) {
|
|
182
|
+
if (compare(arr[i], element) === 0)
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
return false;
|
|
107
186
|
}
|
|
187
|
+
// Binary Search
|
|
188
|
+
let low = 0, high = len - 1;
|
|
189
|
+
while (low <= high) {
|
|
190
|
+
const mid = (low + high) >>> 1;
|
|
191
|
+
const cmp = compare(arr[mid], element);
|
|
192
|
+
if (cmp === 0)
|
|
193
|
+
return true;
|
|
194
|
+
if (cmp < 0)
|
|
195
|
+
low = mid + 1;
|
|
196
|
+
else
|
|
197
|
+
high = mid - 1;
|
|
198
|
+
}
|
|
199
|
+
return false;
|
|
108
200
|
}
|
|
109
|
-
// === Copy-on-Write Support ===
|
|
110
|
-
/**
|
|
111
|
-
* Creates a shallow copy of the set in O(1) time.
|
|
112
|
-
*/
|
|
113
|
-
clone() {
|
|
114
|
-
const clone = new RecursiveSet();
|
|
115
|
-
clone._tree = this._tree;
|
|
116
|
-
return clone;
|
|
117
|
-
}
|
|
118
|
-
// === Mutable Operations ===
|
|
119
201
|
add(element) {
|
|
120
|
-
|
|
121
|
-
|
|
202
|
+
this._checkFrozen('add() to');
|
|
203
|
+
// Validation (Inlined for Performance)
|
|
204
|
+
if (typeof element === 'object' && element !== null) {
|
|
205
|
+
if (!(element instanceof RecursiveSet || element instanceof Tuple || Array.isArray(element))) {
|
|
206
|
+
throw new Error("Unsupported Type: Use Tuple, Array or RecursiveSet.");
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else if (Number.isNaN(element)) {
|
|
122
210
|
throw new Error("NaN is not supported");
|
|
123
211
|
}
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
212
|
+
const arr = this._elements;
|
|
213
|
+
const len = arr.length;
|
|
214
|
+
// Optimization: Append to end (common in construction)
|
|
215
|
+
if (len > 0) {
|
|
216
|
+
const lastCmp = compare(arr[len - 1], element);
|
|
217
|
+
if (lastCmp < 0) {
|
|
218
|
+
arr.push(element);
|
|
219
|
+
this._hashCode = null;
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
if (lastCmp === 0)
|
|
223
|
+
return this;
|
|
224
|
+
}
|
|
225
|
+
else {
|
|
226
|
+
arr.push(element);
|
|
227
|
+
this._hashCode = null;
|
|
228
|
+
return this;
|
|
130
229
|
}
|
|
131
|
-
//
|
|
132
|
-
if (
|
|
230
|
+
// Small Array Strategy
|
|
231
|
+
if (len < 16) {
|
|
232
|
+
for (let i = 0; i < len; i++) {
|
|
233
|
+
const cmp = compare(arr[i], element);
|
|
234
|
+
if (cmp === 0)
|
|
235
|
+
return this;
|
|
236
|
+
if (cmp > 0) {
|
|
237
|
+
arr.splice(i, 0, element);
|
|
238
|
+
this._hashCode = null;
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
arr.push(element); // Should be unreachable given append check, but safe fallback
|
|
133
243
|
return this;
|
|
134
|
-
|
|
244
|
+
}
|
|
245
|
+
// Large Array Strategy
|
|
246
|
+
let low = 0, high = len - 1, idx = 0;
|
|
247
|
+
while (low <= high) {
|
|
248
|
+
const mid = (low + high) >>> 1;
|
|
249
|
+
const cmp = compare(arr[mid], element);
|
|
250
|
+
if (cmp === 0)
|
|
251
|
+
return this;
|
|
252
|
+
if (cmp < 0) {
|
|
253
|
+
idx = mid + 1;
|
|
254
|
+
low = mid + 1;
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
idx = mid;
|
|
258
|
+
high = mid - 1;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
arr.splice(idx, 0, element);
|
|
262
|
+
this._hashCode = null;
|
|
135
263
|
return this;
|
|
136
264
|
}
|
|
137
265
|
remove(element) {
|
|
138
|
-
this.
|
|
266
|
+
this._checkFrozen('remove() from');
|
|
267
|
+
const arr = this._elements;
|
|
268
|
+
const len = arr.length;
|
|
269
|
+
if (len < 16) {
|
|
270
|
+
for (let i = 0; i < len; i++) {
|
|
271
|
+
if (compare(arr[i], element) === 0) {
|
|
272
|
+
arr.splice(i, 1);
|
|
273
|
+
this._hashCode = null;
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return this;
|
|
278
|
+
}
|
|
279
|
+
let low = 0, high = len - 1;
|
|
280
|
+
while (low <= high) {
|
|
281
|
+
const mid = (low + high) >>> 1;
|
|
282
|
+
const cmp = compare(arr[mid], element);
|
|
283
|
+
if (cmp === 0) {
|
|
284
|
+
arr.splice(mid, 1);
|
|
285
|
+
this._hashCode = null;
|
|
286
|
+
return this;
|
|
287
|
+
}
|
|
288
|
+
if (cmp < 0)
|
|
289
|
+
low = mid + 1;
|
|
290
|
+
else
|
|
291
|
+
high = mid - 1;
|
|
292
|
+
}
|
|
139
293
|
return this;
|
|
140
294
|
}
|
|
141
295
|
clear() {
|
|
142
|
-
this.
|
|
296
|
+
this._checkFrozen('clear()');
|
|
297
|
+
this._elements = [];
|
|
298
|
+
this._hashCode = 0;
|
|
143
299
|
return this;
|
|
144
300
|
}
|
|
145
|
-
|
|
301
|
+
mutableCopy() {
|
|
302
|
+
const s = new RecursiveSet();
|
|
303
|
+
s._elements = this._elements.slice();
|
|
304
|
+
return s;
|
|
305
|
+
}
|
|
306
|
+
clone() { return this.mutableCopy(); }
|
|
146
307
|
union(other) {
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
308
|
+
const s = new RecursiveSet();
|
|
309
|
+
const arrA = this._elements;
|
|
310
|
+
const arrB = other._elements;
|
|
311
|
+
if (arrA.length === 0)
|
|
312
|
+
return other.clone();
|
|
313
|
+
if (arrB.length === 0)
|
|
314
|
+
return this.clone();
|
|
315
|
+
const res = [];
|
|
316
|
+
let i = 0, j = 0;
|
|
317
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
318
|
+
while (i < lenA && j < lenB) {
|
|
319
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
320
|
+
if (cmp < 0)
|
|
321
|
+
res.push(arrA[i++]);
|
|
322
|
+
else if (cmp > 0)
|
|
323
|
+
res.push(arrB[j++]);
|
|
324
|
+
else {
|
|
325
|
+
res.push(arrA[i++]);
|
|
326
|
+
j++;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
while (i < lenA)
|
|
330
|
+
res.push(arrA[i++]);
|
|
331
|
+
while (j < lenB)
|
|
332
|
+
res.push(arrB[j++]);
|
|
333
|
+
s._elements = res;
|
|
334
|
+
return s;
|
|
151
335
|
}
|
|
152
336
|
intersection(other) {
|
|
153
|
-
const
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
337
|
+
const s = new RecursiveSet();
|
|
338
|
+
const arrA = this._elements;
|
|
339
|
+
const arrB = other._elements;
|
|
340
|
+
const res = [];
|
|
341
|
+
let i = 0, j = 0;
|
|
342
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
343
|
+
while (i < lenA && j < lenB) {
|
|
344
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
345
|
+
if (cmp < 0)
|
|
346
|
+
i++;
|
|
347
|
+
else if (cmp > 0)
|
|
348
|
+
j++;
|
|
349
|
+
else {
|
|
350
|
+
res.push(arrA[i++]);
|
|
351
|
+
j++;
|
|
158
352
|
}
|
|
159
353
|
}
|
|
160
|
-
|
|
354
|
+
s._elements = res;
|
|
355
|
+
return s;
|
|
161
356
|
}
|
|
162
357
|
difference(other) {
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
358
|
+
const s = new RecursiveSet();
|
|
359
|
+
const arrA = this._elements;
|
|
360
|
+
const arrB = other._elements;
|
|
361
|
+
const res = [];
|
|
362
|
+
let i = 0, j = 0;
|
|
363
|
+
const lenA = arrA.length, lenB = arrB.length;
|
|
364
|
+
while (i < lenA && j < lenB) {
|
|
365
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
366
|
+
if (cmp < 0)
|
|
367
|
+
res.push(arrA[i++]);
|
|
368
|
+
else if (cmp > 0)
|
|
369
|
+
j++;
|
|
370
|
+
else {
|
|
371
|
+
i++;
|
|
372
|
+
j++;
|
|
167
373
|
}
|
|
168
374
|
}
|
|
169
|
-
|
|
375
|
+
while (i < lenA)
|
|
376
|
+
res.push(arrA[i++]);
|
|
377
|
+
s._elements = res;
|
|
378
|
+
return s;
|
|
170
379
|
}
|
|
171
380
|
symmetricDifference(other) {
|
|
172
|
-
|
|
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
|
+
res.push(arrB[j++]);
|
|
393
|
+
else {
|
|
394
|
+
i++;
|
|
395
|
+
j++;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
while (i < lenA)
|
|
399
|
+
res.push(arrA[i++]);
|
|
400
|
+
while (j < lenB)
|
|
401
|
+
res.push(arrB[j++]);
|
|
402
|
+
s._elements = res;
|
|
403
|
+
return s;
|
|
173
404
|
}
|
|
174
405
|
powerset() {
|
|
175
406
|
const n = this.size;
|
|
176
|
-
if (n >
|
|
177
|
-
throw new Error("Powerset
|
|
178
|
-
const elements = [];
|
|
179
|
-
this._tree.forEach((key) => { elements.push(key); });
|
|
407
|
+
if (n > 20)
|
|
408
|
+
throw new Error("Powerset too large");
|
|
180
409
|
const subsets = [];
|
|
181
|
-
|
|
410
|
+
const max = 1 << n;
|
|
411
|
+
for (let i = 0; i < max; i++) {
|
|
182
412
|
const subset = new RecursiveSet();
|
|
183
413
|
for (let j = 0; j < n; j++) {
|
|
184
|
-
if (i & (1 << j))
|
|
185
|
-
subset.
|
|
186
|
-
}
|
|
414
|
+
if (i & (1 << j))
|
|
415
|
+
subset._elements.push(this._elements[j]);
|
|
187
416
|
}
|
|
188
417
|
subsets.push(subset);
|
|
189
418
|
}
|
|
190
419
|
return new RecursiveSet(...subsets);
|
|
191
420
|
}
|
|
192
|
-
/**
|
|
193
|
-
* Returns the Cartesian product as a set of Tuples.
|
|
194
|
-
* Uses the Tuple class to ensure structural equality.
|
|
195
|
-
*/
|
|
196
421
|
cartesianProduct(other) {
|
|
197
422
|
const result = new RecursiveSet();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
423
|
+
const arrA = this._elements;
|
|
424
|
+
const arrB = other._elements;
|
|
425
|
+
for (const x of arrA) {
|
|
426
|
+
for (const y of arrB) {
|
|
427
|
+
result._elements.push(new Tuple(x, y));
|
|
201
428
|
}
|
|
202
429
|
}
|
|
203
430
|
return result;
|
|
204
431
|
}
|
|
205
|
-
// === Predicates ===
|
|
206
|
-
has(element) {
|
|
207
|
-
return this._tree.get(element) !== undefined;
|
|
208
|
-
}
|
|
209
432
|
isSubset(other) {
|
|
210
433
|
if (this.size > other.size)
|
|
211
434
|
return false;
|
|
212
|
-
|
|
213
|
-
|
|
435
|
+
let i = 0, j = 0;
|
|
436
|
+
const arrA = this._elements, arrB = other._elements;
|
|
437
|
+
while (i < arrA.length && j < arrB.length) {
|
|
438
|
+
const cmp = compare(arrA[i], arrB[j]);
|
|
439
|
+
if (cmp < 0)
|
|
214
440
|
return false;
|
|
441
|
+
if (cmp > 0)
|
|
442
|
+
j++;
|
|
443
|
+
else {
|
|
444
|
+
i++;
|
|
445
|
+
j++;
|
|
446
|
+
}
|
|
215
447
|
}
|
|
216
|
-
return
|
|
217
|
-
}
|
|
218
|
-
isSuperset(other) {
|
|
219
|
-
return other.isSubset(this);
|
|
220
|
-
}
|
|
221
|
-
isProperSubset(other) {
|
|
222
|
-
return this.isSubset(other) && !this.equals(other);
|
|
223
|
-
}
|
|
224
|
-
isEmpty() {
|
|
225
|
-
return this.size === 0;
|
|
226
|
-
}
|
|
227
|
-
equals(other) {
|
|
228
|
-
return RecursiveSet.compare(this, other) === 0;
|
|
229
|
-
}
|
|
230
|
-
// === Utility ===
|
|
231
|
-
get size() {
|
|
232
|
-
return this._tree.length;
|
|
233
|
-
}
|
|
234
|
-
toSet() {
|
|
235
|
-
const result = new Set();
|
|
236
|
-
this._tree.forEach((key) => { result.add(key); });
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
// Lazy Iterator
|
|
240
|
-
*[Symbol.iterator]() {
|
|
241
|
-
let iter = this._tree.begin;
|
|
242
|
-
while (iter.valid) {
|
|
243
|
-
yield iter.key;
|
|
244
|
-
iter.next();
|
|
245
|
-
}
|
|
448
|
+
return i === arrA.length;
|
|
246
449
|
}
|
|
450
|
+
isSuperset(other) { return other.isSubset(this); }
|
|
451
|
+
isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
|
|
452
|
+
equals(other) { return this.compare(other) === 0; }
|
|
453
|
+
toSet() { return new Set(this._elements); }
|
|
454
|
+
*[Symbol.iterator]() { yield* this._elements; }
|
|
247
455
|
toString() {
|
|
248
456
|
if (this.isEmpty())
|
|
249
457
|
return "∅";
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
else if (key instanceof Tuple) {
|
|
256
|
-
elements.push(key.toString());
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
elements.push(String(key));
|
|
260
|
-
}
|
|
458
|
+
const elementsStr = this._elements.map(el => {
|
|
459
|
+
if (Array.isArray(el))
|
|
460
|
+
return `[${el.join(', ')}]`;
|
|
461
|
+
return String(el);
|
|
261
462
|
});
|
|
262
|
-
return `{${
|
|
263
|
-
}
|
|
264
|
-
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
265
|
-
return this.toString();
|
|
463
|
+
return `{${elementsStr.join(', ')}}`;
|
|
266
464
|
}
|
|
465
|
+
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
267
466
|
}
|
|
268
|
-
|
|
269
|
-
export function
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
export function singleton(element) {
|
|
273
|
-
return new RecursiveSet(element);
|
|
274
|
-
}
|
|
275
|
-
export function fromIterable(iterable) {
|
|
276
|
-
return new RecursiveSet(...iterable);
|
|
277
|
-
}
|
|
467
|
+
export function emptySet() { return new RecursiveSet(); }
|
|
468
|
+
export function singleton(element) { return new RecursiveSet(element); }
|
|
469
|
+
export function fromIterable(iterable) { return new RecursiveSet(...iterable); }
|
|
278
470
|
//# sourceMappingURL=index.js.map
|