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.
@@ -6,14 +6,18 @@
6
6
  * - Min-heap or max-heap behavior via comparator functions
7
7
  * - O(1) item lookup using Map for efficient priority updates
8
8
  * - O(1) access to highest-priority item
9
- * - O(log_d n) insert and priority increase operations
10
- * - O(d · log_d n) pop and priority decrease operations
9
+ * - O(log_d n) insert and increasePriority operations
10
+ * - O(d · log_d n) pop and decreasePriority operations
11
+ * - O((d+1) · log_d n) updatePriority (bidirectional)
12
+ * - Optional instrumentation hooks for performance analysis
11
13
  *
12
- * @version 2.2.0
14
+ * @version 2.4.0
13
15
  * @license Apache-2.0
14
16
  * @copyright 2023-2025 Eric Jacopin
15
17
  */
16
18
 
19
+ import type { OperationType } from './instrumentation';
20
+
17
21
  /** Type alias for position indices (cross-language consistency) */
18
22
  export type Position = number;
19
23
 
@@ -41,6 +45,37 @@ export interface PriorityQueueOptions<T, K> {
41
45
  keyExtractor: KeyExtractor<T, K>;
42
46
  /** Initial capacity hint for pre-allocation */
43
47
  initialCapacity?: number;
48
+
49
+ /**
50
+ * Optional hook called before each heap operation.
51
+ *
52
+ * This enables opt-in instrumentation for performance analysis without
53
+ * adding overhead when not used. Pair with an instrumented comparator
54
+ * to track comparison counts per operation type.
55
+ *
56
+ * @param type - The operation about to be performed
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const cmp = instrumentComparator(minBy((v) => v.priority));
61
+ * const pq = new PriorityQueue({
62
+ * comparator: cmp,
63
+ * keyExtractor: (v) => v.id,
64
+ * onBeforeOperation: (op) => cmp.startOperation(op),
65
+ * onAfterOperation: () => cmp.endOperation(),
66
+ * });
67
+ * ```
68
+ *
69
+ * @see instrumentComparator from './instrumentation'
70
+ */
71
+ onBeforeOperation?: (type: OperationType) => void;
72
+
73
+ /**
74
+ * Optional hook called after each heap operation completes.
75
+ *
76
+ * @see onBeforeOperation for usage example
77
+ */
78
+ onAfterOperation?: () => void;
44
79
  }
45
80
 
46
81
  /**
@@ -61,6 +96,7 @@ export interface PriorityQueueOptions<T, K> {
61
96
  * - pop(): O(d · log_d n)
62
97
  * - increasePriority(): O(log_d n)
63
98
  * - decreasePriority(): O(d · log_d n)
99
+ * - updatePriority(): O((d+1) · log_d n)
64
100
  * - contains(): O(1)
65
101
  * - len(), isEmpty(), d(): O(1)
66
102
  *
@@ -83,6 +119,12 @@ export class PriorityQueue<T, K = string | number> {
83
119
  /** Key extractor for identity-based lookup */
84
120
  private readonly keyExtractor: KeyExtractor<T, K>;
85
121
 
122
+ /** Optional hook called before operations (for instrumentation) */
123
+ private readonly onBeforeOperation: ((type: OperationType) => void) | undefined;
124
+
125
+ /** Optional hook called after operations (for instrumentation) */
126
+ private readonly onAfterOperation: (() => void) | undefined;
127
+
86
128
  /**
87
129
  * Create a new d-ary heap priority queue.
88
130
  *
@@ -108,6 +150,8 @@ export class PriorityQueue<T, K = string | number> {
108
150
  this.depth = d;
109
151
  this.comparator = options.comparator;
110
152
  this.keyExtractor = options.keyExtractor;
153
+ this.onBeforeOperation = options.onBeforeOperation;
154
+ this.onAfterOperation = options.onAfterOperation;
111
155
 
112
156
  // Pre-allocate if capacity hint provided
113
157
  const capacity = options.initialCapacity ?? 0;
@@ -255,17 +299,22 @@ export class PriorityQueue<T, K = string | number> {
255
299
  * to update existing items.
256
300
  */
257
301
  insert(item: T): void {
302
+ this.onBeforeOperation?.('insert');
303
+
258
304
  const index = this.container.length;
259
305
  this.container.push(item);
260
306
 
261
307
  // Fast path: first item doesn't need sift-up
262
308
  if (index === 0) {
263
309
  this.positions.set(this.keyExtractor(item), 0);
310
+ this.onAfterOperation?.();
264
311
  return;
265
312
  }
266
313
 
267
314
  this.positions.set(this.keyExtractor(item), index);
268
315
  this.moveUp(index);
316
+
317
+ this.onAfterOperation?.();
269
318
  }
270
319
 
271
320
  /**
@@ -337,15 +386,20 @@ export class PriorityQueue<T, K = string | number> {
337
386
  * This method only moves items upward for performance.
338
387
  */
339
388
  increasePriority(updatedItem: T): void {
389
+ this.onBeforeOperation?.('increasePriority');
390
+
340
391
  const key = this.keyExtractor(updatedItem);
341
392
  const index = this.positions.get(key);
342
393
 
343
394
  if (index === undefined) {
395
+ this.onAfterOperation?.();
344
396
  throw new Error('Item not found in priority queue');
345
397
  }
346
398
 
347
399
  this.container[index] = updatedItem;
348
400
  this.moveUp(index);
401
+
402
+ this.onAfterOperation?.();
349
403
  }
350
404
 
351
405
  /** Alias for increasePriority() - snake_case for cross-language consistency */
@@ -375,6 +429,30 @@ export class PriorityQueue<T, K = string | number> {
375
429
  this.increasePriorityByIndex(index);
376
430
  }
377
431
 
432
+ /**
433
+ * Decrease the priority of the item at the given index.
434
+ * Time complexity: O(d · log_d n)
435
+ *
436
+ * @param index - Index of the item in the heap array
437
+ * @throws Error if index is out of bounds
438
+ *
439
+ * @remarks
440
+ * This is a lower-level method. Prefer decreasePriority() with the item itself.
441
+ * The item at the given index should already have its priority value updated
442
+ * in the container before calling this method.
443
+ */
444
+ decreasePriorityByIndex(index: number): void {
445
+ if (index < 0 || index >= this.container.length) {
446
+ throw new Error('Index out of bounds');
447
+ }
448
+ this.moveDown(index);
449
+ }
450
+
451
+ /** Alias for decreasePriorityByIndex() - snake_case for cross-language consistency */
452
+ decrease_priority_by_index(index: number): void {
453
+ this.decreasePriorityByIndex(index);
454
+ }
455
+
378
456
  /**
379
457
  * Decrease the priority of an existing item (move toward leaves).
380
458
  * Time complexity: O(d · log_d n)
@@ -385,20 +463,23 @@ export class PriorityQueue<T, K = string | number> {
385
463
  * @remarks
386
464
  * For min-heap: increasing the priority value decreases importance.
387
465
  * For max-heap: decreasing the priority value decreases importance.
388
- * This method checks both directions for robustness.
466
+ * This method only moves items downward. Use updatePriority() if direction is unknown.
389
467
  */
390
468
  decreasePriority(updatedItem: T): void {
469
+ this.onBeforeOperation?.('decreasePriority');
470
+
391
471
  const key = this.keyExtractor(updatedItem);
392
472
  const index = this.positions.get(key);
393
473
 
394
474
  if (index === undefined) {
475
+ this.onAfterOperation?.();
395
476
  throw new Error('Item not found in priority queue');
396
477
  }
397
478
 
398
479
  this.container[index] = updatedItem;
399
- // Check both directions since we don't know if priority actually decreased
400
- this.moveUp(index);
401
480
  this.moveDown(index);
481
+
482
+ this.onAfterOperation?.();
402
483
  }
403
484
 
404
485
  /** Alias for decreasePriority() - snake_case for cross-language consistency */
@@ -406,6 +487,42 @@ export class PriorityQueue<T, K = string | number> {
406
487
  this.decreasePriority(updatedItem);
407
488
  }
408
489
 
490
+ /**
491
+ * Update the priority of an existing item when direction is unknown.
492
+ * Time complexity: O((d+1) · log_d n) - checks both directions
493
+ *
494
+ * @param updatedItem - Item with same identity but updated priority
495
+ * @throws Error if item not found
496
+ *
497
+ * @remarks
498
+ * Use this method when you don't know whether the priority increased or decreased.
499
+ * If you know the direction, prefer increasePriority() or decreasePriority() for
500
+ * better performance (log_d n vs (d+1) · log_d n comparisons).
501
+ */
502
+ updatePriority(updatedItem: T): void {
503
+ this.onBeforeOperation?.('updatePriority');
504
+
505
+ const key = this.keyExtractor(updatedItem);
506
+ const index = this.positions.get(key);
507
+
508
+ if (index === undefined) {
509
+ this.onAfterOperation?.();
510
+ throw new Error('Item not found in priority queue');
511
+ }
512
+
513
+ this.container[index] = updatedItem;
514
+ // Check both directions since we don't know which way priority changed
515
+ this.moveUp(index);
516
+ this.moveDown(index);
517
+
518
+ this.onAfterOperation?.();
519
+ }
520
+
521
+ /** Alias for updatePriority() - snake_case for cross-language consistency */
522
+ update_priority(updatedItem: T): void {
523
+ this.updatePriority(updatedItem);
524
+ }
525
+
409
526
  /**
410
527
  * Remove and return the highest-priority item.
411
528
  * Time complexity: O(d · log_d n)
@@ -413,10 +530,13 @@ export class PriorityQueue<T, K = string | number> {
413
530
  * @returns The removed item, or undefined if empty
414
531
  */
415
532
  pop(): T | undefined {
533
+ this.onBeforeOperation?.('pop');
534
+
416
535
  const container = this.container;
417
536
  const n = container.length;
418
537
 
419
538
  if (n === 0) {
539
+ this.onAfterOperation?.();
420
540
  return undefined;
421
541
  }
422
542
 
@@ -426,6 +546,7 @@ export class PriorityQueue<T, K = string | number> {
426
546
 
427
547
  if (n === 1) {
428
548
  container.length = 0;
549
+ this.onAfterOperation?.();
429
550
  return top;
430
551
  }
431
552
 
@@ -437,6 +558,7 @@ export class PriorityQueue<T, K = string | number> {
437
558
 
438
559
  this.moveDown(0);
439
560
 
561
+ this.onAfterOperation?.();
440
562
  return top;
441
563
  }
442
564
 
@@ -2,7 +2,7 @@
2
2
  * Pre-built comparator factories for common use cases.
3
3
  *
4
4
  * @module comparators
5
- * @version 2.2.0
5
+ * @version 2.4.0
6
6
  * @license Apache-2.0
7
7
  */
8
8
 
package/src/index.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @packageDocumentation
7
7
  * @module d-ary-heap
8
- * @version 2.2.0
8
+ * @version 2.4.0
9
9
  * @license Apache-2.0
10
10
  * @copyright 2023-2025 Eric Jacopin
11
11
  */
@@ -28,3 +28,20 @@ export {
28
28
  reverse,
29
29
  chain,
30
30
  } from './comparators';
31
+
32
+ // Instrumentation utilities for performance analysis (opt-in, zero-cost when disabled)
33
+ export {
34
+ createComparisonStats,
35
+ instrumentComparator,
36
+ theoreticalInsertComparisons,
37
+ theoreticalPopComparisons,
38
+ theoreticalIncreasePriorityComparisons,
39
+ theoreticalDecreasePriorityComparisons,
40
+ theoreticalUpdatePriorityComparisons,
41
+ } from './instrumentation';
42
+
43
+ export type {
44
+ OperationType,
45
+ ComparisonStats,
46
+ InstrumentedComparator,
47
+ } from './instrumentation';
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Instrumentation utilities for d-ary heap performance analysis.
3
+ *
4
+ * This module provides opt-in instrumentation to count comparisons performed
5
+ * during heap operations. It is designed for:
6
+ *
7
+ * - **Educational purposes**: Understanding the theoretical vs actual cost of heap operations
8
+ * - **Benchmarking**: Measuring real comparison counts across different arities
9
+ * - **Visualization**: Powering interactive demos that show heap behavior
10
+ *
11
+ * ## Design Philosophy: Zero-Cost When Disabled
12
+ *
13
+ * Instrumentation follows these principles:
14
+ *
15
+ * 1. **Opt-in only**: No overhead when not using instrumentation
16
+ * 2. **Non-breaking**: Existing code continues to work unchanged
17
+ * 3. **Per-operation tracking**: Distinguish insert/pop/decreasePriority comparisons
18
+ *
19
+ * ## Cross-Language Consistency
20
+ *
21
+ * Currently, instrumentation is implemented in TypeScript only. The table below
22
+ * shows the idiomatic zero-cost approach for each language, planned for v2.5.0:
23
+ *
24
+ * | Language | Mechanism | Overhead When Disabled | Status |
25
+ * |------------|----------------------------------|------------------------|--------|
26
+ * | TypeScript | Optional hooks + instrumented comparator | Zero (JIT optimization) | ✅ Implemented |
27
+ * | Go | Nil stats pointer | ~1 cycle (nil check) | Planned v2.5.0 |
28
+ * | Rust | Generic over StatsCollector trait | Zero (monomorphization) | Planned v2.5.0 |
29
+ * | C++ | Template policy class | Zero (inlining) | Planned v2.5.0 |
30
+ * | Zig | Comptime bool parameter | Zero (branch elimination) | Planned v2.5.0 |
31
+ *
32
+ * ## Usage Example
33
+ *
34
+ * ```typescript
35
+ * import { PriorityQueue, minBy, instrumentComparator } from 'd-ary-heap';
36
+ *
37
+ * // 1. Wrap your comparator with instrumentation
38
+ * const comparator = instrumentComparator(minBy((v: Vertex) => v.distance));
39
+ *
40
+ * // 2. Create priority queue with operation hooks
41
+ * const pq = new PriorityQueue({
42
+ * d: 4,
43
+ * comparator,
44
+ * keyExtractor: (v) => v.id,
45
+ * onBeforeOperation: (op) => comparator.startOperation(op),
46
+ * onAfterOperation: () => comparator.endOperation(),
47
+ * });
48
+ *
49
+ * // 3. Use normally - comparisons are tracked automatically
50
+ * pq.insert({ id: 'A', distance: 0 });
51
+ * pq.insert({ id: 'B', distance: 5 });
52
+ * pq.pop();
53
+ *
54
+ * // 4. Access statistics
55
+ * console.log(comparator.stats);
56
+ * // { insert: 1, pop: 2, decreasePriority: 0, increasePriority: 0, updatePriority: 0, total: 3 }
57
+ *
58
+ * // 5. Reset for next measurement
59
+ * comparator.stats.reset();
60
+ * ```
61
+ *
62
+ * ## Theoretical Complexity Reference
63
+ *
64
+ * For a d-ary heap with n elements:
65
+ *
66
+ * | Operation | Comparisons (worst case) |
67
+ * |------------------|-------------------------------|
68
+ * | insert | ⌊log_d(n)⌋ |
69
+ * | pop | d × ⌊log_d(n)⌋ |
70
+ * | increasePriority | ⌊log_d(n)⌋ (moveUp only) |
71
+ * | decreasePriority | d × ⌊log_d(n)⌋ (moveDown only)|
72
+ * | updatePriority | (d+1) × ⌊log_d(n)⌋ (both) |
73
+ *
74
+ * The demo visualization compares actual counts against these theoretical bounds.
75
+ *
76
+ * @module instrumentation
77
+ * @version 2.4.0
78
+ * @license Apache-2.0
79
+ */
80
+
81
+ import type { Comparator } from './PriorityQueue';
82
+
83
+ /**
84
+ * Operation types that can be tracked.
85
+ *
86
+ * Note: `updatePriority` is tracked separately for when the caller doesn't know
87
+ * whether priority increased or decreased (checks both directions).
88
+ */
89
+ export type OperationType = 'insert' | 'pop' | 'decreasePriority' | 'increasePriority' | 'updatePriority';
90
+
91
+ /**
92
+ * Statistics tracking comparison counts per operation type.
93
+ *
94
+ * All counts start at zero and accumulate until `reset()` is called.
95
+ */
96
+ export interface ComparisonStats {
97
+ /** Comparisons during insert operations (moveUp) */
98
+ insert: number;
99
+
100
+ /** Comparisons during pop operations (moveDown + bestChildPosition) */
101
+ pop: number;
102
+
103
+ /** Comparisons during decreasePriority operations (moveDown only) */
104
+ decreasePriority: number;
105
+
106
+ /** Comparisons during increasePriority operations (moveUp only) */
107
+ increasePriority: number;
108
+
109
+ /** Comparisons during updatePriority operations (moveUp + moveDown) */
110
+ updatePriority: number;
111
+
112
+ /** Total comparisons across all operation types */
113
+ readonly total: number;
114
+
115
+ /** Reset all counters to zero */
116
+ reset(): void;
117
+ }
118
+
119
+ /**
120
+ * An instrumented comparator that tracks comparison counts.
121
+ *
122
+ * This extends a regular comparator with:
123
+ * - `stats`: Current comparison counts
124
+ * - `startOperation(type)`: Begin tracking for an operation
125
+ * - `endOperation()`: Stop tracking current operation
126
+ *
127
+ * The comparator itself remains a valid `Comparator<T>` and can be used
128
+ * anywhere a regular comparator is expected.
129
+ */
130
+ export interface InstrumentedComparator<T> extends Comparator<T> {
131
+ /** Current comparison statistics */
132
+ readonly stats: ComparisonStats;
133
+
134
+ /**
135
+ * Signal the start of a heap operation.
136
+ * Comparisons will be attributed to this operation type until `endOperation()`.
137
+ *
138
+ * @param type - The operation type being started
139
+ */
140
+ startOperation(type: OperationType): void;
141
+
142
+ /**
143
+ * Signal the end of the current heap operation.
144
+ * Subsequent comparisons will not be counted until the next `startOperation()`.
145
+ */
146
+ endOperation(): void;
147
+ }
148
+
149
+ /**
150
+ * Create comparison statistics tracker.
151
+ *
152
+ * @returns Fresh stats object with all counts at zero
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * const stats = createComparisonStats();
157
+ * stats.insert = 5;
158
+ * stats.pop = 10;
159
+ * console.log(stats.total); // 15
160
+ * stats.reset();
161
+ * console.log(stats.total); // 0
162
+ * ```
163
+ */
164
+ export function createComparisonStats(): ComparisonStats {
165
+ const stats: ComparisonStats = {
166
+ insert: 0,
167
+ pop: 0,
168
+ decreasePriority: 0,
169
+ increasePriority: 0,
170
+ updatePriority: 0,
171
+
172
+ get total(): number {
173
+ return this.insert + this.pop + this.decreasePriority + this.increasePriority + this.updatePriority;
174
+ },
175
+
176
+ reset(): void {
177
+ this.insert = 0;
178
+ this.pop = 0;
179
+ this.decreasePriority = 0;
180
+ this.increasePriority = 0;
181
+ this.updatePriority = 0;
182
+ },
183
+ };
184
+
185
+ return stats;
186
+ }
187
+
188
+ /**
189
+ * Wrap a comparator with instrumentation to track comparison counts.
190
+ *
191
+ * The returned comparator:
192
+ * - Behaves identically to the original for comparison purposes
193
+ * - Tracks how many times it's called, attributed to operation types
194
+ * - Has zero overhead when `startOperation()` hasn't been called
195
+ *
196
+ * ## How It Works
197
+ *
198
+ * 1. Call `startOperation('insert')` before `pq.insert()`
199
+ * 2. The comparator increments `stats.insert` for each comparison
200
+ * 3. Call `endOperation()` after the operation completes
201
+ * 4. Repeat for other operations
202
+ *
203
+ * The `PriorityQueue` class supports `onBeforeOperation` and `onAfterOperation`
204
+ * hooks to automate this.
205
+ *
206
+ * ## Performance Note
207
+ *
208
+ * When `currentOperation` is null (between operations), the instrumented
209
+ * comparator performs only a single null check before calling the original.
210
+ * Modern JavaScript engines optimize this extremely well.
211
+ *
212
+ * @param comparator - The original comparator to instrument
213
+ * @returns An instrumented comparator with stats tracking
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * import { minBy, instrumentComparator } from 'd-ary-heap';
218
+ *
219
+ * const cmp = instrumentComparator(minBy<number, number>(x => x));
220
+ *
221
+ * // Manual usage (without hooks)
222
+ * cmp.startOperation('insert');
223
+ * console.log(cmp(5, 3)); // false, and stats.insert++
224
+ * console.log(cmp(3, 5)); // true, and stats.insert++
225
+ * cmp.endOperation();
226
+ *
227
+ * console.log(cmp.stats.insert); // 2
228
+ * ```
229
+ */
230
+ export function instrumentComparator<T>(comparator: Comparator<T>): InstrumentedComparator<T> {
231
+ const stats = createComparisonStats();
232
+ let currentOperation: OperationType | null = null;
233
+
234
+ // Create the instrumented function
235
+ const instrumented = ((a: T, b: T): boolean => {
236
+ // Only count when actively tracking an operation
237
+ if (currentOperation !== null) {
238
+ stats[currentOperation]++;
239
+ }
240
+ return comparator(a, b);
241
+ }) as InstrumentedComparator<T>;
242
+
243
+ // Attach stats (read-only from outside)
244
+ Object.defineProperty(instrumented, 'stats', {
245
+ value: stats,
246
+ writable: false,
247
+ enumerable: true,
248
+ });
249
+
250
+ // Attach operation control methods
251
+ instrumented.startOperation = (type: OperationType): void => {
252
+ currentOperation = type;
253
+ };
254
+
255
+ instrumented.endOperation = (): void => {
256
+ currentOperation = null;
257
+ };
258
+
259
+ return instrumented;
260
+ }
261
+
262
+ /**
263
+ * Calculate theoretical comparison count for an insert operation.
264
+ *
265
+ * Insert performs at most ⌊log_d(n)⌋ comparisons (one per level during moveUp).
266
+ *
267
+ * @param n - Number of elements in heap AFTER insert
268
+ * @param d - Heap arity
269
+ * @returns Theoretical worst-case comparison count
270
+ */
271
+ export function theoreticalInsertComparisons(n: number, d: number): number {
272
+ if (n <= 1) return 0;
273
+ return Math.floor(Math.log(n) / Math.log(d));
274
+ }
275
+
276
+ /**
277
+ * Calculate theoretical comparison count for a pop operation.
278
+ *
279
+ * Pop performs at most d × ⌊log_d(n)⌋ comparisons:
280
+ * - At each level, find best among d children (d-1 comparisons)
281
+ * - Compare best child with current (1 comparison)
282
+ * - Total: d comparisons per level × ⌊log_d(n)⌋ levels
283
+ *
284
+ * @param n - Number of elements in heap BEFORE pop
285
+ * @param d - Heap arity
286
+ * @returns Theoretical worst-case comparison count
287
+ */
288
+ export function theoreticalPopComparisons(n: number, d: number): number {
289
+ if (n <= 1) return 0;
290
+ const height = Math.floor(Math.log(n) / Math.log(d));
291
+ return d * height;
292
+ }
293
+
294
+ /**
295
+ * Calculate theoretical comparison count for an increasePriority operation.
296
+ *
297
+ * IncreasePriority performs only moveUp (item became more important).
298
+ * Worst case: ⌊log_d(n)⌋ comparisons (one per level).
299
+ *
300
+ * @param n - Number of elements in heap
301
+ * @param d - Heap arity
302
+ * @returns Theoretical worst-case comparison count
303
+ */
304
+ export function theoreticalIncreasePriorityComparisons(n: number, d: number): number {
305
+ if (n <= 1) return 0;
306
+ return Math.floor(Math.log(n) / Math.log(d));
307
+ }
308
+
309
+ /**
310
+ * Calculate theoretical comparison count for a decreasePriority operation.
311
+ *
312
+ * DecreasePriority performs only moveDown (item became less important).
313
+ * Worst case: d × ⌊log_d(n)⌋ comparisons.
314
+ *
315
+ * @param n - Number of elements in heap
316
+ * @param d - Heap arity
317
+ * @returns Theoretical worst-case comparison count
318
+ */
319
+ export function theoreticalDecreasePriorityComparisons(n: number, d: number): number {
320
+ if (n <= 1) return 0;
321
+ const height = Math.floor(Math.log(n) / Math.log(d));
322
+ return d * height;
323
+ }
324
+
325
+ /**
326
+ * Calculate theoretical comparison count for an updatePriority operation.
327
+ *
328
+ * UpdatePriority performs both moveUp and moveDown (direction unknown).
329
+ * Worst case: (d + 1) × ⌊log_d(n)⌋ comparisons.
330
+ *
331
+ * @param n - Number of elements in heap
332
+ * @param d - Heap arity
333
+ * @returns Theoretical worst-case comparison count
334
+ */
335
+ export function theoreticalUpdatePriorityComparisons(n: number, d: number): number {
336
+ if (n <= 1) return 0;
337
+ const height = Math.floor(Math.log(n) / Math.log(d));
338
+ return (d + 1) * height;
339
+ }