max-priority-queue-typed 2.4.0 → 2.4.1
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/types/data-structures/base/linear-base.d.ts +6 -6
- package/dist/types/data-structures/binary-tree/binary-tree.d.ts +6 -6
- package/dist/types/data-structures/binary-tree/bst.d.ts +2 -1
- package/dist/types/data-structures/binary-tree/index.d.ts +3 -3
- package/dist/types/data-structures/binary-tree/red-black-tree.d.ts +150 -20
- package/dist/types/data-structures/binary-tree/tree-map.d.ts +188 -0
- package/dist/types/data-structures/binary-tree/tree-multi-map.d.ts +238 -147
- package/dist/types/data-structures/binary-tree/tree-multi-set.d.ts +270 -0
- package/dist/types/data-structures/binary-tree/tree-set.d.ts +181 -0
- package/dist/types/interfaces/binary-tree.d.ts +2 -2
- package/dist/types/types/data-structures/binary-tree/index.d.ts +3 -3
- package/dist/types/types/data-structures/binary-tree/tree-map.d.ts +33 -0
- package/dist/types/types/data-structures/binary-tree/tree-multi-set.d.ts +16 -0
- package/dist/types/types/data-structures/binary-tree/tree-set.d.ts +33 -0
- package/package.json +2 -2
- package/src/data-structures/base/linear-base.ts +2 -12
- package/src/data-structures/binary-tree/avl-tree.ts +1 -1
- package/src/data-structures/binary-tree/binary-tree.ts +45 -21
- package/src/data-structures/binary-tree/bst.ts +85 -10
- package/src/data-structures/binary-tree/index.ts +3 -3
- package/src/data-structures/binary-tree/red-black-tree.ts +568 -76
- package/src/data-structures/binary-tree/tree-map.ts +439 -0
- package/src/data-structures/binary-tree/tree-multi-map.ts +488 -325
- package/src/data-structures/binary-tree/tree-multi-set.ts +502 -0
- package/src/data-structures/binary-tree/tree-set.ts +407 -0
- package/src/data-structures/queue/deque.ts +10 -0
- package/src/interfaces/binary-tree.ts +2 -2
- package/src/types/data-structures/binary-tree/index.ts +3 -3
- package/src/types/data-structures/binary-tree/tree-map.ts +45 -0
- package/src/types/data-structures/binary-tree/tree-multi-set.ts +19 -0
- package/src/types/data-structures/binary-tree/tree-set.ts +39 -0
- package/dist/types/data-structures/binary-tree/avl-tree-counter.d.ts +0 -236
- package/dist/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +0 -197
- package/dist/types/data-structures/binary-tree/tree-counter.d.ts +0 -243
- package/dist/types/types/data-structures/binary-tree/avl-tree-counter.d.ts +0 -2
- package/dist/types/types/data-structures/binary-tree/avl-tree-multi-map.d.ts +0 -2
- package/dist/types/types/data-structures/binary-tree/tree-counter.d.ts +0 -2
- package/src/data-structures/binary-tree/avl-tree-counter.ts +0 -539
- package/src/data-structures/binary-tree/avl-tree-multi-map.ts +0 -438
- package/src/data-structures/binary-tree/tree-counter.ts +0 -575
- package/src/types/data-structures/binary-tree/avl-tree-counter.ts +0 -3
- package/src/types/data-structures/binary-tree/avl-tree-multi-map.ts +0 -3
- package/src/types/data-structures/binary-tree/tree-counter.ts +0 -3
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TreeMultiSet (ordered multiset) — a restricted, native-like API backed by RedBlackTree.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - No node exposure
|
|
6
|
+
* - Strict default comparator (number/string/Date), otherwise require comparator
|
|
7
|
+
* - Default iteration is expanded (each element yielded `count(x)` times)
|
|
8
|
+
* - `delete(x)` removes one occurrence by default
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { Comparator, TreeMultiSetOptions } from '../../types';
|
|
12
|
+
import { RedBlackTree } from './red-black-tree';
|
|
13
|
+
import { TreeSet } from './tree-set';
|
|
14
|
+
|
|
15
|
+
export class TreeMultiSet<K = any, R = K> implements Iterable<K> {
|
|
16
|
+
readonly #core: RedBlackTree<K, number>;
|
|
17
|
+
readonly #isDefaultComparator: boolean;
|
|
18
|
+
private _size = 0; // total occurrences (sumCounts)
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Creates a new TreeMultiSet.
|
|
22
|
+
* @param elements - Initial elements to add, or raw elements if `toElementFn` is provided.
|
|
23
|
+
* @param options - Configuration options including optional `toElementFn` to transform raw elements.
|
|
24
|
+
* @remarks Time O(m log m), Space O(m) where m is the number of initial elements
|
|
25
|
+
* @example
|
|
26
|
+
* // Standard usage with elements
|
|
27
|
+
* const mset = new TreeMultiSet([1, 2, 2, 3]);
|
|
28
|
+
*
|
|
29
|
+
* // Using toElementFn to transform raw objects
|
|
30
|
+
* const items = [{ score: 100 }, { score: 200 }, { score: 100 }];
|
|
31
|
+
* const mset = new TreeMultiSet<number, Item>(items, { toElementFn: item => item.score });
|
|
32
|
+
*/
|
|
33
|
+
constructor(elements: Iterable<R> | Iterable<K> = [], options: TreeMultiSetOptions<K, R> = {}) {
|
|
34
|
+
const toElementFn = options.toElementFn;
|
|
35
|
+
const comparator = options.comparator ?? TreeSet.createDefaultComparator<K>();
|
|
36
|
+
this.#isDefaultComparator = options.comparator === undefined;
|
|
37
|
+
this.#core = new RedBlackTree<K, number>([], { comparator, isMapMode: options.isMapMode });
|
|
38
|
+
|
|
39
|
+
for (const item of elements) {
|
|
40
|
+
const k = toElementFn ? toElementFn(item as R) : (item as K);
|
|
41
|
+
this.add(k);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Validates the key against the default comparator rules.
|
|
47
|
+
* @remarks Time O(1), Space O(1)
|
|
48
|
+
*/
|
|
49
|
+
private _validateKey(key: K): void {
|
|
50
|
+
if (!this.#isDefaultComparator) return;
|
|
51
|
+
|
|
52
|
+
if (typeof key === 'number') {
|
|
53
|
+
if (Number.isNaN(key)) throw new TypeError('TreeMultiSet: NaN is not a valid key');
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (typeof key === 'string') return;
|
|
58
|
+
|
|
59
|
+
if (key instanceof Date) {
|
|
60
|
+
if (Number.isNaN(key.getTime())) throw new TypeError('TreeMultiSet: invalid Date key');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
throw new TypeError('TreeMultiSet: comparator is required for non-number/non-string/non-Date keys');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Validates that count is a non-negative safe integer.
|
|
69
|
+
* @remarks Time O(1), Space O(1)
|
|
70
|
+
*/
|
|
71
|
+
private _validateCount(n: number): void {
|
|
72
|
+
if (!Number.isSafeInteger(n) || n < 0) throw new RangeError('TreeMultiSet: count must be a safe integer >= 0');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Total occurrences (sumCounts).
|
|
77
|
+
* @remarks Time O(1), Space O(1)
|
|
78
|
+
*/
|
|
79
|
+
get size(): number {
|
|
80
|
+
return this._size;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Number of distinct keys.
|
|
85
|
+
* @remarks Time O(1), Space O(1)
|
|
86
|
+
*/
|
|
87
|
+
get distinctSize(): number {
|
|
88
|
+
return this.#core.size;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Whether the multiset is empty.
|
|
93
|
+
* @remarks Time O(1), Space O(1)
|
|
94
|
+
*/
|
|
95
|
+
isEmpty(): boolean {
|
|
96
|
+
return this.size === 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Whether the multiset contains the given key.
|
|
101
|
+
* @remarks Time O(log n), Space O(1)
|
|
102
|
+
*/
|
|
103
|
+
has(key: K): boolean {
|
|
104
|
+
this._validateKey(key);
|
|
105
|
+
return this.count(key) > 0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns the count of occurrences for the given key.
|
|
110
|
+
* @remarks Time O(log n), Space O(1)
|
|
111
|
+
*/
|
|
112
|
+
count(key: K): number {
|
|
113
|
+
this._validateKey(key);
|
|
114
|
+
return this.#core.get(key) ?? 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Add `n` occurrences of `key`.
|
|
119
|
+
* @returns True if the multiset changed.
|
|
120
|
+
* @remarks Time O(log n), Space O(1)
|
|
121
|
+
*/
|
|
122
|
+
add(key: K, n = 1): boolean {
|
|
123
|
+
this._validateKey(key);
|
|
124
|
+
this._validateCount(n);
|
|
125
|
+
if (n === 0) return false;
|
|
126
|
+
|
|
127
|
+
const old = this.#core.get(key) ?? 0;
|
|
128
|
+
const next = old + n;
|
|
129
|
+
this.#core.set(key, next);
|
|
130
|
+
this._size += n;
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Set count for `key` to exactly `n`.
|
|
136
|
+
* @returns True if changed.
|
|
137
|
+
* @remarks Time O(log n), Space O(1)
|
|
138
|
+
*/
|
|
139
|
+
setCount(key: K, n: number): boolean {
|
|
140
|
+
this._validateKey(key);
|
|
141
|
+
this._validateCount(n);
|
|
142
|
+
|
|
143
|
+
const old = this.#core.get(key) ?? 0;
|
|
144
|
+
if (old === n) return false;
|
|
145
|
+
|
|
146
|
+
if (n === 0) {
|
|
147
|
+
if (old !== 0) this.#core.delete(key);
|
|
148
|
+
} else {
|
|
149
|
+
this.#core.set(key, n);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this._size += n - old;
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Delete `n` occurrences of `key` (default 1).
|
|
158
|
+
* @returns True if any occurrence was removed.
|
|
159
|
+
* @remarks Time O(log n), Space O(1)
|
|
160
|
+
*/
|
|
161
|
+
delete(key: K, n = 1): boolean {
|
|
162
|
+
this._validateKey(key);
|
|
163
|
+
this._validateCount(n);
|
|
164
|
+
if (n === 0) return false;
|
|
165
|
+
|
|
166
|
+
const old = this.#core.get(key) ?? 0;
|
|
167
|
+
if (old === 0) return false;
|
|
168
|
+
|
|
169
|
+
const removed = Math.min(old, n);
|
|
170
|
+
const next = old - removed;
|
|
171
|
+
|
|
172
|
+
if (next === 0) this.#core.delete(key);
|
|
173
|
+
else this.#core.set(key, next);
|
|
174
|
+
|
|
175
|
+
this._size -= removed;
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Delete all occurrences of the given key.
|
|
181
|
+
* @returns True if any occurrence was removed.
|
|
182
|
+
* @remarks Time O(log n), Space O(1)
|
|
183
|
+
*/
|
|
184
|
+
deleteAll(key: K): boolean {
|
|
185
|
+
this._validateKey(key);
|
|
186
|
+
const old = this.#core.get(key) ?? 0;
|
|
187
|
+
if (old === 0) return false;
|
|
188
|
+
this.#core.delete(key);
|
|
189
|
+
this._size -= old;
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Iterates over distinct keys (each key yielded once).
|
|
195
|
+
* @remarks Time O(n), Space O(1)
|
|
196
|
+
*/
|
|
197
|
+
*keysDistinct(): IterableIterator<K> {
|
|
198
|
+
yield* this.#core.keys();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Iterates over entries as [key, count] pairs.
|
|
203
|
+
* @remarks Time O(n), Space O(1)
|
|
204
|
+
*/
|
|
205
|
+
*entries(): IterableIterator<[K, number]> {
|
|
206
|
+
for (const [k, v] of this.#core) {
|
|
207
|
+
yield [k, v ?? 0];
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Expanded iteration (default). Each key is yielded `count(key)` times.
|
|
213
|
+
* @remarks Time O(size), Space O(1) where size is total occurrences
|
|
214
|
+
*/
|
|
215
|
+
*[Symbol.iterator](): Iterator<K> {
|
|
216
|
+
for (const [k, c] of this.entries()) {
|
|
217
|
+
for (let i = 0; i < c; i++) yield k;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Returns an array with all elements (expanded).
|
|
223
|
+
* @remarks Time O(size), Space O(size)
|
|
224
|
+
*/
|
|
225
|
+
toArray(): K[] {
|
|
226
|
+
return [...this];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Returns an array with distinct keys only.
|
|
231
|
+
* @remarks Time O(n), Space O(n)
|
|
232
|
+
*/
|
|
233
|
+
toDistinctArray(): K[] {
|
|
234
|
+
return [...this.keysDistinct()];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Returns an array of [key, count] entries.
|
|
239
|
+
* @remarks Time O(n), Space O(n)
|
|
240
|
+
*/
|
|
241
|
+
toEntries(): Array<[K, number]> {
|
|
242
|
+
return [...this.entries()];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Expose comparator for advanced usage/testing (read-only).
|
|
247
|
+
* @remarks Time O(1), Space O(1)
|
|
248
|
+
*/
|
|
249
|
+
get comparator(): Comparator<K> {
|
|
250
|
+
return (this.#core as any)._comparator;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// ━━━ clear ━━━
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Remove all elements from the multiset.
|
|
257
|
+
* @remarks Time O(1), Space O(1)
|
|
258
|
+
* @example
|
|
259
|
+
* const ms = new TreeMultiSet([1, 2, 2, 3]);
|
|
260
|
+
* ms.clear();
|
|
261
|
+
* ms.size; // 0
|
|
262
|
+
*/
|
|
263
|
+
clear(): void {
|
|
264
|
+
this.#core.clear();
|
|
265
|
+
this._size = 0;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ━━━ Navigable methods ━━━
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Returns the smallest key, or undefined if empty.
|
|
272
|
+
* @remarks Time O(log n), Space O(1)
|
|
273
|
+
* @example
|
|
274
|
+
* const ms = new TreeMultiSet([3, 1, 4]);
|
|
275
|
+
* ms.first(); // 1
|
|
276
|
+
*/
|
|
277
|
+
first(): K | undefined {
|
|
278
|
+
return this.#core.getLeftMost();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Returns the largest key, or undefined if empty.
|
|
283
|
+
* @remarks Time O(log n), Space O(1)
|
|
284
|
+
* @example
|
|
285
|
+
* const ms = new TreeMultiSet([3, 1, 4]);
|
|
286
|
+
* ms.last(); // 4
|
|
287
|
+
*/
|
|
288
|
+
last(): K | undefined {
|
|
289
|
+
return this.#core.getRightMost();
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Removes all occurrences of the smallest key and returns it.
|
|
294
|
+
* @remarks Time O(log n), Space O(1)
|
|
295
|
+
* @example
|
|
296
|
+
* const ms = new TreeMultiSet([1, 1, 2, 3]);
|
|
297
|
+
* ms.pollFirst(); // 1
|
|
298
|
+
* ms.has(1); // false
|
|
299
|
+
*/
|
|
300
|
+
pollFirst(): K | undefined {
|
|
301
|
+
const key = this.first();
|
|
302
|
+
if (key === undefined) return undefined;
|
|
303
|
+
this.deleteAll(key);
|
|
304
|
+
return key;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Removes all occurrences of the largest key and returns it.
|
|
309
|
+
* @remarks Time O(log n), Space O(1)
|
|
310
|
+
* @example
|
|
311
|
+
* const ms = new TreeMultiSet([1, 2, 3, 3]);
|
|
312
|
+
* ms.pollLast(); // 3
|
|
313
|
+
* ms.has(3); // false
|
|
314
|
+
*/
|
|
315
|
+
pollLast(): K | undefined {
|
|
316
|
+
const key = this.last();
|
|
317
|
+
if (key === undefined) return undefined;
|
|
318
|
+
this.deleteAll(key);
|
|
319
|
+
return key;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Returns the smallest key >= given key, or undefined.
|
|
324
|
+
* @remarks Time O(log n), Space O(1)
|
|
325
|
+
* @example
|
|
326
|
+
* const ms = new TreeMultiSet([10, 20, 30]);
|
|
327
|
+
* ms.ceiling(15); // 20
|
|
328
|
+
* ms.ceiling(20); // 20
|
|
329
|
+
*/
|
|
330
|
+
ceiling(key: K): K | undefined {
|
|
331
|
+
this._validateKey(key);
|
|
332
|
+
return this.#core.ceiling(key);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Returns the largest key <= given key, or undefined.
|
|
337
|
+
* @remarks Time O(log n), Space O(1)
|
|
338
|
+
* @example
|
|
339
|
+
* const ms = new TreeMultiSet([10, 20, 30]);
|
|
340
|
+
* ms.floor(25); // 20
|
|
341
|
+
* ms.floor(20); // 20
|
|
342
|
+
*/
|
|
343
|
+
floor(key: K): K | undefined {
|
|
344
|
+
this._validateKey(key);
|
|
345
|
+
return this.#core.floor(key);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Returns the smallest key > given key, or undefined.
|
|
350
|
+
* @remarks Time O(log n), Space O(1)
|
|
351
|
+
* @example
|
|
352
|
+
* const ms = new TreeMultiSet([10, 20, 30]);
|
|
353
|
+
* ms.higher(10); // 20
|
|
354
|
+
* ms.higher(15); // 20
|
|
355
|
+
*/
|
|
356
|
+
higher(key: K): K | undefined {
|
|
357
|
+
this._validateKey(key);
|
|
358
|
+
return this.#core.higher(key);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Returns the largest key < given key, or undefined.
|
|
363
|
+
* @remarks Time O(log n), Space O(1)
|
|
364
|
+
* @example
|
|
365
|
+
* const ms = new TreeMultiSet([10, 20, 30]);
|
|
366
|
+
* ms.lower(20); // 10
|
|
367
|
+
* ms.lower(15); // 10
|
|
368
|
+
*/
|
|
369
|
+
lower(key: K): K | undefined {
|
|
370
|
+
this._validateKey(key);
|
|
371
|
+
return this.#core.lower(key);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ━━━ Functional methods ━━━
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Iterates over distinct keys with their counts.
|
|
378
|
+
* @remarks Time O(n), Space O(1)
|
|
379
|
+
* @example
|
|
380
|
+
* const ms = new TreeMultiSet([1, 1, 2, 3, 3, 3]);
|
|
381
|
+
* ms.forEach((key, count) => console.log(`${key}: ${count}`));
|
|
382
|
+
* // 1: 2, 2: 1, 3: 3
|
|
383
|
+
*/
|
|
384
|
+
forEach(callback: (key: K, count: number) => void): void {
|
|
385
|
+
for (const [k, c] of this.entries()) {
|
|
386
|
+
callback(k, c);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Creates a new TreeMultiSet with entries that match the predicate.
|
|
392
|
+
* @remarks Time O(n log n), Space O(n)
|
|
393
|
+
* @example
|
|
394
|
+
* const ms = new TreeMultiSet([1, 1, 2, 3, 3, 3]);
|
|
395
|
+
* const filtered = ms.filter((key, count) => count >= 2);
|
|
396
|
+
* // TreeMultiSet { 1: 2, 3: 3 }
|
|
397
|
+
*/
|
|
398
|
+
filter(predicate: (key: K, count: number) => boolean): TreeMultiSet<K> {
|
|
399
|
+
const result = new TreeMultiSet<K>([], {
|
|
400
|
+
comparator: this.#isDefaultComparator ? undefined : this.comparator,
|
|
401
|
+
isMapMode: (this.#core as any)._isMapMode
|
|
402
|
+
});
|
|
403
|
+
for (const [k, c] of this.entries()) {
|
|
404
|
+
if (predicate(k, c)) {
|
|
405
|
+
result.add(k, c);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Reduces the multiset to a single value.
|
|
413
|
+
* @remarks Time O(n), Space O(1)
|
|
414
|
+
* @example
|
|
415
|
+
* const ms = new TreeMultiSet([1, 1, 2, 3, 3, 3]);
|
|
416
|
+
* const total = ms.reduce((acc, key, count) => acc + count, 0); // 6
|
|
417
|
+
*/
|
|
418
|
+
reduce<U>(callback: (accumulator: U, key: K, count: number) => U, initialValue: U): U {
|
|
419
|
+
let acc = initialValue;
|
|
420
|
+
for (const [k, c] of this.entries()) {
|
|
421
|
+
acc = callback(acc, k, c);
|
|
422
|
+
}
|
|
423
|
+
return acc;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Maps keys and counts to a new TreeMultiSet.
|
|
428
|
+
* When multiple keys map to the same new key, counts are merged (added).
|
|
429
|
+
* @remarks Time O(n log n), Space O(n)
|
|
430
|
+
* @example
|
|
431
|
+
* const ms = new TreeMultiSet([1, 1, 2, 3, 3, 3]);
|
|
432
|
+
* const mapped = ms.map((key, count) => [key * 10, count]);
|
|
433
|
+
* // TreeMultiSet { 10: 2, 20: 1, 30: 3 }
|
|
434
|
+
* @example
|
|
435
|
+
* // Collision: counts merge
|
|
436
|
+
* const ms = new TreeMultiSet([1, 2, 3]);
|
|
437
|
+
* const merged = ms.map((key, count) => [key % 2, count]);
|
|
438
|
+
* // { 0: 1, 1: 2 } (1 and 3 both map to 1, counts add)
|
|
439
|
+
*/
|
|
440
|
+
map<K2>(
|
|
441
|
+
mapper: (key: K, count: number) => [K2, number],
|
|
442
|
+
options?: { comparator?: Comparator<K2> }
|
|
443
|
+
): TreeMultiSet<K2> {
|
|
444
|
+
const result = new TreeMultiSet<K2>([], {
|
|
445
|
+
comparator: options?.comparator,
|
|
446
|
+
isMapMode: (this.#core as any)._isMapMode
|
|
447
|
+
});
|
|
448
|
+
for (const [k, c] of this.entries()) {
|
|
449
|
+
const [newKey, newCount] = mapper(k, c);
|
|
450
|
+
result.add(newKey, newCount);
|
|
451
|
+
}
|
|
452
|
+
return result;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Creates an independent copy of this multiset.
|
|
457
|
+
* @remarks Time O(n log n), Space O(n)
|
|
458
|
+
* @example
|
|
459
|
+
* const ms = new TreeMultiSet([1, 1, 2]);
|
|
460
|
+
* const copy = ms.clone();
|
|
461
|
+
* copy.add(3);
|
|
462
|
+
* ms.has(3); // false (original unchanged)
|
|
463
|
+
*/
|
|
464
|
+
clone(): TreeMultiSet<K> {
|
|
465
|
+
const result = new TreeMultiSet<K>([], {
|
|
466
|
+
comparator: this.#isDefaultComparator ? undefined : this.comparator,
|
|
467
|
+
isMapMode: (this.#core as any)._isMapMode
|
|
468
|
+
});
|
|
469
|
+
for (const [k, c] of this.entries()) {
|
|
470
|
+
result.add(k, c);
|
|
471
|
+
}
|
|
472
|
+
return result;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// ━━━ Tree utilities ━━━
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Returns keys within the given range.
|
|
479
|
+
* @remarks Time O(log n + k), Space O(k) where k is result size
|
|
480
|
+
* @example
|
|
481
|
+
* const ms = new TreeMultiSet([10, 20, 30, 40, 50]);
|
|
482
|
+
* ms.rangeSearch([15, 45]); // [20, 30, 40]
|
|
483
|
+
*/
|
|
484
|
+
rangeSearch<C extends (key: K) => any>(
|
|
485
|
+
range: [K, K],
|
|
486
|
+
callback?: C
|
|
487
|
+
): (C extends undefined ? K : ReturnType<C>)[] {
|
|
488
|
+
const cb = callback ?? ((k: K) => k);
|
|
489
|
+
return this.#core.rangeSearch(range, node => cb(node.key)) as any;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Prints the internal tree structure (for debugging).
|
|
494
|
+
* @remarks Time O(n), Space O(n)
|
|
495
|
+
* @example
|
|
496
|
+
* const ms = new TreeMultiSet([1, 2, 3]);
|
|
497
|
+
* ms.print();
|
|
498
|
+
*/
|
|
499
|
+
print(): void {
|
|
500
|
+
this.#core.print();
|
|
501
|
+
}
|
|
502
|
+
}
|