recursive-set 5.0.3 → 7.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
@@ -1,178 +1,256 @@
1
1
  "use strict";
2
2
  /**
3
3
  * @module recursive-set
4
- * High-Performance Recursive Set with "Freeze-on-Hash" semantics.
4
+ * @version 7.0.0
5
+ * @description
6
+ * **High-Performance ZFC Set Implementation.**
7
+ * This library sacrifices runtime safety checks for raw speed.
8
+ * * **Strict Contract:**
9
+ * 1. **Finite Numbers Only:** No `NaN`, no `Infinity`.
10
+ * 2. **No Mutation:** Do not mutate arrays/tuples/objects after insertion.
11
+ * 3. **Type Consistency:** Avoid mixing distinct types (Array vs Tuple) that might hash collide.
5
12
  */
6
13
  Object.defineProperty(exports, "__esModule", { value: true });
7
14
  exports.RecursiveSet = exports.Tuple = void 0;
15
+ exports.compare = compare;
8
16
  exports.emptySet = emptySet;
9
17
  exports.singleton = singleton;
10
- exports.fromIterable = fromIterable;
11
- // === HASHING ENGINE (FNV-1a) ===
18
+ // ============================================================================
19
+ // FAST HASHING (Optimized FNV-1a)
20
+ // ============================================================================
12
21
  const FNV_PRIME = 16777619;
13
22
  const FNV_OFFSET = 2166136261;
14
23
  const floatBuffer = new ArrayBuffer(8);
15
- const floatView = new Float64Array(floatBuffer);
16
- const intView = new Int32Array(floatBuffer);
24
+ const view = new DataView(floatBuffer);
25
+ /**
26
+ * Hashes a number using FNV-1a.
27
+ * Optimizes for 32-bit integers to avoid Float64 processing overhead.
28
+ */
17
29
  function hashNumber(val) {
18
- if (Number.isInteger(val)) {
19
- let h = FNV_OFFSET;
20
- h ^= val;
21
- h = Math.imul(h, FNV_PRIME);
22
- return h >>> 0;
23
- }
24
- floatView[0] = val;
30
+ // Integer optimization: Skip float logic if it's a 32-bit int
31
+ if ((val | 0) === val)
32
+ return val | 0;
33
+ view.setFloat64(0, val, true); // Little Endian
25
34
  let h = FNV_OFFSET;
26
- h ^= intView[0];
35
+ h ^= view.getInt32(0, true);
27
36
  h = Math.imul(h, FNV_PRIME);
28
- h ^= intView[1];
37
+ h ^= view.getInt32(4, true);
29
38
  h = Math.imul(h, FNV_PRIME);
30
39
  return h >>> 0;
31
40
  }
32
41
  function hashString(str) {
33
- let hash = FNV_OFFSET;
42
+ let h = FNV_OFFSET;
34
43
  const len = str.length;
35
44
  for (let i = 0; i < len; i++) {
36
- hash ^= str.charCodeAt(i);
37
- hash = Math.imul(hash, FNV_PRIME);
45
+ h ^= str.charCodeAt(i);
46
+ h = Math.imul(h, FNV_PRIME);
38
47
  }
39
- return hash >>> 0;
48
+ return h >>> 0;
40
49
  }
41
- function hashValue(val) {
42
- if (typeof val === 'string')
43
- return hashString(val);
44
- if (typeof val === 'number')
45
- return hashNumber(val);
46
- // Fast Path: Objects with cached hash
47
- if (val && typeof val === 'object' && 'hashCode' in val) {
48
- return val.hashCode;
49
- }
50
- if (Array.isArray(val)) {
50
+ function hashValue(v) {
51
+ if (typeof v === 'number')
52
+ return hashNumber(v);
53
+ if (typeof v === 'string')
54
+ return hashString(v);
55
+ if (v instanceof RecursiveSet || v instanceof Tuple)
56
+ return v.hashCode;
57
+ if (Array.isArray(v)) {
51
58
  let h = FNV_OFFSET;
52
- const len = val.length;
53
- for (let i = 0; i < len; i++) {
54
- h ^= hashValue(val[i]);
59
+ for (let i = 0; i < v.length; i++) {
60
+ h ^= hashValue(v[i]);
55
61
  h = Math.imul(h, FNV_PRIME);
56
62
  }
57
63
  return h >>> 0;
58
64
  }
59
65
  return 0;
60
66
  }
61
- // === COMPARATOR ===
67
+ // ============================================================================
68
+ // COMPARATOR (Optimized)
69
+ // ============================================================================
70
+ /**
71
+ * Global comparator for Total Ordering.
72
+ * * **WARNING: UNSAFE OPTIMIZATIONS**
73
+ * - Uses `a - b` for numbers. **Precondition:** Only finite numbers allowed.
74
+ * Inputting `NaN` or `Infinity` results in undefined sorting behavior.
75
+ * - **Hash Collisions:** If two distinct object types (e.g. Array vs Tuple) have the
76
+ * same hash, they may be treated as equal. Avoid mixing structure types in the same set.
77
+ */
62
78
  function compare(a, b) {
63
79
  if (a === b)
64
80
  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
81
+ // Fast path for primitives
82
+ if (typeof a === 'number' && typeof b === 'number')
83
+ return a - b;
84
+ if (typeof a === 'string' && typeof b === 'string')
85
+ return a < b ? -1 : 1;
73
86
  const typeA = typeof a;
74
87
  const typeB = typeof b;
75
- if (typeA === 'string' && typeB === 'string')
76
- return a < b ? -1 : 1;
77
- if (typeA === 'number' && typeB === 'number')
78
- return a < b ? -1 : 1;
79
- const isSetA = a instanceof RecursiveSet;
80
- const isSetB = b instanceof RecursiveSet;
81
- if (isSetA && isSetB) {
82
- return a.compare(b);
83
- }
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;
88
+ // Type Score: Number(1) < String(2) < Array/Tuple(3) < Set(4)
89
+ if (typeA !== typeB) {
90
+ const scoreA = (typeA === 'number') ? 1 : (typeA === 'string' ? 2 : 3);
91
+ const scoreB = (typeB === 'number') ? 1 : (typeB === 'string' ? 2 : 3);
90
92
  return scoreA - scoreB;
91
93
  }
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;
103
- }
104
- return 0;
94
+ // Hash Short-Circuit
95
+ const h1 = hashValue(a);
96
+ const h2 = hashValue(b);
97
+ if (h1 !== h2)
98
+ return h1 < h2 ? -1 : 1;
99
+ // Deep Compare
100
+ if (a instanceof RecursiveSet && b instanceof RecursiveSet)
101
+ return a.compare(b);
102
+ if (a instanceof Tuple && b instanceof Tuple)
103
+ return compareSequences(a.raw, b.raw);
104
+ if (Array.isArray(a) && Array.isArray(b))
105
+ return compareSequences(a, b);
106
+ return 0;
107
+ }
108
+ function compareSequences(a, b) {
109
+ const len = a.length;
110
+ if (len !== b.length)
111
+ return len - b.length;
112
+ for (let i = 0; i < len; i++) {
113
+ const diff = compare(a[i], b[i]);
114
+ if (diff !== 0)
115
+ return diff;
105
116
  }
106
- return a < b ? -1 : 1;
117
+ return 0;
107
118
  }
108
- // === CLASSES ===
119
+ // ============================================================================
120
+ // CLASSES
121
+ // ============================================================================
122
+ /**
123
+ * Immutable Tuple container.
124
+ * * **Contract:**
125
+ * - Creates a defensive copy of the input array.
126
+ * - Freezes the internal storage (`Object.freeze`).
127
+ * - **Note:** Freezing is **shallow**. Do not mutate nested elements.
128
+ * @template T - Array type of the tuple elements.
129
+ */
109
130
  class Tuple {
110
- values;
131
+ #values;
111
132
  hashCode;
112
133
  constructor(...values) {
113
- this.values = values;
114
- this.hashCode = hashValue(values);
134
+ this.#values = values.slice(); // Defensive copy
135
+ Object.freeze(this.#values); // Freeze for safety
136
+ this.hashCode = hashValue(this.#values);
115
137
  }
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(', ')})`; }
138
+ /** * Returns the readonly internal array.
139
+ * **Warning:** Readonly is only enforced by TypeScript.
140
+ * Mutating the underlying array via `as any` breaks invariants.
141
+ */
142
+ get raw() { return this.#values; }
143
+ get length() { return this.#values.length; }
144
+ /** Alias for compatibility. */
145
+ get values() { return this.#values; }
146
+ /** Iterates over tuple elements. */
147
+ *[Symbol.iterator]() { yield* this.#values; }
148
+ /** Returns string representation "(a, b)". */
149
+ toString() { return `(${this.#values.join(', ')})`; }
150
+ /** Custom inspection for Node.js console.log to print "(a, b)" cleanly. */
120
151
  [Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
121
152
  }
122
153
  exports.Tuple = Tuple;
154
+ /**
155
+ * High-Performance Recursive Set.
156
+ * * **Lifecycle & Safety:**
157
+ * - **Freeze-on-Hash:** The set is effectively immutable once `hashCode` is accessed
158
+ * (or it is added to another set/map).
159
+ * - **Runtime Checks:** Mutation methods verify frozen state via a fast boolean check.
160
+ * * @template T - Type of elements.
161
+ */
123
162
  class RecursiveSet {
124
- _elements;
125
- _hashCode = null;
126
- _isFrozen = false;
163
+ #elements;
164
+ #hashCode = null;
165
+ #isFrozen = false;
166
+ // Static wrapper for compatibility
127
167
  static compare(a, b) { return compare(a, b); }
168
+ /**
169
+ * Creates a new RecursiveSet.
170
+ * Elements are sorted and deduplicated ($O(N \log N)$).
171
+ * @param elements Initial elements.
172
+ */
128
173
  constructor(...elements) {
129
- if (elements.length === 0) {
130
- this._elements = [];
131
- this._hashCode = 0;
174
+ if (elements.length > 1) {
175
+ elements.sort(compare);
176
+ this.#elements = this.#unique(elements);
132
177
  }
133
178
  else {
134
- this._elements = elements;
135
- this._elements.sort(compare);
136
- this._unique();
179
+ this.#elements = elements;
137
180
  }
138
181
  }
139
- _checkFrozen(op) {
140
- if (this._isFrozen) {
141
- throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet.\n` +
142
- `This Set has been hashed or used in a collection (Value Semantics).\n` +
143
- `Use .mutableCopy() to create a modifiable copy.`);
182
+ // === CRITICAL PERFORMANCE METHODS ===
183
+ /** * **Bulk Load (O(N log N))**: Creates a set from a raw array.
184
+ * Sorts and deduplicates. Much faster than iterative insertion.
185
+ */
186
+ static fromArray(elements) {
187
+ const s = new RecursiveSet();
188
+ if (elements.length > 1) {
189
+ elements.sort(compare);
190
+ s.#elements = s.#unique(elements);
144
191
  }
192
+ else {
193
+ s.#elements = elements;
194
+ }
195
+ return s;
145
196
  }
146
- _unique() {
147
- const arr = this._elements;
148
- const len = arr.length;
149
- if (len < 2)
150
- return;
151
- let write = 1;
152
- for (let read = 1; read < len; read++) {
153
- if (compare(arr[read], arr[read - 1]) !== 0) {
154
- arr[write++] = arr[read];
197
+ /** * **UNSAFE (O(1))**: Bypasses all checks.
198
+ * @param sortedUnique Input must be ALREADY sorted and deduplicated.
199
+ * Use only if you strictly guarantee invariants.
200
+ */
201
+ static fromSortedUnsafe(sortedUnique) {
202
+ const s = new RecursiveSet();
203
+ s.#elements = sortedUnique;
204
+ return s;
205
+ }
206
+ #unique(sorted) {
207
+ if (sorted.length < 2)
208
+ return sorted;
209
+ const out = [sorted[0]];
210
+ let last = sorted[0];
211
+ const len = sorted.length;
212
+ for (let i = 1; i < len; i++) {
213
+ const curr = sorted[i];
214
+ if (compare(curr, last) !== 0) {
215
+ out.push(curr);
216
+ last = curr;
155
217
  }
156
218
  }
157
- arr.length = write;
219
+ return out;
158
220
  }
159
- /**
160
- * Calculates/Caches hash code and FREEZES the set.
221
+ #checkFrozen(op) {
222
+ if (this.#isFrozen) {
223
+ throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet. Use mutableCopy().`);
224
+ }
225
+ }
226
+ /** * Returns the internal sorted array (readonly).
227
+ * **Warning:** Readonly is only enforced by TypeScript.
228
+ * Mutating the underlying array via `as any` breaks invariants (Binary Search/Sort rely on strict ordering).
229
+ */
230
+ get raw() { return this.#elements; }
231
+ /** * Computes the hash code.
232
+ * **Side Effect**: Freezes the set to prevent hash corruption.
161
233
  */
162
234
  get hashCode() {
163
- if (this._hashCode !== null)
164
- return this._hashCode;
235
+ if (this.#hashCode !== null)
236
+ return this.#hashCode;
165
237
  let h = 0;
166
- const arr = this._elements;
238
+ const arr = this.#elements;
167
239
  const len = arr.length;
168
240
  for (let i = 0; i < len; i++) {
169
- h = Math.imul(31, h) + hashValue(arr[i]);
241
+ h = (Math.imul(31, h) + hashValue(arr[i])) | 0;
170
242
  }
171
- this._hashCode = h | 0;
172
- this._isFrozen = true;
173
- return this._hashCode;
243
+ this.#hashCode = h;
244
+ this.#isFrozen = true; // Freeze on hash access
245
+ return h;
174
246
  }
175
- get isFrozen() { return this._isFrozen; }
247
+ get isFrozen() { return this.#isFrozen; }
248
+ get size() { return this.#elements.length; }
249
+ isEmpty() { return this.#elements.length === 0; }
250
+ /**
251
+ * Compares this set with another set for ordering.
252
+ * Uses hash comparison first, then deep structural comparison.
253
+ */
176
254
  compare(other) {
177
255
  if (this === other)
178
256
  return 0;
@@ -180,32 +258,33 @@ class RecursiveSet {
180
258
  const h2 = other.hashCode;
181
259
  if (h1 !== h2)
182
260
  return h1 < h2 ? -1 : 1;
183
- const arrA = this._elements;
184
- const arrB = other._elements;
261
+ const arrA = this.#elements;
262
+ const arrB = other.#elements;
185
263
  const len = arrA.length;
186
264
  if (len !== arrB.length)
187
265
  return len - arrB.length;
188
266
  for (let i = 0; i < len; i++) {
189
- const cmp = compare(arrA[i], arrB[i]);
190
- if (cmp !== 0)
191
- return cmp;
267
+ const diff = compare(arrA[i], arrB[i]);
268
+ if (diff !== 0)
269
+ return diff;
192
270
  }
193
271
  return 0;
194
272
  }
195
- get size() { return this._elements.length; }
196
- isEmpty() { return this._elements.length === 0; }
273
+ equals(other) { return this.compare(other) === 0; }
274
+ /**
275
+ * Checks if element exists.
276
+ * Uses Binary Search ($O(\log N)$) for larger sets, linear scan for small sets.
277
+ */
197
278
  has(element) {
198
- const arr = this._elements;
279
+ const arr = this.#elements;
199
280
  const len = arr.length;
200
- // Linear Scan (Prefetch-friendly for small sets)
201
- if (len < 16) {
281
+ if (len < 10) { // Linear scan optimization
202
282
  for (let i = 0; i < len; i++) {
203
283
  if (compare(arr[i], element) === 0)
204
284
  return true;
205
285
  }
206
286
  return false;
207
287
  }
208
- // Binary Search
209
288
  let low = 0, high = len - 1;
210
289
  while (low <= high) {
211
290
  const mid = (low + high) >>> 1;
@@ -219,91 +298,57 @@ class RecursiveSet {
219
298
  }
220
299
  return false;
221
300
  }
301
+ /**
302
+ * Adds an element.
303
+ * @throws if set is frozen.
304
+ * Complexity: $O(N)$ (Array splice).
305
+ */
222
306
  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;
234
- const len = arr.length;
235
- // Optimization: Append to end (common in construction)
236
- if (len > 0) {
237
- const lastCmp = compare(arr[len - 1], element);
307
+ this.#checkFrozen('add() to');
308
+ const arr = this.#elements;
309
+ // Optimization: Check last element first (append is common)
310
+ if (arr.length > 0) {
311
+ const lastCmp = compare(arr[arr.length - 1], element);
238
312
  if (lastCmp < 0) {
239
313
  arr.push(element);
240
- this._hashCode = null;
314
+ this.#hashCode = null;
241
315
  return this;
242
316
  }
243
317
  if (lastCmp === 0)
244
318
  return this;
245
319
  }
246
- else {
247
- arr.push(element);
248
- this._hashCode = null;
249
- return this;
250
- }
251
- // Small Array Strategy
252
- if (len < 16) {
253
- for (let i = 0; i < len; i++) {
254
- const cmp = compare(arr[i], element);
255
- if (cmp === 0)
256
- return this;
257
- if (cmp > 0) {
258
- arr.splice(i, 0, element);
259
- this._hashCode = null;
260
- return this;
261
- }
262
- }
263
- arr.push(element); // Should be unreachable given append check, but safe fallback
264
- return this;
265
- }
266
- // Large Array Strategy
267
- let low = 0, high = len - 1, idx = 0;
320
+ let low = 0, high = arr.length - 1, idx = arr.length;
268
321
  while (low <= high) {
269
322
  const mid = (low + high) >>> 1;
270
323
  const cmp = compare(arr[mid], element);
271
324
  if (cmp === 0)
272
325
  return this;
273
- if (cmp < 0) {
274
- idx = mid + 1;
326
+ if (cmp < 0)
275
327
  low = mid + 1;
276
- }
277
328
  else {
278
329
  idx = mid;
279
330
  high = mid - 1;
280
331
  }
281
332
  }
282
333
  arr.splice(idx, 0, element);
283
- this._hashCode = null;
334
+ this.#hashCode = null;
284
335
  return this;
285
336
  }
337
+ /**
338
+ * Removes an element.
339
+ * @throws if set is frozen.
340
+ * Complexity: $O(N)$.
341
+ */
286
342
  remove(element) {
287
- this._checkFrozen('remove() from');
288
- const arr = this._elements;
289
- const len = arr.length;
290
- if (len < 16) {
291
- for (let i = 0; i < len; i++) {
292
- if (compare(arr[i], element) === 0) {
293
- arr.splice(i, 1);
294
- this._hashCode = null;
295
- return this;
296
- }
297
- }
298
- return this;
299
- }
300
- let low = 0, high = len - 1;
343
+ this.#checkFrozen('remove() from');
344
+ const arr = this.#elements;
345
+ let low = 0, high = arr.length - 1;
301
346
  while (low <= high) {
302
347
  const mid = (low + high) >>> 1;
303
348
  const cmp = compare(arr[mid], element);
304
349
  if (cmp === 0) {
305
350
  arr.splice(mid, 1);
306
- this._hashCode = null;
351
+ this.#hashCode = null;
307
352
  return this;
308
353
  }
309
354
  if (cmp < 0)
@@ -314,78 +359,84 @@ class RecursiveSet {
314
359
  return this;
315
360
  }
316
361
  clear() {
317
- this._checkFrozen('clear()');
318
- this._elements = [];
319
- this._hashCode = 0;
362
+ this.#checkFrozen('clear()');
363
+ this.#elements = [];
364
+ this.#hashCode = 0;
320
365
  return this;
321
366
  }
322
367
  mutableCopy() {
323
368
  const s = new RecursiveSet();
324
- s._elements = this._elements.slice();
369
+ s.#elements = this.#elements.slice();
325
370
  return s;
326
371
  }
327
372
  clone() { return this.mutableCopy(); }
373
+ // === OPTIMIZED SET OPERATIONS (Merge Scan) ===
374
+ /**
375
+ * Computes Union $A \cup B$.
376
+ * Implementation: Merge Scan.
377
+ * Complexity: $O(|A| + |B|)$.
378
+ */
328
379
  union(other) {
329
- const s = new RecursiveSet();
330
- const arrA = this._elements;
331
- const arrB = other._elements;
332
- if (arrA.length === 0)
333
- return other.clone();
334
- if (arrB.length === 0)
335
- return this.clone();
380
+ const A = this.#elements;
381
+ const B = other.raw; // Efficient access via getter
336
382
  const res = [];
337
383
  let i = 0, j = 0;
338
- const lenA = arrA.length, lenB = arrB.length;
384
+ const lenA = A.length, lenB = B.length;
339
385
  while (i < lenA && j < lenB) {
340
- const cmp = compare(arrA[i], arrB[j]);
386
+ const cmp = compare(A[i], B[j]);
341
387
  if (cmp < 0)
342
- res.push(arrA[i++]);
388
+ res.push(A[i++]);
343
389
  else if (cmp > 0)
344
- res.push(arrB[j++]);
390
+ res.push(B[j++]);
345
391
  else {
346
- res.push(arrA[i++]);
392
+ res.push(A[i++]);
347
393
  j++;
348
394
  }
349
395
  }
350
396
  while (i < lenA)
351
- res.push(arrA[i++]);
397
+ res.push(A[i++]);
352
398
  while (j < lenB)
353
- res.push(arrB[j++]);
354
- s._elements = res;
355
- return s;
399
+ res.push(B[j++]);
400
+ return RecursiveSet.fromSortedUnsafe(res);
356
401
  }
402
+ /**
403
+ * Computes Intersection $A \cap B$.
404
+ * Implementation: Synchronous Scan.
405
+ * Complexity: $O(|A| + |B|)$.
406
+ */
357
407
  intersection(other) {
358
- const s = new RecursiveSet();
359
- const arrA = this._elements;
360
- const arrB = other._elements;
408
+ const A = this.#elements;
409
+ const B = other.raw;
361
410
  const res = [];
362
411
  let i = 0, j = 0;
363
- const lenA = arrA.length, lenB = arrB.length;
412
+ const lenA = A.length, lenB = B.length;
364
413
  while (i < lenA && j < lenB) {
365
- const cmp = compare(arrA[i], arrB[j]);
414
+ const cmp = compare(A[i], B[j]);
366
415
  if (cmp < 0)
367
416
  i++;
368
417
  else if (cmp > 0)
369
418
  j++;
370
419
  else {
371
- res.push(arrA[i++]);
420
+ res.push(A[i++]);
372
421
  j++;
373
422
  }
374
423
  }
375
- s._elements = res;
376
- return s;
424
+ return RecursiveSet.fromSortedUnsafe(res);
377
425
  }
426
+ /**
427
+ * Computes Difference $A \setminus B$.
428
+ * Complexity: $O(|A| + |B|)$.
429
+ */
378
430
  difference(other) {
379
- const s = new RecursiveSet();
380
- const arrA = this._elements;
381
- const arrB = other._elements;
431
+ const A = this.#elements;
432
+ const B = other.raw;
382
433
  const res = [];
383
434
  let i = 0, j = 0;
384
- const lenA = arrA.length, lenB = arrB.length;
435
+ const lenA = A.length, lenB = B.length;
385
436
  while (i < lenA && j < lenB) {
386
- const cmp = compare(arrA[i], arrB[j]);
437
+ const cmp = compare(A[i], B[j]);
387
438
  if (cmp < 0)
388
- res.push(arrA[i++]);
439
+ res.push(A[i++]);
389
440
  else if (cmp > 0)
390
441
  j++;
391
442
  else {
@@ -394,35 +445,62 @@ class RecursiveSet {
394
445
  }
395
446
  }
396
447
  while (i < lenA)
397
- res.push(arrA[i++]);
398
- s._elements = res;
399
- return s;
448
+ res.push(A[i++]);
449
+ return RecursiveSet.fromSortedUnsafe(res);
400
450
  }
451
+ /**
452
+ * Computes Symmetric Difference $A \triangle B$.
453
+ * Complexity: $O(|A| + |B|)$.
454
+ */
401
455
  symmetricDifference(other) {
402
- const s = new RecursiveSet();
403
- const arrA = this._elements;
404
- const arrB = other._elements;
456
+ const A = this.#elements;
457
+ const B = other.raw;
405
458
  const res = [];
406
459
  let i = 0, j = 0;
407
- const lenA = arrA.length, lenB = arrB.length;
460
+ const lenA = A.length, lenB = B.length;
408
461
  while (i < lenA && j < lenB) {
409
- const cmp = compare(arrA[i], arrB[j]);
462
+ const cmp = compare(A[i], B[j]);
410
463
  if (cmp < 0)
411
- res.push(arrA[i++]);
464
+ res.push(A[i++]);
412
465
  else if (cmp > 0)
413
- res.push(arrB[j++]);
466
+ res.push(B[j++]);
414
467
  else {
415
468
  i++;
416
469
  j++;
417
470
  }
418
471
  }
419
472
  while (i < lenA)
420
- res.push(arrA[i++]);
473
+ res.push(A[i++]);
421
474
  while (j < lenB)
422
- res.push(arrB[j++]);
423
- s._elements = res;
424
- return s;
475
+ res.push(B[j++]);
476
+ return RecursiveSet.fromSortedUnsafe(res);
477
+ }
478
+ /**
479
+ * Computes Cartesian Product $A \times B$.
480
+ * Complexity: $O(|A| \cdot |B| \cdot \log(|A| \cdot |B|))$ (due to sorting).
481
+ */
482
+ cartesianProduct(other) {
483
+ const result = [];
484
+ const arrA = this.#elements;
485
+ const arrB = other.raw;
486
+ const lenA = arrA.length;
487
+ const lenB = arrB.length;
488
+ for (let i = 0; i < lenA; i++) {
489
+ const a = arrA[i];
490
+ for (let j = 0; j < lenB; j++) {
491
+ result.push(new Tuple(a, arrB[j]));
492
+ }
493
+ }
494
+ // Hashes are not monotonic, so we MUST sort.
495
+ result.sort(compare);
496
+ // But uniqueness is guaranteed mathematically, so use unsafe create
497
+ return RecursiveSet.fromSortedUnsafe(result);
425
498
  }
499
+ /**
500
+ * Computes the Power Set $\mathcal{P}(A)$.
501
+ * Complexity: $O(2^N)$.
502
+ * @throws if size > 20.
503
+ */
426
504
  powerset() {
427
505
  const n = this.size;
428
506
  if (n > 20)
@@ -430,33 +508,24 @@ class RecursiveSet {
430
508
  const subsets = [];
431
509
  const max = 1 << n;
432
510
  for (let i = 0; i < max; i++) {
433
- const subset = new RecursiveSet();
511
+ const subsetElements = [];
434
512
  for (let j = 0; j < n; j++) {
435
513
  if (i & (1 << j))
436
- subset._elements.push(this._elements[j]);
514
+ subsetElements.push(this.#elements[j]);
437
515
  }
438
- subsets.push(subset);
516
+ // Elements inside subset are already sorted -> Unsafe
517
+ subsets.push(RecursiveSet.fromSortedUnsafe(subsetElements));
439
518
  }
440
- return new RecursiveSet(...subsets);
441
- }
442
- cartesianProduct(other) {
443
- const result = new RecursiveSet();
444
- const arrA = this._elements;
445
- const arrB = other._elements;
446
- for (const x of arrA) {
447
- for (const y of arrB) {
448
- result._elements.push(new Tuple(x, y));
449
- }
450
- }
451
- return result;
519
+ // Subsets themselves need sorting
520
+ return RecursiveSet.fromArray(subsets);
452
521
  }
453
522
  isSubset(other) {
454
523
  if (this.size > other.size)
455
524
  return false;
456
525
  let i = 0, j = 0;
457
- const arrA = this._elements, arrB = other._elements;
458
- while (i < arrA.length && j < arrB.length) {
459
- const cmp = compare(arrA[i], arrB[j]);
526
+ const A = this.#elements, B = other.raw;
527
+ while (i < A.length && j < B.length) {
528
+ const cmp = compare(A[i], B[j]);
460
529
  if (cmp < 0)
461
530
  return false;
462
531
  if (cmp > 0)
@@ -466,27 +535,20 @@ class RecursiveSet {
466
535
  j++;
467
536
  }
468
537
  }
469
- return i === arrA.length;
538
+ return i === A.length;
470
539
  }
471
540
  isSuperset(other) { return other.isSubset(this); }
472
- isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
473
- equals(other) { return this.compare(other) === 0; }
474
- toSet() { return new Set(this._elements); }
475
- *[Symbol.iterator]() { yield* this._elements; }
476
- toString() {
477
- if (this.isEmpty())
478
- return "∅";
479
- const elementsStr = this._elements.map(el => {
480
- if (Array.isArray(el))
481
- return `[${el.join(', ')}]`;
482
- return String(el);
483
- });
484
- return `{${elementsStr.join(', ')}}`;
485
- }
541
+ /** Iterates over set elements in sorted order. */
542
+ *[Symbol.iterator]() { yield* this.#elements; }
543
+ /** Returns string representation e.g. "{1, 2, 3}". */
544
+ toString() { return `{${this.#elements.join(', ')}}`; }
545
+ /** Custom inspection for Node.js console to avoid printing internal state. */
486
546
  [Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
487
547
  }
488
548
  exports.RecursiveSet = RecursiveSet;
549
+ // Exports
550
+ /** Factory: Creates an empty RecursiveSet */
489
551
  function emptySet() { return new RecursiveSet(); }
552
+ /** Factory: Creates a singleton RecursiveSet containing {element} */
490
553
  function singleton(element) { return new RecursiveSet(element); }
491
- function fromIterable(iterable) { return new RecursiveSet(...iterable); }
492
554
  //# sourceMappingURL=index.js.map