@mark1russell7/highways 0.1.2 → 0.1.4

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/src/visibility.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { dist, inflate, segmentHitsRect, type Point } from "./geom.ts";
1
+ import { dist, inflate, segmentHitsRect, type Box, type Point } from "./geom.ts";
2
2
  import { Grid } from "./grid.ts";
3
3
  import type { Node } from "./model.ts";
4
4
 
@@ -14,14 +14,21 @@ export class Obstacles {
14
14
 
15
15
  /** This method tells if the segment `ab` stays out of each box, made larger by `pad`. The nodes in `skip` do not count. */
16
16
  clear(a: Point, b: Point, pad: number, skip?: ReadonlySet<Node>): boolean {
17
- let ok = true;
18
- this.#grid.querySegment(a, b, pad, (n) => {
19
- if (ok && !skip?.has(n)) {
20
- const x = n.box;
21
- if (segmentHitsRect(a, b, x.x - pad, x.y - pad, x.x + x.w + pad, x.y + x.h + pad)) ok = false;
22
- }
17
+ return this.hit(a, b, pad, skip) === undefined;
18
+ }
19
+
20
+ /**
21
+ * This method gives a node whose box, made larger by `pad`, the segment `ab` goes into, or `undefined` when the segment is clear.
22
+ * The nodes in `skip` do not count. The test stops at the first such node.
23
+ */
24
+ hit(a: Point, b: Point, pad: number, skip?: ReadonlySet<Node>): Node | undefined {
25
+ let found: Node | undefined;
26
+ this.#grid.someSegment(a, b, pad, (n) => {
27
+ if (skip?.has(n) || !hits(a, b, n, pad)) return false;
28
+ found = n;
29
+ return true;
23
30
  });
24
- return ok;
31
+ return found;
25
32
  }
26
33
 
27
34
  /** This method tells if the polyline stays out of each box, made larger by `pad`. The nodes in `skip` do not count. */
@@ -30,6 +37,11 @@ export class Obstacles {
30
37
  return true;
31
38
  }
32
39
 
40
+ /** This method gives each node whose bucket the box touches to `visit`. A node can be outside the box. */
41
+ query(box: Box, visit: (n: Node) => void): void {
42
+ this.#grid.query(box, visit);
43
+ }
44
+
33
45
  /** This method tells if the point is inside a box, made larger by `pad`. The node `skip` does not count. */
34
46
  inside(p: Point, pad: number, skip?: Node): boolean {
35
47
  let hit = false;
@@ -43,6 +55,26 @@ export class Obstacles {
43
55
  }
44
56
  }
45
57
 
58
+ /** This function tells if the segment `ab` goes into the box of the node, made larger by `pad`. */
59
+ function hits(a: Point, b: Point, n: Node, pad: number): boolean {
60
+ const x = n.box;
61
+ return segmentHitsRect(a, b, x.x - pad, x.y - pad, x.x + x.w + pad, x.y + x.h + pad);
62
+ }
63
+
64
+ /** A free point at an end of a search of `CornerGraph.pathAmong`, and the cost to reach it. */
65
+ export interface Terminal {
66
+ readonly point: Point;
67
+ readonly cost: number;
68
+ }
69
+
70
+ /** A path of `CornerGraph.pathAmong`: its points, the index of its first point in `from`, the index of its last point in `to`, and its cost. */
71
+ export interface Among {
72
+ readonly points: Point[];
73
+ readonly from: number;
74
+ readonly to: number;
75
+ readonly cost: number;
76
+ }
77
+
46
78
  /** The options of the corner graph. */
47
79
  export interface CornerGraphOptions {
48
80
  /** The distance from a box to its corners. */
@@ -54,6 +86,11 @@ export interface CornerGraphOptions {
54
86
  * The lattice links groups of nodes across empty space. Without it, the nearest corners of a group are all in that group.
55
87
  */
56
88
  readonly lattice?: number;
89
+ /**
90
+ * With `cones`, each corner also links to the nearest corner that it can see in each of four cones around the axes.
91
+ * Then a path can go along a long corridor between rows or columns of boxes. The default is false.
92
+ */
93
+ readonly cones?: boolean;
57
94
  }
58
95
 
59
96
  /**
@@ -78,11 +115,18 @@ export class CornerGraph {
78
115
  #open = new Int32Array(0);
79
116
  #closed = new Int32Array(0);
80
117
  #stamp = 0;
81
- // The stamp of the last call of nearestVisible that tested each corner.
82
- #tested = new Int32Array(0);
83
- #testStamp = 0;
118
+ // The corners in each bucket of the point grid, by the key of the bucket. A corner is in one bucket: the bucket of its point.
119
+ readonly #buckets = new Map<number, number[]>();
120
+ // The distance from the point of the last call of nearestVisible to each corner that the call found.
121
+ #dist = new Float64Array(0);
122
+ // The corners of one ring of nearestVisible, nearest first.
123
+ readonly #near = new MinHeap();
84
124
  // The corners that each end of a path sees, by the coordinates of the end.
85
125
  readonly #endCache = new Map<string, number[]>();
126
+ // The corners that each end of a path of `pathAmong` sees. Such a path has several ends, so each end takes fewer corners.
127
+ readonly #fewCache = new Map<string, number[]>();
128
+ // The connected part of the graph of each corner. The first call of `path` makes it.
129
+ #part: Int32Array | undefined;
86
130
 
87
131
  /** This constructor makes the graph of the nodes. */
88
132
  constructor(nodes: readonly Node[], obstacles: Obstacles, options: CornerGraphOptions) {
@@ -125,7 +169,8 @@ export class CornerGraph {
125
169
  }
126
170
  const links: Set<number>[] = this.points.map(() => new Set<number>());
127
171
  for (let i = 0; i < this.points.length; i++) {
128
- for (const j of this.nearestVisible(this.points[i]!, this.#k, i)) {
172
+ // The k nearest corners of a corner in a long corridor are often all on one side. The cones add a link along the corridor.
173
+ for (const j of this.#nearestVisible(this.points[i]!, this.#k, i, this.#pad, options.cones ?? false)) {
129
174
  links[i]!.add(j);
130
175
  links[j]!.add(i);
131
176
  }
@@ -138,7 +183,12 @@ export class CornerGraph {
138
183
  }
139
184
 
140
185
  #addPoint(c: Point): void {
141
- this.#pointGrid.add(this.points.length, { x: c.x, y: c.y, w: 0, h: 0 });
186
+ const j = this.points.length;
187
+ this.#pointGrid.add(j, { x: c.x, y: c.y, w: 0, h: 0 });
188
+ const key = bucketKey(Math.floor(c.x / this.#cell), Math.floor(c.y / this.#cell));
189
+ const list = this.#buckets.get(key);
190
+ if (list === undefined) this.#buckets.set(key, [j]);
191
+ else list.push(j);
142
192
  this.points.push(c);
143
193
  }
144
194
 
@@ -172,24 +222,91 @@ export class CornerGraph {
172
222
  return this.#nearestVisible(p, k, self, this.#pad);
173
223
  }
174
224
 
175
- #nearestVisible(p: Point, k: number, self: number, pad: number): number[] {
225
+ /**
226
+ * This method tests the corners in rings of radius `r` = 1, 2, 4 and 8 cells. In each ring, it tests the corners
227
+ * within `r` that an earlier ring did not test, nearest first, and equal distances by index.
228
+ * The ring of `r` takes the corners of the buckets that the square of side `2r` around `p` touches.
229
+ * A corner of these buckets beyond `r` waits for the next ring. So the method visits each bucket one time.
230
+ * With `cones`, the method also gives the nearest visible corner of each of four cones around the axes that the `k` nearest corners leave empty.
231
+ */
232
+ #nearestVisible(p: Point, k: number, self: number, pad: number, cones = false): number[] {
176
233
  const out: number[] = [];
177
- if (this.#tested.length < this.points.length) this.#tested = new Int32Array(this.points.length);
178
- const tested = this.#tested;
179
- const stamp = ++this.#testStamp;
180
- for (let r = this.#cell; r <= this.#cell * 8; r *= 2) {
181
- const cands: [number, number][] = [];
182
- this.#pointGrid.query({ x: p.x - r, y: p.y - r, w: 2 * r, h: 2 * r }, (j) => {
183
- if (j !== self && tested[j] !== stamp) cands.push([dist(p, this.points[j]!), j]);
184
- });
185
- cands.sort((a, b) => a[0] - b[0] || a[1] - b[1]);
186
- for (const [d, j] of cands) {
187
- // A corner beyond r can be nearer than a corner of the next ring, so it waits for that ring.
188
- if (d > r) break;
189
- tested[j] = stamp;
190
- if (this.#obstacles.clear(p, this.points[j]!, pad)) out.push(j);
191
- if (out.length >= k) return out;
234
+ // The bits of the full cones: 1 is +x, 2 is +y, 4 is -x and 8 is -y.
235
+ let full = 0;
236
+ if (this.#dist.length < this.points.length) this.#dist = new Float64Array(this.points.length);
237
+ const dd = this.#dist;
238
+ const points = this.points;
239
+ const cell = this.#cell;
240
+ const reach = CONE_CELLS * cell;
241
+ const blockers: Node[] = [];
242
+ const now = this.#near;
243
+ // The corners that the rings found beyond their radius. The next ring takes them.
244
+ let wait: number[] = [];
245
+ // The bucket range of the ring before. The first ring has none.
246
+ let pi0 = 1;
247
+ let pi1 = 0;
248
+ let pj0 = 1;
249
+ let pj1 = 0;
250
+ for (let r = cell; r <= cell * 8; r *= 2) {
251
+ // The ring of `r` holds the corners beyond `r / 2`. With the k nearest corners, the cones take no corner beyond `reach`.
252
+ if (out.length >= k && r / 2 >= reach) return out;
253
+ // These bounds are the bounds of a query of the box at (p.x - r, p.y - r) with the side 2r.
254
+ const x0 = p.x - r;
255
+ const y0 = p.y - r;
256
+ const i0 = Math.floor(x0 / cell);
257
+ const j0 = Math.floor(y0 / cell);
258
+ const i1 = Math.floor((x0 + 2 * r) / cell);
259
+ const j1 = Math.floor((y0 + 2 * r) / cell);
260
+ const later: number[] = [];
261
+ now.clear();
262
+ for (const j of wait) {
263
+ if (dd[j]! > r) later.push(j);
264
+ else now.add(dd[j]!, j);
265
+ }
266
+ for (let bi = i0; bi <= i1; bi++) {
267
+ const before = bi >= pi0 && bi <= pi1;
268
+ for (let bj = j0; bj <= j1; bj++) {
269
+ if (before && bj >= pj0 && bj <= pj1) continue;
270
+ const list = this.#buckets.get(bucketKey(bi, bj));
271
+ if (list === undefined) continue;
272
+ for (const j of list) {
273
+ if (j === self) continue;
274
+ const d = dist(p, points[j]!);
275
+ dd[j] = d;
276
+ if (d > r) later.push(j);
277
+ else now.add(d, j);
278
+ }
279
+ }
280
+ }
281
+ // The heap gives the corners nearest first, and equal distances by index. A call often stops after a few corners.
282
+ now.build();
283
+ while (now.size > 0) {
284
+ const j = now.pop();
285
+ // A node that stopped an earlier segment often stops this segment too, so the test takes these nodes first.
286
+ // The order of the nodes does not change the result.
287
+ const q = points[j]!;
288
+ const cone = cones ? coneOf(q.x - p.x, q.y - p.y) : 0;
289
+ if (out.length >= k) {
290
+ // After the k nearest corners, only a corner of an empty cone counts, up to `CONE_CELLS` cells from `p`.
291
+ if (dd[j]! > reach) return out;
292
+ if ((full & cone) !== 0) continue;
293
+ }
294
+ let blocked = false;
295
+ for (let b = 0; b < blockers.length && !blocked; b++) blocked = hits(p, q, blockers[b]!, pad);
296
+ if (blocked) continue;
297
+ const hit = this.#obstacles.hit(p, q, pad);
298
+ if (hit !== undefined) blockers.push(hit);
299
+ else {
300
+ out.push(j);
301
+ full |= cone;
302
+ if (out.length >= k && (!cones || full === 15)) return out;
303
+ }
192
304
  }
305
+ wait = later;
306
+ pi0 = i0;
307
+ pi1 = i1;
308
+ pj0 = j0;
309
+ pj1 = j1;
193
310
  }
194
311
  return out;
195
312
  }
@@ -202,6 +319,35 @@ export class CornerGraph {
202
319
  */
203
320
  path(from: Point, to: Point, cost?: (i: number, j: number, d: number) => number, floor: number = 1): Point[] | undefined {
204
321
  if (this.#obstacles.clear(from, to, this.#pad)) return [from, to];
322
+ return this.#search([{ point: from, cost: 0 }], [{ point: to, cost: 0 }], cost, floor, this.#k * 2)?.points;
323
+ }
324
+
325
+ /**
326
+ * This method gives the shortest path from a point of `from` to a point of `to`, or `undefined` when the graph has none.
327
+ * The cost of a path includes the costs of its two points. One search finds the path, so this method is faster than a call of `path` for each pair.
328
+ */
329
+ pathAmong(from: readonly Terminal[], to: readonly Terminal[]): Among | undefined {
330
+ const found = this.#search(from, to, undefined, 1, this.#k);
331
+ // A straight line between two points that costs less than the found path is better. The test takes the lines in order of cost.
332
+ const lines: Among[] = [];
333
+ from.forEach((a, i) =>
334
+ to.forEach((b, k) => {
335
+ const c = a.cost + dist(a.point, b.point) + b.cost;
336
+ if (!found || c < found.cost) lines.push({ points: [a.point, b.point], from: i, to: k, cost: c });
337
+ }),
338
+ );
339
+ lines.sort((u, v) => u.cost - v.cost);
340
+ return lines.find((l) => this.#obstacles.clear(l.points[0]!, l.points[1]!, this.#pad)) ?? found;
341
+ }
342
+
343
+ /** This method is the search (A*) of `path` and `pathAmong`. */
344
+ #search(
345
+ from: readonly Terminal[],
346
+ to: readonly Terminal[],
347
+ cost: ((i: number, j: number, d: number) => number) | undefined,
348
+ floor: number,
349
+ count: number,
350
+ ): Among | undefined {
205
351
  const n = this.points.length;
206
352
  if (this.#g.length < n) {
207
353
  this.#g = new Float64Array(n);
@@ -210,28 +356,54 @@ export class CornerGraph {
210
356
  this.#closed = new Int32Array(n);
211
357
  }
212
358
  const stamp = ++this.#stamp;
359
+ const points = this.points;
360
+ // The cost from each corner near a point of `to` to that point, and the index of that point.
213
361
  const goal = new Map<number, number>();
214
- for (const j of this.#ends(to)) goal.set(j, dist(this.points[j]!, to));
362
+ const goalOf = new Map<number, number>();
363
+ to.forEach((b, k) => {
364
+ for (const j of this.#ends(b.point, count)) {
365
+ const tail = dist(points[j]!, b.point) + b.cost;
366
+ const now = goal.get(j);
367
+ if (now === undefined || tail < now) {
368
+ goal.set(j, tail);
369
+ goalOf.set(j, k);
370
+ }
371
+ }
372
+ });
215
373
  if (goal.size === 0) return undefined;
374
+ const starts = from.map((a) => this.#ends(a.point, count));
375
+ // Without a start and a goal in one connected part, A* finds no path. This test stops it before it visits the whole part.
376
+ if (!starts.some((list) => this.#joined(list, goal))) return undefined;
216
377
  const heap = new MinHeap();
217
- const h = (i: number): number => floor * dist(this.points[i]!, to);
378
+ const G = this.#g;
379
+ const parentOf = this.#parent;
380
+ const open = this.#open;
381
+ const closed = this.#closed;
382
+ const estimate = (p: Point): number => {
383
+ let h = Infinity;
384
+ for (const b of to) h = Math.min(h, dist(p, b.point));
385
+ return floor * h;
386
+ };
218
387
  const relax = (j: number, g: number, parent: number): void => {
219
- if (this.#closed[j] === stamp) return;
220
- if (this.#open[j] === stamp && this.#g[j]! <= g) return;
221
- this.#open[j] = stamp;
222
- this.#g[j] = g;
223
- this.#parent[j] = parent;
224
- heap.push(g + h(j), j);
388
+ if (closed[j] === stamp) return;
389
+ if (open[j] === stamp && G[j]! <= g) return;
390
+ open[j] = stamp;
391
+ G[j] = g;
392
+ parentOf[j] = parent;
393
+ heap.push(g + estimate(points[j]!), j);
225
394
  };
226
- for (const j of this.#ends(from)) relax(j, dist(from, this.points[j]!), -1);
395
+ // The parent of a first corner is -1 - i, where i is the index of its point in `from`.
396
+ from.forEach((a, i) => {
397
+ for (const j of starts[i]!) relax(j, a.cost + dist(a.point, points[j]!), -1 - i);
398
+ });
227
399
  let best = Infinity;
228
400
  let bestEnd = -1;
229
401
  while (heap.size > 0) {
230
402
  if (heap.topKey >= best) break;
231
403
  const i = heap.pop();
232
- if (this.#closed[i] === stamp) continue;
233
- this.#closed[i] = stamp;
234
- const gi = this.#g[i]!;
404
+ if (closed[i] === stamp) continue;
405
+ closed[i] = stamp;
406
+ const gi = G[i]!;
235
407
  const tail = goal.get(i);
236
408
  if (tail !== undefined && gi + tail < best) {
237
409
  best = gi + tail;
@@ -241,29 +413,67 @@ export class CornerGraph {
241
413
  const len = this.#len[i]!;
242
414
  for (let m = 0; m < adj.length; m++) {
243
415
  const j = adj[m]!;
416
+ if (closed[j] === stamp) continue;
244
417
  const d = len[m]!;
245
418
  relax(j, gi + (cost ? cost(i, j, d) : d), i);
246
419
  }
247
420
  }
248
421
  if (bestEnd < 0) return undefined;
249
- const out: Point[] = [to];
250
- for (let i = bestEnd; i >= 0; i = this.#parent[i]!) out.push(this.points[i]!);
251
- out.push(from);
252
- return out.reverse();
422
+ const k = goalOf.get(bestEnd)!;
423
+ const out: Point[] = [to[k]!.point];
424
+ for (let i = bestEnd; ; i = parentOf[i]!) {
425
+ out.push(points[i]!);
426
+ if (parentOf[i]! < 0) {
427
+ const f = -1 - parentOf[i]!;
428
+ out.push(from[f]!.point);
429
+ return { points: out.reverse(), from: f, to: k, cost: best };
430
+ }
431
+ }
432
+ }
433
+
434
+ /** This method tells if a corner of `starts` and a corner of `goal` are in one connected part of the graph. */
435
+ #joined(starts: readonly number[], goal: ReadonlyMap<number, number>): boolean {
436
+ const part = (this.#part ??= this.#parts());
437
+ const goals = new Set<number>();
438
+ for (const j of goal.keys()) goals.add(part[j]!);
439
+ for (const j of starts) if (goals.has(part[j]!)) return true;
440
+ return false;
441
+ }
442
+
443
+ /** This method gives the number of the connected part of each corner. */
444
+ #parts(): Int32Array {
445
+ const part = new Int32Array(this.points.length).fill(-1);
446
+ const stack: number[] = [];
447
+ let count = 0;
448
+ for (let i = 0; i < part.length; i++) {
449
+ if (part[i] !== -1) continue;
450
+ part[i] = count;
451
+ stack.push(i);
452
+ while (stack.length > 0) {
453
+ for (const j of this.#adj[stack.pop()!]!) {
454
+ if (part[j] !== -1) continue;
455
+ part[j] = count;
456
+ stack.push(j);
457
+ }
458
+ }
459
+ count++;
460
+ }
461
+ return part;
253
462
  }
254
463
 
255
464
  /**
256
- * This method gives the corners that an end of a path can see. An end near a box sees no corner with the clearance.
465
+ * This method gives at most `count` corners that an end of a path can see. An end near a box sees no corner with the clearance.
257
466
  * Then the end takes the corners that it sees without the clearance, so the path does not cut through the boxes.
258
467
  * The graph does not change, so the method keeps the corners of each end.
259
468
  */
260
- #ends(p: Point): number[] {
469
+ #ends(p: Point, count: number): number[] {
470
+ const cache = count === this.#k * 2 ? this.#endCache : this.#fewCache;
261
471
  const key = `${p.x},${p.y}`;
262
- let out = this.#endCache.get(key);
472
+ let out = cache.get(key);
263
473
  if (!out) {
264
- out = this.nearestVisible(p, this.#k * 2);
265
- if (out.length === 0) out = this.#nearestVisible(p, this.#k * 2, -1, 0);
266
- this.#endCache.set(key, out);
474
+ out = this.nearestVisible(p, count);
475
+ if (out.length === 0) out = this.#nearestVisible(p, count, -1, 0);
476
+ cache.set(key, out);
267
477
  }
268
478
  return out;
269
479
  }
@@ -283,65 +493,97 @@ export class CornerGraph {
283
493
  }
284
494
  }
285
495
 
286
- /** A binary min-heap of (key, value) pairs. Equal keys come out by the smaller value, so the order is deterministic. */
287
- class MinHeap {
288
- readonly #k: number[] = [];
289
- readonly #v: number[] = [];
496
+ /** A link of a cone is at most this number of cells long. A longer search costs time on a dense level and finds few new links. */
497
+ const CONE_CELLS = 3;
290
498
 
291
- get size(): number {
292
- return this.#k.length;
293
- }
499
+ /** This function gives the bit of the cone of the offset (`dx`, `dy`): 1 is +x, 2 is +y, 4 is -x and 8 is -y. A diagonal offset is in a cone of the x axis. */
500
+ const coneOf = (dx: number, dy: number): number => (Math.abs(dx) >= Math.abs(dy) ? (dx > 0 ? 1 : 4) : dy > 0 ? 2 : 8);
294
501
 
295
- #less(a: number, b: number): boolean {
296
- return this.#k[a]! < this.#k[b]! || (this.#k[a] === this.#k[b] && this.#v[a]! < this.#v[b]!);
297
- }
502
+ /** This function packs the two indices of a bucket into one number. Each index stays within ±2^20. */
503
+ const bucketKey = (i: number, j: number): number => (i + 1_048_576) * 2_097_152 + (j + 1_048_576);
298
504
 
299
- #swap(a: number, b: number): void {
300
- const k = this.#k[a]!;
301
- this.#k[a] = this.#k[b]!;
302
- this.#k[b] = k;
303
- const v = this.#v[a]!;
304
- this.#v[a] = this.#v[b]!;
305
- this.#v[b] = v;
306
- }
505
+ /**
506
+ * A binary min-heap of (key, value) pairs in typed arrays. The pairs come out by key, and equal keys by value, so the order is deterministic.
507
+ * The method `push` adds a pair to the heap. The method `add` only appends a pair, and the method `build` then makes the heap of all the pairs.
508
+ */
509
+ class MinHeap {
510
+ #d = new Float64Array(64);
511
+ #j = new Int32Array(64);
512
+ size = 0;
307
513
 
308
514
  /** The smallest key. */
309
515
  get topKey(): number {
310
- return this.#k[0]!;
516
+ return this.#d[0]!;
311
517
  }
312
518
 
313
519
  push(key: number, value: number): void {
314
- this.#k.push(key);
315
- this.#v.push(value);
316
- let i = this.#k.length - 1;
520
+ this.add(key, value);
521
+ const d = this.#d;
522
+ const jj = this.#j;
523
+ let i = this.size - 1;
317
524
  while (i > 0) {
318
525
  const p = (i - 1) >> 1;
319
- if (!this.#less(i, p)) break;
320
- this.#swap(i, p);
526
+ if (!(key < d[p]! || (key === d[p] && value < jj[p]!))) break;
527
+ d[i] = d[p]!;
528
+ jj[i] = jj[p]!;
321
529
  i = p;
322
530
  }
531
+ d[i] = key;
532
+ jj[i] = value;
533
+ }
534
+
535
+ clear(): void {
536
+ this.size = 0;
537
+ }
538
+
539
+ add(d: number, j: number): void {
540
+ if (this.size === this.#d.length) {
541
+ const dd = new Float64Array(2 * this.size);
542
+ dd.set(this.#d);
543
+ this.#d = dd;
544
+ const jj = new Int32Array(2 * this.size);
545
+ jj.set(this.#j);
546
+ this.#j = jj;
547
+ }
548
+ this.#d[this.size] = d;
549
+ this.#j[this.size] = j;
550
+ this.size++;
551
+ }
552
+
553
+ build(): void {
554
+ for (let i = (this.size >> 1) - 1; i >= 0; i--) this.#down(i);
323
555
  }
324
556
 
325
557
  /** This method removes the pair with the smallest key and gives its value. */
326
558
  pop(): number {
327
- const top = this.#v[0]!;
328
- const k = this.#k.pop()!;
329
- const v = this.#v.pop()!;
330
- if (this.#k.length > 0) {
331
- this.#k[0] = k;
332
- this.#v[0] = v;
333
- let i = 0;
334
- for (;;) {
335
- const l = 2 * i + 1;
336
- const r = l + 1;
337
- let m = i;
338
- if (l < this.#k.length && this.#less(l, m)) m = l;
339
- if (r < this.#k.length && this.#less(r, m)) m = r;
340
- if (m === i) break;
341
- this.#swap(i, m);
342
- i = m;
343
- }
559
+ const top = this.#j[0]!;
560
+ this.size--;
561
+ if (this.size > 0) {
562
+ this.#d[0] = this.#d[this.size]!;
563
+ this.#j[0] = this.#j[this.size]!;
564
+ this.#down(0);
344
565
  }
345
566
  return top;
346
567
  }
568
+
569
+ #down(i: number): void {
570
+ const d = this.#d;
571
+ const jj = this.#j;
572
+ const n = this.size;
573
+ const di = d[i]!;
574
+ const ji = jj[i]!;
575
+ for (;;) {
576
+ const l = 2 * i + 1;
577
+ if (l >= n) break;
578
+ const r = l + 1;
579
+ let m = l;
580
+ if (r < n && (d[r]! < d[l]! || (d[r] === d[l] && jj[r]! < jj[l]!))) m = r;
581
+ if (!(d[m]! < di || (d[m] === di && jj[m]! < ji))) break;
582
+ d[i] = d[m]!;
583
+ jj[i] = jj[m]!;
584
+ i = m;
585
+ }
586
+ d[i] = di;
587
+ jj[i] = ji;
588
+ }
347
589
  }