@statelyai/graph 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -144
- package/dist/{algorithms-DldwenLt.mjs → algorithms-oVD9PYil.mjs} +218 -30
- package/dist/algorithms.d.mts +91 -3
- package/dist/algorithms.mjs +2 -2
- package/dist/formats/adjacency-list/index.d.mts +1 -1
- package/dist/formats/converter/index.d.mts +1 -1
- package/dist/formats/cytoscape/index.d.mts +1 -1
- package/dist/formats/d3/index.d.mts +1 -1
- package/dist/formats/dot/index.d.mts +1 -1
- package/dist/formats/edge-list/index.d.mts +1 -1
- package/dist/formats/elk/index.d.mts +1 -1
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gml/index.d.mts +1 -1
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +147 -55
- package/dist/formats/jgf/index.d.mts +1 -1
- package/dist/formats/mermaid/index.d.mts +6 -1
- package/dist/formats/tgf/index.d.mts +1 -1
- package/dist/formats/xyflow/index.d.mts +1 -1
- package/dist/index.d.mts +100 -3
- package/dist/index.mjs +364 -8
- package/dist/queries.d.mts +1 -1
- package/dist/schemas.d.mts +2 -0
- package/dist/schemas.mjs +1 -0
- package/dist/{types-FBZCrmnG.d.mts → types-DF-HNw50.d.mts} +59 -7
- package/package.json +1 -1
|
@@ -30,9 +30,10 @@ function resolveEdge(config) {
|
|
|
30
30
|
id: config.id,
|
|
31
31
|
sourceId: config.sourceId,
|
|
32
32
|
targetId: config.targetId,
|
|
33
|
-
label: config.label ??
|
|
33
|
+
label: config.label ?? null,
|
|
34
34
|
data: config.data
|
|
35
35
|
};
|
|
36
|
+
if (config.weight !== void 0) edge.weight = config.weight;
|
|
36
37
|
if (config.x !== void 0) edge.x = config.x;
|
|
37
38
|
if (config.y !== void 0) edge.y = config.y;
|
|
38
39
|
if (config.width !== void 0) edge.width = config.width;
|
|
@@ -570,6 +571,8 @@ function collectDescendants(graph, id) {
|
|
|
570
571
|
/**
|
|
571
572
|
* Breadth-first traversal generator yielding nodes level by level.
|
|
572
573
|
*
|
|
574
|
+
* **O(V + E)** time, **O(V)** space.
|
|
575
|
+
*
|
|
573
576
|
* @example
|
|
574
577
|
* ```ts
|
|
575
578
|
* import { createGraph, bfs } from '@statelyai/graph';
|
|
@@ -603,6 +606,8 @@ function* bfs(graph, startId) {
|
|
|
603
606
|
/**
|
|
604
607
|
* Depth-first traversal generator yielding nodes as visited.
|
|
605
608
|
*
|
|
609
|
+
* **O(V + E)** time, **O(V)** space.
|
|
610
|
+
*
|
|
606
611
|
* @example
|
|
607
612
|
* ```ts
|
|
608
613
|
* import { createGraph, dfs } from '@statelyai/graph';
|
|
@@ -648,9 +653,56 @@ function getSuccessorIds(graph, nodeId) {
|
|
|
648
653
|
const idx = getIndex(graph);
|
|
649
654
|
return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
|
|
650
655
|
}
|
|
656
|
+
var MinPriorityQueue = class {
|
|
657
|
+
items = [];
|
|
658
|
+
constructor(compare) {
|
|
659
|
+
this.compare = compare;
|
|
660
|
+
}
|
|
661
|
+
get size() {
|
|
662
|
+
return this.items.length;
|
|
663
|
+
}
|
|
664
|
+
push(item) {
|
|
665
|
+
this.items.push(item);
|
|
666
|
+
this.bubbleUp(this.items.length - 1);
|
|
667
|
+
}
|
|
668
|
+
pop() {
|
|
669
|
+
if (this.items.length === 0) return void 0;
|
|
670
|
+
const first = this.items[0];
|
|
671
|
+
const last = this.items.pop();
|
|
672
|
+
if (this.items.length > 0) {
|
|
673
|
+
this.items[0] = last;
|
|
674
|
+
this.bubbleDown(0);
|
|
675
|
+
}
|
|
676
|
+
return first;
|
|
677
|
+
}
|
|
678
|
+
bubbleUp(index) {
|
|
679
|
+
let current = index;
|
|
680
|
+
while (current > 0) {
|
|
681
|
+
const parent = Math.floor((current - 1) / 2);
|
|
682
|
+
if (this.compare(this.items[current], this.items[parent]) >= 0) break;
|
|
683
|
+
[this.items[current], this.items[parent]] = [this.items[parent], this.items[current]];
|
|
684
|
+
current = parent;
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
bubbleDown(index) {
|
|
688
|
+
let current = index;
|
|
689
|
+
while (true) {
|
|
690
|
+
const left = current * 2 + 1;
|
|
691
|
+
const right = left + 1;
|
|
692
|
+
let smallest = current;
|
|
693
|
+
if (left < this.items.length && this.compare(this.items[left], this.items[smallest]) < 0) smallest = left;
|
|
694
|
+
if (right < this.items.length && this.compare(this.items[right], this.items[smallest]) < 0) smallest = right;
|
|
695
|
+
if (smallest === current) break;
|
|
696
|
+
[this.items[current], this.items[smallest]] = [this.items[smallest], this.items[current]];
|
|
697
|
+
current = smallest;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
};
|
|
651
701
|
/**
|
|
652
702
|
* Checks whether the graph contains no cycles.
|
|
653
703
|
*
|
|
704
|
+
* **O(V + E)** time.
|
|
705
|
+
*
|
|
654
706
|
* @example
|
|
655
707
|
* ```ts
|
|
656
708
|
* import { createGraph, isAcyclic } from '@statelyai/graph';
|
|
@@ -711,6 +763,8 @@ function isAcyclicUndirected(graph) {
|
|
|
711
763
|
* Returns connected components as arrays of nodes.
|
|
712
764
|
* Treats all edges as undirected for connectivity.
|
|
713
765
|
*
|
|
766
|
+
* **O(V + E)** time.
|
|
767
|
+
*
|
|
714
768
|
* @example
|
|
715
769
|
* ```ts
|
|
716
770
|
* import { createGraph, getConnectedComponents } from '@statelyai/graph';
|
|
@@ -763,6 +817,8 @@ function getConnectedComponents(graph) {
|
|
|
763
817
|
/**
|
|
764
818
|
* Returns a topological ordering of nodes, or `null` if the graph is cyclic.
|
|
765
819
|
*
|
|
820
|
+
* **O(V + E)** time (Kahn's algorithm).
|
|
821
|
+
*
|
|
766
822
|
* @example
|
|
767
823
|
* ```ts
|
|
768
824
|
* import { createGraph, getTopologicalSort } from '@statelyai/graph';
|
|
@@ -806,6 +862,8 @@ function getTopologicalSort(graph) {
|
|
|
806
862
|
/**
|
|
807
863
|
* Checks whether a path exists between two nodes.
|
|
808
864
|
*
|
|
865
|
+
* **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
|
|
866
|
+
*
|
|
809
867
|
* @example
|
|
810
868
|
* ```ts
|
|
811
869
|
* import { createGraph, hasPath } from '@statelyai/graph';
|
|
@@ -828,6 +886,8 @@ function hasPath(graph, sourceId, targetId) {
|
|
|
828
886
|
/**
|
|
829
887
|
* Checks whether the graph is connected (all nodes reachable from any node).
|
|
830
888
|
*
|
|
889
|
+
* **O(V + E)** time.
|
|
890
|
+
*
|
|
831
891
|
* @example
|
|
832
892
|
* ```ts
|
|
833
893
|
* import { createGraph, isConnected } from '@statelyai/graph';
|
|
@@ -847,6 +907,8 @@ function isConnected(graph) {
|
|
|
847
907
|
/**
|
|
848
908
|
* Checks whether the graph is a tree (connected and acyclic).
|
|
849
909
|
*
|
|
910
|
+
* **O(V + E)** time.
|
|
911
|
+
*
|
|
850
912
|
* @example
|
|
851
913
|
* ```ts
|
|
852
914
|
* import { createGraph, isTree } from '@statelyai/graph';
|
|
@@ -905,7 +967,7 @@ function getNeighborEdges(graph, nodeId) {
|
|
|
905
967
|
/**
|
|
906
968
|
* Returns all shortest paths from a source node.
|
|
907
969
|
* Returns all paths of equal minimum length per target (not just one).
|
|
908
|
-
* Uses BFS
|
|
970
|
+
* Uses BFS when all edges are unweighted; Dijkstra otherwise.
|
|
909
971
|
*/
|
|
910
972
|
/** Compute distance + prev maps via BFS or Dijkstra. */
|
|
911
973
|
function computeShortestDistances(graph, sourceId, getWeight) {
|
|
@@ -913,7 +975,7 @@ function computeShortestDistances(graph, sourceId, getWeight) {
|
|
|
913
975
|
const prev = /* @__PURE__ */ new Map();
|
|
914
976
|
dist.set(sourceId, 0);
|
|
915
977
|
prev.set(sourceId, []);
|
|
916
|
-
if (!getWeight) {
|
|
978
|
+
if (!getWeight && !graph.edges.some((e) => e.weight !== void 0)) {
|
|
917
979
|
const queue = [sourceId];
|
|
918
980
|
while (queue.length > 0) {
|
|
919
981
|
const id = queue.shift();
|
|
@@ -935,19 +997,19 @@ function computeShortestDistances(graph, sourceId, getWeight) {
|
|
|
935
997
|
}
|
|
936
998
|
}
|
|
937
999
|
} else {
|
|
1000
|
+
const effectiveWeight = getWeight ?? ((e) => e.weight ?? 1);
|
|
938
1001
|
const visited = /* @__PURE__ */ new Set();
|
|
939
|
-
const pq =
|
|
1002
|
+
const pq = new MinPriorityQueue((a, b) => a.dist - b.dist);
|
|
1003
|
+
pq.push({
|
|
940
1004
|
id: sourceId,
|
|
941
1005
|
dist: 0
|
|
942
|
-
}
|
|
943
|
-
while (pq.
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
const { id, dist: d } = pq.splice(minIdx, 1)[0];
|
|
947
|
-
if (visited.has(id)) continue;
|
|
1006
|
+
});
|
|
1007
|
+
while (pq.size > 0) {
|
|
1008
|
+
const { id, dist: d } = pq.pop();
|
|
1009
|
+
if (visited.has(id) || d !== dist.get(id)) continue;
|
|
948
1010
|
visited.add(id);
|
|
949
1011
|
for (const { neighborId, edge } of getNeighborEdges(graph, id)) {
|
|
950
|
-
const newDist = d +
|
|
1012
|
+
const newDist = d + effectiveWeight(edge);
|
|
951
1013
|
const existing = dist.get(neighborId);
|
|
952
1014
|
if (existing === void 0 || newDist < existing) {
|
|
953
1015
|
dist.set(neighborId, newDist);
|
|
@@ -996,6 +1058,9 @@ function* reconstructPaths(graph, prev, sourceNode, targetId) {
|
|
|
996
1058
|
* Lazily yields all shortest paths from a source node.
|
|
997
1059
|
* Use `getShortestPaths` for the full array.
|
|
998
1060
|
*
|
|
1061
|
+
* **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted),
|
|
1062
|
+
* plus **O(P)** per path yielded where P is the path length.
|
|
1063
|
+
*
|
|
999
1064
|
* @example
|
|
1000
1065
|
* ```ts
|
|
1001
1066
|
* import { createGraph, genShortestPaths } from '@statelyai/graph';
|
|
@@ -1027,6 +1092,8 @@ function* genShortestPaths(graph, opts) {
|
|
|
1027
1092
|
* Returns all shortest paths from a source node as an array.
|
|
1028
1093
|
* Delegates to `genShortestPaths` internally.
|
|
1029
1094
|
*
|
|
1095
|
+
* **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
|
|
1096
|
+
*
|
|
1030
1097
|
* @example
|
|
1031
1098
|
* ```ts
|
|
1032
1099
|
* import { createGraph, getShortestPaths } from '@statelyai/graph';
|
|
@@ -1050,6 +1117,8 @@ function getShortestPaths(graph, opts) {
|
|
|
1050
1117
|
/**
|
|
1051
1118
|
* Returns a single shortest path from source to target, or `undefined` if unreachable.
|
|
1052
1119
|
*
|
|
1120
|
+
* **O(V + E)** time (BFS) or **O((V + E) log V)** (Dijkstra when weighted).
|
|
1121
|
+
*
|
|
1053
1122
|
* @example
|
|
1054
1123
|
* ```ts
|
|
1055
1124
|
* import { createGraph, getShortestPath } from '@statelyai/graph';
|
|
@@ -1074,6 +1143,8 @@ function getShortestPath(graph, opts) {
|
|
|
1074
1143
|
* Returns all simple (acyclic) paths from a source node as an array.
|
|
1075
1144
|
* Delegates to `genSimplePaths` internally.
|
|
1076
1145
|
*
|
|
1146
|
+
* **O(V!)** worst-case (exponential in dense graphs).
|
|
1147
|
+
*
|
|
1077
1148
|
* @example
|
|
1078
1149
|
* ```ts
|
|
1079
1150
|
* import { createGraph, getSimplePaths } from '@statelyai/graph';
|
|
@@ -1099,6 +1170,8 @@ function getSimplePaths(graph, opts) {
|
|
|
1099
1170
|
* Lazily yields all simple (acyclic) paths from a source node via DFS backtracking.
|
|
1100
1171
|
* Use `getSimplePaths` for the full array.
|
|
1101
1172
|
*
|
|
1173
|
+
* **O(V!)** worst-case (exponential in dense graphs).
|
|
1174
|
+
*
|
|
1102
1175
|
* @example
|
|
1103
1176
|
* ```ts
|
|
1104
1177
|
* import { createGraph, genSimplePaths } from '@statelyai/graph';
|
|
@@ -1127,22 +1200,21 @@ function* genSimplePaths(graph, opts) {
|
|
|
1127
1200
|
const targetId = opts?.to;
|
|
1128
1201
|
const visited = /* @__PURE__ */ new Set();
|
|
1129
1202
|
const currentSteps = [];
|
|
1130
|
-
|
|
1131
|
-
function dfsCollect(nodeId) {
|
|
1203
|
+
function* dfsCollect(nodeId) {
|
|
1132
1204
|
visited.add(nodeId);
|
|
1133
1205
|
if (targetId !== void 0) {
|
|
1134
1206
|
if (nodeId === targetId) {
|
|
1135
|
-
|
|
1207
|
+
yield {
|
|
1136
1208
|
source: sourceNode,
|
|
1137
1209
|
steps: [...currentSteps]
|
|
1138
|
-
}
|
|
1210
|
+
};
|
|
1139
1211
|
visited.delete(nodeId);
|
|
1140
1212
|
return;
|
|
1141
1213
|
}
|
|
1142
|
-
} else if (currentSteps.length > 0)
|
|
1214
|
+
} else if (currentSteps.length > 0) yield {
|
|
1143
1215
|
source: sourceNode,
|
|
1144
1216
|
steps: [...currentSteps]
|
|
1145
|
-
}
|
|
1217
|
+
};
|
|
1146
1218
|
for (const { neighborId, edge } of getNeighborEdges(graph, nodeId)) if (!visited.has(neighborId)) {
|
|
1147
1219
|
const neighborNi = idx.nodeById.get(neighborId);
|
|
1148
1220
|
const neighborNode = neighborNi !== void 0 ? graph.nodes[neighborNi] : graph.nodes.find((n) => n.id === neighborId);
|
|
@@ -1150,17 +1222,18 @@ function* genSimplePaths(graph, opts) {
|
|
|
1150
1222
|
edge,
|
|
1151
1223
|
node: neighborNode
|
|
1152
1224
|
});
|
|
1153
|
-
dfsCollect(neighborId);
|
|
1225
|
+
yield* dfsCollect(neighborId);
|
|
1154
1226
|
currentSteps.pop();
|
|
1155
1227
|
}
|
|
1156
1228
|
visited.delete(nodeId);
|
|
1157
1229
|
}
|
|
1158
|
-
dfsCollect(sourceId);
|
|
1159
|
-
yield* found;
|
|
1230
|
+
yield* dfsCollect(sourceId);
|
|
1160
1231
|
}
|
|
1161
1232
|
/**
|
|
1162
1233
|
* Returns a single simple (acyclic) path from source to target, or `undefined` if unreachable.
|
|
1163
1234
|
*
|
|
1235
|
+
* **O(V + E)** typical, **O(V!)** worst-case.
|
|
1236
|
+
*
|
|
1164
1237
|
* @example
|
|
1165
1238
|
* ```ts
|
|
1166
1239
|
* import { createGraph, getSimplePath } from '@statelyai/graph';
|
|
@@ -1185,6 +1258,8 @@ function getSimplePath(graph, opts) {
|
|
|
1185
1258
|
* Returns strongly connected components using Tarjan's algorithm.
|
|
1186
1259
|
* Only meaningful for directed graphs.
|
|
1187
1260
|
*
|
|
1261
|
+
* **O(V + E)** time.
|
|
1262
|
+
*
|
|
1188
1263
|
* @example
|
|
1189
1264
|
* ```ts
|
|
1190
1265
|
* import { createGraph, getStronglyConnectedComponents } from '@statelyai/graph';
|
|
@@ -1244,6 +1319,8 @@ function getStronglyConnectedComponents(graph) {
|
|
|
1244
1319
|
* Returns all elementary cycles as an array of paths.
|
|
1245
1320
|
* Delegates to `genCycles` internally.
|
|
1246
1321
|
*
|
|
1322
|
+
* **O((V + E) · C)** where C is the number of elementary cycles (can be exponential).
|
|
1323
|
+
*
|
|
1247
1324
|
* @example
|
|
1248
1325
|
* ```ts
|
|
1249
1326
|
* import { createGraph, getCycles } from '@statelyai/graph';
|
|
@@ -1267,6 +1344,8 @@ function getCycles(graph) {
|
|
|
1267
1344
|
* Lazily yields elementary cycles one at a time.
|
|
1268
1345
|
* Use `getCycles` for the full array.
|
|
1269
1346
|
*
|
|
1347
|
+
* **O((V + E) · C)** where C is the number of elementary cycles (can be exponential).
|
|
1348
|
+
*
|
|
1270
1349
|
* @example
|
|
1271
1350
|
* ```ts
|
|
1272
1351
|
* import { createGraph, genCycles } from '@statelyai/graph';
|
|
@@ -1406,6 +1485,8 @@ function getNeighborEdgesAll(graph, nodeId) {
|
|
|
1406
1485
|
* Returns a single canonical preorder (DFS visit-order) sequence.
|
|
1407
1486
|
* Visits neighbors in the order they appear in the adjacency list.
|
|
1408
1487
|
*
|
|
1488
|
+
* **O(V + E)** time.
|
|
1489
|
+
*
|
|
1409
1490
|
* @example
|
|
1410
1491
|
* ```ts
|
|
1411
1492
|
* import { createGraph, getPreorder } from '@statelyai/graph';
|
|
@@ -1449,6 +1530,8 @@ function getPreorder(graph, opts) {
|
|
|
1449
1530
|
* Returns a single canonical postorder (DFS finish-order) sequence.
|
|
1450
1531
|
* Visits neighbors in the order they appear in the adjacency list.
|
|
1451
1532
|
*
|
|
1533
|
+
* **O(V + E)** time.
|
|
1534
|
+
*
|
|
1452
1535
|
* @example
|
|
1453
1536
|
* ```ts
|
|
1454
1537
|
* import { createGraph, getPostorder } from '@statelyai/graph';
|
|
@@ -1490,6 +1573,8 @@ function getPostorder(graph, opts) {
|
|
|
1490
1573
|
/**
|
|
1491
1574
|
* Returns all possible preorder sequences as an array. Can be exponential -- prefer `genPreorders`.
|
|
1492
1575
|
*
|
|
1576
|
+
* **O(V! · V)** worst-case (exponential).
|
|
1577
|
+
*
|
|
1493
1578
|
* @example
|
|
1494
1579
|
* ```ts
|
|
1495
1580
|
* import { createGraph, getPreorders } from '@statelyai/graph';
|
|
@@ -1513,6 +1598,8 @@ function getPreorders(graph, opts) {
|
|
|
1513
1598
|
/**
|
|
1514
1599
|
* Returns all possible postorder sequences as an array. Can be exponential -- prefer `genPostorders`.
|
|
1515
1600
|
*
|
|
1601
|
+
* **O(V! · V)** worst-case (exponential).
|
|
1602
|
+
*
|
|
1516
1603
|
* @example
|
|
1517
1604
|
* ```ts
|
|
1518
1605
|
* import { createGraph, getPostorders } from '@statelyai/graph';
|
|
@@ -1538,6 +1625,8 @@ function getPostorders(graph, opts) {
|
|
|
1538
1625
|
* Different neighbor exploration orders yield different sequences.
|
|
1539
1626
|
* Use `getPreorder()` for a single canonical ordering.
|
|
1540
1627
|
*
|
|
1628
|
+
* **O(V! · V)** worst-case (exponential).
|
|
1629
|
+
*
|
|
1541
1630
|
* @example
|
|
1542
1631
|
* ```ts
|
|
1543
1632
|
* import { createGraph, genPreorders } from '@statelyai/graph';
|
|
@@ -1602,6 +1691,8 @@ function* genPreorders(graph, opts) {
|
|
|
1602
1691
|
* Different neighbor exploration orders yield different sequences.
|
|
1603
1692
|
* Use `getPostorder()` for a single canonical ordering.
|
|
1604
1693
|
*
|
|
1694
|
+
* **O(V! · V)** worst-case (exponential).
|
|
1695
|
+
*
|
|
1605
1696
|
* @example
|
|
1606
1697
|
* ```ts
|
|
1607
1698
|
* import { createGraph, genPostorders } from '@statelyai/graph';
|
|
@@ -1665,6 +1756,8 @@ function* genPostorders(graph, opts) {
|
|
|
1665
1756
|
* Only meaningful for connected undirected graphs (or the component reachable
|
|
1666
1757
|
* from an arbitrary start node in directed graphs).
|
|
1667
1758
|
*
|
|
1759
|
+
* **O(E log E)** using either edge sorting (Kruskal) or a min-heap (Prim).
|
|
1760
|
+
*
|
|
1668
1761
|
* @example
|
|
1669
1762
|
* ```ts
|
|
1670
1763
|
* import { createGraph, getMinimumSpanningTree } from '@statelyai/graph';
|
|
@@ -1687,7 +1780,7 @@ function* genPostorders(graph, opts) {
|
|
|
1687
1780
|
*/
|
|
1688
1781
|
function getMinimumSpanningTree(graph, opts) {
|
|
1689
1782
|
const algorithm = opts?.algorithm ?? "prim";
|
|
1690
|
-
const getWeight = opts?.getWeight ?? (() => 1);
|
|
1783
|
+
const getWeight = opts?.getWeight ?? ((e) => e.weight ?? 1);
|
|
1691
1784
|
const mstEdges = algorithm === "kruskal" ? kruskalMST(graph, getWeight) : primMST(graph, getWeight);
|
|
1692
1785
|
return createGraph({
|
|
1693
1786
|
id: graph.id,
|
|
@@ -1705,7 +1798,8 @@ function getMinimumSpanningTree(graph, opts) {
|
|
|
1705
1798
|
sourceId: e.sourceId,
|
|
1706
1799
|
targetId: e.targetId,
|
|
1707
1800
|
label: e.label,
|
|
1708
|
-
data: e.data
|
|
1801
|
+
data: e.data,
|
|
1802
|
+
...e.weight !== void 0 && { weight: e.weight }
|
|
1709
1803
|
}))
|
|
1710
1804
|
});
|
|
1711
1805
|
}
|
|
@@ -1714,7 +1808,7 @@ function primMST(graph, getWeight) {
|
|
|
1714
1808
|
const idx = getIndex(graph);
|
|
1715
1809
|
const inMST = /* @__PURE__ */ new Set();
|
|
1716
1810
|
const mstEdges = [];
|
|
1717
|
-
const candidates =
|
|
1811
|
+
const candidates = new MinPriorityQueue((a, b) => a.weight - b.weight);
|
|
1718
1812
|
function addEdgesOf(nodeId) {
|
|
1719
1813
|
for (const eid of idx.outEdges.get(nodeId) ?? []) {
|
|
1720
1814
|
const ai = idx.edgeById.get(eid);
|
|
@@ -1738,10 +1832,8 @@ function primMST(graph, getWeight) {
|
|
|
1738
1832
|
const startId = graph.nodes[0].id;
|
|
1739
1833
|
inMST.add(startId);
|
|
1740
1834
|
addEdgesOf(startId);
|
|
1741
|
-
while (candidates.
|
|
1742
|
-
|
|
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];
|
|
1835
|
+
while (candidates.size > 0 && inMST.size < graph.nodes.length) {
|
|
1836
|
+
const { edge } = candidates.pop();
|
|
1745
1837
|
const targetId = graph.type === "undirected" && inMST.has(edge.targetId) ? edge.sourceId : edge.targetId;
|
|
1746
1838
|
if (inMST.has(targetId)) continue;
|
|
1747
1839
|
inMST.add(targetId);
|
|
@@ -1780,7 +1872,9 @@ function kruskalMST(graph, getWeight) {
|
|
|
1780
1872
|
/**
|
|
1781
1873
|
* Returns shortest paths between all pairs of nodes.
|
|
1782
1874
|
* Algorithm 'dijkstra' (default): runs getShortestPaths per source node.
|
|
1783
|
-
* Algorithm 'floyd-warshall': classic
|
|
1875
|
+
* Algorithm 'floyd-warshall': classic dynamic programming.
|
|
1876
|
+
*
|
|
1877
|
+
* **O(V · (V + E) log V)** (Dijkstra) or **O(V³)** (Floyd-Warshall).
|
|
1784
1878
|
*
|
|
1785
1879
|
* @example
|
|
1786
1880
|
* ```ts
|
|
@@ -1815,7 +1909,7 @@ function dijkstraAllPaths(graph, getWeight) {
|
|
|
1815
1909
|
}
|
|
1816
1910
|
function floydWarshallAllPaths(graph, getWeight) {
|
|
1817
1911
|
const idx = getIndex(graph);
|
|
1818
|
-
const w = getWeight ?? (() => 1);
|
|
1912
|
+
const w = getWeight ?? ((e) => e.weight ?? 1);
|
|
1819
1913
|
const nodeIds = graph.nodes.map((n$1) => n$1.id);
|
|
1820
1914
|
const n = nodeIds.length;
|
|
1821
1915
|
const idxOf = /* @__PURE__ */ new Map();
|
|
@@ -1898,6 +1992,100 @@ function fwReconstruct(graph, prev, idxOf, nodeIds, sourceNode, sourceIdx, targe
|
|
|
1898
1992
|
return results;
|
|
1899
1993
|
}
|
|
1900
1994
|
/**
|
|
1995
|
+
* Returns a shortest path using A* search with an admissible heuristic.
|
|
1996
|
+
* More efficient than Dijkstra when a good heuristic is available.
|
|
1997
|
+
*
|
|
1998
|
+
* **O((V + E) log V)** time with a good heuristic; degrades to Dijkstra
|
|
1999
|
+
* with `heuristic: () => 0`.
|
|
2000
|
+
*
|
|
2001
|
+
* @example
|
|
2002
|
+
* ```ts
|
|
2003
|
+
* import { createGraph, getAStarPath } from '@statelyai/graph';
|
|
2004
|
+
*
|
|
2005
|
+
* const graph = createGraph({
|
|
2006
|
+
* nodes: [
|
|
2007
|
+
* { id: 'a', x: 0, y: 0 },
|
|
2008
|
+
* { id: 'b', x: 1, y: 0 },
|
|
2009
|
+
* { id: 'c', x: 1, y: 1 },
|
|
2010
|
+
* ],
|
|
2011
|
+
* edges: [
|
|
2012
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b', weight: 1 },
|
|
2013
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c', weight: 1 },
|
|
2014
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c', weight: 3 },
|
|
2015
|
+
* ],
|
|
2016
|
+
* });
|
|
2017
|
+
*
|
|
2018
|
+
* const path = getAStarPath(graph, {
|
|
2019
|
+
* from: 'a',
|
|
2020
|
+
* to: 'c',
|
|
2021
|
+
* heuristic: (nodeId) => {
|
|
2022
|
+
* const node = graph.nodes.find(n => n.id === nodeId)!;
|
|
2023
|
+
* const target = graph.nodes.find(n => n.id === 'c')!;
|
|
2024
|
+
* return Math.abs(node.x! - target.x!) + Math.abs(node.y! - target.y!);
|
|
2025
|
+
* },
|
|
2026
|
+
* });
|
|
2027
|
+
* // path: a -> b -> c (weight 2, cheaper than direct a -> c)
|
|
2028
|
+
* ```
|
|
2029
|
+
*/
|
|
2030
|
+
function getAStarPath(graph, opts) {
|
|
2031
|
+
const idx = getIndex(graph);
|
|
2032
|
+
const { from: sourceId, to: targetId, heuristic } = opts;
|
|
2033
|
+
const getWeight = opts.getWeight ?? ((e) => e.weight ?? 1);
|
|
2034
|
+
const sourceNi = idx.nodeById.get(sourceId);
|
|
2035
|
+
if (sourceNi === void 0) return void 0;
|
|
2036
|
+
if (!idx.nodeById.has(targetId)) return void 0;
|
|
2037
|
+
if (sourceId === targetId) return {
|
|
2038
|
+
source: graph.nodes[sourceNi],
|
|
2039
|
+
steps: []
|
|
2040
|
+
};
|
|
2041
|
+
const gScore = /* @__PURE__ */ new Map();
|
|
2042
|
+
const cameFrom = /* @__PURE__ */ new Map();
|
|
2043
|
+
const closedSet = /* @__PURE__ */ new Set();
|
|
2044
|
+
const openSet = new MinPriorityQueue((a, b) => a.f - b.f);
|
|
2045
|
+
gScore.set(sourceId, 0);
|
|
2046
|
+
openSet.push({
|
|
2047
|
+
id: sourceId,
|
|
2048
|
+
f: heuristic(sourceId)
|
|
2049
|
+
});
|
|
2050
|
+
while (openSet.size > 0) {
|
|
2051
|
+
const { id: currentId } = openSet.pop();
|
|
2052
|
+
if (closedSet.has(currentId)) continue;
|
|
2053
|
+
if (currentId === targetId) {
|
|
2054
|
+
const steps = [];
|
|
2055
|
+
let cur = targetId;
|
|
2056
|
+
while (cur !== sourceId) {
|
|
2057
|
+
const prev = cameFrom.get(cur);
|
|
2058
|
+
const ni = idx.nodeById.get(cur);
|
|
2059
|
+
steps.unshift({
|
|
2060
|
+
edge: prev.edge,
|
|
2061
|
+
node: graph.nodes[ni]
|
|
2062
|
+
});
|
|
2063
|
+
cur = prev.from;
|
|
2064
|
+
}
|
|
2065
|
+
return {
|
|
2066
|
+
source: graph.nodes[sourceNi],
|
|
2067
|
+
steps
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
closedSet.add(currentId);
|
|
2071
|
+
for (const { neighborId, edge } of getNeighborEdges(graph, currentId)) {
|
|
2072
|
+
if (closedSet.has(neighborId)) continue;
|
|
2073
|
+
const tentativeG = (gScore.get(currentId) ?? Infinity) + getWeight(edge);
|
|
2074
|
+
if (tentativeG < (gScore.get(neighborId) ?? Infinity)) {
|
|
2075
|
+
cameFrom.set(neighborId, {
|
|
2076
|
+
from: currentId,
|
|
2077
|
+
edge
|
|
2078
|
+
});
|
|
2079
|
+
gScore.set(neighborId, tentativeG);
|
|
2080
|
+
openSet.push({
|
|
2081
|
+
id: neighborId,
|
|
2082
|
+
f: tentativeG + heuristic(neighborId)
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
/**
|
|
1901
2089
|
* Joins two paths end-to-end. The last node of the head path must equal
|
|
1902
2090
|
* the source of the tail path (the overlap node).
|
|
1903
2091
|
*
|
|
@@ -1933,4 +2121,4 @@ function joinPaths(headPath, tailPath) {
|
|
|
1933
2121
|
}
|
|
1934
2122
|
|
|
1935
2123
|
//#endregion
|
|
1936
|
-
export {
|
|
2124
|
+
export { addEntities as A, hasEdge as B, hasPath as C, joinPaths as D, isTree as E, deleteEdge as F, updateEdge as H, deleteEntities as I, deleteNode as L, createGraph as M, createGraphFromTransition as N, GraphInstance as O, createVisualGraph as P, getEdge as R, getTopologicalSort as S, isConnected as T, updateEntities as U, hasNode as V, updateNode 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, getNode as z };
|