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/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
- // === 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)
14
18
  const floatBuffer = new ArrayBuffer(8);
15
- const floatView = new Float64Array(floatBuffer);
16
- const intView = new Int32Array(floatBuffer);
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
- if (Number.isInteger(val)) {
26
+ // Integer Path: Handle Safe Integers correctly (up to 2^53)
27
+ if (Number.isSafeInteger(val)) {
19
28
  let h = FNV_OFFSET;
20
- h ^= val;
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
- floatView[0] = val;
37
+ // Float Path: IEEE 754 Bit Pattern (Little Endian Enforced)
38
+ view.setFloat64(0, val, true);
25
39
  let h = FNV_OFFSET;
26
- h ^= intView[0];
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 ^= intView[1];
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 hash = FNV_OFFSET;
49
+ let h = FNV_OFFSET;
34
50
  const len = str.length;
35
51
  for (let i = 0; i < len; i++) {
36
- hash ^= str.charCodeAt(i);
37
- hash = Math.imul(hash, FNV_PRIME);
52
+ h ^= str.charCodeAt(i);
53
+ h = Math.imul(h, FNV_PRIME);
38
54
  }
39
- return hash >>> 0;
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 Path: Objects with cached hash
47
- if (val && typeof val === 'object' && 'hashCode' in 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
- // === 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
+ */
62
106
  function compare(a, b) {
63
107
  if (a === b)
64
108
  return 0;
65
- // 1. Hash Short-Circuit (Optimization)
66
- const aH = a?.hashCode;
67
- const bH = b?.hashCode;
68
- const ha = (aH !== undefined) ? aH : hashValue(a);
69
- const hb = (bH !== undefined) ? bH : hashValue(b);
70
- if (ha !== hb)
71
- return ha < hb ? -1 : 1;
72
- // 2. Structural Type Check
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 (typeA === 'number' && typeB === 'number')
118
+ if (scoreA === 2)
78
119
  return a < b ? -1 : 1;
79
- const isSetA = a instanceof RecursiveSet;
80
- const isSetB = b instanceof RecursiveSet;
81
- 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) {
82
127
  return a.compare(b);
83
128
  }
84
- const isSeqA = Array.isArray(a) || a instanceof Tuple;
85
- const isSeqB = Array.isArray(b) || b instanceof Tuple;
86
- // Sort Order: Primitives (0) < Sequences (1) < Sets (2)
87
- if (isSetA !== isSetB || isSeqA !== isSeqB) {
88
- const scoreA = isSetA ? 2 : isSeqA ? 1 : 0;
89
- const scoreB = isSetB ? 2 : isSeqB ? 1 : 0;
90
- 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;
91
139
  }
92
- // 3. Sequence Comparison
93
- if (isSeqA && isSeqB) {
94
- const valA = (a instanceof Tuple) ? a.values : a;
95
- const valB = (b instanceof Tuple) ? b.values : b;
96
- const len = valA.length;
97
- if (len !== valB.length)
98
- return len - valB.length;
99
- for (let i = 0; i < len; i++) {
100
- const diff = compare(valA[i], valB[i]);
101
- if (diff !== 0)
102
- 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;
103
166
  }
104
- return 0;
105
167
  }
106
- return a < b ? -1 : 1;
168
+ throw new Error("Unsupported Type: Use number, string, Tuple, Array or RecursiveSet.");
107
169
  }
108
- // === 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
+ */
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
- this.values = values;
114
- 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);
115
194
  }
116
- get length() { return this.values.length; }
117
- get(i) { return this.values[i]; }
118
- *[Symbol.iterator]() { yield* this.values; }
119
- 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(', ')})`; }
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
- _elements;
125
- _hashCode = null;
126
- _isFrozen = false;
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 === 0) {
130
- this._elements = [];
131
- this._hashCode = 0;
132
- }
133
- else {
134
- this._elements = elements;
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
- _checkFrozen(op) {
140
- if (this._isFrozen) {
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
- _unique() {
147
- const arr = this._elements;
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/Caches hash code and FREEZES the set.
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._hashCode !== null)
164
- return this._hashCode;
259
+ if (this.#hashCode !== null)
260
+ return this.#hashCode;
165
261
  let h = 0;
166
- const arr = this._elements;
262
+ const arr = this.#elements;
167
263
  const len = arr.length;
168
264
  for (let i = 0; i < len; i++) {
169
- 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;
170
267
  }
171
- this._hashCode = h | 0;
172
- this._isFrozen = true;
173
- return this._hashCode;
268
+ this.#hashCode = h | 0;
269
+ this.#isFrozen = true;
270
+ return this.#hashCode;
174
271
  }
175
- 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
+ */
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._elements;
184
- const arrB = other._elements;
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._elements.length; }
196
- 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
+ */
197
302
  has(element) {
198
- const arr = this._elements;
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._checkFrozen('add() to');
224
- // Validation (Inlined for Performance)
225
- if (typeof element === 'object' && element !== null) {
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 in construction)
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._hashCode = null;
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._hashCode = null;
347
+ this.#hashCode = null;
249
348
  return this;
250
349
  }
251
- // Small Array Strategy
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._hashCode = null;
358
+ this.#hashCode = null;
260
359
  return this;
261
360
  }
262
361
  }
263
- arr.push(element); // Should be unreachable given append check, but safe fallback
362
+ arr.push(element);
264
363
  return this;
265
364
  }
266
- // Large Array Strategy
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._hashCode = null;
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._checkFrozen('remove() from');
288
- const arr = this._elements;
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._hashCode = null;
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._hashCode = null;
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._checkFrozen('clear()');
318
- this._elements = [];
319
- this._hashCode = 0;
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._elements = this._elements.slice();
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._elements;
331
- const arrB = other._elements;
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._elements = res;
461
+ s.#elements = res;
355
462
  return s;
356
463
  }
357
464
  intersection(other) {
358
465
  const s = new RecursiveSet();
359
- const arrA = this._elements;
360
- const arrB = other._elements;
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._elements = res;
482
+ s.#elements = res;
376
483
  return s;
377
484
  }
378
485
  difference(other) {
379
486
  const s = new RecursiveSet();
380
- const arrA = this._elements;
381
- const arrB = other._elements;
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._elements = res;
505
+ s.#elements = res;
399
506
  return s;
400
507
  }
401
508
  symmetricDifference(other) {
402
509
  const s = new RecursiveSet();
403
- const arrA = this._elements;
404
- const arrB = other._elements;
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._elements = res;
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._elements.push(this._elements[j]);
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._elements;
445
- const arrB = other._elements;
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._elements.push(new Tuple(x, y));
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._elements, arrB = other._elements;
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._elements); }
475
- *[Symbol.iterator]() { yield* this._elements; }
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._elements.map(el => {
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); }