recursive-set 3.0.0 → 5.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/esm/index.js CHANGED
@@ -1,278 +1,470 @@
1
- import createTree from 'functional-red-black-tree';
2
1
  /**
3
2
  * @module recursive-set
4
- * A mutable recursive set implementation.
5
- * Powered by functional Red-Black Trees for O(log n) operations and O(1) cloning.
3
+ * High-Performance Recursive Set with "Freeze-on-Hash" semantics.
6
4
  */
7
- /**
8
- * A lightweight wrapper for Tuples to enable Value-Equality in RecursiveSet.
9
- * Immutable by design.
10
- */
11
- export class Tuple {
12
- values;
13
- constructor(...values) {
14
- this.values = values;
5
+ // === HASHING ENGINE (FNV-1a) ===
6
+ const FNV_PRIME = 16777619;
7
+ const FNV_OFFSET = 2166136261;
8
+ function hashString(str) {
9
+ let hash = FNV_OFFSET;
10
+ const len = str.length;
11
+ for (let i = 0; i < len; i++) {
12
+ hash ^= str.charCodeAt(i);
13
+ hash = Math.imul(hash, FNV_PRIME);
15
14
  }
16
- get length() {
17
- return this.values.length;
15
+ return hash >>> 0;
16
+ }
17
+ function hashValue(val) {
18
+ if (typeof val === 'string')
19
+ return hashString(val);
20
+ if (typeof val === 'number') {
21
+ let hash = FNV_OFFSET;
22
+ hash ^= (val | 0);
23
+ hash = Math.imul(hash, FNV_PRIME);
24
+ return hash >>> 0;
18
25
  }
19
- get(index) {
20
- return this.values[index];
26
+ // Fast Path: Objects with cached hash
27
+ if (val && typeof val === 'object' && 'hashCode' in val) {
28
+ return val.hashCode;
21
29
  }
22
- *[Symbol.iterator]() {
23
- for (const val of this.values) {
24
- yield val;
30
+ if (Array.isArray(val)) {
31
+ let h = FNV_OFFSET;
32
+ const len = val.length;
33
+ for (let i = 0; i < len; i++) {
34
+ h ^= hashValue(val[i]);
35
+ h = Math.imul(h, FNV_PRIME);
25
36
  }
37
+ return h >>> 0;
26
38
  }
27
- toString() {
28
- return `(${this.values.map(v => String(v)).join(', ')})`;
39
+ return 0;
40
+ }
41
+ // === COMPARATOR ===
42
+ function compare(a, b) {
43
+ if (a === b)
44
+ return 0;
45
+ // 1. Hash Short-Circuit (Optimization)
46
+ const aH = a?.hashCode;
47
+ const bH = b?.hashCode;
48
+ const ha = (aH !== undefined) ? aH : hashValue(a);
49
+ const hb = (bH !== undefined) ? bH : hashValue(b);
50
+ if (ha !== hb)
51
+ return ha < hb ? -1 : 1;
52
+ // 2. Structural Type Check
53
+ const typeA = typeof a;
54
+ const typeB = typeof b;
55
+ if (typeA === 'string' && typeB === 'string')
56
+ return a < b ? -1 : 1;
57
+ if (typeA === 'number' && typeB === 'number')
58
+ return a < b ? -1 : 1;
59
+ const isSetA = a instanceof RecursiveSet;
60
+ const isSetB = b instanceof RecursiveSet;
61
+ if (isSetA && isSetB) {
62
+ return a.compare(b);
29
63
  }
30
- [Symbol.for('nodejs.util.inspect.custom')]() {
31
- return this.toString();
64
+ const isSeqA = Array.isArray(a) || a instanceof Tuple;
65
+ const isSeqB = Array.isArray(b) || b instanceof Tuple;
66
+ // Sort Order: Primitives (0) < Sequences (1) < Sets (2)
67
+ if (isSetA !== isSetB || isSeqA !== isSeqB) {
68
+ const scoreA = isSetA ? 2 : isSeqA ? 1 : 0;
69
+ const scoreB = isSetB ? 2 : isSeqB ? 1 : 0;
70
+ return scoreA - scoreB;
71
+ }
72
+ // 3. Sequence Comparison
73
+ if (isSeqA && isSeqB) {
74
+ const valA = (a instanceof Tuple) ? a.values : a;
75
+ const valB = (b instanceof Tuple) ? b.values : b;
76
+ const len = valA.length;
77
+ if (len !== valB.length)
78
+ return len - valB.length;
79
+ for (let i = 0; i < len; i++) {
80
+ const diff = compare(valA[i], valB[i]);
81
+ if (diff !== 0)
82
+ return diff;
83
+ }
84
+ return 0;
32
85
  }
86
+ return a < b ? -1 : 1;
87
+ }
88
+ // === CLASSES ===
89
+ export class Tuple {
90
+ values;
91
+ hashCode;
92
+ constructor(...values) {
93
+ this.values = values;
94
+ this.hashCode = hashValue(values);
95
+ }
96
+ get length() { return this.values.length; }
97
+ get(i) { return this.values[i]; }
98
+ *[Symbol.iterator]() { yield* this.values; }
99
+ toString() { return `(${this.values.join(', ')})`; }
100
+ [Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
33
101
  }
34
102
  export class RecursiveSet {
35
- _tree;
103
+ _elements;
104
+ _hashCode = null;
105
+ _isFrozen = false;
106
+ static compare(a, b) { return compare(a, b); }
107
+ constructor(...elements) {
108
+ if (elements.length === 0) {
109
+ this._elements = [];
110
+ this._hashCode = 0;
111
+ }
112
+ else {
113
+ this._elements = elements;
114
+ this._elements.sort(compare);
115
+ this._unique();
116
+ }
117
+ }
118
+ _checkFrozen(op) {
119
+ if (this._isFrozen) {
120
+ throw new Error(`InvalidOperation: Cannot ${op} a frozen RecursiveSet.\n` +
121
+ `This Set has been hashed or used in a collection (Value Semantics).\n` +
122
+ `Use .mutableCopy() to create a modifiable copy.`);
123
+ }
124
+ }
125
+ _unique() {
126
+ const arr = this._elements;
127
+ const len = arr.length;
128
+ if (len < 2)
129
+ return;
130
+ let write = 1;
131
+ for (let read = 1; read < len; read++) {
132
+ if (compare(arr[read], arr[read - 1]) !== 0) {
133
+ arr[write++] = arr[read];
134
+ }
135
+ }
136
+ arr.length = write;
137
+ }
36
138
  /**
37
- * Static comparator for Red-Black Tree ordering.
38
- * Supports Primitives, RecursiveSets and Tuples.
39
- * REJECTS plain JS Objects and Arrays to enforce strict semantics.
139
+ * Calculates/Caches hash code and FREEZES the set.
40
140
  */
41
- static compare(a, b) {
42
- if (a === b)
43
- return 0;
44
- const isSetA = a instanceof RecursiveSet;
45
- const isSetB = b instanceof RecursiveSet;
46
- const isTupA = a instanceof Tuple;
47
- const isTupB = b instanceof Tuple;
48
- // Sort Order: Primitives (0) < Tuples (1) < Sets (2)
49
- const getTypeScore = (isSet, isTup) => {
50
- if (isSet)
51
- return 2;
52
- if (isTup)
53
- return 1;
54
- return 0;
55
- };
56
- const scoreA = getTypeScore(isSetA, isTupA);
57
- const scoreB = getTypeScore(isSetB, isTupB);
58
- if (scoreA !== scoreB)
59
- return scoreA < scoreB ? -1 : 1;
60
- // 1. Sets
61
- if (isSetA && isSetB) {
62
- const setA = a;
63
- const setB = b;
64
- if (setA.size !== setB.size)
65
- return setA.size < setB.size ? -1 : 1;
66
- let iterA = setA._tree.begin;
67
- let iterB = setB._tree.begin;
68
- while (iterA.valid && iterB.valid) {
69
- const cmp = RecursiveSet.compare(iterA.key, iterB.key);
70
- if (cmp !== 0)
71
- return cmp;
72
- iterA.next();
73
- iterB.next();
74
- }
75
- return 0;
141
+ get hashCode() {
142
+ if (this._hashCode !== null)
143
+ return this._hashCode;
144
+ let h = 0;
145
+ const arr = this._elements;
146
+ const len = arr.length;
147
+ for (let i = 0; i < len; i++) {
148
+ h = Math.imul(31, h) + hashValue(arr[i]);
76
149
  }
77
- // 2. Tuples
78
- if (isTupA && isTupB) {
79
- const tupA = a;
80
- const tupB = b;
81
- if (tupA.length !== tupB.length)
82
- return tupA.length < tupB.length ? -1 : 1;
83
- for (let i = 0; i < tupA.length; i++) {
84
- const cmp = RecursiveSet.compare(tupA.get(i), tupB.get(i));
85
- if (cmp !== 0)
86
- return cmp;
87
- }
150
+ this._hashCode = h | 0;
151
+ this._isFrozen = true;
152
+ return this._hashCode;
153
+ }
154
+ get isFrozen() { return this._isFrozen; }
155
+ compare(other) {
156
+ if (this === other)
88
157
  return 0;
158
+ const h1 = this.hashCode;
159
+ const h2 = other.hashCode;
160
+ if (h1 !== h2)
161
+ return h1 < h2 ? -1 : 1;
162
+ const arrA = this._elements;
163
+ const arrB = other._elements;
164
+ const len = arrA.length;
165
+ if (len !== arrB.length)
166
+ return len - arrB.length;
167
+ for (let i = 0; i < len; i++) {
168
+ const cmp = compare(arrA[i], arrB[i]);
169
+ if (cmp !== 0)
170
+ return cmp;
89
171
  }
90
- // 3. Primitives (guaranteed by add() validation)
91
- const tA = typeof a;
92
- const tB = typeof b;
93
- if (tA !== tB)
94
- return tA > tB ? 1 : -1;
95
- // @ts-ignore
96
- if (a < b)
97
- return -1;
98
- // @ts-ignore
99
- if (a > b)
100
- return 1;
101
172
  return 0;
102
173
  }
103
- constructor(...elements) {
104
- this._tree = createTree(RecursiveSet.compare);
105
- for (const el of elements) {
106
- this.add(el);
174
+ get size() { return this._elements.length; }
175
+ isEmpty() { return this._elements.length === 0; }
176
+ has(element) {
177
+ const arr = this._elements;
178
+ const len = arr.length;
179
+ // Linear Scan (Prefetch-friendly for small sets)
180
+ if (len < 16) {
181
+ for (let i = 0; i < len; i++) {
182
+ if (compare(arr[i], element) === 0)
183
+ return true;
184
+ }
185
+ return false;
107
186
  }
187
+ // Binary Search
188
+ let low = 0, high = len - 1;
189
+ while (low <= high) {
190
+ const mid = (low + high) >>> 1;
191
+ const cmp = compare(arr[mid], element);
192
+ if (cmp === 0)
193
+ return true;
194
+ if (cmp < 0)
195
+ low = mid + 1;
196
+ else
197
+ high = mid - 1;
198
+ }
199
+ return false;
108
200
  }
109
- // === Copy-on-Write Support ===
110
- /**
111
- * Creates a shallow copy of the set in O(1) time.
112
- */
113
- clone() {
114
- const clone = new RecursiveSet();
115
- clone._tree = this._tree;
116
- return clone;
117
- }
118
- // === Mutable Operations ===
119
201
  add(element) {
120
- // Validation
121
- if (typeof element === "number" && Number.isNaN(element)) {
202
+ this._checkFrozen('add() to');
203
+ // Validation (Inlined for Performance)
204
+ if (typeof element === 'object' && element !== null) {
205
+ if (!(element instanceof RecursiveSet || element instanceof Tuple || Array.isArray(element))) {
206
+ throw new Error("Unsupported Type: Use Tuple, Array or RecursiveSet.");
207
+ }
208
+ }
209
+ else if (Number.isNaN(element)) {
122
210
  throw new Error("NaN is not supported");
123
211
  }
124
- const isSet = element instanceof RecursiveSet;
125
- const isTup = element instanceof Tuple;
126
- const isObject = element !== null && typeof element === 'object' && !isSet && !isTup;
127
- if (isObject) {
128
- throw new Error("Plain Objects and Arrays are not supported. " +
129
- "Use Tuple for sequences or RecursiveSet for nested structures.");
212
+ const arr = this._elements;
213
+ const len = arr.length;
214
+ // Optimization: Append to end (common in construction)
215
+ if (len > 0) {
216
+ const lastCmp = compare(arr[len - 1], element);
217
+ if (lastCmp < 0) {
218
+ arr.push(element);
219
+ this._hashCode = null;
220
+ return this;
221
+ }
222
+ if (lastCmp === 0)
223
+ return this;
224
+ }
225
+ else {
226
+ arr.push(element);
227
+ this._hashCode = null;
228
+ return this;
130
229
  }
131
- // Idempotency
132
- if (this.has(element))
230
+ // Small Array Strategy
231
+ if (len < 16) {
232
+ for (let i = 0; i < len; i++) {
233
+ const cmp = compare(arr[i], element);
234
+ if (cmp === 0)
235
+ return this;
236
+ if (cmp > 0) {
237
+ arr.splice(i, 0, element);
238
+ this._hashCode = null;
239
+ return this;
240
+ }
241
+ }
242
+ arr.push(element); // Should be unreachable given append check, but safe fallback
133
243
  return this;
134
- this._tree = this._tree.insert(element, true);
244
+ }
245
+ // Large Array Strategy
246
+ let low = 0, high = len - 1, idx = 0;
247
+ while (low <= high) {
248
+ const mid = (low + high) >>> 1;
249
+ const cmp = compare(arr[mid], element);
250
+ if (cmp === 0)
251
+ return this;
252
+ if (cmp < 0) {
253
+ idx = mid + 1;
254
+ low = mid + 1;
255
+ }
256
+ else {
257
+ idx = mid;
258
+ high = mid - 1;
259
+ }
260
+ }
261
+ arr.splice(idx, 0, element);
262
+ this._hashCode = null;
135
263
  return this;
136
264
  }
137
265
  remove(element) {
138
- this._tree = this._tree.remove(element);
266
+ this._checkFrozen('remove() from');
267
+ const arr = this._elements;
268
+ const len = arr.length;
269
+ if (len < 16) {
270
+ for (let i = 0; i < len; i++) {
271
+ if (compare(arr[i], element) === 0) {
272
+ arr.splice(i, 1);
273
+ this._hashCode = null;
274
+ return this;
275
+ }
276
+ }
277
+ return this;
278
+ }
279
+ let low = 0, high = len - 1;
280
+ while (low <= high) {
281
+ const mid = (low + high) >>> 1;
282
+ const cmp = compare(arr[mid], element);
283
+ if (cmp === 0) {
284
+ arr.splice(mid, 1);
285
+ this._hashCode = null;
286
+ return this;
287
+ }
288
+ if (cmp < 0)
289
+ low = mid + 1;
290
+ else
291
+ high = mid - 1;
292
+ }
139
293
  return this;
140
294
  }
141
295
  clear() {
142
- this._tree = createTree(RecursiveSet.compare);
296
+ this._checkFrozen('clear()');
297
+ this._elements = [];
298
+ this._hashCode = 0;
143
299
  return this;
144
300
  }
145
- // === Set Operations ===
301
+ mutableCopy() {
302
+ const s = new RecursiveSet();
303
+ s._elements = this._elements.slice();
304
+ return s;
305
+ }
306
+ clone() { return this.mutableCopy(); }
146
307
  union(other) {
147
- const result = this.clone();
148
- for (const el of other)
149
- result.add(el);
150
- return result;
308
+ const s = new RecursiveSet();
309
+ const arrA = this._elements;
310
+ const arrB = other._elements;
311
+ if (arrA.length === 0)
312
+ return other.clone();
313
+ if (arrB.length === 0)
314
+ return this.clone();
315
+ const res = [];
316
+ let i = 0, j = 0;
317
+ const lenA = arrA.length, lenB = arrB.length;
318
+ while (i < lenA && j < lenB) {
319
+ const cmp = compare(arrA[i], arrB[j]);
320
+ if (cmp < 0)
321
+ res.push(arrA[i++]);
322
+ else if (cmp > 0)
323
+ res.push(arrB[j++]);
324
+ else {
325
+ res.push(arrA[i++]);
326
+ j++;
327
+ }
328
+ }
329
+ while (i < lenA)
330
+ res.push(arrA[i++]);
331
+ while (j < lenB)
332
+ res.push(arrB[j++]);
333
+ s._elements = res;
334
+ return s;
151
335
  }
152
336
  intersection(other) {
153
- const result = new RecursiveSet();
154
- const [smaller, larger] = this.size < other.size ? [this, other] : [other, this];
155
- for (const el of smaller) {
156
- if (larger.has(el)) {
157
- result.add(el);
337
+ const s = new RecursiveSet();
338
+ const arrA = this._elements;
339
+ const arrB = other._elements;
340
+ const res = [];
341
+ let i = 0, j = 0;
342
+ const lenA = arrA.length, lenB = arrB.length;
343
+ while (i < lenA && j < lenB) {
344
+ const cmp = compare(arrA[i], arrB[j]);
345
+ if (cmp < 0)
346
+ i++;
347
+ else if (cmp > 0)
348
+ j++;
349
+ else {
350
+ res.push(arrA[i++]);
351
+ j++;
158
352
  }
159
353
  }
160
- return result;
354
+ s._elements = res;
355
+ return s;
161
356
  }
162
357
  difference(other) {
163
- const result = new RecursiveSet();
164
- for (const el of this) {
165
- if (!other.has(el)) {
166
- result.add(el);
358
+ const s = new RecursiveSet();
359
+ const arrA = this._elements;
360
+ const arrB = other._elements;
361
+ const res = [];
362
+ let i = 0, j = 0;
363
+ const lenA = arrA.length, lenB = arrB.length;
364
+ while (i < lenA && j < lenB) {
365
+ const cmp = compare(arrA[i], arrB[j]);
366
+ if (cmp < 0)
367
+ res.push(arrA[i++]);
368
+ else if (cmp > 0)
369
+ j++;
370
+ else {
371
+ i++;
372
+ j++;
167
373
  }
168
374
  }
169
- return result;
375
+ while (i < lenA)
376
+ res.push(arrA[i++]);
377
+ s._elements = res;
378
+ return s;
170
379
  }
171
380
  symmetricDifference(other) {
172
- return this.difference(other).union(other.difference(this));
381
+ const s = new RecursiveSet();
382
+ const arrA = this._elements;
383
+ const arrB = other._elements;
384
+ const res = [];
385
+ let i = 0, j = 0;
386
+ const lenA = arrA.length, lenB = arrB.length;
387
+ while (i < lenA && j < lenB) {
388
+ const cmp = compare(arrA[i], arrB[j]);
389
+ if (cmp < 0)
390
+ res.push(arrA[i++]);
391
+ else if (cmp > 0)
392
+ res.push(arrB[j++]);
393
+ else {
394
+ i++;
395
+ j++;
396
+ }
397
+ }
398
+ while (i < lenA)
399
+ res.push(arrA[i++]);
400
+ while (j < lenB)
401
+ res.push(arrB[j++]);
402
+ s._elements = res;
403
+ return s;
173
404
  }
174
405
  powerset() {
175
406
  const n = this.size;
176
- if (n > 30)
177
- throw new Error("Powerset size exceeds 32-bit integer limit");
178
- const elements = [];
179
- this._tree.forEach((key) => { elements.push(key); });
407
+ if (n > 20)
408
+ throw new Error("Powerset too large");
180
409
  const subsets = [];
181
- for (let i = 0; i < (1 << n); i++) {
410
+ const max = 1 << n;
411
+ for (let i = 0; i < max; i++) {
182
412
  const subset = new RecursiveSet();
183
413
  for (let j = 0; j < n; j++) {
184
- if (i & (1 << j)) {
185
- subset.add(elements[j]);
186
- }
414
+ if (i & (1 << j))
415
+ subset._elements.push(this._elements[j]);
187
416
  }
188
417
  subsets.push(subset);
189
418
  }
190
419
  return new RecursiveSet(...subsets);
191
420
  }
192
- /**
193
- * Returns the Cartesian product as a set of Tuples.
194
- * Uses the Tuple class to ensure structural equality.
195
- */
196
421
  cartesianProduct(other) {
197
422
  const result = new RecursiveSet();
198
- for (const x of this) {
199
- for (const y of other) {
200
- result.add(new Tuple(x, y));
423
+ const arrA = this._elements;
424
+ const arrB = other._elements;
425
+ for (const x of arrA) {
426
+ for (const y of arrB) {
427
+ result._elements.push(new Tuple(x, y));
201
428
  }
202
429
  }
203
430
  return result;
204
431
  }
205
- // === Predicates ===
206
- has(element) {
207
- return this._tree.get(element) !== undefined;
208
- }
209
432
  isSubset(other) {
210
433
  if (this.size > other.size)
211
434
  return false;
212
- for (const el of this) {
213
- if (!other.has(el))
435
+ let i = 0, j = 0;
436
+ const arrA = this._elements, arrB = other._elements;
437
+ while (i < arrA.length && j < arrB.length) {
438
+ const cmp = compare(arrA[i], arrB[j]);
439
+ if (cmp < 0)
214
440
  return false;
441
+ if (cmp > 0)
442
+ j++;
443
+ else {
444
+ i++;
445
+ j++;
446
+ }
215
447
  }
216
- return true;
217
- }
218
- isSuperset(other) {
219
- return other.isSubset(this);
220
- }
221
- isProperSubset(other) {
222
- return this.isSubset(other) && !this.equals(other);
223
- }
224
- isEmpty() {
225
- return this.size === 0;
226
- }
227
- equals(other) {
228
- return RecursiveSet.compare(this, other) === 0;
229
- }
230
- // === Utility ===
231
- get size() {
232
- return this._tree.length;
233
- }
234
- toSet() {
235
- const result = new Set();
236
- this._tree.forEach((key) => { result.add(key); });
237
- return result;
238
- }
239
- // Lazy Iterator
240
- *[Symbol.iterator]() {
241
- let iter = this._tree.begin;
242
- while (iter.valid) {
243
- yield iter.key;
244
- iter.next();
245
- }
448
+ return i === arrA.length;
246
449
  }
450
+ isSuperset(other) { return other.isSubset(this); }
451
+ isProperSubset(other) { return this.isSubset(other) && this.size < other.size; }
452
+ equals(other) { return this.compare(other) === 0; }
453
+ toSet() { return new Set(this._elements); }
454
+ *[Symbol.iterator]() { yield* this._elements; }
247
455
  toString() {
248
456
  if (this.isEmpty())
249
457
  return "∅";
250
- const elements = [];
251
- this._tree.forEach((key) => {
252
- if (key instanceof RecursiveSet) {
253
- elements.push(key.toString());
254
- }
255
- else if (key instanceof Tuple) {
256
- elements.push(key.toString());
257
- }
258
- else {
259
- elements.push(String(key));
260
- }
458
+ const elementsStr = this._elements.map(el => {
459
+ if (Array.isArray(el))
460
+ return `[${el.join(', ')}]`;
461
+ return String(el);
261
462
  });
262
- return `{${elements.join(", ")}}`;
263
- }
264
- [Symbol.for('nodejs.util.inspect.custom')]() {
265
- return this.toString();
463
+ return `{${elementsStr.join(', ')}}`;
266
464
  }
465
+ [Symbol.for('nodejs.util.inspect.custom')]() { return this.toString(); }
267
466
  }
268
- // === Helpers ===
269
- export function emptySet() {
270
- return new RecursiveSet();
271
- }
272
- export function singleton(element) {
273
- return new RecursiveSet(element);
274
- }
275
- export function fromIterable(iterable) {
276
- return new RecursiveSet(...iterable);
277
- }
467
+ export function emptySet() { return new RecursiveSet(); }
468
+ export function singleton(element) { return new RecursiveSet(element); }
469
+ export function fromIterable(iterable) { return new RecursiveSet(...iterable); }
278
470
  //# sourceMappingURL=index.js.map