d-ary-heap 2.2.0 → 2.5.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/README.md +100 -6
- package/dist/index.cjs +231 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +324 -6
- package/dist/index.d.ts +324 -6
- package/dist/index.js +225 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/PriorityQueue.ts +128 -6
- package/src/comparators.ts +1 -1
- package/src/index.ts +18 -1
- package/src/instrumentation.ts +339 -0
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|

|
|
2
2
|

|
|
3
3
|
|
|
4
|
-
# d-Heap Priority Queue (TypeScript) v2.
|
|
4
|
+
# d-Heap Priority Queue (TypeScript) v2.5.0
|
|
5
5
|
|
|
6
6
|
A high-performance, generic d-ary heap priority queue with O(1) item lookup, supporting both min-heap and max-heap behavior.
|
|
7
7
|
|
|
8
|
+
**[Live Demo](https://pcfvw.github.io/d-Heap-priority-queue/)** — Interactive visualization with Dijkstra's algorithm
|
|
9
|
+
|
|
8
10
|
## Strengths
|
|
9
11
|
|
|
10
12
|
- **Flexible behavior**: min-heap or max-heap via comparator functions, and configurable arity `d` at construction time.
|
|
@@ -13,7 +15,7 @@ A high-performance, generic d-ary heap priority queue with O(1) item lookup, sup
|
|
|
13
15
|
- O(log_d n): `insert()` and upward reheapification.
|
|
14
16
|
- O(d · log_d n): delete-top (`pop()`), with up to d children examined per level.
|
|
15
17
|
- **O(1) item lookup**: internal Map tracks positions by item key, enabling efficient priority updates.
|
|
16
|
-
- **Practical API**: `insert`, `front`, `peek`, `pop`, `increasePriority`, `decreasePriority`, `isEmpty`, `len`, `contains`.
|
|
18
|
+
- **Practical API**: `insert`, `front`, `peek`, `pop`, `increasePriority`, `decreasePriority`, `updatePriority`, `isEmpty`, `len`, `contains`.
|
|
17
19
|
- **Unified API**: Cross-language standardized methods matching C++, Rust, and Zig implementations.
|
|
18
20
|
- **TypeScript-native**: Full type safety, generics, and IDE support.
|
|
19
21
|
- **Zero dependencies**: No runtime dependencies.
|
|
@@ -107,8 +109,12 @@ Options:
|
|
|
107
109
|
| `increase_priority(item)` | Alias for `increasePriority()` (cross-language compatibility) | O(log_d n) |
|
|
108
110
|
| `increasePriorityByIndex(i)` | Update by index (primary method) | O(log_d n) |
|
|
109
111
|
| `increase_priority_by_index(i)` | Alias for `increasePriorityByIndex()` (cross-language compatibility) | O(log_d n) |
|
|
112
|
+
| `decreasePriorityByIndex(i)` | Update by index (primary method) | O(d · log_d n) |
|
|
113
|
+
| `decrease_priority_by_index(i)` | Alias for `decreasePriorityByIndex()` (cross-language compatibility) | O(d · log_d n) |
|
|
110
114
|
| `decreasePriority(item)` | Update item to lower priority (primary method) | O(d · log_d n) |
|
|
111
115
|
| `decrease_priority(item)` | Alias for `decreasePriority()` (cross-language compatibility) | O(d · log_d n) |
|
|
116
|
+
| `updatePriority(item)` | Update item when direction unknown (primary method) | O((d+1) · log_d n) |
|
|
117
|
+
| `update_priority(item)` | Alias for `updatePriority()` (cross-language compatibility) | O((d+1) · log_d n) |
|
|
112
118
|
| `clear(newD?)` | Remove all items, optionally change arity | O(1) |
|
|
113
119
|
|
|
114
120
|
### Utility Methods
|
|
@@ -124,8 +130,8 @@ Options:
|
|
|
124
130
|
|
|
125
131
|
This TypeScript implementation follows **camelCase** as the primary naming convention (TypeScript/JavaScript standard), with **snake_case aliases** provided for cross-language compatibility:
|
|
126
132
|
|
|
127
|
-
- **Primary methods**: `isEmpty()`, `increasePriority()`, `decreasePriority()`, `toString()`
|
|
128
|
-
- **Compatibility aliases**: `is_empty()`, `increase_priority()`, `decrease_priority()`, `to_string()`
|
|
133
|
+
- **Primary methods**: `isEmpty()`, `increasePriority()`, `increasePriorityByIndex()`, `decreasePriority()`, `decreasePriorityByIndex()`, `updatePriority()`, `toString()`
|
|
134
|
+
- **Compatibility aliases**: `is_empty()`, `increase_priority()`, `increase_priority_by_index()`, `decrease_priority()`, `decrease_priority_by_index()`, `update_priority()`, `to_string()`
|
|
129
135
|
|
|
130
136
|
Use the primary camelCase methods for TypeScript/JavaScript projects, and the snake_case aliases when porting code from C++/Rust implementations.
|
|
131
137
|
|
|
@@ -154,12 +160,100 @@ const byPriorityThenTime = chain(
|
|
|
154
160
|
);
|
|
155
161
|
```
|
|
156
162
|
|
|
163
|
+
## Instrumentation (v2.4.0+)
|
|
164
|
+
|
|
165
|
+
The library provides opt-in instrumentation for counting comparisons during heap operations. This is useful for:
|
|
166
|
+
|
|
167
|
+
- **Educational purposes**: Understanding theoretical vs actual comparison costs
|
|
168
|
+
- **Benchmarking**: Measuring real comparison counts across different arities
|
|
169
|
+
- **Visualization**: Powering interactive demos that show heap behavior
|
|
170
|
+
|
|
171
|
+
### Zero-Cost When Disabled
|
|
172
|
+
|
|
173
|
+
Instrumentation follows a zero-overhead design:
|
|
174
|
+
- No performance impact when not using instrumentation
|
|
175
|
+
- Existing code works unchanged
|
|
176
|
+
- Per-operation tracking distinguishes insert/pop/increasePriority/decreasePriority/updatePriority comparisons
|
|
177
|
+
|
|
178
|
+
### Usage
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import {
|
|
182
|
+
PriorityQueue,
|
|
183
|
+
minBy,
|
|
184
|
+
instrumentComparator,
|
|
185
|
+
theoreticalInsertComparisons,
|
|
186
|
+
theoreticalPopComparisons
|
|
187
|
+
} from 'd-ary-heap';
|
|
188
|
+
|
|
189
|
+
// 1. Wrap your comparator with instrumentation
|
|
190
|
+
const comparator = instrumentComparator(minBy((v: Vertex) => v.distance));
|
|
191
|
+
|
|
192
|
+
// 2. Create priority queue with operation hooks
|
|
193
|
+
const pq = new PriorityQueue({
|
|
194
|
+
d: 4,
|
|
195
|
+
comparator,
|
|
196
|
+
keyExtractor: (v) => v.id,
|
|
197
|
+
onBeforeOperation: (op) => comparator.startOperation(op),
|
|
198
|
+
onAfterOperation: () => comparator.endOperation(),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// 3. Use normally - comparisons are tracked automatically
|
|
202
|
+
pq.insert({ id: 'A', distance: 0 });
|
|
203
|
+
pq.insert({ id: 'B', distance: 5 });
|
|
204
|
+
pq.insert({ id: 'C', distance: 3 });
|
|
205
|
+
pq.pop();
|
|
206
|
+
|
|
207
|
+
// 4. Access statistics
|
|
208
|
+
console.log(comparator.stats);
|
|
209
|
+
// { insert: 3, pop: 2, decreasePriority: 0, increasePriority: 0, updatePriority: 0, total: 5 }
|
|
210
|
+
|
|
211
|
+
// 5. Compare with theoretical bounds
|
|
212
|
+
const n = 3;
|
|
213
|
+
const d = 4;
|
|
214
|
+
console.log('Theoretical insert (worst):', theoreticalInsertComparisons(n, d));
|
|
215
|
+
console.log('Theoretical pop (worst):', theoreticalPopComparisons(n, d));
|
|
216
|
+
|
|
217
|
+
// 6. Reset for next measurement
|
|
218
|
+
comparator.stats.reset();
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Theoretical Complexity
|
|
222
|
+
|
|
223
|
+
For a d-ary heap with n elements:
|
|
224
|
+
|
|
225
|
+
| Operation | Comparisons (worst case) |
|
|
226
|
+
|------------------|--------------------------------|
|
|
227
|
+
| insert | floor(log_d(n)) |
|
|
228
|
+
| pop | d × floor(log_d(n)) |
|
|
229
|
+
| increasePriority | floor(log_d(n)) (moveUp only) |
|
|
230
|
+
| decreasePriority | d × floor(log_d(n)) (moveDown only) |
|
|
231
|
+
| updatePriority | (d+1) × floor(log_d(n)) (both) |
|
|
232
|
+
|
|
233
|
+
### Cross-Language Consistency
|
|
234
|
+
|
|
235
|
+
Currently, instrumentation is implemented in TypeScript only. The table below shows the idiomatic zero-cost approach for each language, planned for v2.5.0:
|
|
236
|
+
|
|
237
|
+
| Language | Mechanism | Overhead When Disabled | Status |
|
|
238
|
+
|------------|----------------------------------|------------------------|--------|
|
|
239
|
+
| TypeScript | Optional hooks + instrumented comparator | Zero (JIT optimization) | ✅ Implemented |
|
|
240
|
+
| Go | Nil stats pointer | ~1 cycle (nil check) | Planned v2.5.0 |
|
|
241
|
+
| Rust | Generic over StatsCollector trait | Zero (monomorphization) | Planned v2.5.0 |
|
|
242
|
+
| C++ | Template policy class | Zero (inlining) | Planned v2.5.0 |
|
|
243
|
+
| Zig | Comptime bool parameter | Zero (branch elimination) | Planned v2.5.0 |
|
|
244
|
+
|
|
157
245
|
## Priority Update Semantics
|
|
158
246
|
|
|
159
|
-
|
|
247
|
+
This library uses **importance-based** semantics:
|
|
160
248
|
|
|
161
249
|
- **`increasePriority()`**: Make an item **more important** (moves toward heap root). Only moves up for O(log_d n) performance.
|
|
162
|
-
- **`decreasePriority()`**: Make an item **less important** (moves toward leaves).
|
|
250
|
+
- **`decreasePriority()`**: Make an item **less important** (moves toward leaves). Only moves down for O(d · log_d n) performance.
|
|
251
|
+
- **`updatePriority()`**: Update when direction is **unknown**. Checks both directions for O((d+1) · log_d n) performance.
|
|
252
|
+
|
|
253
|
+
**When to use each:**
|
|
254
|
+
- Use `increasePriority()` when you know the item became more important
|
|
255
|
+
- Use `decreasePriority()` when you know the item became less important
|
|
256
|
+
- Use `updatePriority()` when you don't know which direction the priority changed
|
|
163
257
|
|
|
164
258
|
**Heap Context:**
|
|
165
259
|
- **Min-heap**: Lower priority values = higher importance
|
package/dist/index.cjs
CHANGED
|
@@ -12,6 +12,10 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
12
12
|
comparator;
|
|
13
13
|
/** Key extractor for identity-based lookup */
|
|
14
14
|
keyExtractor;
|
|
15
|
+
/** Optional hook called before operations (for instrumentation) */
|
|
16
|
+
onBeforeOperation;
|
|
17
|
+
/** Optional hook called after operations (for instrumentation) */
|
|
18
|
+
onAfterOperation;
|
|
15
19
|
/**
|
|
16
20
|
* Create a new d-ary heap priority queue.
|
|
17
21
|
*
|
|
@@ -36,6 +40,8 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
36
40
|
this.depth = d;
|
|
37
41
|
this.comparator = options.comparator;
|
|
38
42
|
this.keyExtractor = options.keyExtractor;
|
|
43
|
+
this.onBeforeOperation = options.onBeforeOperation;
|
|
44
|
+
this.onAfterOperation = options.onAfterOperation;
|
|
39
45
|
const capacity = options.initialCapacity ?? 0;
|
|
40
46
|
this.container = capacity > 0 ? new Array(capacity) : [];
|
|
41
47
|
if (capacity > 0) this.container.length = 0;
|
|
@@ -163,14 +169,17 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
163
169
|
* to update existing items.
|
|
164
170
|
*/
|
|
165
171
|
insert(item) {
|
|
172
|
+
this.onBeforeOperation?.("insert");
|
|
166
173
|
const index = this.container.length;
|
|
167
174
|
this.container.push(item);
|
|
168
175
|
if (index === 0) {
|
|
169
176
|
this.positions.set(this.keyExtractor(item), 0);
|
|
177
|
+
this.onAfterOperation?.();
|
|
170
178
|
return;
|
|
171
179
|
}
|
|
172
180
|
this.positions.set(this.keyExtractor(item), index);
|
|
173
181
|
this.moveUp(index);
|
|
182
|
+
this.onAfterOperation?.();
|
|
174
183
|
}
|
|
175
184
|
/**
|
|
176
185
|
* Insert multiple items into the heap.
|
|
@@ -229,13 +238,16 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
229
238
|
* This method only moves items upward for performance.
|
|
230
239
|
*/
|
|
231
240
|
increasePriority(updatedItem) {
|
|
241
|
+
this.onBeforeOperation?.("increasePriority");
|
|
232
242
|
const key = this.keyExtractor(updatedItem);
|
|
233
243
|
const index = this.positions.get(key);
|
|
234
244
|
if (index === void 0) {
|
|
245
|
+
this.onAfterOperation?.();
|
|
235
246
|
throw new Error("Item not found in priority queue");
|
|
236
247
|
}
|
|
237
248
|
this.container[index] = updatedItem;
|
|
238
249
|
this.moveUp(index);
|
|
250
|
+
this.onAfterOperation?.();
|
|
239
251
|
}
|
|
240
252
|
/** Alias for increasePriority() - snake_case for cross-language consistency */
|
|
241
253
|
increase_priority(updatedItem) {
|
|
@@ -261,6 +273,28 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
261
273
|
increase_priority_by_index(index) {
|
|
262
274
|
this.increasePriorityByIndex(index);
|
|
263
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* Decrease the priority of the item at the given index.
|
|
278
|
+
* Time complexity: O(d · log_d n)
|
|
279
|
+
*
|
|
280
|
+
* @param index - Index of the item in the heap array
|
|
281
|
+
* @throws Error if index is out of bounds
|
|
282
|
+
*
|
|
283
|
+
* @remarks
|
|
284
|
+
* This is a lower-level method. Prefer decreasePriority() with the item itself.
|
|
285
|
+
* The item at the given index should already have its priority value updated
|
|
286
|
+
* in the container before calling this method.
|
|
287
|
+
*/
|
|
288
|
+
decreasePriorityByIndex(index) {
|
|
289
|
+
if (index < 0 || index >= this.container.length) {
|
|
290
|
+
throw new Error("Index out of bounds");
|
|
291
|
+
}
|
|
292
|
+
this.moveDown(index);
|
|
293
|
+
}
|
|
294
|
+
/** Alias for decreasePriorityByIndex() - snake_case for cross-language consistency */
|
|
295
|
+
decrease_priority_by_index(index) {
|
|
296
|
+
this.decreasePriorityByIndex(index);
|
|
297
|
+
}
|
|
264
298
|
/**
|
|
265
299
|
* Decrease the priority of an existing item (move toward leaves).
|
|
266
300
|
* Time complexity: O(d · log_d n)
|
|
@@ -271,22 +305,53 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
271
305
|
* @remarks
|
|
272
306
|
* For min-heap: increasing the priority value decreases importance.
|
|
273
307
|
* For max-heap: decreasing the priority value decreases importance.
|
|
274
|
-
* This method
|
|
308
|
+
* This method only moves items downward. Use updatePriority() if direction is unknown.
|
|
275
309
|
*/
|
|
276
310
|
decreasePriority(updatedItem) {
|
|
311
|
+
this.onBeforeOperation?.("decreasePriority");
|
|
277
312
|
const key = this.keyExtractor(updatedItem);
|
|
278
313
|
const index = this.positions.get(key);
|
|
279
314
|
if (index === void 0) {
|
|
315
|
+
this.onAfterOperation?.();
|
|
280
316
|
throw new Error("Item not found in priority queue");
|
|
281
317
|
}
|
|
282
318
|
this.container[index] = updatedItem;
|
|
283
|
-
this.moveUp(index);
|
|
284
319
|
this.moveDown(index);
|
|
320
|
+
this.onAfterOperation?.();
|
|
285
321
|
}
|
|
286
322
|
/** Alias for decreasePriority() - snake_case for cross-language consistency */
|
|
287
323
|
decrease_priority(updatedItem) {
|
|
288
324
|
this.decreasePriority(updatedItem);
|
|
289
325
|
}
|
|
326
|
+
/**
|
|
327
|
+
* Update the priority of an existing item when direction is unknown.
|
|
328
|
+
* Time complexity: O((d+1) · log_d n) - checks both directions
|
|
329
|
+
*
|
|
330
|
+
* @param updatedItem - Item with same identity but updated priority
|
|
331
|
+
* @throws Error if item not found
|
|
332
|
+
*
|
|
333
|
+
* @remarks
|
|
334
|
+
* Use this method when you don't know whether the priority increased or decreased.
|
|
335
|
+
* If you know the direction, prefer increasePriority() or decreasePriority() for
|
|
336
|
+
* better performance (log_d n vs (d+1) · log_d n comparisons).
|
|
337
|
+
*/
|
|
338
|
+
updatePriority(updatedItem) {
|
|
339
|
+
this.onBeforeOperation?.("updatePriority");
|
|
340
|
+
const key = this.keyExtractor(updatedItem);
|
|
341
|
+
const index = this.positions.get(key);
|
|
342
|
+
if (index === void 0) {
|
|
343
|
+
this.onAfterOperation?.();
|
|
344
|
+
throw new Error("Item not found in priority queue");
|
|
345
|
+
}
|
|
346
|
+
this.container[index] = updatedItem;
|
|
347
|
+
this.moveUp(index);
|
|
348
|
+
this.moveDown(index);
|
|
349
|
+
this.onAfterOperation?.();
|
|
350
|
+
}
|
|
351
|
+
/** Alias for updatePriority() - snake_case for cross-language consistency */
|
|
352
|
+
update_priority(updatedItem) {
|
|
353
|
+
this.updatePriority(updatedItem);
|
|
354
|
+
}
|
|
290
355
|
/**
|
|
291
356
|
* Remove and return the highest-priority item.
|
|
292
357
|
* Time complexity: O(d · log_d n)
|
|
@@ -294,9 +359,11 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
294
359
|
* @returns The removed item, or undefined if empty
|
|
295
360
|
*/
|
|
296
361
|
pop() {
|
|
362
|
+
this.onBeforeOperation?.("pop");
|
|
297
363
|
const container = this.container;
|
|
298
364
|
const n = container.length;
|
|
299
365
|
if (n === 0) {
|
|
366
|
+
this.onAfterOperation?.();
|
|
300
367
|
return void 0;
|
|
301
368
|
}
|
|
302
369
|
const keyExtractor = this.keyExtractor;
|
|
@@ -304,6 +371,7 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
304
371
|
this.positions.delete(keyExtractor(top));
|
|
305
372
|
if (n === 1) {
|
|
306
373
|
container.length = 0;
|
|
374
|
+
this.onAfterOperation?.();
|
|
307
375
|
return top;
|
|
308
376
|
}
|
|
309
377
|
const lastItem = container[n - 1];
|
|
@@ -311,6 +379,7 @@ var PriorityQueue = class _PriorityQueue {
|
|
|
311
379
|
this.positions.set(keyExtractor(lastItem), 0);
|
|
312
380
|
container.length = n - 1;
|
|
313
381
|
this.moveDown(0);
|
|
382
|
+
this.onAfterOperation?.();
|
|
314
383
|
return top;
|
|
315
384
|
}
|
|
316
385
|
/**
|
|
@@ -472,6 +541,73 @@ function chain(...comparators) {
|
|
|
472
541
|
return false;
|
|
473
542
|
};
|
|
474
543
|
}
|
|
544
|
+
|
|
545
|
+
// src/instrumentation.ts
|
|
546
|
+
function createComparisonStats() {
|
|
547
|
+
const stats = {
|
|
548
|
+
insert: 0,
|
|
549
|
+
pop: 0,
|
|
550
|
+
decreasePriority: 0,
|
|
551
|
+
increasePriority: 0,
|
|
552
|
+
updatePriority: 0,
|
|
553
|
+
get total() {
|
|
554
|
+
return this.insert + this.pop + this.decreasePriority + this.increasePriority + this.updatePriority;
|
|
555
|
+
},
|
|
556
|
+
reset() {
|
|
557
|
+
this.insert = 0;
|
|
558
|
+
this.pop = 0;
|
|
559
|
+
this.decreasePriority = 0;
|
|
560
|
+
this.increasePriority = 0;
|
|
561
|
+
this.updatePriority = 0;
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
return stats;
|
|
565
|
+
}
|
|
566
|
+
function instrumentComparator(comparator) {
|
|
567
|
+
const stats = createComparisonStats();
|
|
568
|
+
let currentOperation = null;
|
|
569
|
+
const instrumented = ((a, b) => {
|
|
570
|
+
if (currentOperation !== null) {
|
|
571
|
+
stats[currentOperation]++;
|
|
572
|
+
}
|
|
573
|
+
return comparator(a, b);
|
|
574
|
+
});
|
|
575
|
+
Object.defineProperty(instrumented, "stats", {
|
|
576
|
+
value: stats,
|
|
577
|
+
writable: false,
|
|
578
|
+
enumerable: true
|
|
579
|
+
});
|
|
580
|
+
instrumented.startOperation = (type) => {
|
|
581
|
+
currentOperation = type;
|
|
582
|
+
};
|
|
583
|
+
instrumented.endOperation = () => {
|
|
584
|
+
currentOperation = null;
|
|
585
|
+
};
|
|
586
|
+
return instrumented;
|
|
587
|
+
}
|
|
588
|
+
function theoreticalInsertComparisons(n, d) {
|
|
589
|
+
if (n <= 1) return 0;
|
|
590
|
+
return Math.floor(Math.log(n) / Math.log(d));
|
|
591
|
+
}
|
|
592
|
+
function theoreticalPopComparisons(n, d) {
|
|
593
|
+
if (n <= 1) return 0;
|
|
594
|
+
const height = Math.floor(Math.log(n) / Math.log(d));
|
|
595
|
+
return d * height;
|
|
596
|
+
}
|
|
597
|
+
function theoreticalIncreasePriorityComparisons(n, d) {
|
|
598
|
+
if (n <= 1) return 0;
|
|
599
|
+
return Math.floor(Math.log(n) / Math.log(d));
|
|
600
|
+
}
|
|
601
|
+
function theoreticalDecreasePriorityComparisons(n, d) {
|
|
602
|
+
if (n <= 1) return 0;
|
|
603
|
+
const height = Math.floor(Math.log(n) / Math.log(d));
|
|
604
|
+
return d * height;
|
|
605
|
+
}
|
|
606
|
+
function theoreticalUpdatePriorityComparisons(n, d) {
|
|
607
|
+
if (n <= 1) return 0;
|
|
608
|
+
const height = Math.floor(Math.log(n) / Math.log(d));
|
|
609
|
+
return (d + 1) * height;
|
|
610
|
+
}
|
|
475
611
|
/**
|
|
476
612
|
* d-ary Heap Priority Queue - TypeScript Implementation
|
|
477
613
|
*
|
|
@@ -480,10 +616,12 @@ function chain(...comparators) {
|
|
|
480
616
|
* - Min-heap or max-heap behavior via comparator functions
|
|
481
617
|
* - O(1) item lookup using Map for efficient priority updates
|
|
482
618
|
* - O(1) access to highest-priority item
|
|
483
|
-
* - O(log_d n) insert and
|
|
484
|
-
* - O(d · log_d n) pop and
|
|
619
|
+
* - O(log_d n) insert and increasePriority operations
|
|
620
|
+
* - O(d · log_d n) pop and decreasePriority operations
|
|
621
|
+
* - O((d+1) · log_d n) updatePriority (bidirectional)
|
|
622
|
+
* - Optional instrumentation hooks for performance analysis
|
|
485
623
|
*
|
|
486
|
-
* @version 2.
|
|
624
|
+
* @version 2.4.0
|
|
487
625
|
* @license Apache-2.0
|
|
488
626
|
* @copyright 2023-2025 Eric Jacopin
|
|
489
627
|
*/
|
|
@@ -491,7 +629,86 @@ function chain(...comparators) {
|
|
|
491
629
|
* Pre-built comparator factories for common use cases.
|
|
492
630
|
*
|
|
493
631
|
* @module comparators
|
|
494
|
-
* @version 2.
|
|
632
|
+
* @version 2.4.0
|
|
633
|
+
* @license Apache-2.0
|
|
634
|
+
*/
|
|
635
|
+
/**
|
|
636
|
+
* Instrumentation utilities for d-ary heap performance analysis.
|
|
637
|
+
*
|
|
638
|
+
* This module provides opt-in instrumentation to count comparisons performed
|
|
639
|
+
* during heap operations. It is designed for:
|
|
640
|
+
*
|
|
641
|
+
* - **Educational purposes**: Understanding the theoretical vs actual cost of heap operations
|
|
642
|
+
* - **Benchmarking**: Measuring real comparison counts across different arities
|
|
643
|
+
* - **Visualization**: Powering interactive demos that show heap behavior
|
|
644
|
+
*
|
|
645
|
+
* ## Design Philosophy: Zero-Cost When Disabled
|
|
646
|
+
*
|
|
647
|
+
* Instrumentation follows these principles:
|
|
648
|
+
*
|
|
649
|
+
* 1. **Opt-in only**: No overhead when not using instrumentation
|
|
650
|
+
* 2. **Non-breaking**: Existing code continues to work unchanged
|
|
651
|
+
* 3. **Per-operation tracking**: Distinguish insert/pop/decreasePriority comparisons
|
|
652
|
+
*
|
|
653
|
+
* ## Cross-Language Consistency
|
|
654
|
+
*
|
|
655
|
+
* Currently, instrumentation is implemented in TypeScript only. The table below
|
|
656
|
+
* shows the idiomatic zero-cost approach for each language, planned for v2.5.0:
|
|
657
|
+
*
|
|
658
|
+
* | Language | Mechanism | Overhead When Disabled | Status |
|
|
659
|
+
* |------------|----------------------------------|------------------------|--------|
|
|
660
|
+
* | TypeScript | Optional hooks + instrumented comparator | Zero (JIT optimization) | ✅ Implemented |
|
|
661
|
+
* | Go | Nil stats pointer | ~1 cycle (nil check) | Planned v2.5.0 |
|
|
662
|
+
* | Rust | Generic over StatsCollector trait | Zero (monomorphization) | Planned v2.5.0 |
|
|
663
|
+
* | C++ | Template policy class | Zero (inlining) | Planned v2.5.0 |
|
|
664
|
+
* | Zig | Comptime bool parameter | Zero (branch elimination) | Planned v2.5.0 |
|
|
665
|
+
*
|
|
666
|
+
* ## Usage Example
|
|
667
|
+
*
|
|
668
|
+
* ```typescript
|
|
669
|
+
* import { PriorityQueue, minBy, instrumentComparator } from 'd-ary-heap';
|
|
670
|
+
*
|
|
671
|
+
* // 1. Wrap your comparator with instrumentation
|
|
672
|
+
* const comparator = instrumentComparator(minBy((v: Vertex) => v.distance));
|
|
673
|
+
*
|
|
674
|
+
* // 2. Create priority queue with operation hooks
|
|
675
|
+
* const pq = new PriorityQueue({
|
|
676
|
+
* d: 4,
|
|
677
|
+
* comparator,
|
|
678
|
+
* keyExtractor: (v) => v.id,
|
|
679
|
+
* onBeforeOperation: (op) => comparator.startOperation(op),
|
|
680
|
+
* onAfterOperation: () => comparator.endOperation(),
|
|
681
|
+
* });
|
|
682
|
+
*
|
|
683
|
+
* // 3. Use normally - comparisons are tracked automatically
|
|
684
|
+
* pq.insert({ id: 'A', distance: 0 });
|
|
685
|
+
* pq.insert({ id: 'B', distance: 5 });
|
|
686
|
+
* pq.pop();
|
|
687
|
+
*
|
|
688
|
+
* // 4. Access statistics
|
|
689
|
+
* console.log(comparator.stats);
|
|
690
|
+
* // { insert: 1, pop: 2, decreasePriority: 0, increasePriority: 0, updatePriority: 0, total: 3 }
|
|
691
|
+
*
|
|
692
|
+
* // 5. Reset for next measurement
|
|
693
|
+
* comparator.stats.reset();
|
|
694
|
+
* ```
|
|
695
|
+
*
|
|
696
|
+
* ## Theoretical Complexity Reference
|
|
697
|
+
*
|
|
698
|
+
* For a d-ary heap with n elements:
|
|
699
|
+
*
|
|
700
|
+
* | Operation | Comparisons (worst case) |
|
|
701
|
+
* |------------------|-------------------------------|
|
|
702
|
+
* | insert | ⌊log_d(n)⌋ |
|
|
703
|
+
* | pop | d × ⌊log_d(n)⌋ |
|
|
704
|
+
* | increasePriority | ⌊log_d(n)⌋ (moveUp only) |
|
|
705
|
+
* | decreasePriority | d × ⌊log_d(n)⌋ (moveDown only)|
|
|
706
|
+
* | updatePriority | (d+1) × ⌊log_d(n)⌋ (both) |
|
|
707
|
+
*
|
|
708
|
+
* The demo visualization compares actual counts against these theoretical bounds.
|
|
709
|
+
*
|
|
710
|
+
* @module instrumentation
|
|
711
|
+
* @version 2.4.0
|
|
495
712
|
* @license Apache-2.0
|
|
496
713
|
*/
|
|
497
714
|
/**
|
|
@@ -501,13 +718,15 @@ function chain(...comparators) {
|
|
|
501
718
|
*
|
|
502
719
|
* @packageDocumentation
|
|
503
720
|
* @module d-ary-heap
|
|
504
|
-
* @version 2.
|
|
721
|
+
* @version 2.4.0
|
|
505
722
|
* @license Apache-2.0
|
|
506
723
|
* @copyright 2023-2025 Eric Jacopin
|
|
507
724
|
*/
|
|
508
725
|
|
|
509
726
|
exports.PriorityQueue = PriorityQueue;
|
|
510
727
|
exports.chain = chain;
|
|
728
|
+
exports.createComparisonStats = createComparisonStats;
|
|
729
|
+
exports.instrumentComparator = instrumentComparator;
|
|
511
730
|
exports.maxBy = maxBy;
|
|
512
731
|
exports.maxNumber = maxNumber;
|
|
513
732
|
exports.maxString = maxString;
|
|
@@ -515,5 +734,10 @@ exports.minBy = minBy;
|
|
|
515
734
|
exports.minNumber = minNumber;
|
|
516
735
|
exports.minString = minString;
|
|
517
736
|
exports.reverse = reverse;
|
|
737
|
+
exports.theoreticalDecreasePriorityComparisons = theoreticalDecreasePriorityComparisons;
|
|
738
|
+
exports.theoreticalIncreasePriorityComparisons = theoreticalIncreasePriorityComparisons;
|
|
739
|
+
exports.theoreticalInsertComparisons = theoreticalInsertComparisons;
|
|
740
|
+
exports.theoreticalPopComparisons = theoreticalPopComparisons;
|
|
741
|
+
exports.theoreticalUpdatePriorityComparisons = theoreticalUpdatePriorityComparisons;
|
|
518
742
|
//# sourceMappingURL=index.cjs.map
|
|
519
743
|
//# sourceMappingURL=index.cjs.map
|