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/LICENSE +20 -20
- package/README.md +207 -186
- package/dist/cjs/index.js +323 -261
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.d.ts +125 -22
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +323 -261
- package/dist/esm/index.js.map +1 -1
- package/package.json +3 -4
package/dist/cjs/index.js
CHANGED
|
@@ -1,178 +1,256 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* @module recursive-set
|
|
4
|
-
*
|
|
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
|
-
|
|
11
|
-
//
|
|
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
|
|
16
|
-
|
|
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
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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 ^=
|
|
35
|
+
h ^= view.getInt32(0, true);
|
|
27
36
|
h = Math.imul(h, FNV_PRIME);
|
|
28
|
-
h ^=
|
|
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
|
|
42
|
+
let h = FNV_OFFSET;
|
|
34
43
|
const len = str.length;
|
|
35
44
|
for (let i = 0; i < len; i++) {
|
|
36
|
-
|
|
37
|
-
|
|
45
|
+
h ^= str.charCodeAt(i);
|
|
46
|
+
h = Math.imul(h, FNV_PRIME);
|
|
38
47
|
}
|
|
39
|
-
return
|
|
48
|
+
return h >>> 0;
|
|
40
49
|
}
|
|
41
|
-
function hashValue(
|
|
42
|
-
if (typeof
|
|
43
|
-
return
|
|
44
|
-
if (typeof
|
|
45
|
-
return
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
117
|
+
return 0;
|
|
107
118
|
}
|
|
108
|
-
//
|
|
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
|
|
114
|
-
|
|
134
|
+
this.#values = values.slice(); // Defensive copy
|
|
135
|
+
Object.freeze(this.#values); // Freeze for safety
|
|
136
|
+
this.hashCode = hashValue(this.#values);
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
130
|
-
|
|
131
|
-
this
|
|
174
|
+
if (elements.length > 1) {
|
|
175
|
+
elements.sort(compare);
|
|
176
|
+
this.#elements = this.#unique(elements);
|
|
132
177
|
}
|
|
133
178
|
else {
|
|
134
|
-
this
|
|
135
|
-
this._elements.sort(compare);
|
|
136
|
-
this._unique();
|
|
179
|
+
this.#elements = elements;
|
|
137
180
|
}
|
|
138
181
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
219
|
+
return out;
|
|
158
220
|
}
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
164
|
-
return this
|
|
235
|
+
if (this.#hashCode !== null)
|
|
236
|
+
return this.#hashCode;
|
|
165
237
|
let h = 0;
|
|
166
|
-
const arr = this
|
|
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
|
|
172
|
-
this
|
|
173
|
-
return
|
|
243
|
+
this.#hashCode = h;
|
|
244
|
+
this.#isFrozen = true; // Freeze on hash access
|
|
245
|
+
return h;
|
|
174
246
|
}
|
|
175
|
-
get isFrozen() { return this
|
|
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
|
|
184
|
-
const arrB = other
|
|
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
|
|
190
|
-
if (
|
|
191
|
-
return
|
|
267
|
+
const diff = compare(arrA[i], arrB[i]);
|
|
268
|
+
if (diff !== 0)
|
|
269
|
+
return diff;
|
|
192
270
|
}
|
|
193
271
|
return 0;
|
|
194
272
|
}
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
279
|
+
const arr = this.#elements;
|
|
199
280
|
const len = arr.length;
|
|
200
|
-
|
|
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
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
314
|
+
this.#hashCode = null;
|
|
241
315
|
return this;
|
|
242
316
|
}
|
|
243
317
|
if (lastCmp === 0)
|
|
244
318
|
return this;
|
|
245
319
|
}
|
|
246
|
-
|
|
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
|
|
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
|
|
288
|
-
const arr = this
|
|
289
|
-
|
|
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
|
|
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
|
|
318
|
-
this
|
|
319
|
-
this
|
|
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
|
|
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
|
|
330
|
-
const
|
|
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 =
|
|
384
|
+
const lenA = A.length, lenB = B.length;
|
|
339
385
|
while (i < lenA && j < lenB) {
|
|
340
|
-
const cmp = compare(
|
|
386
|
+
const cmp = compare(A[i], B[j]);
|
|
341
387
|
if (cmp < 0)
|
|
342
|
-
res.push(
|
|
388
|
+
res.push(A[i++]);
|
|
343
389
|
else if (cmp > 0)
|
|
344
|
-
res.push(
|
|
390
|
+
res.push(B[j++]);
|
|
345
391
|
else {
|
|
346
|
-
res.push(
|
|
392
|
+
res.push(A[i++]);
|
|
347
393
|
j++;
|
|
348
394
|
}
|
|
349
395
|
}
|
|
350
396
|
while (i < lenA)
|
|
351
|
-
res.push(
|
|
397
|
+
res.push(A[i++]);
|
|
352
398
|
while (j < lenB)
|
|
353
|
-
res.push(
|
|
354
|
-
|
|
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
|
|
359
|
-
const
|
|
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 =
|
|
412
|
+
const lenA = A.length, lenB = B.length;
|
|
364
413
|
while (i < lenA && j < lenB) {
|
|
365
|
-
const cmp = compare(
|
|
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(
|
|
420
|
+
res.push(A[i++]);
|
|
372
421
|
j++;
|
|
373
422
|
}
|
|
374
423
|
}
|
|
375
|
-
|
|
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
|
|
380
|
-
const
|
|
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 =
|
|
435
|
+
const lenA = A.length, lenB = B.length;
|
|
385
436
|
while (i < lenA && j < lenB) {
|
|
386
|
-
const cmp = compare(
|
|
437
|
+
const cmp = compare(A[i], B[j]);
|
|
387
438
|
if (cmp < 0)
|
|
388
|
-
res.push(
|
|
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(
|
|
398
|
-
|
|
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
|
|
403
|
-
const
|
|
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 =
|
|
460
|
+
const lenA = A.length, lenB = B.length;
|
|
408
461
|
while (i < lenA && j < lenB) {
|
|
409
|
-
const cmp = compare(
|
|
462
|
+
const cmp = compare(A[i], B[j]);
|
|
410
463
|
if (cmp < 0)
|
|
411
|
-
res.push(
|
|
464
|
+
res.push(A[i++]);
|
|
412
465
|
else if (cmp > 0)
|
|
413
|
-
res.push(
|
|
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(
|
|
473
|
+
res.push(A[i++]);
|
|
421
474
|
while (j < lenB)
|
|
422
|
-
res.push(
|
|
423
|
-
|
|
424
|
-
|
|
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
|
|
511
|
+
const subsetElements = [];
|
|
434
512
|
for (let j = 0; j < n; j++) {
|
|
435
513
|
if (i & (1 << j))
|
|
436
|
-
|
|
514
|
+
subsetElements.push(this.#elements[j]);
|
|
437
515
|
}
|
|
438
|
-
|
|
516
|
+
// Elements inside subset are already sorted -> Unsafe
|
|
517
|
+
subsets.push(RecursiveSet.fromSortedUnsafe(subsetElements));
|
|
439
518
|
}
|
|
440
|
-
|
|
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
|
|
458
|
-
while (i <
|
|
459
|
-
const cmp = compare(
|
|
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 ===
|
|
538
|
+
return i === A.length;
|
|
470
539
|
}
|
|
471
540
|
isSuperset(other) { return other.isSubset(this); }
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
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
|