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