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