@statelyai/graph 0.5.0 → 0.7.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.
@@ -1,7 +1,16 @@
1
1
  import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-DyfgLuzw.mjs";
2
2
 
3
3
  //#region src/graph.ts
4
- function resolveNode(config) {
4
+ /**
5
+ * Create a resolved graph node from a config. Fills in defaults.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * const node = createGraphNode({ id: 'a', data: { label: 'hi' } });
10
+ * // { type: 'node', id: 'a', label: '', data: { label: 'hi' } }
11
+ * ```
12
+ */
13
+ function createGraphNode(config) {
5
14
  if (!config.id) throw new Error("Node id must be a non-empty string");
6
15
  if (config.parentId === "") throw new Error("Node parentId must be a non-empty string");
7
16
  const node = {
@@ -21,7 +30,16 @@ function resolveNode(config) {
21
30
  if (config.style !== void 0) node.style = config.style;
22
31
  return node;
23
32
  }
24
- function resolveEdge(config) {
33
+ /**
34
+ * Create a resolved graph edge from a config. Fills in defaults.
35
+ *
36
+ * @example
37
+ * ```ts
38
+ * const edge = createGraphEdge({ id: 'e1', sourceId: 'a', targetId: 'b' });
39
+ * // { type: 'edge', id: 'e1', sourceId: 'a', targetId: 'b', label: null, data: undefined }
40
+ * ```
41
+ */
42
+ function createGraphEdge(config) {
25
43
  if (!config.id) throw new Error("Edge id must be a non-empty string");
26
44
  if (!config.sourceId) throw new Error("Edge sourceId must be a non-empty string");
27
45
  if (!config.targetId) throw new Error("Edge targetId must be a non-empty string");
@@ -30,9 +48,10 @@ function resolveEdge(config) {
30
48
  id: config.id,
31
49
  sourceId: config.sourceId,
32
50
  targetId: config.targetId,
33
- label: config.label ?? "",
51
+ label: config.label ?? null,
34
52
  data: config.data
35
53
  };
54
+ if (config.weight !== void 0) edge.weight = config.weight;
36
55
  if (config.x !== void 0) edge.x = config.x;
37
56
  if (config.y !== void 0) edge.y = config.y;
38
57
  if (config.width !== void 0) edge.width = config.width;
@@ -57,8 +76,8 @@ function createGraph(config) {
57
76
  id: config?.id ?? "",
58
77
  type: config?.type ?? "directed",
59
78
  initialNodeId: config?.initialNodeId ?? null,
60
- nodes: (config?.nodes ?? []).map(resolveNode),
61
- edges: (config?.edges ?? []).map(resolveEdge),
79
+ nodes: (config?.nodes ?? []).map(createGraphNode),
80
+ edges: (config?.edges ?? []).map(createGraphEdge),
62
81
  data: config?.data ?? void 0
63
82
  };
64
83
  if (config?.direction !== void 0) graph.direction = config.direction;
@@ -256,7 +275,7 @@ function hasEdge(graph, id) {
256
275
  * ```
257
276
  */
258
277
  function addNode(graph, config) {
259
- const node = resolveNode(config);
278
+ const node = createGraphNode(config);
260
279
  const idx = getIndex(graph);
261
280
  if (idx.nodeById.has(config.id)) throw new Error(`Node "${config.id}" already exists`);
262
281
  if (config.parentId && !idx.nodeById.has(config.parentId)) throw new Error(`Parent node "${config.parentId}" does not exist`);
@@ -275,7 +294,7 @@ function addNode(graph, config) {
275
294
  * ```
276
295
  */
277
296
  function addEdge(graph, config) {
278
- const edge = resolveEdge(config);
297
+ const edge = createGraphEdge(config);
279
298
  const idx = getIndex(graph);
280
299
  if (idx.edgeById.has(config.id)) throw new Error(`Edge "${config.id}" already exists`);
281
300
  if (!idx.nodeById.has(config.sourceId)) throw new Error(`Source node "${config.sourceId}" does not exist`);
@@ -570,6 +589,8 @@ function collectDescendants(graph, id) {
570
589
  /**
571
590
  * Breadth-first traversal generator yielding nodes level by level.
572
591
  *
592
+ * **O(V + E)** time, **O(V)** space.
593
+ *
573
594
  * @example
574
595
  * ```ts
575
596
  * import { createGraph, bfs } from '@statelyai/graph';
@@ -603,6 +624,8 @@ function* bfs(graph, startId) {
603
624
  /**
604
625
  * Depth-first traversal generator yielding nodes as visited.
605
626
  *
627
+ * **O(V + E)** time, **O(V)** space.
628
+ *
606
629
  * @example
607
630
  * ```ts
608
631
  * import { createGraph, dfs } from '@statelyai/graph';
@@ -648,9 +671,56 @@ function getSuccessorIds(graph, nodeId) {
648
671
  const idx = getIndex(graph);
649
672
  return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
650
673
  }
674
+ var MinPriorityQueue = class {
675
+ items = [];
676
+ constructor(compare) {
677
+ this.compare = compare;
678
+ }
679
+ get size() {
680
+ return this.items.length;
681
+ }
682
+ push(item) {
683
+ this.items.push(item);
684
+ this.bubbleUp(this.items.length - 1);
685
+ }
686
+ pop() {
687
+ if (this.items.length === 0) return void 0;
688
+ const first = this.items[0];
689
+ const last = this.items.pop();
690
+ if (this.items.length > 0) {
691
+ this.items[0] = last;
692
+ this.bubbleDown(0);
693
+ }
694
+ return first;
695
+ }
696
+ bubbleUp(index) {
697
+ let current = index;
698
+ while (current > 0) {
699
+ const parent = Math.floor((current - 1) / 2);
700
+ if (this.compare(this.items[current], this.items[parent]) >= 0) break;
701
+ [this.items[current], this.items[parent]] = [this.items[parent], this.items[current]];
702
+ current = parent;
703
+ }
704
+ }
705
+ bubbleDown(index) {
706
+ let current = index;
707
+ while (true) {
708
+ const left = current * 2 + 1;
709
+ const right = left + 1;
710
+ let smallest = current;
711
+ if (left < this.items.length && this.compare(this.items[left], this.items[smallest]) < 0) smallest = left;
712
+ if (right < this.items.length && this.compare(this.items[right], this.items[smallest]) < 0) smallest = right;
713
+ if (smallest === current) break;
714
+ [this.items[current], this.items[smallest]] = [this.items[smallest], this.items[current]];
715
+ current = smallest;
716
+ }
717
+ }
718
+ };
651
719
  /**
652
720
  * Checks whether the graph contains no cycles.
653
721
  *
722
+ * **O(V + E)** time.
723
+ *
654
724
  * @example
655
725
  * ```ts
656
726
  * import { createGraph, isAcyclic } from '@statelyai/graph';
@@ -711,6 +781,8 @@ function isAcyclicUndirected(graph) {
711
781
  * Returns connected components as arrays of nodes.
712
782
  * Treats all edges as undirected for connectivity.
713
783
  *
784
+ * **O(V + E)** time.
785
+ *
714
786
  * @example
715
787
  * ```ts
716
788
  * import { createGraph, getConnectedComponents } from '@statelyai/graph';
@@ -763,6 +835,8 @@ function getConnectedComponents(graph) {
763
835
  /**
764
836
  * Returns a topological ordering of nodes, or `null` if the graph is cyclic.
765
837
  *
838
+ * **O(V + E)** time (Kahn's algorithm).
839
+ *
766
840
  * @example
767
841
  * ```ts
768
842
  * import { createGraph, getTopologicalSort } from '@statelyai/graph';
@@ -806,6 +880,8 @@ function getTopologicalSort(graph) {
806
880
  /**
807
881
  * Checks whether a path exists between two nodes.
808
882
  *
883
+ * **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
884
+ *
809
885
  * @example
810
886
  * ```ts
811
887
  * import { createGraph, hasPath } from '@statelyai/graph';
@@ -828,6 +904,8 @@ function hasPath(graph, sourceId, targetId) {
828
904
  /**
829
905
  * Checks whether the graph is connected (all nodes reachable from any node).
830
906
  *
907
+ * **O(V + E)** time.
908
+ *
831
909
  * @example
832
910
  * ```ts
833
911
  * import { createGraph, isConnected } from '@statelyai/graph';
@@ -847,6 +925,8 @@ function isConnected(graph) {
847
925
  /**
848
926
  * Checks whether the graph is a tree (connected and acyclic).
849
927
  *
928
+ * **O(V + E)** time.
929
+ *
850
930
  * @example
851
931
  * ```ts
852
932
  * import { createGraph, isTree } from '@statelyai/graph';
@@ -905,7 +985,7 @@ function getNeighborEdges(graph, nodeId) {
905
985
  /**
906
986
  * Returns all shortest paths from a source node.
907
987
  * Returns all paths of equal minimum length per target (not just one).
908
- * Uses BFS by default; Dijkstra when `opts.getWeight` is provided.
988
+ * Uses BFS when all edges are unweighted; Dijkstra otherwise.
909
989
  */
910
990
  /** Compute distance + prev maps via BFS or Dijkstra. */
911
991
  function computeShortestDistances(graph, sourceId, getWeight) {
@@ -913,7 +993,7 @@ function computeShortestDistances(graph, sourceId, getWeight) {
913
993
  const prev = /* @__PURE__ */ new Map();
914
994
  dist.set(sourceId, 0);
915
995
  prev.set(sourceId, []);
916
- if (!getWeight) {
996
+ if (!getWeight && !graph.edges.some((e) => e.weight !== void 0)) {
917
997
  const queue = [sourceId];
918
998
  while (queue.length > 0) {
919
999
  const id = queue.shift();
@@ -935,19 +1015,19 @@ function computeShortestDistances(graph, sourceId, getWeight) {
935
1015
  }
936
1016
  }
937
1017
  } else {
1018
+ const effectiveWeight = getWeight ?? ((e) => e.weight ?? 1);
938
1019
  const visited = /* @__PURE__ */ new Set();
939
- const pq = [{
1020
+ const pq = new MinPriorityQueue((a, b) => a.dist - b.dist);
1021
+ pq.push({
940
1022
  id: sourceId,
941
1023
  dist: 0
942
- }];
943
- while (pq.length > 0) {
944
- let minIdx = 0;
945
- for (let i = 1; i < pq.length; i++) if (pq[i].dist < pq[minIdx].dist) minIdx = i;
946
- const { id, dist: d } = pq.splice(minIdx, 1)[0];
947
- if (visited.has(id)) continue;
1024
+ });
1025
+ while (pq.size > 0) {
1026
+ const { id, dist: d } = pq.pop();
1027
+ if (visited.has(id) || d !== dist.get(id)) continue;
948
1028
  visited.add(id);
949
1029
  for (const { neighborId, edge } of getNeighborEdges(graph, id)) {
950
- const newDist = d + getWeight(edge);
1030
+ const newDist = d + effectiveWeight(edge);
951
1031
  const existing = dist.get(neighborId);
952
1032
  if (existing === void 0 || newDist < existing) {
953
1033
  dist.set(neighborId, newDist);
@@ -996,6 +1076,9 @@ function* reconstructPaths(graph, prev, sourceNode, targetId) {
996
1076
  * Lazily yields all shortest paths from a source node.
997
1077
  * Use `getShortestPaths` for the full array.
998
1078
  *
1079
+ * **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted),
1080
+ * plus **O(P)** per path yielded where P is the path length.
1081
+ *
999
1082
  * @example
1000
1083
  * ```ts
1001
1084
  * import { createGraph, genShortestPaths } from '@statelyai/graph';
@@ -1027,6 +1110,8 @@ function* genShortestPaths(graph, opts) {
1027
1110
  * Returns all shortest paths from a source node as an array.
1028
1111
  * Delegates to `genShortestPaths` internally.
1029
1112
  *
1113
+ * **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
1114
+ *
1030
1115
  * @example
1031
1116
  * ```ts
1032
1117
  * import { createGraph, getShortestPaths } from '@statelyai/graph';
@@ -1050,6 +1135,8 @@ function getShortestPaths(graph, opts) {
1050
1135
  /**
1051
1136
  * Returns a single shortest path from source to target, or `undefined` if unreachable.
1052
1137
  *
1138
+ * **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
1139
+ *
1053
1140
  * @example
1054
1141
  * ```ts
1055
1142
  * import { createGraph, getShortestPath } from '@statelyai/graph';
@@ -1074,6 +1161,8 @@ function getShortestPath(graph, opts) {
1074
1161
  * Returns all simple (acyclic) paths from a source node as an array.
1075
1162
  * Delegates to `genSimplePaths` internally.
1076
1163
  *
1164
+ * **O(V!)** worst-case (exponential in dense graphs).
1165
+ *
1077
1166
  * @example
1078
1167
  * ```ts
1079
1168
  * import { createGraph, getSimplePaths } from '@statelyai/graph';
@@ -1099,6 +1188,8 @@ function getSimplePaths(graph, opts) {
1099
1188
  * Lazily yields all simple (acyclic) paths from a source node via DFS backtracking.
1100
1189
  * Use `getSimplePaths` for the full array.
1101
1190
  *
1191
+ * **O(V!)** worst-case (exponential in dense graphs).
1192
+ *
1102
1193
  * @example
1103
1194
  * ```ts
1104
1195
  * import { createGraph, genSimplePaths } from '@statelyai/graph';
@@ -1127,22 +1218,21 @@ function* genSimplePaths(graph, opts) {
1127
1218
  const targetId = opts?.to;
1128
1219
  const visited = /* @__PURE__ */ new Set();
1129
1220
  const currentSteps = [];
1130
- const found = [];
1131
- function dfsCollect(nodeId) {
1221
+ function* dfsCollect(nodeId) {
1132
1222
  visited.add(nodeId);
1133
1223
  if (targetId !== void 0) {
1134
1224
  if (nodeId === targetId) {
1135
- found.push({
1225
+ yield {
1136
1226
  source: sourceNode,
1137
1227
  steps: [...currentSteps]
1138
- });
1228
+ };
1139
1229
  visited.delete(nodeId);
1140
1230
  return;
1141
1231
  }
1142
- } else if (currentSteps.length > 0) found.push({
1232
+ } else if (currentSteps.length > 0) yield {
1143
1233
  source: sourceNode,
1144
1234
  steps: [...currentSteps]
1145
- });
1235
+ };
1146
1236
  for (const { neighborId, edge } of getNeighborEdges(graph, nodeId)) if (!visited.has(neighborId)) {
1147
1237
  const neighborNi = idx.nodeById.get(neighborId);
1148
1238
  const neighborNode = neighborNi !== void 0 ? graph.nodes[neighborNi] : graph.nodes.find((n) => n.id === neighborId);
@@ -1150,17 +1240,18 @@ function* genSimplePaths(graph, opts) {
1150
1240
  edge,
1151
1241
  node: neighborNode
1152
1242
  });
1153
- dfsCollect(neighborId);
1243
+ yield* dfsCollect(neighborId);
1154
1244
  currentSteps.pop();
1155
1245
  }
1156
1246
  visited.delete(nodeId);
1157
1247
  }
1158
- dfsCollect(sourceId);
1159
- yield* found;
1248
+ yield* dfsCollect(sourceId);
1160
1249
  }
1161
1250
  /**
1162
1251
  * Returns a single simple (acyclic) path from source to target, or `undefined` if unreachable.
1163
1252
  *
1253
+ * **O(V + E)** typical, **O(V!)** worst-case.
1254
+ *
1164
1255
  * @example
1165
1256
  * ```ts
1166
1257
  * import { createGraph, getSimplePath } from '@statelyai/graph';
@@ -1185,6 +1276,8 @@ function getSimplePath(graph, opts) {
1185
1276
  * Returns strongly connected components using Tarjan's algorithm.
1186
1277
  * Only meaningful for directed graphs.
1187
1278
  *
1279
+ * **O(V + E)** time.
1280
+ *
1188
1281
  * @example
1189
1282
  * ```ts
1190
1283
  * import { createGraph, getStronglyConnectedComponents } from '@statelyai/graph';
@@ -1244,6 +1337,8 @@ function getStronglyConnectedComponents(graph) {
1244
1337
  * Returns all elementary cycles as an array of paths.
1245
1338
  * Delegates to `genCycles` internally.
1246
1339
  *
1340
+ * **O((V + E) · C)** where C is the number of elementary cycles (can be exponential).
1341
+ *
1247
1342
  * @example
1248
1343
  * ```ts
1249
1344
  * import { createGraph, getCycles } from '@statelyai/graph';
@@ -1267,6 +1362,8 @@ function getCycles(graph) {
1267
1362
  * Lazily yields elementary cycles one at a time.
1268
1363
  * Use `getCycles` for the full array.
1269
1364
  *
1365
+ * **O((V + E) · C)** where C is the number of elementary cycles (can be exponential).
1366
+ *
1270
1367
  * @example
1271
1368
  * ```ts
1272
1369
  * import { createGraph, genCycles } from '@statelyai/graph';
@@ -1406,6 +1503,8 @@ function getNeighborEdgesAll(graph, nodeId) {
1406
1503
  * Returns a single canonical preorder (DFS visit-order) sequence.
1407
1504
  * Visits neighbors in the order they appear in the adjacency list.
1408
1505
  *
1506
+ * **O(V + E)** time.
1507
+ *
1409
1508
  * @example
1410
1509
  * ```ts
1411
1510
  * import { createGraph, getPreorder } from '@statelyai/graph';
@@ -1449,6 +1548,8 @@ function getPreorder(graph, opts) {
1449
1548
  * Returns a single canonical postorder (DFS finish-order) sequence.
1450
1549
  * Visits neighbors in the order they appear in the adjacency list.
1451
1550
  *
1551
+ * **O(V + E)** time.
1552
+ *
1452
1553
  * @example
1453
1554
  * ```ts
1454
1555
  * import { createGraph, getPostorder } from '@statelyai/graph';
@@ -1490,6 +1591,8 @@ function getPostorder(graph, opts) {
1490
1591
  /**
1491
1592
  * Returns all possible preorder sequences as an array. Can be exponential -- prefer `genPreorders`.
1492
1593
  *
1594
+ * **O(V! · V)** worst-case (exponential).
1595
+ *
1493
1596
  * @example
1494
1597
  * ```ts
1495
1598
  * import { createGraph, getPreorders } from '@statelyai/graph';
@@ -1513,6 +1616,8 @@ function getPreorders(graph, opts) {
1513
1616
  /**
1514
1617
  * Returns all possible postorder sequences as an array. Can be exponential -- prefer `genPostorders`.
1515
1618
  *
1619
+ * **O(V! · V)** worst-case (exponential).
1620
+ *
1516
1621
  * @example
1517
1622
  * ```ts
1518
1623
  * import { createGraph, getPostorders } from '@statelyai/graph';
@@ -1538,6 +1643,8 @@ function getPostorders(graph, opts) {
1538
1643
  * Different neighbor exploration orders yield different sequences.
1539
1644
  * Use `getPreorder()` for a single canonical ordering.
1540
1645
  *
1646
+ * **O(V! · V)** worst-case (exponential).
1647
+ *
1541
1648
  * @example
1542
1649
  * ```ts
1543
1650
  * import { createGraph, genPreorders } from '@statelyai/graph';
@@ -1602,6 +1709,8 @@ function* genPreorders(graph, opts) {
1602
1709
  * Different neighbor exploration orders yield different sequences.
1603
1710
  * Use `getPostorder()` for a single canonical ordering.
1604
1711
  *
1712
+ * **O(V! · V)** worst-case (exponential).
1713
+ *
1605
1714
  * @example
1606
1715
  * ```ts
1607
1716
  * import { createGraph, genPostorders } from '@statelyai/graph';
@@ -1665,6 +1774,8 @@ function* genPostorders(graph, opts) {
1665
1774
  * Only meaningful for connected undirected graphs (or the component reachable
1666
1775
  * from an arbitrary start node in directed graphs).
1667
1776
  *
1777
+ * **O(E log E)** using either edge sorting (Kruskal) or a min-heap (Prim).
1778
+ *
1668
1779
  * @example
1669
1780
  * ```ts
1670
1781
  * import { createGraph, getMinimumSpanningTree } from '@statelyai/graph';
@@ -1687,7 +1798,7 @@ function* genPostorders(graph, opts) {
1687
1798
  */
1688
1799
  function getMinimumSpanningTree(graph, opts) {
1689
1800
  const algorithm = opts?.algorithm ?? "prim";
1690
- const getWeight = opts?.getWeight ?? (() => 1);
1801
+ const getWeight = opts?.getWeight ?? ((e) => e.weight ?? 1);
1691
1802
  const mstEdges = algorithm === "kruskal" ? kruskalMST(graph, getWeight) : primMST(graph, getWeight);
1692
1803
  return createGraph({
1693
1804
  id: graph.id,
@@ -1705,7 +1816,8 @@ function getMinimumSpanningTree(graph, opts) {
1705
1816
  sourceId: e.sourceId,
1706
1817
  targetId: e.targetId,
1707
1818
  label: e.label,
1708
- data: e.data
1819
+ data: e.data,
1820
+ ...e.weight !== void 0 && { weight: e.weight }
1709
1821
  }))
1710
1822
  });
1711
1823
  }
@@ -1714,7 +1826,7 @@ function primMST(graph, getWeight) {
1714
1826
  const idx = getIndex(graph);
1715
1827
  const inMST = /* @__PURE__ */ new Set();
1716
1828
  const mstEdges = [];
1717
- const candidates = [];
1829
+ const candidates = new MinPriorityQueue((a, b) => a.weight - b.weight);
1718
1830
  function addEdgesOf(nodeId) {
1719
1831
  for (const eid of idx.outEdges.get(nodeId) ?? []) {
1720
1832
  const ai = idx.edgeById.get(eid);
@@ -1738,10 +1850,8 @@ function primMST(graph, getWeight) {
1738
1850
  const startId = graph.nodes[0].id;
1739
1851
  inMST.add(startId);
1740
1852
  addEdgesOf(startId);
1741
- while (candidates.length > 0 && inMST.size < graph.nodes.length) {
1742
- let minIdx = 0;
1743
- for (let i = 1; i < candidates.length; i++) if (candidates[i].weight < candidates[minIdx].weight) minIdx = i;
1744
- const { edge } = candidates.splice(minIdx, 1)[0];
1853
+ while (candidates.size > 0 && inMST.size < graph.nodes.length) {
1854
+ const { edge } = candidates.pop();
1745
1855
  const targetId = graph.type === "undirected" && inMST.has(edge.targetId) ? edge.sourceId : edge.targetId;
1746
1856
  if (inMST.has(targetId)) continue;
1747
1857
  inMST.add(targetId);
@@ -1780,7 +1890,9 @@ function kruskalMST(graph, getWeight) {
1780
1890
  /**
1781
1891
  * Returns shortest paths between all pairs of nodes.
1782
1892
  * Algorithm 'dijkstra' (default): runs getShortestPaths per source node.
1783
- * Algorithm 'floyd-warshall': classic O(V^3) dynamic programming.
1893
+ * Algorithm 'floyd-warshall': classic dynamic programming.
1894
+ *
1895
+ * **O(V · (V + E) log V)** (Dijkstra) or **O(V³)** (Floyd-Warshall).
1784
1896
  *
1785
1897
  * @example
1786
1898
  * ```ts
@@ -1815,7 +1927,7 @@ function dijkstraAllPaths(graph, getWeight) {
1815
1927
  }
1816
1928
  function floydWarshallAllPaths(graph, getWeight) {
1817
1929
  const idx = getIndex(graph);
1818
- const w = getWeight ?? (() => 1);
1930
+ const w = getWeight ?? ((e) => e.weight ?? 1);
1819
1931
  const nodeIds = graph.nodes.map((n$1) => n$1.id);
1820
1932
  const n = nodeIds.length;
1821
1933
  const idxOf = /* @__PURE__ */ new Map();
@@ -1898,6 +2010,100 @@ function fwReconstruct(graph, prev, idxOf, nodeIds, sourceNode, sourceIdx, targe
1898
2010
  return results;
1899
2011
  }
1900
2012
  /**
2013
+ * Returns a shortest path using A* search with an admissible heuristic.
2014
+ * More efficient than Dijkstra when a good heuristic is available.
2015
+ *
2016
+ * **O((V + E) log V)** time with a good heuristic; degrades to Dijkstra
2017
+ * with `heuristic: () => 0`.
2018
+ *
2019
+ * @example
2020
+ * ```ts
2021
+ * import { createGraph, getAStarPath } from '@statelyai/graph';
2022
+ *
2023
+ * const graph = createGraph({
2024
+ * nodes: [
2025
+ * { id: 'a', x: 0, y: 0 },
2026
+ * { id: 'b', x: 1, y: 0 },
2027
+ * { id: 'c', x: 1, y: 1 },
2028
+ * ],
2029
+ * edges: [
2030
+ * { id: 'ab', sourceId: 'a', targetId: 'b', weight: 1 },
2031
+ * { id: 'bc', sourceId: 'b', targetId: 'c', weight: 1 },
2032
+ * { id: 'ac', sourceId: 'a', targetId: 'c', weight: 3 },
2033
+ * ],
2034
+ * });
2035
+ *
2036
+ * const path = getAStarPath(graph, {
2037
+ * from: 'a',
2038
+ * to: 'c',
2039
+ * heuristic: (nodeId) => {
2040
+ * const node = graph.nodes.find(n => n.id === nodeId)!;
2041
+ * const target = graph.nodes.find(n => n.id === 'c')!;
2042
+ * return Math.abs(node.x! - target.x!) + Math.abs(node.y! - target.y!);
2043
+ * },
2044
+ * });
2045
+ * // path: a -> b -> c (weight 2, cheaper than direct a -> c)
2046
+ * ```
2047
+ */
2048
+ function getAStarPath(graph, opts) {
2049
+ const idx = getIndex(graph);
2050
+ const { from: sourceId, to: targetId, heuristic } = opts;
2051
+ const getWeight = opts.getWeight ?? ((e) => e.weight ?? 1);
2052
+ const sourceNi = idx.nodeById.get(sourceId);
2053
+ if (sourceNi === void 0) return void 0;
2054
+ if (!idx.nodeById.has(targetId)) return void 0;
2055
+ if (sourceId === targetId) return {
2056
+ source: graph.nodes[sourceNi],
2057
+ steps: []
2058
+ };
2059
+ const gScore = /* @__PURE__ */ new Map();
2060
+ const cameFrom = /* @__PURE__ */ new Map();
2061
+ const closedSet = /* @__PURE__ */ new Set();
2062
+ const openSet = new MinPriorityQueue((a, b) => a.f - b.f);
2063
+ gScore.set(sourceId, 0);
2064
+ openSet.push({
2065
+ id: sourceId,
2066
+ f: heuristic(sourceId)
2067
+ });
2068
+ while (openSet.size > 0) {
2069
+ const { id: currentId } = openSet.pop();
2070
+ if (closedSet.has(currentId)) continue;
2071
+ if (currentId === targetId) {
2072
+ const steps = [];
2073
+ let cur = targetId;
2074
+ while (cur !== sourceId) {
2075
+ const prev = cameFrom.get(cur);
2076
+ const ni = idx.nodeById.get(cur);
2077
+ steps.unshift({
2078
+ edge: prev.edge,
2079
+ node: graph.nodes[ni]
2080
+ });
2081
+ cur = prev.from;
2082
+ }
2083
+ return {
2084
+ source: graph.nodes[sourceNi],
2085
+ steps
2086
+ };
2087
+ }
2088
+ closedSet.add(currentId);
2089
+ for (const { neighborId, edge } of getNeighborEdges(graph, currentId)) {
2090
+ if (closedSet.has(neighborId)) continue;
2091
+ const tentativeG = (gScore.get(currentId) ?? Infinity) + getWeight(edge);
2092
+ if (tentativeG < (gScore.get(neighborId) ?? Infinity)) {
2093
+ cameFrom.set(neighborId, {
2094
+ from: currentId,
2095
+ edge
2096
+ });
2097
+ gScore.set(neighborId, tentativeG);
2098
+ openSet.push({
2099
+ id: neighborId,
2100
+ f: tentativeG + heuristic(neighborId)
2101
+ });
2102
+ }
2103
+ }
2104
+ }
2105
+ }
2106
+ /**
1901
2107
  * Joins two paths end-to-end. The last node of the head path must equal
1902
2108
  * the source of the tail path (the overlap node).
1903
2109
  *
@@ -1933,4 +2139,4 @@ function joinPaths(headPath, tailPath) {
1933
2139
  }
1934
2140
 
1935
2141
  //#endregion
1936
- export { addNode as A, hasNode as B, isAcyclic as C, GraphInstance as D, joinPaths as E, deleteEntities as F, updateEntities as H, deleteNode as I, getEdge as L, createGraphFromTransition as M, createVisualGraph as N, addEdge as O, deleteEdge as P, getNode as R, hasPath as S, isTree as T, updateNode as U, updateEdge as V, getShortestPaths as _, genPreorders as a, getStronglyConnectedComponents as b, getAllPairsShortestPaths as c, getMinimumSpanningTree as d, getPostorder as f, getShortestPath as g, getPreorders as h, genPostorders as i, createGraph as j, addEntities as k, getConnectedComponents as l, getPreorder as m, dfs as n, genShortestPaths as o, getPostorders as p, genCycles as r, genSimplePaths as s, bfs as t, getCycles as u, getSimplePath as v, isConnected as w, getTopologicalSort as x, getSimplePaths as y, hasEdge as z };
2142
+ export { addEntities as A, getEdge as B, hasPath as C, joinPaths as D, isTree as E, createGraphNode as F, updateEntities as G, hasEdge as H, createVisualGraph as I, updateNode as K, deleteEdge as L, createGraph as M, createGraphEdge as N, GraphInstance as O, createGraphFromTransition as P, deleteEntities as R, getTopologicalSort as S, isConnected as T, hasNode as U, getNode as V, updateEdge as W, getShortestPath as _, genPreorders as a, getSimplePaths as b, getAStarPath as c, getCycles as d, getMinimumSpanningTree as f, getPreorders as g, getPreorder as h, genPostorders as i, addNode as j, addEdge as k, getAllPairsShortestPaths as l, getPostorders as m, dfs as n, genShortestPaths as o, getPostorder as p, genCycles as r, genSimplePaths as s, bfs as t, getConnectedComponents as u, getShortestPaths as v, isAcyclic as w, getStronglyConnectedComponents as x, getSimplePath as y, deleteNode as z };