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
|
@@ -2,187 +2,34 @@
|
|
|
2
2
|
* data-structure-typed
|
|
3
3
|
*
|
|
4
4
|
* @author Pablo Zeng
|
|
5
|
-
* @copyright Copyright (c) 2022 Pablo Zeng
|
|
5
|
+
* @copyright Copyright (c) 2022 Pablo Zeng
|
|
6
6
|
* @license MIT License
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type {
|
|
10
|
-
|
|
11
|
-
ElemOf,
|
|
12
|
-
EntryCallback,
|
|
13
|
-
FamilyPosition,
|
|
14
|
-
RBTNColor,
|
|
15
|
-
TreeMultiMapOptions
|
|
16
|
-
} from '../../types';
|
|
9
|
+
import type { Comparator, TreeMultiMapOptions } from '../../types';
|
|
10
|
+
import { Range } from '../../common';
|
|
17
11
|
import { RedBlackTree, RedBlackTreeNode } from './red-black-tree';
|
|
18
|
-
import {
|
|
12
|
+
import { TreeSet } from './tree-set';
|
|
19
13
|
|
|
20
14
|
/**
|
|
21
|
-
* Node used by TreeMultiMap
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
24
|
-
* @template V
|
|
15
|
+
* Node type used by TreeMultiMap (alias to RedBlackTreeNode for backward compatibility).
|
|
16
|
+
*
|
|
17
|
+
* @deprecated Direct node manipulation is discouraged. Use TreeMultiMap methods instead.
|
|
25
18
|
*/
|
|
26
|
-
export class TreeMultiMapNode<K = any, V = any> {
|
|
27
|
-
key: K
|
|
28
|
-
|
|
29
|
-
parent?: TreeMultiMapNode<K, V> = undefined;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Create a TreeMultiMap node with an optional value bucket.
|
|
33
|
-
* @remarks Time O(1), Space O(1)
|
|
34
|
-
* @param key - Key of the node.
|
|
35
|
-
* @param [value] - Initial array of values.
|
|
36
|
-
* @returns New TreeMultiMapNode instance.
|
|
37
|
-
*/
|
|
38
|
-
constructor(key: K, value: V[] = [], color: RBTNColor = 'BLACK') {
|
|
39
|
-
this.key = key;
|
|
40
|
-
this.value = value;
|
|
41
|
-
this.color = color;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
_left?: TreeMultiMapNode<K, V> | null | undefined = undefined;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Get the left child pointer.
|
|
48
|
-
* @remarks Time O(1), Space O(1)
|
|
49
|
-
* @returns Left child node, or null/undefined.
|
|
50
|
-
*/
|
|
51
|
-
get left(): TreeMultiMapNode<K, V> | null | undefined {
|
|
52
|
-
return this._left;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Set the left child and update its parent pointer.
|
|
57
|
-
* @remarks Time O(1), Space O(1)
|
|
58
|
-
* @param v - New left child node, or null/undefined.
|
|
59
|
-
* @returns void
|
|
60
|
-
*/
|
|
61
|
-
set left(v: TreeMultiMapNode<K, V> | null | undefined) {
|
|
62
|
-
if (v) {
|
|
63
|
-
v.parent = this;
|
|
64
|
-
}
|
|
65
|
-
this._left = v;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
_right?: TreeMultiMapNode<K, V> | null | undefined = undefined;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get the right child pointer.
|
|
72
|
-
* @remarks Time O(1), Space O(1)
|
|
73
|
-
* @returns Right child node, or null/undefined.
|
|
74
|
-
*/
|
|
75
|
-
get right(): TreeMultiMapNode<K, V> | null | undefined {
|
|
76
|
-
return this._right;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Set the right child and update its parent pointer.
|
|
81
|
-
* @remarks Time O(1), Space O(1)
|
|
82
|
-
* @param v - New right child node, or null/undefined.
|
|
83
|
-
* @returns void
|
|
84
|
-
*/
|
|
85
|
-
set right(v: TreeMultiMapNode<K, V> | null | undefined) {
|
|
86
|
-
if (v) {
|
|
87
|
-
v.parent = this;
|
|
88
|
-
}
|
|
89
|
-
this._right = v;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
_height: number = 0;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Gets the height of the node (used in self-balancing trees).
|
|
96
|
-
* @remarks Time O(1), Space O(1)
|
|
97
|
-
*
|
|
98
|
-
* @returns The height.
|
|
99
|
-
*/
|
|
100
|
-
get height(): number {
|
|
101
|
-
return this._height;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Sets the height of the node.
|
|
106
|
-
* @remarks Time O(1), Space O(1)
|
|
107
|
-
*
|
|
108
|
-
* @param value - The new height.
|
|
109
|
-
*/
|
|
110
|
-
set height(value: number) {
|
|
111
|
-
this._height = value;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
_color: RBTNColor = 'BLACK';
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Gets the color of the node (used in Red-Black trees).
|
|
118
|
-
* @remarks Time O(1), Space O(1)
|
|
119
|
-
*
|
|
120
|
-
* @returns The node's color.
|
|
121
|
-
*/
|
|
122
|
-
get color(): RBTNColor {
|
|
123
|
-
return this._color;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Sets the color of the node.
|
|
128
|
-
* @remarks Time O(1), Space O(1)
|
|
129
|
-
*
|
|
130
|
-
* @param value - The new color.
|
|
131
|
-
*/
|
|
132
|
-
set color(value: RBTNColor) {
|
|
133
|
-
this._color = value;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
_count: number = 1;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Gets the count of nodes in the subtree rooted at this node (used in order-statistic trees).
|
|
140
|
-
* @remarks Time O(1), Space O(1)
|
|
141
|
-
*
|
|
142
|
-
* @returns The subtree node count.
|
|
143
|
-
*/
|
|
144
|
-
get count(): number {
|
|
145
|
-
return this._count;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Sets the count of nodes in the subtree.
|
|
150
|
-
* @remarks Time O(1), Space O(1)
|
|
151
|
-
*
|
|
152
|
-
* @param value - The new count.
|
|
153
|
-
*/
|
|
154
|
-
set count(value: number) {
|
|
155
|
-
this._count = value;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Gets the position of the node relative to its parent.
|
|
160
|
-
* @remarks Time O(1), Space O(1)
|
|
161
|
-
*
|
|
162
|
-
* @returns The family position (e.g., 'ROOT', 'LEFT', 'RIGHT').
|
|
163
|
-
*/
|
|
164
|
-
get familyPosition(): FamilyPosition {
|
|
165
|
-
if (!this.parent) {
|
|
166
|
-
return this.left || this.right ? 'ROOT' : 'ISOLATED';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
if (this.parent.left === this) {
|
|
170
|
-
return this.left || this.right ? 'ROOT_LEFT' : 'LEFT';
|
|
171
|
-
} else if (this.parent.right === this) {
|
|
172
|
-
return this.left || this.right ? 'ROOT_RIGHT' : 'RIGHT';
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return 'MAL_NODE';
|
|
19
|
+
export class TreeMultiMapNode<K = any, V = any> extends RedBlackTreeNode<K, V[]> {
|
|
20
|
+
constructor(key: K, value: V[] = []) {
|
|
21
|
+
super(key, value);
|
|
176
22
|
}
|
|
177
23
|
}
|
|
178
24
|
|
|
179
25
|
/**
|
|
180
|
-
*
|
|
181
|
-
* @remarks Time O(1), Space O(1)
|
|
182
|
-
* @template K
|
|
183
|
-
* @template V
|
|
184
|
-
* @template R
|
|
26
|
+
* TreeMultiMap (ordered MultiMap) — key → bucket (Array of values).
|
|
185
27
|
*
|
|
28
|
+
* Semantics (RFC):
|
|
29
|
+
* - Bucketed design: each key appears once; duplicates live in the bucket.
|
|
30
|
+
* - `get(key)` returns a **live** bucket reference.
|
|
31
|
+
* - Default iteration yields bucket entries: `[K, V[]]`.
|
|
32
|
+
* - Navigable operations (`first/last/ceiling/...`) return entry tuples like TreeMap.
|
|
186
33
|
* @example
|
|
187
34
|
* // players ranked by score with their equipment
|
|
188
35
|
* type Equipment = {
|
|
@@ -310,7 +157,7 @@ export class TreeMultiMapNode<K = any, V = any> {
|
|
|
310
157
|
* isMapMode: false
|
|
311
158
|
* });
|
|
312
159
|
*
|
|
313
|
-
* const topPlayersEquipments = playerRankings.rangeSearch([8900, 10000], node => playerRankings.get(node));
|
|
160
|
+
* const topPlayersEquipments = playerRankings.rangeSearch([8900, 10000], node => playerRankings.get(node.key));
|
|
314
161
|
* console.log(topPlayersEquipments); // [
|
|
315
162
|
* // [
|
|
316
163
|
* // {
|
|
@@ -348,202 +195,518 @@ export class TreeMultiMapNode<K = any, V = any> {
|
|
|
348
195
|
* // ]
|
|
349
196
|
* // ];
|
|
350
197
|
*/
|
|
351
|
-
export class TreeMultiMap<K = any, V = any, R = any>
|
|
198
|
+
export class TreeMultiMap<K = any, V = any, R = any> implements Iterable<[K, V[]]> {
|
|
199
|
+
readonly #core: RedBlackTree<K, V[], R>;
|
|
200
|
+
readonly #isDefaultComparator: boolean;
|
|
201
|
+
|
|
352
202
|
/**
|
|
353
|
-
*
|
|
354
|
-
* @
|
|
355
|
-
* @param
|
|
356
|
-
* @
|
|
357
|
-
* @
|
|
203
|
+
* Creates a new TreeMultiMap.
|
|
204
|
+
* @param keysNodesEntriesOrRaws - Initial entries, or raw elements if `toEntryFn` is provided.
|
|
205
|
+
* @param options - Configuration options including optional `toEntryFn` to transform raw elements.
|
|
206
|
+
* @remarks Time O(m log m), Space O(m) where m is the number of initial entries
|
|
207
|
+
* @example
|
|
208
|
+
* // Standard usage with entries
|
|
209
|
+
* const mmap = new TreeMultiMap([['a', ['x', 'y']], ['b', ['z']]]);
|
|
210
|
+
*
|
|
211
|
+
* // Using toEntryFn to transform raw objects
|
|
212
|
+
* const players = [{ score: 100, items: ['sword'] }, { score: 200, items: ['shield', 'bow'] }];
|
|
213
|
+
* const mmap = new TreeMultiMap(players, { toEntryFn: p => [p.score, p.items] });
|
|
358
214
|
*/
|
|
359
215
|
constructor(
|
|
360
|
-
keysNodesEntriesOrRaws: Iterable<
|
|
361
|
-
|
|
362
|
-
> = [],
|
|
363
|
-
options?: TreeMultiMapOptions<K, V[], R>
|
|
216
|
+
keysNodesEntriesOrRaws: Iterable<K | [K | null | undefined, V[] | undefined] | null | undefined | R> = [],
|
|
217
|
+
options: TreeMultiMapOptions<K, V[], R> = {}
|
|
364
218
|
) {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
219
|
+
const comparator = options.comparator ?? TreeSet.createDefaultComparator<K>();
|
|
220
|
+
this.#isDefaultComparator = options.comparator === undefined;
|
|
221
|
+
const toEntryFn = options.toEntryFn;
|
|
222
|
+
this.#core = new RedBlackTree<K, V[], R>([], { ...options, comparator, isMapMode: options.isMapMode });
|
|
223
|
+
|
|
224
|
+
for (const x of keysNodesEntriesOrRaws) {
|
|
225
|
+
if (x === null || x === undefined) continue;
|
|
226
|
+
|
|
227
|
+
// If toEntryFn is provided, use it to transform raw element
|
|
228
|
+
if (toEntryFn) {
|
|
229
|
+
const [k, bucket] = toEntryFn(x as R);
|
|
230
|
+
if (k === null || k === undefined) continue;
|
|
231
|
+
if (bucket !== undefined) {
|
|
232
|
+
this.#core.set(k as K, Array.isArray(bucket) ? [...bucket] : [bucket] as V[]);
|
|
233
|
+
} else {
|
|
234
|
+
this.#core.set(k as K, [] as V[]);
|
|
235
|
+
}
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (Array.isArray(x)) {
|
|
240
|
+
const [k, bucket] = x;
|
|
241
|
+
if (k === null || k === undefined) continue;
|
|
242
|
+
if (bucket !== undefined) {
|
|
243
|
+
// seed bucket (copy)
|
|
244
|
+
this.#core.set(k as K, [...bucket] as V[]);
|
|
245
|
+
} else {
|
|
246
|
+
this.#core.set(k as K, [] as V[]);
|
|
247
|
+
}
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
// key-only
|
|
251
|
+
this.#core.set(x as K, [] as V[]);
|
|
368
252
|
}
|
|
369
253
|
}
|
|
370
254
|
|
|
371
|
-
|
|
372
|
-
|
|
255
|
+
/**
|
|
256
|
+
* Validates the key against the default comparator rules.
|
|
257
|
+
* @remarks Time O(1), Space O(1)
|
|
258
|
+
*/
|
|
259
|
+
private _validateKey(key: K): void {
|
|
260
|
+
if (!this.#isDefaultComparator) return;
|
|
261
|
+
// reuse TreeSet strict validation (same policy)
|
|
262
|
+
// NOTE: TreeSet._validateKey is private, so we replicate the checks.
|
|
263
|
+
if (typeof key === 'number') {
|
|
264
|
+
if (Number.isNaN(key)) throw new TypeError('TreeMultiMap: NaN is not a valid key');
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (typeof key === 'string') return;
|
|
268
|
+
if (key instanceof Date) {
|
|
269
|
+
if (Number.isNaN(key.getTime())) throw new TypeError('TreeMultiMap: invalid Date key');
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
throw new TypeError('TreeMultiMap: comparator is required for non-number/non-string/non-Date keys');
|
|
373
273
|
}
|
|
374
274
|
|
|
375
275
|
/**
|
|
376
|
-
*
|
|
276
|
+
* Number of distinct keys.
|
|
377
277
|
* @remarks Time O(1), Space O(1)
|
|
378
|
-
*
|
|
379
|
-
* @param keyNodeOrEntry - The item to check.
|
|
380
|
-
* @returns True if it's a TreeMultiMapNode, false otherwise.
|
|
381
278
|
*/
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
): keyNodeOrEntry is TreeMultiMapNode<K, V> {
|
|
385
|
-
return keyNodeOrEntry instanceof TreeMultiMapNode;
|
|
279
|
+
get size(): number {
|
|
280
|
+
return this.#core.size;
|
|
386
281
|
}
|
|
387
282
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
283
|
+
/**
|
|
284
|
+
* Whether the map is empty.
|
|
285
|
+
* @remarks Time O(1), Space O(1)
|
|
286
|
+
*/
|
|
287
|
+
isEmpty(): boolean {
|
|
288
|
+
return this.size === 0;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Removes all entries from the map.
|
|
293
|
+
* @remarks Time O(1), Space O(1)
|
|
294
|
+
*/
|
|
295
|
+
clear(): void {
|
|
296
|
+
this.#core.clear();
|
|
297
|
+
}
|
|
391
298
|
|
|
392
|
-
|
|
299
|
+
/**
|
|
300
|
+
* Bucket length for a key (missing => 0).
|
|
301
|
+
* @remarks Time O(log n), Space O(1)
|
|
302
|
+
*/
|
|
303
|
+
count(key: K): number {
|
|
304
|
+
const b = this.get(key);
|
|
305
|
+
return Array.isArray(b) ? b.length : 0;
|
|
306
|
+
}
|
|
393
307
|
|
|
394
308
|
/**
|
|
395
|
-
*
|
|
396
|
-
* @remarks Time O(
|
|
397
|
-
* @param keyNodeOrEntry - Key, node, or [key, values] entry.
|
|
398
|
-
* @param [value] - Single value to set when a bare key is provided.
|
|
399
|
-
* @returns True if inserted or appended; false if ignored.
|
|
309
|
+
* Total number of values across all buckets (Σ bucket.length).
|
|
310
|
+
* @remarks Time O(n), Space O(1)
|
|
400
311
|
*/
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
312
|
+
get totalSize(): number {
|
|
313
|
+
let sum = 0;
|
|
314
|
+
for (const [, bucket] of this) sum += bucket.length;
|
|
315
|
+
return sum;
|
|
316
|
+
}
|
|
406
317
|
|
|
407
|
-
|
|
408
|
-
|
|
318
|
+
/**
|
|
319
|
+
* Whether the map contains the given key.
|
|
320
|
+
* @remarks Time O(log n), Space O(1)
|
|
321
|
+
*/
|
|
322
|
+
has(key: K): boolean {
|
|
323
|
+
this._validateKey(key);
|
|
324
|
+
return this.#core.has(key);
|
|
325
|
+
}
|
|
409
326
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
433
|
-
} else {
|
|
434
|
-
return super.set(key, values);
|
|
435
|
-
}
|
|
436
|
-
};
|
|
327
|
+
/**
|
|
328
|
+
* Live bucket reference (do not auto-delete key if bucket becomes empty via mutation).
|
|
329
|
+
* @remarks Time O(log n), Space O(1)
|
|
330
|
+
*/
|
|
331
|
+
get(key: K): V[] | undefined {
|
|
332
|
+
this._validateKey(key);
|
|
333
|
+
return this.#core.get(key);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Append a single value.
|
|
338
|
+
* @remarks Time O(log n), Space O(1)
|
|
339
|
+
*/
|
|
340
|
+
add(key: K, value: V): boolean {
|
|
341
|
+
this._validateKey(key);
|
|
342
|
+
const bucket = this.#core.get(key);
|
|
343
|
+
if (bucket) {
|
|
344
|
+
bucket.push(value);
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
return this.#core.set(key, [value]);
|
|
348
|
+
}
|
|
437
349
|
|
|
438
|
-
|
|
439
|
-
|
|
350
|
+
/**
|
|
351
|
+
* Alias for compatibility with existing TreeMultiMap semantics.
|
|
352
|
+
* @remarks Time O(log n), Space O(1) for single value; O(log n + m) for bucket append
|
|
353
|
+
*/
|
|
354
|
+
set(entry: [K | null | undefined, V[] | undefined] | K | null | undefined, value?: V): boolean;
|
|
355
|
+
set(key: K, value: V): boolean;
|
|
356
|
+
set(entry: [K | null | undefined, V[] | undefined] | K | null | undefined, value?: V): boolean {
|
|
357
|
+
if (entry === null || entry === undefined) return false;
|
|
358
|
+
if (Array.isArray(entry)) {
|
|
359
|
+
const [k, bucket] = entry;
|
|
360
|
+
if (k === null || k === undefined) return false;
|
|
361
|
+
if (value !== undefined) return this.add(k as K, value);
|
|
362
|
+
if (bucket === undefined) {
|
|
363
|
+
// ensure key exists
|
|
364
|
+
return this.#core.set(k as K, [] as V[]);
|
|
365
|
+
}
|
|
366
|
+
// append bucket
|
|
367
|
+
const existing = this.#core.get(k as K);
|
|
368
|
+
if (existing) {
|
|
369
|
+
existing.push(...bucket);
|
|
370
|
+
return true;
|
|
440
371
|
}
|
|
441
|
-
return
|
|
442
|
-
}
|
|
372
|
+
return this.#core.set(k as K, [...bucket] as V[]);
|
|
373
|
+
}
|
|
374
|
+
// key-only or key+value
|
|
375
|
+
if (value !== undefined) return this.add(entry as K, value);
|
|
376
|
+
return this.#core.set(entry as K, [] as V[]);
|
|
377
|
+
}
|
|
443
378
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Deletes a key and its entire bucket.
|
|
381
|
+
* @remarks Time O(log n), Space O(1)
|
|
382
|
+
*/
|
|
383
|
+
delete(key: K): boolean {
|
|
384
|
+
this._validateKey(key);
|
|
385
|
+
return this.#core.delete(key).length > 0;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Check if a specific value exists in a key's bucket.
|
|
390
|
+
* @remarks Time O(log n + m), Space O(1) where m is bucket size
|
|
391
|
+
*/
|
|
392
|
+
hasEntry(key: K, value: V, eq: (a: V, b: V) => boolean = Object.is): boolean {
|
|
393
|
+
const bucket = this.get(key);
|
|
394
|
+
if (!Array.isArray(bucket)) return false;
|
|
395
|
+
return bucket.some(v => eq(v, value));
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Delete a single occurrence of a value from a key's bucket.
|
|
400
|
+
* @remarks Time O(log n + m), Space O(1) where m is bucket size
|
|
401
|
+
*/
|
|
402
|
+
deleteValue(key: K, value: V, eq: (a: V, b: V) => boolean = Object.is): boolean {
|
|
403
|
+
const bucket = this.get(key);
|
|
404
|
+
if (!Array.isArray(bucket)) return false;
|
|
405
|
+
const idx = bucket.findIndex(v => eq(v, value));
|
|
406
|
+
if (idx === -1) return false;
|
|
407
|
+
bucket.splice(idx, 1);
|
|
408
|
+
if (bucket.length === 0) this.delete(key);
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Delete all occurrences of a value from a key's bucket.
|
|
414
|
+
* @remarks Time O(log n + m), Space O(1) where m is bucket size
|
|
415
|
+
*/
|
|
416
|
+
deleteValues(key: K, value: V, eq: (a: V, b: V) => boolean = Object.is): number {
|
|
417
|
+
const bucket = this.get(key);
|
|
418
|
+
if (!Array.isArray(bucket) || bucket.length === 0) return 0;
|
|
419
|
+
let removed = 0;
|
|
420
|
+
for (let i = bucket.length - 1; i >= 0; i--) {
|
|
421
|
+
if (eq(bucket[i] as V, value)) {
|
|
422
|
+
bucket.splice(i, 1);
|
|
423
|
+
removed++;
|
|
424
|
+
}
|
|
447
425
|
}
|
|
426
|
+
if (bucket.length === 0 && removed > 0) this.delete(key);
|
|
427
|
+
return removed;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// ---- iteration (bucket view) ----
|
|
448
431
|
|
|
449
|
-
|
|
432
|
+
/**
|
|
433
|
+
* Iterates over all entries as [key, bucket] pairs.
|
|
434
|
+
* @remarks Time O(n), Space O(1)
|
|
435
|
+
*/
|
|
436
|
+
*[Symbol.iterator](): Iterator<[K, V[]]> {
|
|
437
|
+
for (const [k, v] of this.#core) {
|
|
438
|
+
// core always stores buckets, but guard anyway
|
|
439
|
+
yield [k, v ?? ([] as V[])];
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Iterates over all keys.
|
|
445
|
+
* @remarks Time O(n), Space O(1)
|
|
446
|
+
*/
|
|
447
|
+
*keys(): IterableIterator<K> {
|
|
448
|
+
yield* this.#core.keys();
|
|
450
449
|
}
|
|
451
450
|
|
|
452
451
|
/**
|
|
453
|
-
*
|
|
454
|
-
* @remarks Time O(
|
|
455
|
-
* @param keyNodeOrEntry - Key, node, or [key, values] entry to locate the bucket.
|
|
456
|
-
* @param value - Value to remove from the bucket.
|
|
457
|
-
* @returns True if the value was removed; false if not found.
|
|
452
|
+
* Iterates over all buckets.
|
|
453
|
+
* @remarks Time O(n), Space O(1)
|
|
458
454
|
*/
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
): boolean {
|
|
463
|
-
const values = this.get(keyNodeOrEntry);
|
|
464
|
-
if (Array.isArray(values)) {
|
|
465
|
-
const index = values.indexOf(value);
|
|
466
|
-
if (index === -1) return false;
|
|
467
|
-
values.splice(index, 1);
|
|
455
|
+
*values(): IterableIterator<V[]> {
|
|
456
|
+
for (const [, bucket] of this) yield bucket;
|
|
457
|
+
}
|
|
468
458
|
|
|
469
|
-
|
|
459
|
+
// ---- entry-flat views ----
|
|
470
460
|
|
|
471
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Iterates over all entries for a specific key.
|
|
463
|
+
* @remarks Time O(log n + m), Space O(1) where m is bucket size
|
|
464
|
+
*/
|
|
465
|
+
*entriesOf(key: K): IterableIterator<[K, V]> {
|
|
466
|
+
const bucket = this.get(key);
|
|
467
|
+
if (!Array.isArray(bucket)) return;
|
|
468
|
+
for (const v of bucket) yield [key, v];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Iterates over all values for a specific key.
|
|
473
|
+
* @remarks Time O(log n + m), Space O(1) where m is bucket size
|
|
474
|
+
*/
|
|
475
|
+
*valuesOf(key: K): IterableIterator<V> {
|
|
476
|
+
const bucket = this.get(key);
|
|
477
|
+
if (!Array.isArray(bucket)) return;
|
|
478
|
+
yield* bucket;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Iterates over all [key, value] pairs (flattened from buckets).
|
|
483
|
+
* @remarks Time O(T), Space O(1) where T is totalSize
|
|
484
|
+
*/
|
|
485
|
+
*flatEntries(): IterableIterator<[K, V]> {
|
|
486
|
+
for (const [k, bucket] of this) {
|
|
487
|
+
for (const v of bucket) yield [k, v];
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// ━━━ Navigable methods (return [K, V[]] | undefined) ━━━
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Returns the entry with the smallest key.
|
|
495
|
+
* @remarks Time O(log n), Space O(1)
|
|
496
|
+
* @example
|
|
497
|
+
* const map = new TreeMultiMap([[1, ['a']], [2, ['b']]]);
|
|
498
|
+
* map.first(); // [1, ['a']]
|
|
499
|
+
*/
|
|
500
|
+
first(): [K, V[]] | undefined {
|
|
501
|
+
const k = this.#core.getLeftMost();
|
|
502
|
+
if (k === undefined) return undefined;
|
|
503
|
+
const b = this.get(k);
|
|
504
|
+
return b === undefined ? undefined : [k, b];
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Returns the entry with the largest key.
|
|
509
|
+
* @remarks Time O(log n), Space O(1)
|
|
510
|
+
* @example
|
|
511
|
+
* const map = new TreeMultiMap([[1, ['a']], [2, ['b']]]);
|
|
512
|
+
* map.last(); // [2, ['b']]
|
|
513
|
+
*/
|
|
514
|
+
last(): [K, V[]] | undefined {
|
|
515
|
+
const k = this.#core.getRightMost();
|
|
516
|
+
if (k === undefined) return undefined;
|
|
517
|
+
const b = this.get(k);
|
|
518
|
+
return b === undefined ? undefined : [k, b];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Removes and returns the entry with the smallest key.
|
|
523
|
+
* @remarks Time O(log n), Space O(1)
|
|
524
|
+
* @example
|
|
525
|
+
* const map = new TreeMultiMap([[1, ['a']], [2, ['b']]]);
|
|
526
|
+
* map.pollFirst(); // [1, ['a']]
|
|
527
|
+
* map.has(1); // false
|
|
528
|
+
*/
|
|
529
|
+
pollFirst(): [K, V[]] | undefined {
|
|
530
|
+
const e = this.first();
|
|
531
|
+
if (!e) return undefined;
|
|
532
|
+
this.delete(e[0]);
|
|
533
|
+
return e;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* Removes and returns the entry with the largest key.
|
|
538
|
+
* @remarks Time O(log n), Space O(1)
|
|
539
|
+
* @example
|
|
540
|
+
* const map = new TreeMultiMap([[1, ['a']], [2, ['b']]]);
|
|
541
|
+
* map.pollLast(); // [2, ['b']]
|
|
542
|
+
* map.has(2); // false
|
|
543
|
+
*/
|
|
544
|
+
pollLast(): [K, V[]] | undefined {
|
|
545
|
+
const e = this.last();
|
|
546
|
+
if (!e) return undefined;
|
|
547
|
+
this.delete(e[0]);
|
|
548
|
+
return e;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Returns the entry with the smallest key >= given key.
|
|
553
|
+
* @remarks Time O(log n), Space O(1)
|
|
554
|
+
* @example
|
|
555
|
+
* const map = new TreeMultiMap([[10, ['a']], [20, ['b']], [30, ['c']]]);
|
|
556
|
+
* map.ceiling(15); // [20, ['b']]
|
|
557
|
+
* map.ceiling(20); // [20, ['b']]
|
|
558
|
+
*/
|
|
559
|
+
ceiling(key: K): [K, V[]] | undefined {
|
|
560
|
+
this._validateKey(key);
|
|
561
|
+
const k = this.#core.ceiling(key);
|
|
562
|
+
if (k === undefined) return undefined;
|
|
563
|
+
const b = this.get(k);
|
|
564
|
+
return b === undefined ? undefined : [k, b];
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Returns the entry with the largest key <= given key.
|
|
569
|
+
* @remarks Time O(log n), Space O(1)
|
|
570
|
+
* @example
|
|
571
|
+
* const map = new TreeMultiMap([[10, ['a']], [20, ['b']], [30, ['c']]]);
|
|
572
|
+
* map.floor(25); // [20, ['b']]
|
|
573
|
+
* map.floor(20); // [20, ['b']]
|
|
574
|
+
*/
|
|
575
|
+
floor(key: K): [K, V[]] | undefined {
|
|
576
|
+
this._validateKey(key);
|
|
577
|
+
const k = this.#core.floor(key);
|
|
578
|
+
if (k === undefined) return undefined;
|
|
579
|
+
const b = this.get(k);
|
|
580
|
+
return b === undefined ? undefined : [k, b];
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Returns the entry with the smallest key > given key.
|
|
585
|
+
* @remarks Time O(log n), Space O(1)
|
|
586
|
+
* @example
|
|
587
|
+
* const map = new TreeMultiMap([[10, ['a']], [20, ['b']], [30, ['c']]]);
|
|
588
|
+
* map.higher(10); // [20, ['b']]
|
|
589
|
+
* map.higher(15); // [20, ['b']]
|
|
590
|
+
*/
|
|
591
|
+
higher(key: K): [K, V[]] | undefined {
|
|
592
|
+
this._validateKey(key);
|
|
593
|
+
const k = this.#core.higher(key);
|
|
594
|
+
if (k === undefined) return undefined;
|
|
595
|
+
const b = this.get(k);
|
|
596
|
+
return b === undefined ? undefined : [k, b];
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Returns the entry with the largest key < given key.
|
|
601
|
+
* @remarks Time O(log n), Space O(1)
|
|
602
|
+
* @example
|
|
603
|
+
* const map = new TreeMultiMap([[10, ['a']], [20, ['b']], [30, ['c']]]);
|
|
604
|
+
* map.lower(20); // [10, ['a']]
|
|
605
|
+
* map.lower(15); // [10, ['a']]
|
|
606
|
+
*/
|
|
607
|
+
lower(key: K): [K, V[]] | undefined {
|
|
608
|
+
this._validateKey(key);
|
|
609
|
+
const k = this.#core.lower(key);
|
|
610
|
+
if (k === undefined) return undefined;
|
|
611
|
+
const b = this.get(k);
|
|
612
|
+
return b === undefined ? undefined : [k, b];
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ━━━ Tree utilities ━━━
|
|
616
|
+
|
|
617
|
+
/**
|
|
618
|
+
* Prints the internal tree structure (for debugging).
|
|
619
|
+
* @remarks Time O(n), Space O(n)
|
|
620
|
+
*/
|
|
621
|
+
print(): void {
|
|
622
|
+
this.#core.print();
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Executes a callback for each entry.
|
|
627
|
+
* @remarks Time O(n), Space O(1)
|
|
628
|
+
*/
|
|
629
|
+
forEach(callback: (value: V[], key: K, map: this) => void): void {
|
|
630
|
+
for (const [k, v] of this) {
|
|
631
|
+
callback(v, k, this);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Creates a new map with entries that pass the predicate.
|
|
637
|
+
* @remarks Time O(n), Space O(n)
|
|
638
|
+
*/
|
|
639
|
+
filter(predicate: (value: V[], key: K, map: this) => boolean): TreeMultiMap<K, V, R> {
|
|
640
|
+
const filtered: [K, V[]][] = [];
|
|
641
|
+
for (const [k, v] of this) {
|
|
642
|
+
if (predicate(v, k, this)) filtered.push([k, v]);
|
|
643
|
+
}
|
|
644
|
+
return new TreeMultiMap<K, V, R>(filtered, { comparator: this.comparator });
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* Creates a new map by transforming each entry.
|
|
649
|
+
* @remarks Time O(n log n), Space O(n)
|
|
650
|
+
*/
|
|
651
|
+
map<V2>(
|
|
652
|
+
mapper: (value: V[], key: K, map: this) => [K, V2[]]
|
|
653
|
+
): TreeMultiMap<K, V2, R> {
|
|
654
|
+
const mapped: [K, V2[]][] = [];
|
|
655
|
+
for (const [k, v] of this) {
|
|
656
|
+
mapped.push(mapper(v, k, this));
|
|
472
657
|
}
|
|
473
|
-
return
|
|
658
|
+
return new TreeMultiMap<K, V2, R>(mapped, { comparator: this.comparator });
|
|
474
659
|
}
|
|
475
660
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
661
|
+
/**
|
|
662
|
+
* Reduces all entries to a single value.
|
|
663
|
+
* @remarks Time O(n), Space O(1)
|
|
664
|
+
*/
|
|
665
|
+
reduce<U>(callback: (accumulator: U, value: V[], key: K, map: this) => U, initialValue: U): U {
|
|
666
|
+
let acc = initialValue;
|
|
667
|
+
for (const [k, v] of this) {
|
|
668
|
+
acc = callback(acc, v, k, this);
|
|
669
|
+
}
|
|
670
|
+
return acc;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* Sets multiple entries at once.
|
|
675
|
+
* @remarks Time O(m log n), Space O(m) where m is input size
|
|
676
|
+
*/
|
|
677
|
+
setMany(keysNodesEntriesOrRaws: Iterable<K | [K | null | undefined, V[] | undefined]>): boolean[] {
|
|
678
|
+
const results: boolean[] = [];
|
|
679
|
+
for (const x of keysNodesEntriesOrRaws) {
|
|
680
|
+
// Call implementation directly: entry can be K or [K, V[]] or [K, undefined]
|
|
681
|
+
results.push(this.set(x));
|
|
682
|
+
}
|
|
683
|
+
return results;
|
|
684
|
+
}
|
|
481
685
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
686
|
+
/**
|
|
687
|
+
* Searches for entries within a key range.
|
|
688
|
+
* @remarks Time O(log n + k), Space O(k) where k is result size
|
|
689
|
+
*/
|
|
690
|
+
rangeSearch<C extends (node: RedBlackTreeNode<K, V[]>) => unknown>(
|
|
691
|
+
range: Range<K> | [K, K],
|
|
692
|
+
callback?: C
|
|
693
|
+
): ReturnType<C>[] {
|
|
694
|
+
return this.#core.rangeSearch(range, callback as (node: RedBlackTreeNode<K, V[]>) => ReturnType<C>);
|
|
695
|
+
}
|
|
487
696
|
|
|
488
697
|
/**
|
|
489
|
-
*
|
|
490
|
-
* @remarks Time O(
|
|
491
|
-
* @template MK
|
|
492
|
-
* @template MV
|
|
493
|
-
* @template MR
|
|
494
|
-
* @param callback - Function mapping (key, values, index, tree) → [newKey, newValue].
|
|
495
|
-
* @param [options] - Options for the output tree.
|
|
496
|
-
* @param [thisArg] - Value for `this` inside the callback.
|
|
497
|
-
* @returns A new RedBlackTree (or TreeMultiMap when mapping to array values; see overloads).
|
|
698
|
+
* Creates a shallow clone of this map.
|
|
699
|
+
* @remarks Time O(n log n), Space O(n)
|
|
498
700
|
*/
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
options?: Partial<TreeMultiMapOptions<MK, MV, MR>>,
|
|
502
|
-
thisArg?: unknown
|
|
503
|
-
): RedBlackTree<MK, MV, MR> {
|
|
504
|
-
const out = this._createLike<MK, MV, MR>([], options);
|
|
505
|
-
let i = 0;
|
|
506
|
-
for (const [k, v] of this) out.set(callback.call(thisArg, v, k, i++, this));
|
|
507
|
-
return out;
|
|
701
|
+
clone(): TreeMultiMap<K, V, R> {
|
|
702
|
+
return new TreeMultiMap<K, V, R>(this, { comparator: this.comparator, isMapMode: this.#core.isMapMode });
|
|
508
703
|
}
|
|
509
704
|
|
|
510
705
|
/**
|
|
511
|
-
*
|
|
706
|
+
* Expose comparator for advanced usage/testing (read-only).
|
|
512
707
|
* @remarks Time O(1), Space O(1)
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
* @param [options] - Optional constructor options for the like-kind instance.
|
|
517
|
-
* @returns An empty like-kind instance.
|
|
518
|
-
*/
|
|
519
|
-
protected override _createInstance<TK = K, TV = V, TR = R>(options?: Partial<TreeMultiMapOptions<TK, TV, TR>>): this {
|
|
520
|
-
const Ctor = this.constructor as unknown as new (
|
|
521
|
-
iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
|
|
522
|
-
opts?: TreeMultiMapOptions<TK, TV, TR>
|
|
523
|
-
) => RedBlackTree<TK, TV, TR>;
|
|
524
|
-
return new Ctor([], { ...(this._snapshotOptions?.<TK, TV, TR>() ?? {}), ...(options ?? {}) }) as unknown as this;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* (Protected) Create a like-kind instance and seed it from an iterable.
|
|
529
|
-
* @remarks Time O(N log N), Space O(N)
|
|
530
|
-
* @template TK
|
|
531
|
-
* @template TV
|
|
532
|
-
* @template TR
|
|
533
|
-
* @param iter - Iterable used to seed the new tree.
|
|
534
|
-
* @param [options] - Options merged with the current snapshot.
|
|
535
|
-
* @returns A like-kind RedBlackTree built from the iterable.
|
|
536
|
-
*/
|
|
537
|
-
protected override _createLike<TK = K, TV = V, TR = R>(
|
|
538
|
-
iter: Iterable<
|
|
539
|
-
TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR
|
|
540
|
-
> = [],
|
|
541
|
-
options?: Partial<TreeMultiMapOptions<TK, TV, TR>>
|
|
542
|
-
): RedBlackTree<TK, TV, TR> {
|
|
543
|
-
const Ctor = this.constructor as unknown as new (
|
|
544
|
-
iter?: Iterable<TK | RedBlackTreeNode<TK, TV> | [TK | null | undefined, TV | undefined] | null | undefined | TR>,
|
|
545
|
-
opts?: TreeMultiMapOptions<TK, TV, TR>
|
|
546
|
-
) => RedBlackTree<TK, TV, TR>;
|
|
547
|
-
return new Ctor(iter, { ...(this._snapshotOptions?.<TK, TV, TR>() ?? {}), ...(options ?? {}) });
|
|
708
|
+
*/
|
|
709
|
+
get comparator(): Comparator<K> {
|
|
710
|
+
return this.#core.comparator;
|
|
548
711
|
}
|
|
549
712
|
}
|