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/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
- // === HASHING ENGINE (FNV-1a) ===
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 hash = FNV_OFFSET;
49
+ let h = FNV_OFFSET;
16
50
  const len = str.length;
17
51
  for (let i = 0; i < len; i++) {
18
- hash ^= str.charCodeAt(i);
19
- hash = Math.imul(hash, FNV_PRIME);
52
+ h ^= str.charCodeAt(i);
53
+ h = Math.imul(h, FNV_PRIME);
20
54
  }
21
- return hash >>> 0;
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
- let hash = FNV_OFFSET;
28
- hash ^= (val | 0);
29
- hash = Math.imul(hash, FNV_PRIME);
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
- // === COMPARATOR ===
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. 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')
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 (typeA === 'number' && typeB === 'number')
118
+ if (scoreA === 2)
64
119
  return a < b ? -1 : 1;
65
- const isSetA = a instanceof RecursiveSet;
66
- const isSetB = b instanceof RecursiveSet;
67
- if (isSetA && isSetB) {
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
- 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;
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
- // 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;
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
- return a < b ? -1 : 1;
168
+ throw new Error("Unsupported Type: Use number, string, Tuple, Array or RecursiveSet.");
93
169
  }
94
- // === CLASSES ===
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
- this.values = values;
100
- this.hashCode = hashValue(values);
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
- 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(', ')})`; }
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
- _elements;
111
- _hashCode = null;
112
- _isFrozen = false;
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 === 0) {
116
- this._elements = [];
117
- this._hashCode = 0;
118
- }
119
- else {
120
- this._elements = elements;
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
- _checkFrozen(op) {
126
- if (this._isFrozen) {
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
- _unique() {
133
- const arr = this._elements;
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/Caches hash code and FREEZES the set.
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._hashCode !== null)
150
- return this._hashCode;
259
+ if (this.#hashCode !== null)
260
+ return this.#hashCode;
151
261
  let h = 0;
152
- const arr = this._elements;
262
+ const arr = this.#elements;
153
263
  const len = arr.length;
154
264
  for (let i = 0; i < len; i++) {
155
- h = Math.imul(31, h) + hashValue(arr[i]);
265
+ // Wrap to 32-bit at each step for consistency
266
+ h = (Math.imul(31, h) + hashValue(arr[i])) | 0;
156
267
  }
157
- this._hashCode = h | 0;
158
- this._isFrozen = true;
159
- return this._hashCode;
268
+ this.#hashCode = h | 0;
269
+ this.#isFrozen = true;
270
+ return this.#hashCode;
160
271
  }
161
- get isFrozen() { return this._isFrozen; }
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._elements;
170
- const arrB = other._elements;
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._elements.length; }
182
- isEmpty() { return this._elements.length === 0; }
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._elements;
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._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)) {
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 in construction)
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._hashCode = null;
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._hashCode = null;
347
+ this.#hashCode = null;
235
348
  return this;
236
349
  }
237
- // Small Array Strategy
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._hashCode = null;
358
+ this.#hashCode = null;
246
359
  return this;
247
360
  }
248
361
  }
249
- arr.push(element); // Should be unreachable given append check, but safe fallback
362
+ arr.push(element);
250
363
  return this;
251
364
  }
252
- // Large Array Strategy
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._hashCode = null;
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._checkFrozen('remove() from');
274
- const arr = this._elements;
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._hashCode = null;
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._hashCode = null;
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._checkFrozen('clear()');
304
- this._elements = [];
305
- this._hashCode = 0;
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._elements = this._elements.slice();
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._elements;
317
- const arrB = other._elements;
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._elements = res;
461
+ s.#elements = res;
341
462
  return s;
342
463
  }
343
464
  intersection(other) {
344
465
  const s = new RecursiveSet();
345
- const arrA = this._elements;
346
- const arrB = other._elements;
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._elements = res;
482
+ s.#elements = res;
362
483
  return s;
363
484
  }
364
485
  difference(other) {
365
486
  const s = new RecursiveSet();
366
- const arrA = this._elements;
367
- const arrB = other._elements;
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._elements = res;
505
+ s.#elements = res;
385
506
  return s;
386
507
  }
387
508
  symmetricDifference(other) {
388
509
  const s = new RecursiveSet();
389
- const arrA = this._elements;
390
- const arrB = other._elements;
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._elements = res;
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._elements.push(this._elements[j]);
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._elements;
431
- const arrB = other._elements;
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._elements.push(new Tuple(x, y));
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._elements, arrB = other._elements;
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._elements); }
461
- *[Symbol.iterator]() { yield* this._elements; }
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._elements.map(el => {
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); }