@davidsouther/agathon 0.0.0-stage → 2026.11.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.
Files changed (62) hide show
  1. package/package.json +19 -4
  2. package/src/assert.ts +67 -0
  3. package/src/geometry/box.ts +76 -0
  4. package/src/geometry/circle.ts +296 -0
  5. package/src/geometry/geometry.ts +318 -0
  6. package/src/geometry/path.ts +315 -0
  7. package/src/geometry/quadtree.ts +321 -0
  8. package/src/geometry/rectangle.ts +129 -0
  9. package/src/geometry/rngon.ts +132 -0
  10. package/src/geometry/types.ts +102 -0
  11. package/src/geometry.ts +61 -0
  12. package/src/highlight/highlight.ts +85 -0
  13. package/src/index.ts +5 -0
  14. package/src/layout/grid/manager.ts +55 -0
  15. package/src/layout/grid.ts +1 -0
  16. package/src/layout/hierarchic/README.md +21 -0
  17. package/src/layout/hierarchic/acyclic.ts +249 -0
  18. package/src/layout/hierarchic/children.ts +169 -0
  19. package/src/layout/hierarchic/debug.ts +186 -0
  20. package/src/layout/hierarchic/layout.ts +266 -0
  21. package/src/layout/hierarchic/manager.ts +65 -0
  22. package/src/layout/hierarchic/normalize.ts +177 -0
  23. package/src/layout/hierarchic/order.ts +321 -0
  24. package/src/layout/hierarchic/position.ts +514 -0
  25. package/src/layout/hierarchic/rank.ts +629 -0
  26. package/src/layout/hierarchic.ts +2 -0
  27. package/src/layout/hulls/arrange.ts +94 -0
  28. package/src/layout/hulls/hulls.ts +243 -0
  29. package/src/layout/hulls/segments.ts +174 -0
  30. package/src/layout/hulls.ts +3 -0
  31. package/src/layout/manager/child_manager.ts +42 -0
  32. package/src/layout/manager/drag_manager.ts +112 -0
  33. package/src/layout/manager/manager.ts +425 -0
  34. package/src/layout/manager/pan_manager.ts +146 -0
  35. package/src/layout/manager/visibility_manager.ts +84 -0
  36. package/src/layout/manager/zoom_manager.ts +263 -0
  37. package/src/layout/manager.ts +6 -0
  38. package/src/layout/properties/defaults.ts +99 -0
  39. package/src/layout/properties/symbol.ts +99 -0
  40. package/src/layout/properties/weak_map.ts +73 -0
  41. package/src/layout/properties.ts +111 -0
  42. package/src/layout/types.ts +470 -0
  43. package/src/layout/util.ts +444 -0
  44. package/src/layout.ts +6 -0
  45. package/src/samples/diff_graph.ts +15 -0
  46. package/src/samples/edge_cases.ts +168 -0
  47. package/src/samples/gansner_with_states.ts +67 -0
  48. package/src/samples/load_generator.ts +213 -0
  49. package/src/samples/search_graph.ts +35 -0
  50. package/src/tsabre/algo.ts +126 -0
  51. package/src/tsabre/builder.ts +97 -0
  52. package/src/tsabre/compress.ts +15 -0
  53. package/src/tsabre/dijkstra/dijkstra.ts +76 -0
  54. package/src/tsabre/dijkstra/heap.ts +83 -0
  55. package/src/tsabre/graph.ts +808 -0
  56. package/src/tsabre/parse.ts +634 -0
  57. package/src/tsabre/print.ts +188 -0
  58. package/src/tsabre/sourcebuilder/code_content_buffer.ts +275 -0
  59. package/src/tsabre/sourcebuilder/printable.ts +33 -0
  60. package/src/tsabre.ts +6 -0
  61. package/src/types.ts +424 -0
  62. package/README.md +0 -3
@@ -0,0 +1,213 @@
1
+ /**
2
+ * @fileoverview Generate a large graph, with some parameters to control size.
3
+ */
4
+
5
+ import type { LayoutGraph } from "../layout/types.ts";
6
+ import { TsabreGraph } from "../tsabre/graph.ts";
7
+ import type { Edge, Node, NodeId } from "../types.ts";
8
+
9
+ const DINO_NAMES = `Vulcanodontidae
10
+ Tyrannosauridae
11
+ Troodontidae
12
+ Titanosauriformes
13
+ Titanosauridae
14
+ Thyreophora
15
+ Theropoda
16
+ Therizinosauridae
17
+ Tetanurae
18
+ Stegosauria
19
+ Spinosauroidea
20
+ Spinosauridae
21
+ Somphospondyli
22
+ Scelidosauridae
23
+ Sauropodomorpha
24
+ Sauropoda
25
+ Psittacosauridae
26
+ Protoceratopsidae
27
+ Plateosauridae
28
+ Pisanosauridae
29
+ Pachycephalosauria
30
+ Ornithopoda
31
+ Ornithomimidae
32
+ Ornithischia
33
+ Omeisauridae
34
+ Nodosauridae
35
+ Noasauridae
36
+ Neovenatoridae
37
+ Neosauropoda
38
+ Neoceratosauria
39
+ Metriacanthosauridae
40
+ Megalosauridae
41
+ Megalosauria
42
+ Massospondylidae
43
+ Maniraptoriformes
44
+ Maniraptora
45
+ Macronaria
46
+ Iojasaurus
47
+ Iguanodontidae
48
+ Hypsilophodontidae
49
+ Heterodontosauridae
50
+ Herrerasauria
51
+ Hecodontosaurus
52
+ Hadrosauridae
53
+ Fabrosauridae
54
+ Euhelopodidae
55
+ Dromaeosauridae
56
+ Diplodocidae
57
+ Deinonychosauria
58
+ Coelurosauria
59
+ Coeluridae
60
+ Coelophysoidea
61
+ Cetiosauridae
62
+ Ceratosauridae
63
+ Ceratosauria
64
+ Ceratopsidae
65
+ Ceratopsia
66
+ Cerapoda
67
+ Carnosauria
68
+ Carcharodontosauridae
69
+ Camarasauridae
70
+ Brachiosauridae
71
+ Aves
72
+ Ankylosauridae
73
+ Ankylosauria
74
+ Alvarezsauridae
75
+ Allosauroidea
76
+ Allosauridae
77
+ Abelisauroidea
78
+ Abelisauridae`.split("\n");
79
+
80
+ const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVQXYZabcdefghijklmnopqrstuvqxyz".slice();
81
+
82
+ /** Generate a very large graph that looks like an Istio mesh. */
83
+ export function generateLoadMesh(
84
+ size = 20,
85
+ serviceCount = 1,
86
+ workloadCount = 2,
87
+ workloadConnections = 3,
88
+ ): string {
89
+ let services: string[] = [];
90
+ let workloads: string[] = [];
91
+ const servicesToWorkloads: string[] = [];
92
+ const workloadsToServices: string[] = [];
93
+
94
+ for (let i = 0; i < Math.min(size, DINO_NAMES.length); i++) {
95
+ const name = DINO_NAMES[i];
96
+
97
+ const newServices = [];
98
+ for (let j = 0; j < serviceCount; j++) {
99
+ newServices.push(`${name}_service_v${j}`);
100
+ }
101
+ services = services.concat(newServices);
102
+
103
+ const newWorkloads = [];
104
+ for (let j = 0; j < workloadCount; j++) {
105
+ const workload = `${name}_workload_v${j}`;
106
+ newWorkloads.push(workload);
107
+ for (const service of newServices) {
108
+ servicesToWorkloads.push(`${service} -> ${workload}`);
109
+ }
110
+ }
111
+ workloads = workloads.concat(newWorkloads);
112
+ }
113
+
114
+ for (const workload of workloads) {
115
+ for (let i = 0; i < workloadConnections; i++) {
116
+ const service = services[(Math.random() * services.length) | 0];
117
+ workloadsToServices.push(`${workload} -> ${service}`);
118
+ }
119
+ }
120
+
121
+ const graph = `digraph {
122
+ [timing=true;]
123
+ ${services.join(";\n")};
124
+
125
+ ${workloads.join(";\n")};
126
+
127
+ ${servicesToWorkloads.join(";\n")};
128
+
129
+ ${workloadsToServices.join(";\n")};
130
+ }`;
131
+
132
+ return graph;
133
+ }
134
+
135
+ function makeAlphaId(i: number): string {
136
+ let id = "";
137
+ do {
138
+ const c = i % ALPHABET.length;
139
+ i = Math.floor(i / ALPHABET.length);
140
+ id = ALPHABET[c] + id;
141
+ } while (i >= ALPHABET.length);
142
+ id = ALPHABET[i] + id;
143
+ return id;
144
+ }
145
+
146
+ /** Generate a very large mesh with similar hulls */
147
+ export function generateLoadGraph(
148
+ nodes: number,
149
+ depth: number,
150
+ children: number,
151
+ connections: number,
152
+ seed?: number,
153
+ ): LayoutGraph {
154
+ const g = new TsabreGraph<Node, Edge>();
155
+ const grandChildren: NodeId[] = [];
156
+
157
+ function makeChildren(pid: NodeId, d: number) {
158
+ for (let o = 0; o < children; o++) {
159
+ const id = `${pid}_${ALPHABET[o]}`;
160
+ g.setNode(id, { id });
161
+ g.setParent(id, pid);
162
+ if (d <= 0) {
163
+ grandChildren.push(id);
164
+ } else {
165
+ makeChildren(id, d - 1);
166
+ }
167
+ }
168
+ }
169
+
170
+ /** For testing, controllable prng */
171
+ function random() {
172
+ if (seed === undefined) return Math.random();
173
+ seed = (A * seed + C) % M;
174
+ return seed / M;
175
+ }
176
+
177
+ for (let n = 0; n < nodes; n++) {
178
+ // const id = DINO_NAMES[Math.random() * DINO_NAMES.length | 0];
179
+ const id = makeAlphaId(n);
180
+ g.setNode(id, { id });
181
+ makeChildren(id, depth - 1);
182
+ }
183
+
184
+ for (const source of grandChildren) {
185
+ for (let i = 0; i < connections; i++) {
186
+ let target = source;
187
+ while (target === source) {
188
+ target = grandChildren[(random() * grandChildren.length) | 0];
189
+ }
190
+ const edge = { source, target };
191
+ g.setEdge(edge, edge);
192
+ }
193
+ }
194
+
195
+ // Include edges for top level segments.
196
+ // TODO: Remove when layout handles top-level segments.
197
+ for (const id of g.children()) {
198
+ for (const segment of g.nodeSegments(id)) {
199
+ for (const edge of segment.edges) {
200
+ const source = g.ancestors(edge.source).pop()!;
201
+ const target = g.ancestors(edge.target).pop()!;
202
+ g.setEdge({ source, target }, { source, target });
203
+ }
204
+ }
205
+ }
206
+
207
+ return g;
208
+ }
209
+
210
+ // For prng, values from vbasic, older glibc, etc.
211
+ const A = 1103515245;
212
+ const C = 12345;
213
+ const M = 2 ** 32;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * String graph of the Cloud Topo "NetViz Graph" which is a good starting point
3
+ * for search tests.
4
+ */
5
+ export const SEARCH_GRAPH = `digraph {
6
+ [printDebug = true;]
7
+
8
+ //compute/country/pol;
9
+ //compute/us-east1-c/instances/instance1;
10
+ //compute/us-east1-c/instances/instance2;
11
+ //compute/eu-west1-c/instances/instance3;
12
+ //compute/us-east1-c/instanceGroups/us-ig;
13
+ //compute/eu-west1-c/instanceGroups/eu-ig;
14
+ //compute/us-east1/subnet1;
15
+ //compute/eu-west1/subnet1;
16
+ //compute/us-east1/net1;
17
+ //compute/us-east1/net2;
18
+ //compute/eu-west1/net1;
19
+ //compute/regions/us-east1;
20
+ //compute/regions/eu-west1;
21
+
22
+ //compute/country/pol -> //compute/us-east1-c/instances/instance1;
23
+ //compute/eu-west1-c/instances/instance3 -> //compute/us-east1-c/instances/instance2;
24
+ //compute/us-east1-c/instanceGroups/us-ig -> //compute/us-east1-c/instances/instance1;
25
+ //compute/us-east1-c/instanceGroups/us-ig -> //compute/us-east1-c/instances/instance2;
26
+ //compute/us-east1/subnet1 -> //compute/us-east1-c/instanceGroups/us-ig;
27
+ //compute/us-east1/net1 -> //compute/us-east1/subnet1;
28
+ //compute/regions/us-east1 -> //compute/us-east1/net1;
29
+ //compute/regions/us-east1 -> //compute/us-east1/net2;
30
+ //compute/eu-west1-c/instanceGroups/eu-ig -> //compute/eu-west1-c/instances/instance3;
31
+ //compute/eu-west1/subnet1 -> //compute/eu-west1-c/instanceGroups/eu-ig;
32
+ //compute/eu-west1/net1 -> //compute/eu-west1/subnet1;
33
+ //compute/regions/eu-west1 -> //compute/eu-west1/net1;
34
+ //compute/regions/eu-west1 -> //compute/eu-west1-c/instanceGroups/eu-ig;
35
+ }`;
@@ -0,0 +1,126 @@
1
+ import type { NodeId, SmartGraph } from "../types.ts";
2
+
3
+ export * from "./dijkstra/dijkstra.ts";
4
+ export * from "./dijkstra/heap.ts";
5
+
6
+ /** A list of nodes in some path of a graph, be it a traversal or cycle. */
7
+ export type Path = NodeId[];
8
+
9
+ /**
10
+ * Break the graph into sets of all connected nodes.
11
+ */
12
+ export function components(g: SmartGraph<unknown, unknown>): Set<Set<NodeId>> {
13
+ const components = new Set<Set<NodeId>>();
14
+ const visited = new Set<NodeId>();
15
+
16
+ function dfs(id: NodeId, component: Set<NodeId>) {
17
+ if (visited.has(id)) return;
18
+ visited.add(id);
19
+ component.add(id);
20
+ for (const next of g.neighbors(id)) {
21
+ dfs(next, component);
22
+ }
23
+ }
24
+
25
+ for (const id of g.nodes) {
26
+ const cmp = new Set<NodeId>();
27
+ dfs(id, cmp);
28
+ if (cmp.size > 0) components.add(cmp);
29
+ }
30
+
31
+ return components;
32
+ }
33
+
34
+ /** Order to conduct a DFS */
35
+ export const Order = {
36
+ PRE: 0,
37
+ POST: 1,
38
+ } as const;
39
+ export type Order = (typeof Order)[keyof typeof Order];
40
+
41
+ /**
42
+ * Return all node ids in a graph using either pre or post order search.
43
+ */
44
+ export function dfs(
45
+ graph: SmartGraph<unknown, unknown>,
46
+ start: Iterable<NodeId>,
47
+ order: Order = Order.PRE,
48
+ ): NodeId[] {
49
+ const visit: NodeId[] = [];
50
+ const visited = new Set<NodeId>();
51
+ for (const id of start) {
52
+ doDfs(id);
53
+ }
54
+ return visit;
55
+
56
+ function doDfs(id: NodeId) {
57
+ if (visited.has(id)) return;
58
+ if (!graph.hasNode(id)) return;
59
+ visited.add(id);
60
+
61
+ if (order === Order.PRE) visit.push(id);
62
+ for (const nextId of graph.successors(id)) {
63
+ doDfs(nextId);
64
+ }
65
+ if (order === Order.POST) visit.push(id);
66
+ }
67
+ }
68
+
69
+ interface TarjanVisit {
70
+ onStack: boolean;
71
+ lowLink: number;
72
+ index: number;
73
+ }
74
+
75
+ /**
76
+ * Tarjan's algorithm to find strongly connected components.
77
+ */
78
+ export function tarjan(g: SmartGraph<unknown, unknown>): Path[] {
79
+ let index = 0;
80
+ const stack: NodeId[] = [];
81
+ const visited = new Map<NodeId, TarjanVisit>();
82
+ const results: Path[] = [];
83
+
84
+ function dfs(id: NodeId) {
85
+ if (visited.has(id)) return;
86
+
87
+ const entry = { onStack: true, lowLink: index, index: index++ };
88
+ visited.set(id, entry);
89
+ stack.push(id);
90
+
91
+ for (const sId of g.successors(id)) {
92
+ if (!visited.has(sId)) {
93
+ dfs(sId);
94
+ entry.lowLink = Math.min(entry.lowLink, visited.get(sId)!.lowLink);
95
+ } else if (visited.get(sId)!.onStack) {
96
+ entry.lowLink = Math.min(entry.lowLink, visited.get(sId)!.index);
97
+ }
98
+ }
99
+
100
+ if (entry.lowLink === entry.index) {
101
+ const cmpt: Path = [];
102
+ let tId: NodeId;
103
+ do {
104
+ tId = stack.pop()!;
105
+ visited.get(tId)!.onStack = false;
106
+ cmpt.push(tId);
107
+ } while (id !== tId);
108
+ results.push(cmpt);
109
+ }
110
+ }
111
+
112
+ for (const id of g.nodes) {
113
+ dfs(id);
114
+ }
115
+
116
+ return results;
117
+ }
118
+
119
+ /**
120
+ * Return a list of all lists of nodes involved in cycles.
121
+ */
122
+ export function findCycles(g: SmartGraph<unknown, unknown>): Path[] {
123
+ return tarjan(g).filter(
124
+ (c) => c.length > 1 || (c.length === 1 && g.hasEdge({ source: c[0], target: c[0] })),
125
+ );
126
+ }
@@ -0,0 +1,97 @@
1
+ import { assertExists } from "../assert.ts";
2
+ import type { Edge, GraphData, Node, NodeId, SmartGraph } from "../types.ts";
3
+
4
+ import { GRAPH_ROOT, TsabreGraph } from "./graph.ts";
5
+
6
+ /**
7
+ * Given a set of graph-like data, instantiate a full graph.
8
+ *
9
+ * Takes an optional trackSegments parameter which is passed on to the
10
+ * underlying SmartGraph. When true, the SmartGraph will create segments for
11
+ * edges. When false, it will not.
12
+ */
13
+ export function fromData<N extends Node, E extends Edge>(
14
+ data: GraphData<N, E>,
15
+ trackSegments = true,
16
+ ): SmartGraph<N, E> {
17
+ const graph = new TsabreGraph<N, E>({ trackSegments });
18
+ mergeData<N, E>(graph, data);
19
+ return graph;
20
+ }
21
+
22
+ /**
23
+ * Given a set of graph-like data, put all of it into an existing graph.
24
+ */
25
+ export function mergeData<N extends Node, E extends Edge>(
26
+ graph: SmartGraph<N, E>,
27
+ data: GraphData<N, E>,
28
+ ): void {
29
+ const nodes = new Map<string, N>([...data.nodes].map((node) => [node.id, node]));
30
+ const addNode = (nodeId: NodeId) => {
31
+ if (graph.hasNode(nodeId) || !nodes.has(nodeId)) {
32
+ return;
33
+ }
34
+ const node = assertExists(nodes.get(nodeId));
35
+ graph.setNode(nodeId, node);
36
+ if (node.parent) {
37
+ addNode(node.parent);
38
+ graph.setParent(node.id, node.parent);
39
+ }
40
+ };
41
+
42
+ for (const id of nodes.keys()) {
43
+ addNode(id);
44
+ }
45
+
46
+ for (const edge of data.edges) {
47
+ const [source, target, name] = [edge.source, edge.target, edge.name];
48
+ graph.setEdge({ source, target, name }, edge);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Given a graph and a function taking a node id and value which returns a
54
+ * boolean, construct a new graph with only those nodes matching the
55
+ * filter function, and only edges between those filtered nodes. Nodes
56
+ * whose parent is missing will be promoted to the next parent up,
57
+ * possibly being the graph root.
58
+ */
59
+ export function filterNodes<N extends Node, E extends Edge>(
60
+ g: SmartGraph<N, E>,
61
+ fn: (value: N) => boolean,
62
+ ): SmartGraph<N, E> {
63
+ const graph = new TsabreGraph<N, E>();
64
+ // Copy nodes
65
+ for (const value of g.allNodes) {
66
+ if (fn(value)) {
67
+ graph.setNode(value.id, value);
68
+ }
69
+ }
70
+
71
+ // Copy edges
72
+ for (const edge of g.allEdges) {
73
+ if (graph.hasNode(edge.source) && graph.hasNode(edge.target)) {
74
+ graph.setEdge(edge, g.edge(edge)!);
75
+ }
76
+ }
77
+
78
+ // Copy parents
79
+ const parents = new Map<NodeId, NodeId>();
80
+ const findParent = (id: NodeId): NodeId => {
81
+ const parent = g.parent(id);
82
+ if (parent === GRAPH_ROOT || graph.hasNode(parent)) {
83
+ parents.set(id, parent);
84
+ return parent;
85
+ } else if (parents.has(parent)) {
86
+ return parents.get(parent)!;
87
+ } else {
88
+ return findParent(parent);
89
+ }
90
+ };
91
+
92
+ for (const id of graph.nodes) {
93
+ graph.setParent(id, findParent(id));
94
+ }
95
+
96
+ return graph;
97
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * @fileoverview Functions for compressings/decompressing strings.
3
+ */
4
+
5
+ // DOIT(dsouther): Find a zlib impl
6
+
7
+ /** Compresses a string to a base64 representation of deflated content. */
8
+ export function deflate(str: string): string {
9
+ return Buffer.from(str, "utf8").toString("base64");
10
+ }
11
+
12
+ /** Decompresses a string from a base64 representation of deflated content. */
13
+ export function inflate(b64: string): string {
14
+ return Buffer.from(b64, "base64").toString("utf8");
15
+ }
@@ -0,0 +1,76 @@
1
+ import { assertExists } from "../../assert.ts";
2
+ import type { Edge, NodeId, SmartGraph } from "../../types.ts";
3
+ import { edgeId } from "../graph.ts";
4
+ import { Heap } from "./heap.ts";
5
+
6
+ /**
7
+ * A node in the Dijkstra search. Will have a distance from the source node and
8
+ * which node would precede it on the shortest path. If the node is the source,
9
+ * it will have no predecessor and distance 0. If the node is unconnected, it
10
+ * will have no predecessor and distance POSITIVE_INFINITY.
11
+ */
12
+ export interface PathNode {
13
+ distance: number;
14
+ predecessor?: NodeId;
15
+ }
16
+
17
+ /**
18
+ * From a starting node in a graph, return a table of PathNode entries for every
19
+ * node in the graph. Takes an optional weight function (default to constant 1)
20
+ * and an optional edge function (defaults to outedges of a node).
21
+ *
22
+ * Utilizes a straightforward Dijkstra variant, tracking the unsettled nodes in
23
+ * a heap-based priority queue. The algorithm thus runs in O(E + NlogN), and of
24
+ * course utilizes O(N) space.
25
+ */
26
+ export function dijkstra(
27
+ g: SmartGraph<unknown, Edge>,
28
+ source: NodeId,
29
+ weightFn: (e: Edge) => number = () => 1,
30
+ edgeFn: (id: NodeId) => Iterable<Edge> = (id: NodeId) => g.outEdges(id),
31
+ ): Map<NodeId, PathNode> {
32
+ const results = new Map<NodeId, PathNode>();
33
+ const pq = new Heap<string>();
34
+
35
+ // Initialize the PQ with the source at 0 and all other nodes at infinity
36
+ for (const node of g.nodes) {
37
+ const distance = node === source ? 0 : Number.POSITIVE_INFINITY;
38
+ results.set(node, { distance });
39
+ pq.insert(distance, node);
40
+ }
41
+
42
+ while (!pq.isEmpty) {
43
+ // first iteration will always start with source, later iterations will
44
+ // find the next closest unsettled node.
45
+ const currentId = assertExists(pq.pop())[1];
46
+ const currentHop = assertExists(results.get(currentId));
47
+
48
+ // If the distance is infinity, this node never got visited in the search,
49
+ // and all other nodes left in the PQ are also unconnected. Stop early.
50
+ if (currentHop.distance === Number.POSITIVE_INFINITY) break;
51
+
52
+ const edges = edgeFn(currentId); // For debugging, store locally
53
+ for (const edge of edges) {
54
+ // Figure out which side is the other end of the edge, and load its hop.
55
+ const nextId = edge.source === currentId ? edge.target : edge.source;
56
+ const nextHop = assertExists(results.get(nextId), `${nextId} not in graph`);
57
+
58
+ // Negative weights break priority queues and Dijkstra.
59
+ const weight = weightFn(edge);
60
+ if (weight < 0) {
61
+ throw new Error(`disjkstra requires positive weights, got ${weight} on ${edgeId(edge)}`);
62
+ }
63
+
64
+ // Update the current hop, if this edge is shorter than its current
65
+ // distance.
66
+ const distance = currentHop.distance + weight;
67
+ if (distance < nextHop.distance) {
68
+ nextHop.distance = distance;
69
+ nextHop.predecessor = currentId;
70
+ pq.insert(distance, nextId);
71
+ }
72
+ }
73
+ }
74
+
75
+ return results;
76
+ }
@@ -0,0 +1,83 @@
1
+ import { assert } from "../../assert.ts";
2
+
3
+ export class Heap<Value = undefined> {
4
+ private index = 0;
5
+ private heap: Array<[number, Value]>;
6
+
7
+ get size(): number {
8
+ return this.index;
9
+ }
10
+
11
+ get isEmpty(): boolean {
12
+ return this.index === 0;
13
+ }
14
+
15
+ constructor(capacity = 15) {
16
+ this.heap = new Array(capacity);
17
+ }
18
+
19
+ insert(priority: number, value: Value): void {
20
+ assert(!isNaN(priority), "AssertionError: heap priority must be defined");
21
+ this.heap[this.index] = [priority, value];
22
+
23
+ // Shuffle up
24
+ let i = this.index,
25
+ p = parent(i);
26
+ while (i > 0 && this.heap[i][0] < this.heap[p][0]) {
27
+ const t = this.heap[p];
28
+ this.heap[p] = this.heap[i];
29
+ this.heap[i] = t;
30
+ i = p;
31
+ p = parent(i);
32
+ }
33
+
34
+ this.index += 1;
35
+ }
36
+
37
+ peek(): [number, Value] | undefined {
38
+ return this.heap[0];
39
+ }
40
+
41
+ pop(): [number, Value] | undefined {
42
+ if (this.isEmpty) return undefined;
43
+ const ret = this.heap[0];
44
+ this.index -= 1;
45
+ this.heap[0] = this.heap[this.index];
46
+ delete this.heap[this.index];
47
+
48
+ // Shuffle down
49
+ let i = 0;
50
+ while (i < this.index) {
51
+ const m = this.heap[i][0];
52
+ const li = left(i);
53
+ const ri = right(i);
54
+
55
+ const l = this.heap[li]?.[0] ?? Number.POSITIVE_INFINITY;
56
+ const r = this.heap[ri]?.[0] ?? Number.POSITIVE_INFINITY;
57
+
58
+ const ni = m > l ? (l > r ? ri : li) : m > r ? (l > r ? ri : li) : i;
59
+ if (ni === i) {
60
+ break;
61
+ }
62
+
63
+ const t = this.heap[ni];
64
+ this.heap[ni] = this.heap[i];
65
+ this.heap[i] = t;
66
+ i = ni;
67
+ }
68
+
69
+ return ret;
70
+ }
71
+ }
72
+
73
+ function parent(i: number): number {
74
+ return Math.floor((i - 1) / 2);
75
+ }
76
+
77
+ function left(i: number): number {
78
+ return i * 2 + 1;
79
+ }
80
+
81
+ function right(i: number): number {
82
+ return i * 2 + 2;
83
+ }