recursive-set 5.0.3 → 6.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 +74 -99
- package/dist/cjs/index.js +249 -136
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +57 -9
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +249 -136
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -2,49 +2,69 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* @module recursive-set
|
|
4
4
|
* High-Performance Recursive Set with "Freeze-on-Hash" semantics.
|
|
5
|
+
* Version: 6.0.0
|
|
5
6
|
*/
|
|
6
7
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
8
|
exports.RecursiveSet = exports.Tuple = void 0;
|
|
8
9
|
exports.emptySet = emptySet;
|
|
9
10
|
exports.singleton = singleton;
|
|
10
11
|
exports.fromIterable = fromIterable;
|
|
11
|
-
//
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// HASHING ENGINE (FNV-1a with DataView & Safe Integer Split)
|
|
14
|
+
// ============================================================================
|
|
12
15
|
const FNV_PRIME = 16777619;
|
|
13
16
|
const FNV_OFFSET = 2166136261;
|
|
17
|
+
// Shared buffer to avoid allocation overhead (reused for all number hashing)
|
|
14
18
|
const floatBuffer = new ArrayBuffer(8);
|
|
15
|
-
const
|
|
16
|
-
|
|
19
|
+
const view = new DataView(floatBuffer);
|
|
20
|
+
/**
|
|
21
|
+
* Hashes a number using FNV-1a.
|
|
22
|
+
* Handles both safe integers (via high/low split) and floats (via IEEE 754 bits).
|
|
23
|
+
* Ensures platform consistency by enforcing Little Endian byte order.
|
|
24
|
+
*/
|
|
17
25
|
function hashNumber(val) {
|
|
18
|
-
|
|
26
|
+
// Integer Path: Handle Safe Integers correctly (up to 2^53)
|
|
27
|
+
if (Number.isSafeInteger(val)) {
|
|
19
28
|
let h = FNV_OFFSET;
|
|
20
|
-
|
|
29
|
+
const lowU = val >>> 0;
|
|
30
|
+
const high = ((val - lowU) / 4294967296) | 0;
|
|
31
|
+
h ^= lowU;
|
|
32
|
+
h = Math.imul(h, FNV_PRIME);
|
|
33
|
+
h ^= high;
|
|
21
34
|
h = Math.imul(h, FNV_PRIME);
|
|
22
35
|
return h >>> 0;
|
|
23
36
|
}
|
|
24
|
-
|
|
37
|
+
// Float Path: IEEE 754 Bit Pattern (Little Endian Enforced)
|
|
38
|
+
view.setFloat64(0, val, true);
|
|
25
39
|
let h = FNV_OFFSET;
|
|
26
|
-
|
|
40
|
+
const low = view.getInt32(0, true);
|
|
41
|
+
const high = view.getInt32(4, true);
|
|
42
|
+
h ^= low;
|
|
27
43
|
h = Math.imul(h, FNV_PRIME);
|
|
28
|
-
h ^=
|
|
44
|
+
h ^= high;
|
|
29
45
|
h = Math.imul(h, FNV_PRIME);
|
|
30
46
|
return h >>> 0;
|
|
31
47
|
}
|
|
32
48
|
function hashString(str) {
|
|
33
|
-
let
|
|
49
|
+
let h = FNV_OFFSET;
|
|
34
50
|
const len = str.length;
|
|
35
51
|
for (let i = 0; i < len; i++) {
|
|
36
|
-
|
|
37
|
-
|
|
52
|
+
h ^= str.charCodeAt(i);
|
|
53
|
+
h = Math.imul(h, FNV_PRIME);
|
|
38
54
|
}
|
|
39
|
-
return
|
|
55
|
+
return h >>> 0;
|
|
40
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Computes a 32-bit hash code for a supported value.
|
|
59
|
+
* @param val The value to hash (number, string, Tuple, RecursiveSet, or Array)
|
|
60
|
+
*/
|
|
41
61
|
function hashValue(val) {
|
|
42
62
|
if (typeof val === 'string')
|
|
43
63
|
return hashString(val);
|
|
44
64
|
if (typeof val === 'number')
|
|
45
65
|
return hashNumber(val);
|
|
46
|
-
// Fast
|
|
47
|
-
if (val
|
|
66
|
+
// Strict Fast-Path: Only trust our own Primitives
|
|
67
|
+
if (val instanceof RecursiveSet || val instanceof Tuple) {
|
|
48
68
|
return val.hashCode;
|
|
49
69
|
}
|
|
50
70
|
if (Array.isArray(val)) {
|
|
@@ -58,93 +78,168 @@ function hashValue(val) {
|
|
|
58
78
|
}
|
|
59
79
|
return 0;
|
|
60
80
|
}
|
|
61
|
-
//
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// STRICT COMPARATOR (Total Order)
|
|
83
|
+
// ============================================================================
|
|
84
|
+
/**
|
|
85
|
+
* Determines the sort priority of a type.
|
|
86
|
+
* Order: number (1) < string (2) < Sequence (3) < Set (4)
|
|
87
|
+
*/
|
|
88
|
+
function getTypeScore(a) {
|
|
89
|
+
if (typeof a === 'number')
|
|
90
|
+
return 1;
|
|
91
|
+
if (typeof a === 'string')
|
|
92
|
+
return 2;
|
|
93
|
+
if (Array.isArray(a) || a instanceof Tuple)
|
|
94
|
+
return 3;
|
|
95
|
+
if (a instanceof RecursiveSet)
|
|
96
|
+
return 4;
|
|
97
|
+
return 0;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Compares two values for sorting.
|
|
101
|
+
* Implements a strict total order:
|
|
102
|
+
* 1. Semantic Type Score (Number < String < Seq < Set)
|
|
103
|
+
* 2. Hash Code (Short-Circuit)
|
|
104
|
+
* 3. Deep Structural Comparison
|
|
105
|
+
*/
|
|
62
106
|
function compare(a, b) {
|
|
63
107
|
if (a === b)
|
|
64
108
|
return 0;
|
|
65
|
-
// 1.
|
|
66
|
-
const
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const typeA = typeof a;
|
|
74
|
-
const typeB = typeof b;
|
|
75
|
-
if (typeA === 'string' && typeB === 'string')
|
|
109
|
+
// 1. Semantic Grouping
|
|
110
|
+
const scoreA = getTypeScore(a);
|
|
111
|
+
const scoreB = getTypeScore(b);
|
|
112
|
+
if (scoreA !== scoreB) {
|
|
113
|
+
return scoreA < scoreB ? -1 : 1;
|
|
114
|
+
}
|
|
115
|
+
// 2. PRIMITIVES: Value Sort (Human readable & Fast)
|
|
116
|
+
if (scoreA === 1)
|
|
76
117
|
return a < b ? -1 : 1;
|
|
77
|
-
if (
|
|
118
|
+
if (scoreA === 2)
|
|
78
119
|
return a < b ? -1 : 1;
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
120
|
+
// 3. OBJECTS: Hash Optimization (Performance Protection)
|
|
121
|
+
const ha = hashValue(a);
|
|
122
|
+
const hb = hashValue(b);
|
|
123
|
+
if (ha !== hb)
|
|
124
|
+
return ha < hb ? -1 : 1;
|
|
125
|
+
// 3. Fallback / Structural Comparison
|
|
126
|
+
if (scoreA === 4) {
|
|
82
127
|
return a.compare(b);
|
|
83
128
|
}
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
129
|
+
// Sequence Comparison (Arrays & Tuples)
|
|
130
|
+
const valA = (a instanceof Tuple) ? a.values : a;
|
|
131
|
+
const valB = (b instanceof Tuple) ? b.values : b;
|
|
132
|
+
const len = valA.length;
|
|
133
|
+
if (len !== valB.length)
|
|
134
|
+
return len - valB.length;
|
|
135
|
+
for (let i = 0; i < len; i++) {
|
|
136
|
+
const diff = compare(valA[i], valB[i]);
|
|
137
|
+
if (diff !== 0)
|
|
138
|
+
return diff;
|
|
91
139
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
return 0;
|
|
141
|
+
}
|
|
142
|
+
// ============================================================================
|
|
143
|
+
// VALIDATION
|
|
144
|
+
// ============================================================================
|
|
145
|
+
/**
|
|
146
|
+
* Validates that an element is of a supported immutable-compatible type.
|
|
147
|
+
* Throws if the type is mutable (plain object) or unsupported (function, symbol).
|
|
148
|
+
* @throws {Error} if element type is invalid.
|
|
149
|
+
*/
|
|
150
|
+
function validateType(element) {
|
|
151
|
+
if (typeof element === 'number') {
|
|
152
|
+
if (Number.isNaN(element))
|
|
153
|
+
throw new Error("NaN is not supported");
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (typeof element === 'string')
|
|
157
|
+
return;
|
|
158
|
+
if (Array.isArray(element)) {
|
|
159
|
+
for (const item of element)
|
|
160
|
+
validateType(item);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (typeof element === 'object' && element !== null) {
|
|
164
|
+
if (element instanceof RecursiveSet || element instanceof Tuple) {
|
|
165
|
+
return;
|
|
103
166
|
}
|
|
104
|
-
return 0;
|
|
105
167
|
}
|
|
106
|
-
|
|
168
|
+
throw new Error("Unsupported Type: Use number, string, Tuple, Array or RecursiveSet.");
|
|
107
169
|
}
|
|
108
|
-
//
|
|
170
|
+
// ============================================================================
|
|
171
|
+
// CLASSES
|
|
172
|
+
// ============================================================================
|
|
173
|
+
/**
|
|
174
|
+
* Immutable Tuple container.
|
|
175
|
+
* * Guarantees structural immutability by freezing the internal array storage.
|
|
176
|
+
* Note: Immutability is shallow. If you store mutable Arrays inside a Tuple,
|
|
177
|
+
* the Tuple logic remains correct, but the content inside the Array might change.
|
|
178
|
+
* For strict value semantics, use Tuple<Primitive | RecursiveSet | Tuple>.
|
|
179
|
+
* * @template T The tuple type.
|
|
180
|
+
*/
|
|
109
181
|
class Tuple {
|
|
110
|
-
values;
|
|
182
|
+
#values;
|
|
111
183
|
hashCode;
|
|
184
|
+
/**
|
|
185
|
+
* Creates a new Tuple.
|
|
186
|
+
* @param values Elements of the tuple.
|
|
187
|
+
*/
|
|
112
188
|
constructor(...values) {
|
|
113
|
-
|
|
114
|
-
|
|
189
|
+
for (const v of values)
|
|
190
|
+
validateType(v);
|
|
191
|
+
this.#values = [...values];
|
|
192
|
+
Object.freeze(this.#values);
|
|
193
|
+
this.hashCode = hashValue(this.#values);
|
|
115
194
|
}
|
|
116
|
-
|
|
117
|
-
get(
|
|
118
|
-
|
|
119
|
-
|
|
195
|
+
/** Returns the read-only backing array. */
|
|
196
|
+
get values() { return this.#values; }
|
|
197
|
+
get length() { return this.#values.length; }
|
|
198
|
+
get(i) { return this.#values[i]; }
|
|
199
|
+
*[Symbol.iterator]() { yield* this.#values; }
|
|
200
|
+
toString() { return `(${this.#values.join(', ')})`; }
|
|
120
201
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
121
202
|
}
|
|
122
203
|
exports.Tuple = Tuple;
|
|
204
|
+
/**
|
|
205
|
+
* A mathematical Set with Value Semantics.
|
|
206
|
+
* * Features:
|
|
207
|
+
* - **Strict Typing:** Supports number, string, Tuple, Array, RecursiveSet.
|
|
208
|
+
* - **Sorted Storage:** Elements are internally sorted for O(1) equality checks via hash.
|
|
209
|
+
* - **Freeze-on-Hash:** Once the hash code is accessed (e.g. when added to another Set),
|
|
210
|
+
* this Set becomes immutable to prevent hash corruption.
|
|
211
|
+
* * @template T The type of elements in the set.
|
|
212
|
+
*/
|
|
123
213
|
class RecursiveSet {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
214
|
+
#elements = [];
|
|
215
|
+
#hashCode = null;
|
|
216
|
+
#isFrozen = false;
|
|
217
|
+
/**
|
|
218
|
+
* Exposes the static compare function used internally.
|
|
219
|
+
*/
|
|
127
220
|
static compare(a, b) { return compare(a, b); }
|
|
221
|
+
/**
|
|
222
|
+
* Creates a new RecursiveSet.
|
|
223
|
+
* @param elements Initial elements (will be sorted and deduplicated).
|
|
224
|
+
*/
|
|
128
225
|
constructor(...elements) {
|
|
129
|
-
if (elements.length
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
this
|
|
135
|
-
this._elements.sort(compare);
|
|
136
|
-
this._unique();
|
|
226
|
+
if (elements.length > 0) {
|
|
227
|
+
for (const el of elements)
|
|
228
|
+
validateType(el);
|
|
229
|
+
this.#elements = elements;
|
|
230
|
+
this.#elements.sort(compare);
|
|
231
|
+
this.#unique();
|
|
137
232
|
}
|
|
138
233
|
}
|
|
139
|
-
|
|
140
|
-
if (this
|
|
234
|
+
#checkFrozen(op) {
|
|
235
|
+
if (this.#isFrozen) {
|
|
141
236
|
throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet.\n` +
|
|
142
237
|
`This Set has been hashed or used in a collection (Value Semantics).\n` +
|
|
143
238
|
`Use .mutableCopy() to create a modifiable copy.`);
|
|
144
239
|
}
|
|
145
240
|
}
|
|
146
|
-
|
|
147
|
-
const arr = this
|
|
241
|
+
#unique() {
|
|
242
|
+
const arr = this.#elements;
|
|
148
243
|
const len = arr.length;
|
|
149
244
|
if (len < 2)
|
|
150
245
|
return;
|
|
@@ -157,22 +252,28 @@ class RecursiveSet {
|
|
|
157
252
|
arr.length = write;
|
|
158
253
|
}
|
|
159
254
|
/**
|
|
160
|
-
* Calculates
|
|
255
|
+
* Calculates and caches the hash code.
|
|
256
|
+
* Freezes the set to ensure the hash remains valid.
|
|
161
257
|
*/
|
|
162
258
|
get hashCode() {
|
|
163
|
-
if (this
|
|
164
|
-
return this
|
|
259
|
+
if (this.#hashCode !== null)
|
|
260
|
+
return this.#hashCode;
|
|
165
261
|
let h = 0;
|
|
166
|
-
const arr = this
|
|
262
|
+
const arr = this.#elements;
|
|
167
263
|
const len = arr.length;
|
|
168
264
|
for (let i = 0; i < len; i++) {
|
|
169
|
-
|
|
265
|
+
// Wrap to 32-bit at each step for consistency
|
|
266
|
+
h = (Math.imul(31, h) + hashValue(arr[i])) | 0;
|
|
170
267
|
}
|
|
171
|
-
this
|
|
172
|
-
this
|
|
173
|
-
return this
|
|
268
|
+
this.#hashCode = h | 0;
|
|
269
|
+
this.#isFrozen = true;
|
|
270
|
+
return this.#hashCode;
|
|
174
271
|
}
|
|
175
|
-
|
|
272
|
+
/** Returns true if the set is frozen (hashed). */
|
|
273
|
+
get isFrozen() { return this.#isFrozen; }
|
|
274
|
+
/**
|
|
275
|
+
* Compares this set with another for sorting.
|
|
276
|
+
*/
|
|
176
277
|
compare(other) {
|
|
177
278
|
if (this === other)
|
|
178
279
|
return 0;
|
|
@@ -180,8 +281,8 @@ class RecursiveSet {
|
|
|
180
281
|
const h2 = other.hashCode;
|
|
181
282
|
if (h1 !== h2)
|
|
182
283
|
return h1 < h2 ? -1 : 1;
|
|
183
|
-
const arrA = this
|
|
184
|
-
const arrB = other
|
|
284
|
+
const arrA = this.#elements;
|
|
285
|
+
const arrB = other.#elements;
|
|
185
286
|
const len = arrA.length;
|
|
186
287
|
if (len !== arrB.length)
|
|
187
288
|
return len - arrB.length;
|
|
@@ -192,12 +293,15 @@ class RecursiveSet {
|
|
|
192
293
|
}
|
|
193
294
|
return 0;
|
|
194
295
|
}
|
|
195
|
-
get size() { return this.
|
|
196
|
-
isEmpty() { return this.
|
|
296
|
+
get size() { return this.#elements.length; }
|
|
297
|
+
isEmpty() { return this.#elements.length === 0; }
|
|
298
|
+
/**
|
|
299
|
+
* Checks if the set contains the given element.
|
|
300
|
+
* Uses binary search for sets > 16 elements.
|
|
301
|
+
*/
|
|
197
302
|
has(element) {
|
|
198
|
-
const arr = this
|
|
303
|
+
const arr = this.#elements;
|
|
199
304
|
const len = arr.length;
|
|
200
|
-
// Linear Scan (Prefetch-friendly for small sets)
|
|
201
305
|
if (len < 16) {
|
|
202
306
|
for (let i = 0; i < len; i++) {
|
|
203
307
|
if (compare(arr[i], element) === 0)
|
|
@@ -205,7 +309,6 @@ class RecursiveSet {
|
|
|
205
309
|
}
|
|
206
310
|
return false;
|
|
207
311
|
}
|
|
208
|
-
// Binary Search
|
|
209
312
|
let low = 0, high = len - 1;
|
|
210
313
|
while (low <= high) {
|
|
211
314
|
const mid = (low + high) >>> 1;
|
|
@@ -219,25 +322,21 @@ class RecursiveSet {
|
|
|
219
322
|
}
|
|
220
323
|
return false;
|
|
221
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Adds an element to the set.
|
|
327
|
+
* Throws if the set is frozen.
|
|
328
|
+
*/
|
|
222
329
|
add(element) {
|
|
223
|
-
this
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!(element instanceof RecursiveSet || element instanceof Tuple || Array.isArray(element))) {
|
|
227
|
-
throw new Error("Unsupported Type: Use Tuple, Array or RecursiveSet.");
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
else if (Number.isNaN(element)) {
|
|
231
|
-
throw new Error("NaN is not supported");
|
|
232
|
-
}
|
|
233
|
-
const arr = this._elements;
|
|
330
|
+
this.#checkFrozen('add() to');
|
|
331
|
+
validateType(element);
|
|
332
|
+
const arr = this.#elements;
|
|
234
333
|
const len = arr.length;
|
|
235
|
-
// Optimization: Append to end (common
|
|
334
|
+
// Optimization: Append to end (common pattern)
|
|
236
335
|
if (len > 0) {
|
|
237
336
|
const lastCmp = compare(arr[len - 1], element);
|
|
238
337
|
if (lastCmp < 0) {
|
|
239
338
|
arr.push(element);
|
|
240
|
-
this
|
|
339
|
+
this.#hashCode = null;
|
|
241
340
|
return this;
|
|
242
341
|
}
|
|
243
342
|
if (lastCmp === 0)
|
|
@@ -245,10 +344,10 @@ class RecursiveSet {
|
|
|
245
344
|
}
|
|
246
345
|
else {
|
|
247
346
|
arr.push(element);
|
|
248
|
-
this
|
|
347
|
+
this.#hashCode = null;
|
|
249
348
|
return this;
|
|
250
349
|
}
|
|
251
|
-
// Small Array
|
|
350
|
+
// Small Array: Linear Insert
|
|
252
351
|
if (len < 16) {
|
|
253
352
|
for (let i = 0; i < len; i++) {
|
|
254
353
|
const cmp = compare(arr[i], element);
|
|
@@ -256,14 +355,14 @@ class RecursiveSet {
|
|
|
256
355
|
return this;
|
|
257
356
|
if (cmp > 0) {
|
|
258
357
|
arr.splice(i, 0, element);
|
|
259
|
-
this
|
|
358
|
+
this.#hashCode = null;
|
|
260
359
|
return this;
|
|
261
360
|
}
|
|
262
361
|
}
|
|
263
|
-
arr.push(element);
|
|
362
|
+
arr.push(element);
|
|
264
363
|
return this;
|
|
265
364
|
}
|
|
266
|
-
// Large Array
|
|
365
|
+
// Large Array: Binary Search Insert
|
|
267
366
|
let low = 0, high = len - 1, idx = 0;
|
|
268
367
|
while (low <= high) {
|
|
269
368
|
const mid = (low + high) >>> 1;
|
|
@@ -280,18 +379,22 @@ class RecursiveSet {
|
|
|
280
379
|
}
|
|
281
380
|
}
|
|
282
381
|
arr.splice(idx, 0, element);
|
|
283
|
-
this
|
|
382
|
+
this.#hashCode = null;
|
|
284
383
|
return this;
|
|
285
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Removes an element from the set.
|
|
387
|
+
* Throws if the set is frozen.
|
|
388
|
+
*/
|
|
286
389
|
remove(element) {
|
|
287
|
-
this
|
|
288
|
-
const arr = this
|
|
390
|
+
this.#checkFrozen('remove() from');
|
|
391
|
+
const arr = this.#elements;
|
|
289
392
|
const len = arr.length;
|
|
290
393
|
if (len < 16) {
|
|
291
394
|
for (let i = 0; i < len; i++) {
|
|
292
395
|
if (compare(arr[i], element) === 0) {
|
|
293
396
|
arr.splice(i, 1);
|
|
294
|
-
this
|
|
397
|
+
this.#hashCode = null;
|
|
295
398
|
return this;
|
|
296
399
|
}
|
|
297
400
|
}
|
|
@@ -303,7 +406,7 @@ class RecursiveSet {
|
|
|
303
406
|
const cmp = compare(arr[mid], element);
|
|
304
407
|
if (cmp === 0) {
|
|
305
408
|
arr.splice(mid, 1);
|
|
306
|
-
this
|
|
409
|
+
this.#hashCode = null;
|
|
307
410
|
return this;
|
|
308
411
|
}
|
|
309
412
|
if (cmp < 0)
|
|
@@ -314,21 +417,25 @@ class RecursiveSet {
|
|
|
314
417
|
return this;
|
|
315
418
|
}
|
|
316
419
|
clear() {
|
|
317
|
-
this
|
|
318
|
-
this
|
|
319
|
-
this
|
|
420
|
+
this.#checkFrozen('clear()');
|
|
421
|
+
this.#elements = [];
|
|
422
|
+
this.#hashCode = 0;
|
|
320
423
|
return this;
|
|
321
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Creates a mutable shallow copy of this set.
|
|
427
|
+
* Useful for modifying a set after it has been frozen.
|
|
428
|
+
*/
|
|
322
429
|
mutableCopy() {
|
|
323
430
|
const s = new RecursiveSet();
|
|
324
|
-
s
|
|
431
|
+
s.#elements = this.#elements.slice();
|
|
325
432
|
return s;
|
|
326
433
|
}
|
|
327
434
|
clone() { return this.mutableCopy(); }
|
|
328
435
|
union(other) {
|
|
329
436
|
const s = new RecursiveSet();
|
|
330
|
-
const arrA = this
|
|
331
|
-
const arrB = other
|
|
437
|
+
const arrA = this.#elements;
|
|
438
|
+
const arrB = other.#elements;
|
|
332
439
|
if (arrA.length === 0)
|
|
333
440
|
return other.clone();
|
|
334
441
|
if (arrB.length === 0)
|
|
@@ -351,13 +458,13 @@ class RecursiveSet {
|
|
|
351
458
|
res.push(arrA[i++]);
|
|
352
459
|
while (j < lenB)
|
|
353
460
|
res.push(arrB[j++]);
|
|
354
|
-
s
|
|
461
|
+
s.#elements = res;
|
|
355
462
|
return s;
|
|
356
463
|
}
|
|
357
464
|
intersection(other) {
|
|
358
465
|
const s = new RecursiveSet();
|
|
359
|
-
const arrA = this
|
|
360
|
-
const arrB = other
|
|
466
|
+
const arrA = this.#elements;
|
|
467
|
+
const arrB = other.#elements;
|
|
361
468
|
const res = [];
|
|
362
469
|
let i = 0, j = 0;
|
|
363
470
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -372,13 +479,13 @@ class RecursiveSet {
|
|
|
372
479
|
j++;
|
|
373
480
|
}
|
|
374
481
|
}
|
|
375
|
-
s
|
|
482
|
+
s.#elements = res;
|
|
376
483
|
return s;
|
|
377
484
|
}
|
|
378
485
|
difference(other) {
|
|
379
486
|
const s = new RecursiveSet();
|
|
380
|
-
const arrA = this
|
|
381
|
-
const arrB = other
|
|
487
|
+
const arrA = this.#elements;
|
|
488
|
+
const arrB = other.#elements;
|
|
382
489
|
const res = [];
|
|
383
490
|
let i = 0, j = 0;
|
|
384
491
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -395,13 +502,13 @@ class RecursiveSet {
|
|
|
395
502
|
}
|
|
396
503
|
while (i < lenA)
|
|
397
504
|
res.push(arrA[i++]);
|
|
398
|
-
s
|
|
505
|
+
s.#elements = res;
|
|
399
506
|
return s;
|
|
400
507
|
}
|
|
401
508
|
symmetricDifference(other) {
|
|
402
509
|
const s = new RecursiveSet();
|
|
403
|
-
const arrA = this
|
|
404
|
-
const arrB = other
|
|
510
|
+
const arrA = this.#elements;
|
|
511
|
+
const arrB = other.#elements;
|
|
405
512
|
const res = [];
|
|
406
513
|
let i = 0, j = 0;
|
|
407
514
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -420,7 +527,7 @@ class RecursiveSet {
|
|
|
420
527
|
res.push(arrA[i++]);
|
|
421
528
|
while (j < lenB)
|
|
422
529
|
res.push(arrB[j++]);
|
|
423
|
-
s
|
|
530
|
+
s.#elements = res;
|
|
424
531
|
return s;
|
|
425
532
|
}
|
|
426
533
|
powerset() {
|
|
@@ -433,7 +540,7 @@ class RecursiveSet {
|
|
|
433
540
|
const subset = new RecursiveSet();
|
|
434
541
|
for (let j = 0; j < n; j++) {
|
|
435
542
|
if (i & (1 << j))
|
|
436
|
-
subset.
|
|
543
|
+
subset.#elements.push(this.#elements[j]);
|
|
437
544
|
}
|
|
438
545
|
subsets.push(subset);
|
|
439
546
|
}
|
|
@@ -441,20 +548,23 @@ class RecursiveSet {
|
|
|
441
548
|
}
|
|
442
549
|
cartesianProduct(other) {
|
|
443
550
|
const result = new RecursiveSet();
|
|
444
|
-
const arrA = this
|
|
445
|
-
const arrB = other
|
|
551
|
+
const arrA = this.#elements;
|
|
552
|
+
const arrB = other.#elements;
|
|
446
553
|
for (const x of arrA) {
|
|
447
554
|
for (const y of arrB) {
|
|
448
|
-
result.
|
|
555
|
+
result.#elements.push(new Tuple(x, y));
|
|
449
556
|
}
|
|
450
557
|
}
|
|
558
|
+
// Hash ordering is not monotonic, so we must resort
|
|
559
|
+
result.#elements.sort(compare);
|
|
560
|
+
result.#unique();
|
|
451
561
|
return result;
|
|
452
562
|
}
|
|
453
563
|
isSubset(other) {
|
|
454
564
|
if (this.size > other.size)
|
|
455
565
|
return false;
|
|
456
566
|
let i = 0, j = 0;
|
|
457
|
-
const arrA = this
|
|
567
|
+
const arrA = this.#elements, arrB = other.#elements;
|
|
458
568
|
while (i < arrA.length && j < arrB.length) {
|
|
459
569
|
const cmp = compare(arrA[i], arrB[j]);
|
|
460
570
|
if (cmp < 0)
|
|
@@ -471,12 +581,12 @@ class RecursiveSet {
|
|
|
471
581
|
isSuperset(other) { return other.isSubset(this); }
|
|
472
582
|
isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
|
|
473
583
|
equals(other) { return this.compare(other) === 0; }
|
|
474
|
-
toSet() { return new Set(this
|
|
475
|
-
*[Symbol.iterator]() { yield* this
|
|
584
|
+
toSet() { return new Set(this.#elements); }
|
|
585
|
+
*[Symbol.iterator]() { yield* this.#elements; }
|
|
476
586
|
toString() {
|
|
477
587
|
if (this.isEmpty())
|
|
478
588
|
return "∅";
|
|
479
|
-
const elementsStr = this.
|
|
589
|
+
const elementsStr = this.#elements.map(el => {
|
|
480
590
|
if (Array.isArray(el))
|
|
481
591
|
return `[${el.join(', ')}]`;
|
|
482
592
|
return String(el);
|
|
@@ -486,6 +596,9 @@ class RecursiveSet {
|
|
|
486
596
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
487
597
|
}
|
|
488
598
|
exports.RecursiveSet = RecursiveSet;
|
|
599
|
+
// ============================================================================
|
|
600
|
+
// EXPORTS
|
|
601
|
+
// ============================================================================
|
|
489
602
|
function emptySet() { return new RecursiveSet(); }
|
|
490
603
|
function singleton(element) { return new RecursiveSet(element); }
|
|
491
604
|
function fromIterable(iterable) { return new RecursiveSet(...iterable); }
|