recursive-set 5.0.2 → 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 +262 -135
- 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 +262 -135
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -2,35 +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)
|
|
18
|
+
const floatBuffer = new ArrayBuffer(8);
|
|
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
|
+
*/
|
|
25
|
+
function hashNumber(val) {
|
|
26
|
+
// Integer Path: Handle Safe Integers correctly (up to 2^53)
|
|
27
|
+
if (Number.isSafeInteger(val)) {
|
|
28
|
+
let h = FNV_OFFSET;
|
|
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;
|
|
34
|
+
h = Math.imul(h, FNV_PRIME);
|
|
35
|
+
return h >>> 0;
|
|
36
|
+
}
|
|
37
|
+
// Float Path: IEEE 754 Bit Pattern (Little Endian Enforced)
|
|
38
|
+
view.setFloat64(0, val, true);
|
|
39
|
+
let h = FNV_OFFSET;
|
|
40
|
+
const low = view.getInt32(0, true);
|
|
41
|
+
const high = view.getInt32(4, true);
|
|
42
|
+
h ^= low;
|
|
43
|
+
h = Math.imul(h, FNV_PRIME);
|
|
44
|
+
h ^= high;
|
|
45
|
+
h = Math.imul(h, FNV_PRIME);
|
|
46
|
+
return h >>> 0;
|
|
47
|
+
}
|
|
14
48
|
function hashString(str) {
|
|
15
|
-
let
|
|
49
|
+
let h = FNV_OFFSET;
|
|
16
50
|
const len = str.length;
|
|
17
51
|
for (let i = 0; i < len; i++) {
|
|
18
|
-
|
|
19
|
-
|
|
52
|
+
h ^= str.charCodeAt(i);
|
|
53
|
+
h = Math.imul(h, FNV_PRIME);
|
|
20
54
|
}
|
|
21
|
-
return
|
|
55
|
+
return h >>> 0;
|
|
22
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
|
+
*/
|
|
23
61
|
function hashValue(val) {
|
|
24
62
|
if (typeof val === 'string')
|
|
25
63
|
return hashString(val);
|
|
26
|
-
if (typeof val === 'number')
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return hash >>> 0;
|
|
31
|
-
}
|
|
32
|
-
// Fast Path: Objects with cached hash
|
|
33
|
-
if (val && typeof val === 'object' && 'hashCode' in val) {
|
|
64
|
+
if (typeof val === 'number')
|
|
65
|
+
return hashNumber(val);
|
|
66
|
+
// Strict Fast-Path: Only trust our own Primitives
|
|
67
|
+
if (val instanceof RecursiveSet || val instanceof Tuple) {
|
|
34
68
|
return val.hashCode;
|
|
35
69
|
}
|
|
36
70
|
if (Array.isArray(val)) {
|
|
@@ -44,93 +78,168 @@ function hashValue(val) {
|
|
|
44
78
|
}
|
|
45
79
|
return 0;
|
|
46
80
|
}
|
|
47
|
-
//
|
|
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
|
+
*/
|
|
48
106
|
function compare(a, b) {
|
|
49
107
|
if (a === b)
|
|
50
108
|
return 0;
|
|
51
|
-
// 1.
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const typeA = typeof a;
|
|
60
|
-
const typeB = typeof b;
|
|
61
|
-
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)
|
|
62
117
|
return a < b ? -1 : 1;
|
|
63
|
-
if (
|
|
118
|
+
if (scoreA === 2)
|
|
64
119
|
return a < b ? -1 : 1;
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
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) {
|
|
68
127
|
return a.compare(b);
|
|
69
128
|
}
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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;
|
|
77
139
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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;
|
|
89
166
|
}
|
|
90
|
-
return 0;
|
|
91
167
|
}
|
|
92
|
-
|
|
168
|
+
throw new Error("Unsupported Type: Use number, string, Tuple, Array or RecursiveSet.");
|
|
93
169
|
}
|
|
94
|
-
//
|
|
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
|
+
*/
|
|
95
181
|
class Tuple {
|
|
96
|
-
values;
|
|
182
|
+
#values;
|
|
97
183
|
hashCode;
|
|
184
|
+
/**
|
|
185
|
+
* Creates a new Tuple.
|
|
186
|
+
* @param values Elements of the tuple.
|
|
187
|
+
*/
|
|
98
188
|
constructor(...values) {
|
|
99
|
-
|
|
100
|
-
|
|
189
|
+
for (const v of values)
|
|
190
|
+
validateType(v);
|
|
191
|
+
this.#values = [...values];
|
|
192
|
+
Object.freeze(this.#values);
|
|
193
|
+
this.hashCode = hashValue(this.#values);
|
|
101
194
|
}
|
|
102
|
-
|
|
103
|
-
get(
|
|
104
|
-
|
|
105
|
-
|
|
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(', ')})`; }
|
|
106
201
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
107
202
|
}
|
|
108
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
|
+
*/
|
|
109
213
|
class RecursiveSet {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
214
|
+
#elements = [];
|
|
215
|
+
#hashCode = null;
|
|
216
|
+
#isFrozen = false;
|
|
217
|
+
/**
|
|
218
|
+
* Exposes the static compare function used internally.
|
|
219
|
+
*/
|
|
113
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
|
+
*/
|
|
114
225
|
constructor(...elements) {
|
|
115
|
-
if (elements.length
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
this
|
|
121
|
-
this._elements.sort(compare);
|
|
122
|
-
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();
|
|
123
232
|
}
|
|
124
233
|
}
|
|
125
|
-
|
|
126
|
-
if (this
|
|
234
|
+
#checkFrozen(op) {
|
|
235
|
+
if (this.#isFrozen) {
|
|
127
236
|
throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet.\n` +
|
|
128
237
|
`This Set has been hashed or used in a collection (Value Semantics).\n` +
|
|
129
238
|
`Use .mutableCopy() to create a modifiable copy.`);
|
|
130
239
|
}
|
|
131
240
|
}
|
|
132
|
-
|
|
133
|
-
const arr = this
|
|
241
|
+
#unique() {
|
|
242
|
+
const arr = this.#elements;
|
|
134
243
|
const len = arr.length;
|
|
135
244
|
if (len < 2)
|
|
136
245
|
return;
|
|
@@ -143,22 +252,28 @@ class RecursiveSet {
|
|
|
143
252
|
arr.length = write;
|
|
144
253
|
}
|
|
145
254
|
/**
|
|
146
|
-
* Calculates
|
|
255
|
+
* Calculates and caches the hash code.
|
|
256
|
+
* Freezes the set to ensure the hash remains valid.
|
|
147
257
|
*/
|
|
148
258
|
get hashCode() {
|
|
149
|
-
if (this
|
|
150
|
-
return this
|
|
259
|
+
if (this.#hashCode !== null)
|
|
260
|
+
return this.#hashCode;
|
|
151
261
|
let h = 0;
|
|
152
|
-
const arr = this
|
|
262
|
+
const arr = this.#elements;
|
|
153
263
|
const len = arr.length;
|
|
154
264
|
for (let i = 0; i < len; i++) {
|
|
155
|
-
|
|
265
|
+
// Wrap to 32-bit at each step for consistency
|
|
266
|
+
h = (Math.imul(31, h) + hashValue(arr[i])) | 0;
|
|
156
267
|
}
|
|
157
|
-
this
|
|
158
|
-
this
|
|
159
|
-
return this
|
|
268
|
+
this.#hashCode = h | 0;
|
|
269
|
+
this.#isFrozen = true;
|
|
270
|
+
return this.#hashCode;
|
|
160
271
|
}
|
|
161
|
-
|
|
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
|
+
*/
|
|
162
277
|
compare(other) {
|
|
163
278
|
if (this === other)
|
|
164
279
|
return 0;
|
|
@@ -166,8 +281,8 @@ class RecursiveSet {
|
|
|
166
281
|
const h2 = other.hashCode;
|
|
167
282
|
if (h1 !== h2)
|
|
168
283
|
return h1 < h2 ? -1 : 1;
|
|
169
|
-
const arrA = this
|
|
170
|
-
const arrB = other
|
|
284
|
+
const arrA = this.#elements;
|
|
285
|
+
const arrB = other.#elements;
|
|
171
286
|
const len = arrA.length;
|
|
172
287
|
if (len !== arrB.length)
|
|
173
288
|
return len - arrB.length;
|
|
@@ -178,12 +293,15 @@ class RecursiveSet {
|
|
|
178
293
|
}
|
|
179
294
|
return 0;
|
|
180
295
|
}
|
|
181
|
-
get size() { return this.
|
|
182
|
-
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
|
+
*/
|
|
183
302
|
has(element) {
|
|
184
|
-
const arr = this
|
|
303
|
+
const arr = this.#elements;
|
|
185
304
|
const len = arr.length;
|
|
186
|
-
// Linear Scan (Prefetch-friendly for small sets)
|
|
187
305
|
if (len < 16) {
|
|
188
306
|
for (let i = 0; i < len; i++) {
|
|
189
307
|
if (compare(arr[i], element) === 0)
|
|
@@ -191,7 +309,6 @@ class RecursiveSet {
|
|
|
191
309
|
}
|
|
192
310
|
return false;
|
|
193
311
|
}
|
|
194
|
-
// Binary Search
|
|
195
312
|
let low = 0, high = len - 1;
|
|
196
313
|
while (low <= high) {
|
|
197
314
|
const mid = (low + high) >>> 1;
|
|
@@ -205,25 +322,21 @@ class RecursiveSet {
|
|
|
205
322
|
}
|
|
206
323
|
return false;
|
|
207
324
|
}
|
|
325
|
+
/**
|
|
326
|
+
* Adds an element to the set.
|
|
327
|
+
* Throws if the set is frozen.
|
|
328
|
+
*/
|
|
208
329
|
add(element) {
|
|
209
|
-
this
|
|
210
|
-
|
|
211
|
-
|
|
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)) {
|
|
217
|
-
throw new Error("NaN is not supported");
|
|
218
|
-
}
|
|
219
|
-
const arr = this._elements;
|
|
330
|
+
this.#checkFrozen('add() to');
|
|
331
|
+
validateType(element);
|
|
332
|
+
const arr = this.#elements;
|
|
220
333
|
const len = arr.length;
|
|
221
|
-
// Optimization: Append to end (common
|
|
334
|
+
// Optimization: Append to end (common pattern)
|
|
222
335
|
if (len > 0) {
|
|
223
336
|
const lastCmp = compare(arr[len - 1], element);
|
|
224
337
|
if (lastCmp < 0) {
|
|
225
338
|
arr.push(element);
|
|
226
|
-
this
|
|
339
|
+
this.#hashCode = null;
|
|
227
340
|
return this;
|
|
228
341
|
}
|
|
229
342
|
if (lastCmp === 0)
|
|
@@ -231,10 +344,10 @@ class RecursiveSet {
|
|
|
231
344
|
}
|
|
232
345
|
else {
|
|
233
346
|
arr.push(element);
|
|
234
|
-
this
|
|
347
|
+
this.#hashCode = null;
|
|
235
348
|
return this;
|
|
236
349
|
}
|
|
237
|
-
// Small Array
|
|
350
|
+
// Small Array: Linear Insert
|
|
238
351
|
if (len < 16) {
|
|
239
352
|
for (let i = 0; i < len; i++) {
|
|
240
353
|
const cmp = compare(arr[i], element);
|
|
@@ -242,14 +355,14 @@ class RecursiveSet {
|
|
|
242
355
|
return this;
|
|
243
356
|
if (cmp > 0) {
|
|
244
357
|
arr.splice(i, 0, element);
|
|
245
|
-
this
|
|
358
|
+
this.#hashCode = null;
|
|
246
359
|
return this;
|
|
247
360
|
}
|
|
248
361
|
}
|
|
249
|
-
arr.push(element);
|
|
362
|
+
arr.push(element);
|
|
250
363
|
return this;
|
|
251
364
|
}
|
|
252
|
-
// Large Array
|
|
365
|
+
// Large Array: Binary Search Insert
|
|
253
366
|
let low = 0, high = len - 1, idx = 0;
|
|
254
367
|
while (low <= high) {
|
|
255
368
|
const mid = (low + high) >>> 1;
|
|
@@ -266,18 +379,22 @@ class RecursiveSet {
|
|
|
266
379
|
}
|
|
267
380
|
}
|
|
268
381
|
arr.splice(idx, 0, element);
|
|
269
|
-
this
|
|
382
|
+
this.#hashCode = null;
|
|
270
383
|
return this;
|
|
271
384
|
}
|
|
385
|
+
/**
|
|
386
|
+
* Removes an element from the set.
|
|
387
|
+
* Throws if the set is frozen.
|
|
388
|
+
*/
|
|
272
389
|
remove(element) {
|
|
273
|
-
this
|
|
274
|
-
const arr = this
|
|
390
|
+
this.#checkFrozen('remove() from');
|
|
391
|
+
const arr = this.#elements;
|
|
275
392
|
const len = arr.length;
|
|
276
393
|
if (len < 16) {
|
|
277
394
|
for (let i = 0; i < len; i++) {
|
|
278
395
|
if (compare(arr[i], element) === 0) {
|
|
279
396
|
arr.splice(i, 1);
|
|
280
|
-
this
|
|
397
|
+
this.#hashCode = null;
|
|
281
398
|
return this;
|
|
282
399
|
}
|
|
283
400
|
}
|
|
@@ -289,7 +406,7 @@ class RecursiveSet {
|
|
|
289
406
|
const cmp = compare(arr[mid], element);
|
|
290
407
|
if (cmp === 0) {
|
|
291
408
|
arr.splice(mid, 1);
|
|
292
|
-
this
|
|
409
|
+
this.#hashCode = null;
|
|
293
410
|
return this;
|
|
294
411
|
}
|
|
295
412
|
if (cmp < 0)
|
|
@@ -300,21 +417,25 @@ class RecursiveSet {
|
|
|
300
417
|
return this;
|
|
301
418
|
}
|
|
302
419
|
clear() {
|
|
303
|
-
this
|
|
304
|
-
this
|
|
305
|
-
this
|
|
420
|
+
this.#checkFrozen('clear()');
|
|
421
|
+
this.#elements = [];
|
|
422
|
+
this.#hashCode = 0;
|
|
306
423
|
return this;
|
|
307
424
|
}
|
|
425
|
+
/**
|
|
426
|
+
* Creates a mutable shallow copy of this set.
|
|
427
|
+
* Useful for modifying a set after it has been frozen.
|
|
428
|
+
*/
|
|
308
429
|
mutableCopy() {
|
|
309
430
|
const s = new RecursiveSet();
|
|
310
|
-
s
|
|
431
|
+
s.#elements = this.#elements.slice();
|
|
311
432
|
return s;
|
|
312
433
|
}
|
|
313
434
|
clone() { return this.mutableCopy(); }
|
|
314
435
|
union(other) {
|
|
315
436
|
const s = new RecursiveSet();
|
|
316
|
-
const arrA = this
|
|
317
|
-
const arrB = other
|
|
437
|
+
const arrA = this.#elements;
|
|
438
|
+
const arrB = other.#elements;
|
|
318
439
|
if (arrA.length === 0)
|
|
319
440
|
return other.clone();
|
|
320
441
|
if (arrB.length === 0)
|
|
@@ -337,13 +458,13 @@ class RecursiveSet {
|
|
|
337
458
|
res.push(arrA[i++]);
|
|
338
459
|
while (j < lenB)
|
|
339
460
|
res.push(arrB[j++]);
|
|
340
|
-
s
|
|
461
|
+
s.#elements = res;
|
|
341
462
|
return s;
|
|
342
463
|
}
|
|
343
464
|
intersection(other) {
|
|
344
465
|
const s = new RecursiveSet();
|
|
345
|
-
const arrA = this
|
|
346
|
-
const arrB = other
|
|
466
|
+
const arrA = this.#elements;
|
|
467
|
+
const arrB = other.#elements;
|
|
347
468
|
const res = [];
|
|
348
469
|
let i = 0, j = 0;
|
|
349
470
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -358,13 +479,13 @@ class RecursiveSet {
|
|
|
358
479
|
j++;
|
|
359
480
|
}
|
|
360
481
|
}
|
|
361
|
-
s
|
|
482
|
+
s.#elements = res;
|
|
362
483
|
return s;
|
|
363
484
|
}
|
|
364
485
|
difference(other) {
|
|
365
486
|
const s = new RecursiveSet();
|
|
366
|
-
const arrA = this
|
|
367
|
-
const arrB = other
|
|
487
|
+
const arrA = this.#elements;
|
|
488
|
+
const arrB = other.#elements;
|
|
368
489
|
const res = [];
|
|
369
490
|
let i = 0, j = 0;
|
|
370
491
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -381,13 +502,13 @@ class RecursiveSet {
|
|
|
381
502
|
}
|
|
382
503
|
while (i < lenA)
|
|
383
504
|
res.push(arrA[i++]);
|
|
384
|
-
s
|
|
505
|
+
s.#elements = res;
|
|
385
506
|
return s;
|
|
386
507
|
}
|
|
387
508
|
symmetricDifference(other) {
|
|
388
509
|
const s = new RecursiveSet();
|
|
389
|
-
const arrA = this
|
|
390
|
-
const arrB = other
|
|
510
|
+
const arrA = this.#elements;
|
|
511
|
+
const arrB = other.#elements;
|
|
391
512
|
const res = [];
|
|
392
513
|
let i = 0, j = 0;
|
|
393
514
|
const lenA = arrA.length, lenB = arrB.length;
|
|
@@ -406,7 +527,7 @@ class RecursiveSet {
|
|
|
406
527
|
res.push(arrA[i++]);
|
|
407
528
|
while (j < lenB)
|
|
408
529
|
res.push(arrB[j++]);
|
|
409
|
-
s
|
|
530
|
+
s.#elements = res;
|
|
410
531
|
return s;
|
|
411
532
|
}
|
|
412
533
|
powerset() {
|
|
@@ -419,7 +540,7 @@ class RecursiveSet {
|
|
|
419
540
|
const subset = new RecursiveSet();
|
|
420
541
|
for (let j = 0; j < n; j++) {
|
|
421
542
|
if (i & (1 << j))
|
|
422
|
-
subset.
|
|
543
|
+
subset.#elements.push(this.#elements[j]);
|
|
423
544
|
}
|
|
424
545
|
subsets.push(subset);
|
|
425
546
|
}
|
|
@@ -427,20 +548,23 @@ class RecursiveSet {
|
|
|
427
548
|
}
|
|
428
549
|
cartesianProduct(other) {
|
|
429
550
|
const result = new RecursiveSet();
|
|
430
|
-
const arrA = this
|
|
431
|
-
const arrB = other
|
|
551
|
+
const arrA = this.#elements;
|
|
552
|
+
const arrB = other.#elements;
|
|
432
553
|
for (const x of arrA) {
|
|
433
554
|
for (const y of arrB) {
|
|
434
|
-
result.
|
|
555
|
+
result.#elements.push(new Tuple(x, y));
|
|
435
556
|
}
|
|
436
557
|
}
|
|
558
|
+
// Hash ordering is not monotonic, so we must resort
|
|
559
|
+
result.#elements.sort(compare);
|
|
560
|
+
result.#unique();
|
|
437
561
|
return result;
|
|
438
562
|
}
|
|
439
563
|
isSubset(other) {
|
|
440
564
|
if (this.size > other.size)
|
|
441
565
|
return false;
|
|
442
566
|
let i = 0, j = 0;
|
|
443
|
-
const arrA = this
|
|
567
|
+
const arrA = this.#elements, arrB = other.#elements;
|
|
444
568
|
while (i < arrA.length && j < arrB.length) {
|
|
445
569
|
const cmp = compare(arrA[i], arrB[j]);
|
|
446
570
|
if (cmp < 0)
|
|
@@ -457,12 +581,12 @@ class RecursiveSet {
|
|
|
457
581
|
isSuperset(other) { return other.isSubset(this); }
|
|
458
582
|
isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
|
|
459
583
|
equals(other) { return this.compare(other) === 0; }
|
|
460
|
-
toSet() { return new Set(this
|
|
461
|
-
*[Symbol.iterator]() { yield* this
|
|
584
|
+
toSet() { return new Set(this.#elements); }
|
|
585
|
+
*[Symbol.iterator]() { yield* this.#elements; }
|
|
462
586
|
toString() {
|
|
463
587
|
if (this.isEmpty())
|
|
464
588
|
return "∅";
|
|
465
|
-
const elementsStr = this.
|
|
589
|
+
const elementsStr = this.#elements.map(el => {
|
|
466
590
|
if (Array.isArray(el))
|
|
467
591
|
return `[${el.join(', ')}]`;
|
|
468
592
|
return String(el);
|
|
@@ -472,6 +596,9 @@ class RecursiveSet {
|
|
|
472
596
|
[Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
|
|
473
597
|
}
|
|
474
598
|
exports.RecursiveSet = RecursiveSet;
|
|
599
|
+
// ============================================================================
|
|
600
|
+
// EXPORTS
|
|
601
|
+
// ============================================================================
|
|
475
602
|
function emptySet() { return new RecursiveSet(); }
|
|
476
603
|
function singleton(element) { return new RecursiveSet(element); }
|
|
477
604
|
function fromIterable(iterable) { return new RecursiveSet(...iterable); }
|