@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.
- package/README.md +63 -144
- package/dist/{algorithms-DldwenLt.mjs → algorithms-Dw5jEwDK.mjs} +242 -36
- 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/mermaid/index.mjs +20 -6
- package/dist/formats/tgf/index.d.mts +1 -1
- package/dist/formats/xyflow/index.d.mts +1 -1
- package/dist/index.d.mts +120 -3
- package/dist/index.mjs +364 -8
- package/dist/queries.d.mts +1 -1
- package/dist/queries.mjs +1 -1
- package/dist/schemas.d.mts +7 -5
- package/dist/schemas.mjs +4 -3
- package/dist/{types-FBZCrmnG.d.mts → types-DkKjaQW3.d.mts} +61 -9
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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(
|
|
61
|
-
edges: (config?.edges ?? []).map(
|
|
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 =
|
|
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 =
|
|
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
|
|
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.
|
|
944
|
-
|
|
945
|
-
|
|
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 +
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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.
|
|
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];
|
|
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
|
|
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 {
|
|
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 };
|