@statelyai/graph 0.3.0 → 0.3.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.
- package/dist/{adjacency-list-A4_Eiwj3.mjs → adjacency-list-ITO40kmn.mjs} +33 -0
- package/dist/{algorithms-DBU7nmIV.mjs → algorithms-NWSB2RWj.mjs} +672 -19
- package/dist/algorithms.d.mts +479 -10
- package/dist/algorithms.mjs +1 -1
- package/dist/converter-CchokMDg.mjs +67 -0
- package/dist/{edge-list-DuHMz8hf.mjs → edge-list-CgX6bBIF.mjs} +32 -0
- package/dist/formats/adjacency-list/index.d.mts +35 -1
- package/dist/formats/adjacency-list/index.mjs +1 -1
- package/dist/formats/converter/index.d.mts +37 -3
- package/dist/formats/converter/index.mjs +1 -1
- package/dist/formats/cytoscape/index.d.mts +50 -2
- package/dist/formats/cytoscape/index.mjs +50 -2
- package/dist/formats/d3/index.d.mts +48 -2
- package/dist/formats/d3/index.mjs +48 -2
- package/dist/formats/dot/index.d.mts +56 -2
- package/dist/formats/dot/index.mjs +55 -2
- package/dist/formats/edge-list/index.d.mts +34 -1
- package/dist/formats/edge-list/index.mjs +1 -1
- package/dist/formats/gexf/index.d.mts +1 -1
- package/dist/formats/gexf/index.mjs +1 -1
- package/dist/formats/gml/index.d.mts +58 -2
- package/dist/formats/gml/index.mjs +57 -2
- package/dist/formats/graphml/index.d.mts +1 -1
- package/dist/formats/graphml/index.mjs +1 -1
- package/dist/formats/jgf/index.d.mts +51 -2
- package/dist/formats/jgf/index.mjs +51 -2
- package/dist/formats/mermaid/index.d.mts +201 -8
- package/dist/formats/mermaid/index.mjs +361 -22
- package/dist/formats/tgf/index.d.mts +47 -2
- package/dist/formats/tgf/index.mjs +46 -2
- package/dist/index.d.mts +320 -14
- package/dist/index.mjs +115 -7
- package/dist/{indexing-BFFVMnjF.mjs → indexing-eNDrXdDA.mjs} +31 -2
- package/dist/queries.d.mts +353 -8
- package/dist/queries.mjs +352 -8
- package/dist/{types-B6Tpeerk.d.mts → types-BDXC1O5b.d.mts} +1 -1
- package/package.json +1 -1
- package/dist/converter-DnbeyE_p.mjs +0 -33
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-
|
|
1
|
+
import { a as indexUpdateEdgeEndpoints, i as indexReparentNode, n as indexAddEdge, o as invalidateIndex, r as indexAddNode, t as getIndex } from "./indexing-eNDrXdDA.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/graph.ts
|
|
4
4
|
function resolveNode(config) {
|
|
@@ -36,7 +36,17 @@ function resolveEdge(config) {
|
|
|
36
36
|
if (config.style !== void 0) edge.style = config.style;
|
|
37
37
|
return edge;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Create a graph from a config. Resolves defaults for all fields.
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```ts
|
|
44
|
+
* const graph = createGraph({
|
|
45
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
46
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
47
|
+
* });
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
40
50
|
function createGraph(config) {
|
|
41
51
|
const graph = {
|
|
42
52
|
id: config?.id ?? "",
|
|
@@ -50,7 +60,18 @@ function createGraph(config) {
|
|
|
50
60
|
if (config?.style !== void 0) graph.style = config.style;
|
|
51
61
|
return graph;
|
|
52
62
|
}
|
|
53
|
-
/**
|
|
63
|
+
/**
|
|
64
|
+
* Create a visual graph with required position/size on all nodes and edges.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```ts
|
|
68
|
+
* const graph = createVisualGraph({
|
|
69
|
+
* nodes: [{ id: 'a', x: 0, y: 0, width: 100, height: 50 }],
|
|
70
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', x: 0, y: 0, width: 0, height: 0 }],
|
|
71
|
+
* });
|
|
72
|
+
* // graph.nodes[0].x === 0
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
54
75
|
function createVisualGraph(config) {
|
|
55
76
|
const base = createGraph(config);
|
|
56
77
|
return {
|
|
@@ -62,7 +83,7 @@ function createVisualGraph(config) {
|
|
|
62
83
|
y: n.y ?? 0,
|
|
63
84
|
width: n.width ?? 0,
|
|
64
85
|
height: n.height ?? 0,
|
|
65
|
-
shape: n.shape
|
|
86
|
+
...n.shape !== void 0 && { shape: n.shape }
|
|
66
87
|
})),
|
|
67
88
|
edges: base.edges.map((e) => ({
|
|
68
89
|
...e,
|
|
@@ -75,11 +96,30 @@ function createVisualGraph(config) {
|
|
|
75
96
|
}
|
|
76
97
|
/**
|
|
77
98
|
* Create a graph by BFS exploration of a transition function.
|
|
78
|
-
* Each unique state becomes a node; each (state, event)
|
|
99
|
+
* Each unique state becomes a node; each (state, event) -> nextState becomes an edge.
|
|
79
100
|
*
|
|
80
101
|
* - Node IDs are determined by `serializeState` (default: `JSON.stringify`).
|
|
81
102
|
* - Edge IDs use the format `sourceId|serializedEvent|targetId` for uniqueness
|
|
82
103
|
* and debuggability. Edge labels are just the serialized event string.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* const graph = createGraphFromTransition(
|
|
108
|
+
* (state, event) => {
|
|
109
|
+
* if (state === 'green' && event === 'TIMER') return 'yellow';
|
|
110
|
+
* if (state === 'yellow' && event === 'TIMER') return 'red';
|
|
111
|
+
* if (state === 'red' && event === 'TIMER') return 'green';
|
|
112
|
+
* return state;
|
|
113
|
+
* },
|
|
114
|
+
* {
|
|
115
|
+
* initialState: 'green',
|
|
116
|
+
* events: ['TIMER'],
|
|
117
|
+
* serializeState: (s) => s,
|
|
118
|
+
* serializeEvent: (e) => e,
|
|
119
|
+
* },
|
|
120
|
+
* );
|
|
121
|
+
* // graph.nodes.length === 3
|
|
122
|
+
* ```
|
|
83
123
|
*/
|
|
84
124
|
function createGraphFromTransition(transition, options) {
|
|
85
125
|
const serializeState = options.serializeState ?? JSON.stringify;
|
|
@@ -139,27 +179,76 @@ function createGraphFromTransition(transition, options) {
|
|
|
139
179
|
edges
|
|
140
180
|
});
|
|
141
181
|
}
|
|
142
|
-
/**
|
|
182
|
+
/**
|
|
183
|
+
* Get a node by id, or `undefined` if not found.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```ts
|
|
187
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
188
|
+
* const node = getNode(graph, 'a'); // GraphNode
|
|
189
|
+
* const missing = getNode(graph, 'z'); // undefined
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
143
192
|
function getNode(graph, id) {
|
|
144
193
|
const arrayIdx = getIndex(graph).nodeById.get(id);
|
|
145
194
|
return arrayIdx !== void 0 ? graph.nodes[arrayIdx] : void 0;
|
|
146
195
|
}
|
|
147
|
-
/**
|
|
196
|
+
/**
|
|
197
|
+
* Get an edge by id, or `undefined` if not found.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* const graph = createGraph({
|
|
202
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
203
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
204
|
+
* });
|
|
205
|
+
* const edge = getEdge(graph, 'e1'); // GraphEdge
|
|
206
|
+
* const missing = getEdge(graph, 'z'); // undefined
|
|
207
|
+
* ```
|
|
208
|
+
*/
|
|
148
209
|
function getEdge(graph, id) {
|
|
149
210
|
const arrayIdx = getIndex(graph).edgeById.get(id);
|
|
150
211
|
return arrayIdx !== void 0 ? graph.edges[arrayIdx] : void 0;
|
|
151
212
|
}
|
|
152
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* Check if a node exists in the graph.
|
|
215
|
+
*
|
|
216
|
+
* @example
|
|
217
|
+
* ```ts
|
|
218
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
219
|
+
* hasNode(graph, 'a'); // true
|
|
220
|
+
* hasNode(graph, 'z'); // false
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
153
223
|
function hasNode(graph, id) {
|
|
154
224
|
return getIndex(graph).nodeById.has(id);
|
|
155
225
|
}
|
|
156
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* Check if an edge exists in the graph.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```ts
|
|
231
|
+
* const graph = createGraph({
|
|
232
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
233
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
234
|
+
* });
|
|
235
|
+
* hasEdge(graph, 'e1'); // true
|
|
236
|
+
* hasEdge(graph, 'z'); // false
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
157
239
|
function hasEdge(graph, id) {
|
|
158
240
|
return getIndex(graph).edgeById.has(id);
|
|
159
241
|
}
|
|
160
242
|
/**
|
|
161
243
|
* **Mutable.** Add a node to the graph. Mutates `graph.nodes` in place.
|
|
162
244
|
* @returns The resolved node that was added.
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```ts
|
|
248
|
+
* const graph = createGraph();
|
|
249
|
+
* const node = addNode(graph, { id: 'a', label: 'Node A' });
|
|
250
|
+
* // graph.nodes.length === 1
|
|
251
|
+
* ```
|
|
163
252
|
*/
|
|
164
253
|
function addNode(graph, config) {
|
|
165
254
|
const idx = getIndex(graph);
|
|
@@ -172,6 +261,13 @@ function addNode(graph, config) {
|
|
|
172
261
|
/**
|
|
173
262
|
* **Mutable.** Add an edge to the graph. Mutates `graph.edges` in place.
|
|
174
263
|
* @returns The resolved edge that was added.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts
|
|
267
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }, { id: 'b' }] });
|
|
268
|
+
* const edge = addEdge(graph, { id: 'e1', sourceId: 'a', targetId: 'b' });
|
|
269
|
+
* // graph.edges.length === 1
|
|
270
|
+
* ```
|
|
175
271
|
*/
|
|
176
272
|
function addEdge(graph, config) {
|
|
177
273
|
const idx = getIndex(graph);
|
|
@@ -188,6 +284,16 @@ function addEdge(graph, config) {
|
|
|
188
284
|
*
|
|
189
285
|
* By default, children are deleted recursively.
|
|
190
286
|
* With `{ reparent: true }`, children are re-parented to the deleted node's parent.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```ts
|
|
290
|
+
* const graph = createGraph({
|
|
291
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
292
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
293
|
+
* });
|
|
294
|
+
* deleteNode(graph, 'a');
|
|
295
|
+
* // graph.nodes.length === 1, edge e1 also removed
|
|
296
|
+
* ```
|
|
191
297
|
*/
|
|
192
298
|
function deleteNode(graph, id, opts) {
|
|
193
299
|
const node = getNode(graph, id);
|
|
@@ -205,6 +311,16 @@ function deleteNode(graph, id, opts) {
|
|
|
205
311
|
}
|
|
206
312
|
/**
|
|
207
313
|
* **Mutable.** Delete an edge. Mutates `graph.edges` in place.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* const graph = createGraph({
|
|
318
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
319
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
320
|
+
* });
|
|
321
|
+
* deleteEdge(graph, 'e1');
|
|
322
|
+
* // graph.edges.length === 0
|
|
323
|
+
* ```
|
|
208
324
|
*/
|
|
209
325
|
function deleteEdge(graph, id) {
|
|
210
326
|
if (!hasEdge(graph, id)) throw new Error(`Edge "${id}" does not exist`);
|
|
@@ -214,6 +330,13 @@ function deleteEdge(graph, id) {
|
|
|
214
330
|
/**
|
|
215
331
|
* **Mutable.** Update a node in place.
|
|
216
332
|
* @returns The updated node.
|
|
333
|
+
*
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* const graph = createGraph({ nodes: [{ id: 'a', label: 'old' }] });
|
|
337
|
+
* const updated = updateNode(graph, 'a', { label: 'new' });
|
|
338
|
+
* // updated.label === 'new'
|
|
339
|
+
* ```
|
|
217
340
|
*/
|
|
218
341
|
function updateNode(graph, id, update) {
|
|
219
342
|
const idx = getIndex(graph);
|
|
@@ -238,6 +361,16 @@ function updateNode(graph, id, update) {
|
|
|
238
361
|
/**
|
|
239
362
|
* **Mutable.** Update an edge in place.
|
|
240
363
|
* @returns The updated edge.
|
|
364
|
+
*
|
|
365
|
+
* @example
|
|
366
|
+
* ```ts
|
|
367
|
+
* const graph = createGraph({
|
|
368
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
369
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b', label: 'old' }],
|
|
370
|
+
* });
|
|
371
|
+
* const updated = updateEdge(graph, 'e1', { label: 'new' });
|
|
372
|
+
* // updated.label === 'new'
|
|
373
|
+
* ```
|
|
241
374
|
*/
|
|
242
375
|
function updateEdge(graph, id, update) {
|
|
243
376
|
const idx = getIndex(graph);
|
|
@@ -262,6 +395,16 @@ function updateEdge(graph, id, update) {
|
|
|
262
395
|
/**
|
|
263
396
|
* **Mutable.** Add multiple nodes and edges to the graph.
|
|
264
397
|
* Nodes are added first, then edges (so edges can reference new nodes).
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
* ```ts
|
|
401
|
+
* const graph = createGraph();
|
|
402
|
+
* addEntities(graph, {
|
|
403
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
404
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
405
|
+
* });
|
|
406
|
+
* // graph.nodes.length === 2, graph.edges.length === 1
|
|
407
|
+
* ```
|
|
265
408
|
*/
|
|
266
409
|
function addEntities(graph, entities) {
|
|
267
410
|
for (const nodeConfig of entities.nodes ?? []) addNode(graph, nodeConfig);
|
|
@@ -270,6 +413,16 @@ function addEntities(graph, entities) {
|
|
|
270
413
|
/**
|
|
271
414
|
* **Mutable.** Delete entities by id(s). Automatically detects whether each id
|
|
272
415
|
* is a node or edge. Node deletions cascade to children and connected edges.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* const graph = createGraph({
|
|
420
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
421
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
422
|
+
* });
|
|
423
|
+
* deleteEntities(graph, ['a', 'e1']);
|
|
424
|
+
* // graph.nodes.length === 1, graph.edges.length === 0
|
|
425
|
+
* ```
|
|
273
426
|
*/
|
|
274
427
|
function deleteEntities(graph, ids, opts) {
|
|
275
428
|
const idArray = Array.isArray(ids) ? ids : [ids];
|
|
@@ -279,6 +432,18 @@ function deleteEntities(graph, ids, opts) {
|
|
|
279
432
|
/**
|
|
280
433
|
* **Mutable.** Update multiple nodes and edges in place.
|
|
281
434
|
* Each entry must include an `id` to identify which entity to update.
|
|
435
|
+
*
|
|
436
|
+
* @example
|
|
437
|
+
* ```ts
|
|
438
|
+
* const graph = createGraph({
|
|
439
|
+
* nodes: [{ id: 'a', label: 'old' }],
|
|
440
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'a', label: 'old' }],
|
|
441
|
+
* });
|
|
442
|
+
* updateEntities(graph, {
|
|
443
|
+
* nodes: [{ id: 'a', label: 'new' }],
|
|
444
|
+
* edges: [{ id: 'e1', label: 'new' }],
|
|
445
|
+
* });
|
|
446
|
+
* ```
|
|
282
447
|
*/
|
|
283
448
|
function updateEntities(graph, updates) {
|
|
284
449
|
for (const nodeUpdate of updates.nodes ?? []) {
|
|
@@ -293,13 +458,33 @@ function updateEntities(graph, updates) {
|
|
|
293
458
|
/**
|
|
294
459
|
* OOP wrapper around a plain `Graph` object.
|
|
295
460
|
* Delegates to the standalone mutable functions.
|
|
461
|
+
*
|
|
462
|
+
* @example
|
|
463
|
+
* ```ts
|
|
464
|
+
* const instance = new GraphInstance({
|
|
465
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
466
|
+
* edges: [{ id: 'e1', sourceId: 'a', targetId: 'b' }],
|
|
467
|
+
* });
|
|
468
|
+
* instance.addNode({ id: 'c' });
|
|
469
|
+
* instance.hasNode('c'); // true
|
|
470
|
+
* instance.toJSON(); // plain Graph object
|
|
471
|
+
* ```
|
|
296
472
|
*/
|
|
297
473
|
var GraphInstance = class GraphInstance {
|
|
298
474
|
graph;
|
|
299
475
|
constructor(config) {
|
|
300
476
|
this.graph = createGraph(config);
|
|
301
477
|
}
|
|
302
|
-
/**
|
|
478
|
+
/**
|
|
479
|
+
* Wrap an existing plain graph object.
|
|
480
|
+
*
|
|
481
|
+
* @example
|
|
482
|
+
* ```ts
|
|
483
|
+
* const graph = createGraph({ nodes: [{ id: 'a' }] });
|
|
484
|
+
* const instance = GraphInstance.from(graph);
|
|
485
|
+
* instance.hasNode('a'); // true
|
|
486
|
+
* ```
|
|
487
|
+
*/
|
|
303
488
|
static from(graph) {
|
|
304
489
|
const instance = Object.create(GraphInstance.prototype);
|
|
305
490
|
instance.graph = graph;
|
|
@@ -377,6 +562,23 @@ function collectDescendants(graph, id) {
|
|
|
377
562
|
|
|
378
563
|
//#endregion
|
|
379
564
|
//#region src/algorithms.ts
|
|
565
|
+
/**
|
|
566
|
+
* Breadth-first traversal generator yielding nodes level by level.
|
|
567
|
+
*
|
|
568
|
+
* @example
|
|
569
|
+
* ```ts
|
|
570
|
+
* import { createGraph, bfs } from '@statelyai/graph';
|
|
571
|
+
*
|
|
572
|
+
* const graph = createGraph({
|
|
573
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
574
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }, { id: 'bc', sourceId: 'b', targetId: 'c' }],
|
|
575
|
+
* });
|
|
576
|
+
*
|
|
577
|
+
* for (const node of bfs(graph, 'a')) {
|
|
578
|
+
* console.log(node.id); // 'a', 'b', 'c'
|
|
579
|
+
* }
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
380
582
|
function* bfs(graph, startId) {
|
|
381
583
|
const idx = getIndex(graph);
|
|
382
584
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -393,6 +595,23 @@ function* bfs(graph, startId) {
|
|
|
393
595
|
}
|
|
394
596
|
}
|
|
395
597
|
}
|
|
598
|
+
/**
|
|
599
|
+
* Depth-first traversal generator yielding nodes as visited.
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* import { createGraph, dfs } from '@statelyai/graph';
|
|
604
|
+
*
|
|
605
|
+
* const graph = createGraph({
|
|
606
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
607
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }, { id: 'bc', sourceId: 'b', targetId: 'c' }],
|
|
608
|
+
* });
|
|
609
|
+
*
|
|
610
|
+
* for (const node of dfs(graph, 'a')) {
|
|
611
|
+
* console.log(node.id); // 'a', 'b', 'c'
|
|
612
|
+
* }
|
|
613
|
+
* ```
|
|
614
|
+
*/
|
|
396
615
|
function* dfs(graph, startId) {
|
|
397
616
|
const idx = getIndex(graph);
|
|
398
617
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -424,6 +643,21 @@ function getSuccessorIds(graph, nodeId) {
|
|
|
424
643
|
const idx = getIndex(graph);
|
|
425
644
|
return (idx.outEdges.get(nodeId) ?? []).map((eid) => graph.edges[idx.edgeById.get(eid)].targetId);
|
|
426
645
|
}
|
|
646
|
+
/**
|
|
647
|
+
* Checks whether the graph contains no cycles.
|
|
648
|
+
*
|
|
649
|
+
* @example
|
|
650
|
+
* ```ts
|
|
651
|
+
* import { createGraph, isAcyclic } from '@statelyai/graph';
|
|
652
|
+
*
|
|
653
|
+
* const dag = createGraph({
|
|
654
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
655
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
|
|
656
|
+
* });
|
|
657
|
+
*
|
|
658
|
+
* isAcyclic(dag); // true
|
|
659
|
+
* ```
|
|
660
|
+
*/
|
|
427
661
|
function isAcyclic(graph) {
|
|
428
662
|
if (graph.type === "undirected") return isAcyclicUndirected(graph);
|
|
429
663
|
const WHITE = 0, GRAY = 1, BLACK = 2;
|
|
@@ -468,6 +702,23 @@ function isAcyclicUndirected(graph) {
|
|
|
468
702
|
for (const n of graph.nodes) if (!visited.has(n.id) && hasCycle(n.id, null)) return false;
|
|
469
703
|
return true;
|
|
470
704
|
}
|
|
705
|
+
/**
|
|
706
|
+
* Returns connected components as arrays of nodes.
|
|
707
|
+
* Treats all edges as undirected for connectivity.
|
|
708
|
+
*
|
|
709
|
+
* @example
|
|
710
|
+
* ```ts
|
|
711
|
+
* import { createGraph, getConnectedComponents } from '@statelyai/graph';
|
|
712
|
+
*
|
|
713
|
+
* const graph = createGraph({
|
|
714
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
715
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
|
|
716
|
+
* });
|
|
717
|
+
*
|
|
718
|
+
* const components = getConnectedComponents(graph);
|
|
719
|
+
* // [[nodeA, nodeB], [nodeC]]
|
|
720
|
+
* ```
|
|
721
|
+
*/
|
|
471
722
|
function getConnectedComponents(graph) {
|
|
472
723
|
const idx = getIndex(graph);
|
|
473
724
|
const visited = /* @__PURE__ */ new Set();
|
|
@@ -504,6 +755,25 @@ function getConnectedComponents(graph) {
|
|
|
504
755
|
}
|
|
505
756
|
return components;
|
|
506
757
|
}
|
|
758
|
+
/**
|
|
759
|
+
* Returns a topological ordering of nodes, or `null` if the graph is cyclic.
|
|
760
|
+
*
|
|
761
|
+
* @example
|
|
762
|
+
* ```ts
|
|
763
|
+
* import { createGraph, getTopologicalSort } from '@statelyai/graph';
|
|
764
|
+
*
|
|
765
|
+
* const graph = createGraph({
|
|
766
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
767
|
+
* edges: [
|
|
768
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
769
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
770
|
+
* ],
|
|
771
|
+
* });
|
|
772
|
+
*
|
|
773
|
+
* const sorted = getTopologicalSort(graph);
|
|
774
|
+
* // [nodeA, nodeB, nodeC]
|
|
775
|
+
* ```
|
|
776
|
+
*/
|
|
507
777
|
function getTopologicalSort(graph) {
|
|
508
778
|
const idx = getIndex(graph);
|
|
509
779
|
const inDeg = /* @__PURE__ */ new Map();
|
|
@@ -528,16 +798,65 @@ function getTopologicalSort(graph) {
|
|
|
528
798
|
if (result.length !== graph.nodes.length) return null;
|
|
529
799
|
return result;
|
|
530
800
|
}
|
|
801
|
+
/**
|
|
802
|
+
* Checks whether a path exists between two nodes.
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```ts
|
|
806
|
+
* import { createGraph, hasPath } from '@statelyai/graph';
|
|
807
|
+
*
|
|
808
|
+
* const graph = createGraph({
|
|
809
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
810
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
|
|
811
|
+
* });
|
|
812
|
+
*
|
|
813
|
+
* hasPath(graph, 'a', 'b'); // true
|
|
814
|
+
* hasPath(graph, 'a', 'c'); // false
|
|
815
|
+
* ```
|
|
816
|
+
*/
|
|
531
817
|
function hasPath(graph, sourceId, targetId) {
|
|
532
818
|
return getShortestPaths(graph, {
|
|
533
819
|
from: sourceId,
|
|
534
820
|
to: targetId
|
|
535
821
|
}).length > 0;
|
|
536
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Checks whether the graph is connected (all nodes reachable from any node).
|
|
825
|
+
*
|
|
826
|
+
* @example
|
|
827
|
+
* ```ts
|
|
828
|
+
* import { createGraph, isConnected } from '@statelyai/graph';
|
|
829
|
+
*
|
|
830
|
+
* const graph = createGraph({
|
|
831
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
832
|
+
* edges: [{ id: 'ab', sourceId: 'a', targetId: 'b' }],
|
|
833
|
+
* });
|
|
834
|
+
*
|
|
835
|
+
* isConnected(graph); // true
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
537
838
|
function isConnected(graph) {
|
|
538
839
|
if (graph.nodes.length === 0) return true;
|
|
539
840
|
return getConnectedComponents(graph).length <= 1;
|
|
540
841
|
}
|
|
842
|
+
/**
|
|
843
|
+
* Checks whether the graph is a tree (connected and acyclic).
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* ```ts
|
|
847
|
+
* import { createGraph, isTree } from '@statelyai/graph';
|
|
848
|
+
*
|
|
849
|
+
* const tree = createGraph({
|
|
850
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
851
|
+
* edges: [
|
|
852
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
853
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
854
|
+
* ],
|
|
855
|
+
* });
|
|
856
|
+
*
|
|
857
|
+
* isTree(tree); // true
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
541
860
|
function isTree(graph) {
|
|
542
861
|
return isConnected(graph) && isAcyclic(graph);
|
|
543
862
|
}
|
|
@@ -671,6 +990,24 @@ function* reconstructPaths(graph, prev, sourceNode, targetId) {
|
|
|
671
990
|
/**
|
|
672
991
|
* Lazily yields all shortest paths from a source node.
|
|
673
992
|
* Use `getShortestPaths` for the full array.
|
|
993
|
+
*
|
|
994
|
+
* @example
|
|
995
|
+
* ```ts
|
|
996
|
+
* import { createGraph, genShortestPaths } from '@statelyai/graph';
|
|
997
|
+
*
|
|
998
|
+
* const graph = createGraph({
|
|
999
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1000
|
+
* edges: [
|
|
1001
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1002
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1003
|
+
* ],
|
|
1004
|
+
* initialNodeId: 'a',
|
|
1005
|
+
* });
|
|
1006
|
+
*
|
|
1007
|
+
* for (const path of genShortestPaths(graph)) {
|
|
1008
|
+
* console.log(path.steps.map(s => s.node.id));
|
|
1009
|
+
* }
|
|
1010
|
+
* ```
|
|
674
1011
|
*/
|
|
675
1012
|
function* genShortestPaths(graph, opts) {
|
|
676
1013
|
const idx = getIndex(graph);
|
|
@@ -681,25 +1018,101 @@ function* genShortestPaths(graph, opts) {
|
|
|
681
1018
|
const sourceNode = sourceNi !== void 0 ? graph.nodes[sourceNi] : graph.nodes.find((n) => n.id === sourceId);
|
|
682
1019
|
for (const targetId of targets) yield* reconstructPaths(graph, prev, sourceNode, targetId);
|
|
683
1020
|
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Returns all shortest paths from a source node as an array.
|
|
1023
|
+
* Delegates to `genShortestPaths` internally.
|
|
1024
|
+
*
|
|
1025
|
+
* @example
|
|
1026
|
+
* ```ts
|
|
1027
|
+
* import { createGraph, getShortestPaths } from '@statelyai/graph';
|
|
1028
|
+
*
|
|
1029
|
+
* const graph = createGraph({
|
|
1030
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1031
|
+
* edges: [
|
|
1032
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1033
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1034
|
+
* ],
|
|
1035
|
+
* initialNodeId: 'a',
|
|
1036
|
+
* });
|
|
1037
|
+
*
|
|
1038
|
+
* const paths = getShortestPaths(graph);
|
|
1039
|
+
* // paths to 'b' and 'c' from 'a'
|
|
1040
|
+
* ```
|
|
1041
|
+
*/
|
|
684
1042
|
function getShortestPaths(graph, opts) {
|
|
685
1043
|
return [...genShortestPaths(graph, opts)];
|
|
686
1044
|
}
|
|
687
1045
|
/**
|
|
688
|
-
* Returns a single shortest path from source to target, or undefined if unreachable.
|
|
1046
|
+
* Returns a single shortest path from source to target, or `undefined` if unreachable.
|
|
1047
|
+
*
|
|
1048
|
+
* @example
|
|
1049
|
+
* ```ts
|
|
1050
|
+
* import { createGraph, getShortestPath } from '@statelyai/graph';
|
|
1051
|
+
*
|
|
1052
|
+
* const graph = createGraph({
|
|
1053
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1054
|
+
* edges: [
|
|
1055
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1056
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1057
|
+
* ],
|
|
1058
|
+
* initialNodeId: 'a',
|
|
1059
|
+
* });
|
|
1060
|
+
*
|
|
1061
|
+
* const path = getShortestPath(graph, { to: 'c' });
|
|
1062
|
+
* // path.steps -> [{node: nodeB, edge: ...}, {node: nodeC, edge: ...}]
|
|
1063
|
+
* ```
|
|
689
1064
|
*/
|
|
690
1065
|
function getShortestPath(graph, opts) {
|
|
691
1066
|
for (const path of genShortestPaths(graph, opts)) return path;
|
|
692
1067
|
}
|
|
693
1068
|
/**
|
|
694
|
-
* Returns all simple (acyclic) paths from a source node.
|
|
695
|
-
*
|
|
1069
|
+
* Returns all simple (acyclic) paths from a source node as an array.
|
|
1070
|
+
* Delegates to `genSimplePaths` internally.
|
|
1071
|
+
*
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```ts
|
|
1074
|
+
* import { createGraph, getSimplePaths } from '@statelyai/graph';
|
|
1075
|
+
*
|
|
1076
|
+
* const graph = createGraph({
|
|
1077
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1078
|
+
* edges: [
|
|
1079
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1080
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1081
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1082
|
+
* ],
|
|
1083
|
+
* initialNodeId: 'a',
|
|
1084
|
+
* });
|
|
1085
|
+
*
|
|
1086
|
+
* const paths = getSimplePaths(graph, { to: 'c' });
|
|
1087
|
+
* // two paths: a->b->c and a->c
|
|
1088
|
+
* ```
|
|
696
1089
|
*/
|
|
697
1090
|
function getSimplePaths(graph, opts) {
|
|
698
1091
|
return [...genSimplePaths(graph, opts)];
|
|
699
1092
|
}
|
|
700
1093
|
/**
|
|
701
|
-
* Lazily yields all simple (acyclic) paths from a source node.
|
|
1094
|
+
* Lazily yields all simple (acyclic) paths from a source node via DFS backtracking.
|
|
702
1095
|
* Use `getSimplePaths` for the full array.
|
|
1096
|
+
*
|
|
1097
|
+
* @example
|
|
1098
|
+
* ```ts
|
|
1099
|
+
* import { createGraph, genSimplePaths } from '@statelyai/graph';
|
|
1100
|
+
*
|
|
1101
|
+
* const graph = createGraph({
|
|
1102
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1103
|
+
* edges: [
|
|
1104
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1105
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1106
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1107
|
+
* ],
|
|
1108
|
+
* initialNodeId: 'a',
|
|
1109
|
+
* });
|
|
1110
|
+
*
|
|
1111
|
+
* for (const path of genSimplePaths(graph, { to: 'c' })) {
|
|
1112
|
+
* console.log(path.steps.map(s => s.node.id));
|
|
1113
|
+
* // ['b', 'c'] or ['c']
|
|
1114
|
+
* }
|
|
1115
|
+
* ```
|
|
703
1116
|
*/
|
|
704
1117
|
function* genSimplePaths(graph, opts) {
|
|
705
1118
|
const idx = getIndex(graph);
|
|
@@ -741,11 +1154,49 @@ function* genSimplePaths(graph, opts) {
|
|
|
741
1154
|
yield* found;
|
|
742
1155
|
}
|
|
743
1156
|
/**
|
|
744
|
-
* Returns a single simple (acyclic) path from source to target, or undefined if unreachable.
|
|
1157
|
+
* Returns a single simple (acyclic) path from source to target, or `undefined` if unreachable.
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```ts
|
|
1161
|
+
* import { createGraph, getSimplePath } from '@statelyai/graph';
|
|
1162
|
+
*
|
|
1163
|
+
* const graph = createGraph({
|
|
1164
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1165
|
+
* edges: [
|
|
1166
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1167
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1168
|
+
* ],
|
|
1169
|
+
* initialNodeId: 'a',
|
|
1170
|
+
* });
|
|
1171
|
+
*
|
|
1172
|
+
* const path = getSimplePath(graph, { to: 'c' });
|
|
1173
|
+
* // path.steps -> [{node: nodeB, edge: ...}, {node: nodeC, edge: ...}]
|
|
1174
|
+
* ```
|
|
745
1175
|
*/
|
|
746
1176
|
function getSimplePath(graph, opts) {
|
|
747
1177
|
for (const path of genSimplePaths(graph, opts)) return path;
|
|
748
1178
|
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Returns strongly connected components using Tarjan's algorithm.
|
|
1181
|
+
* Only meaningful for directed graphs.
|
|
1182
|
+
*
|
|
1183
|
+
* @example
|
|
1184
|
+
* ```ts
|
|
1185
|
+
* import { createGraph, getStronglyConnectedComponents } from '@statelyai/graph';
|
|
1186
|
+
*
|
|
1187
|
+
* const graph = createGraph({
|
|
1188
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1189
|
+
* edges: [
|
|
1190
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1191
|
+
* { id: 'ba', sourceId: 'b', targetId: 'a' },
|
|
1192
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1193
|
+
* ],
|
|
1194
|
+
* });
|
|
1195
|
+
*
|
|
1196
|
+
* const sccs = getStronglyConnectedComponents(graph);
|
|
1197
|
+
* // [[nodeA, nodeB], [nodeC]]
|
|
1198
|
+
* ```
|
|
1199
|
+
*/
|
|
749
1200
|
function getStronglyConnectedComponents(graph) {
|
|
750
1201
|
const idx = getIndex(graph);
|
|
751
1202
|
let indexCounter = 0;
|
|
@@ -784,12 +1235,49 @@ function getStronglyConnectedComponents(graph) {
|
|
|
784
1235
|
for (const n of graph.nodes) if (!nodeIndex.has(n.id)) strongconnect(n.id);
|
|
785
1236
|
return result;
|
|
786
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Returns all elementary cycles as an array of paths.
|
|
1240
|
+
* Delegates to `genCycles` internally.
|
|
1241
|
+
*
|
|
1242
|
+
* @example
|
|
1243
|
+
* ```ts
|
|
1244
|
+
* import { createGraph, getCycles } from '@statelyai/graph';
|
|
1245
|
+
*
|
|
1246
|
+
* const graph = createGraph({
|
|
1247
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
1248
|
+
* edges: [
|
|
1249
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1250
|
+
* { id: 'ba', sourceId: 'b', targetId: 'a' },
|
|
1251
|
+
* ],
|
|
1252
|
+
* });
|
|
1253
|
+
*
|
|
1254
|
+
* const cycles = getCycles(graph);
|
|
1255
|
+
* // one cycle: a -> b -> a
|
|
1256
|
+
* ```
|
|
1257
|
+
*/
|
|
787
1258
|
function getCycles(graph) {
|
|
788
1259
|
return [...genCycles(graph)];
|
|
789
1260
|
}
|
|
790
1261
|
/**
|
|
791
|
-
* Lazily yields cycles one at a time.
|
|
1262
|
+
* Lazily yields elementary cycles one at a time.
|
|
792
1263
|
* Use `getCycles` for the full array.
|
|
1264
|
+
*
|
|
1265
|
+
* @example
|
|
1266
|
+
* ```ts
|
|
1267
|
+
* import { createGraph, genCycles } from '@statelyai/graph';
|
|
1268
|
+
*
|
|
1269
|
+
* const graph = createGraph({
|
|
1270
|
+
* nodes: [{ id: 'a' }, { id: 'b' }],
|
|
1271
|
+
* edges: [
|
|
1272
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1273
|
+
* { id: 'ba', sourceId: 'b', targetId: 'a' },
|
|
1274
|
+
* ],
|
|
1275
|
+
* });
|
|
1276
|
+
*
|
|
1277
|
+
* for (const cycle of genCycles(graph)) {
|
|
1278
|
+
* console.log(cycle.steps.map(s => s.node.id)); // ['b', 'a']
|
|
1279
|
+
* }
|
|
1280
|
+
* ```
|
|
793
1281
|
*/
|
|
794
1282
|
function* genCycles(graph) {
|
|
795
1283
|
if (graph.type === "undirected") yield* genCyclesUndirected(graph);
|
|
@@ -912,6 +1400,23 @@ function getNeighborEdgesAll(graph, nodeId) {
|
|
|
912
1400
|
/**
|
|
913
1401
|
* Returns a single canonical preorder (DFS visit-order) sequence.
|
|
914
1402
|
* Visits neighbors in the order they appear in the adjacency list.
|
|
1403
|
+
*
|
|
1404
|
+
* @example
|
|
1405
|
+
* ```ts
|
|
1406
|
+
* import { createGraph, getPreorder } from '@statelyai/graph';
|
|
1407
|
+
*
|
|
1408
|
+
* const graph = createGraph({
|
|
1409
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1410
|
+
* edges: [
|
|
1411
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1412
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1413
|
+
* ],
|
|
1414
|
+
* initialNodeId: 'a',
|
|
1415
|
+
* });
|
|
1416
|
+
*
|
|
1417
|
+
* const order = getPreorder(graph);
|
|
1418
|
+
* // [nodeA, nodeB, nodeC]
|
|
1419
|
+
* ```
|
|
915
1420
|
*/
|
|
916
1421
|
function getPreorder(graph, opts) {
|
|
917
1422
|
const idx = getIndex(graph);
|
|
@@ -938,6 +1443,23 @@ function getPreorder(graph, opts) {
|
|
|
938
1443
|
/**
|
|
939
1444
|
* Returns a single canonical postorder (DFS finish-order) sequence.
|
|
940
1445
|
* Visits neighbors in the order they appear in the adjacency list.
|
|
1446
|
+
*
|
|
1447
|
+
* @example
|
|
1448
|
+
* ```ts
|
|
1449
|
+
* import { createGraph, getPostorder } from '@statelyai/graph';
|
|
1450
|
+
*
|
|
1451
|
+
* const graph = createGraph({
|
|
1452
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1453
|
+
* edges: [
|
|
1454
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1455
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1456
|
+
* ],
|
|
1457
|
+
* initialNodeId: 'a',
|
|
1458
|
+
* });
|
|
1459
|
+
*
|
|
1460
|
+
* const order = getPostorder(graph);
|
|
1461
|
+
* // [nodeC, nodeB, nodeA]
|
|
1462
|
+
* ```
|
|
941
1463
|
*/
|
|
942
1464
|
function getPostorder(graph, opts) {
|
|
943
1465
|
const idx = getIndex(graph);
|
|
@@ -960,11 +1482,49 @@ function getPostorder(graph, opts) {
|
|
|
960
1482
|
}
|
|
961
1483
|
return result;
|
|
962
1484
|
}
|
|
963
|
-
/**
|
|
1485
|
+
/**
|
|
1486
|
+
* Returns all possible preorder sequences as an array. Can be exponential -- prefer `genPreorders`.
|
|
1487
|
+
*
|
|
1488
|
+
* @example
|
|
1489
|
+
* ```ts
|
|
1490
|
+
* import { createGraph, getPreorders } from '@statelyai/graph';
|
|
1491
|
+
*
|
|
1492
|
+
* const graph = createGraph({
|
|
1493
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1494
|
+
* edges: [
|
|
1495
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1496
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1497
|
+
* ],
|
|
1498
|
+
* initialNodeId: 'a',
|
|
1499
|
+
* });
|
|
1500
|
+
*
|
|
1501
|
+
* const allOrders = getPreorders(graph);
|
|
1502
|
+
* // [[nodeA, nodeB, nodeC], [nodeA, nodeC, nodeB]]
|
|
1503
|
+
* ```
|
|
1504
|
+
*/
|
|
964
1505
|
function getPreorders(graph, opts) {
|
|
965
1506
|
return [...genPreorders(graph, opts)];
|
|
966
1507
|
}
|
|
967
|
-
/**
|
|
1508
|
+
/**
|
|
1509
|
+
* Returns all possible postorder sequences as an array. Can be exponential -- prefer `genPostorders`.
|
|
1510
|
+
*
|
|
1511
|
+
* @example
|
|
1512
|
+
* ```ts
|
|
1513
|
+
* import { createGraph, getPostorders } from '@statelyai/graph';
|
|
1514
|
+
*
|
|
1515
|
+
* const graph = createGraph({
|
|
1516
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1517
|
+
* edges: [
|
|
1518
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1519
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1520
|
+
* ],
|
|
1521
|
+
* initialNodeId: 'a',
|
|
1522
|
+
* });
|
|
1523
|
+
*
|
|
1524
|
+
* const allOrders = getPostorders(graph);
|
|
1525
|
+
* // [[nodeB, nodeC, nodeA], [nodeC, nodeB, nodeA]]
|
|
1526
|
+
* ```
|
|
1527
|
+
*/
|
|
968
1528
|
function getPostorders(graph, opts) {
|
|
969
1529
|
return [...genPostorders(graph, opts)];
|
|
970
1530
|
}
|
|
@@ -972,6 +1532,25 @@ function getPostorders(graph, opts) {
|
|
|
972
1532
|
* Lazily yields all possible preorder (DFS visit-order) sequences.
|
|
973
1533
|
* Different neighbor exploration orders yield different sequences.
|
|
974
1534
|
* Use `getPreorder()` for a single canonical ordering.
|
|
1535
|
+
*
|
|
1536
|
+
* @example
|
|
1537
|
+
* ```ts
|
|
1538
|
+
* import { createGraph, genPreorders } from '@statelyai/graph';
|
|
1539
|
+
*
|
|
1540
|
+
* const graph = createGraph({
|
|
1541
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1542
|
+
* edges: [
|
|
1543
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1544
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1545
|
+
* ],
|
|
1546
|
+
* initialNodeId: 'a',
|
|
1547
|
+
* });
|
|
1548
|
+
*
|
|
1549
|
+
* for (const order of genPreorders(graph)) {
|
|
1550
|
+
* console.log(order.map(n => n.id));
|
|
1551
|
+
* // ['a', 'b', 'c'] or ['a', 'c', 'b']
|
|
1552
|
+
* }
|
|
1553
|
+
* ```
|
|
975
1554
|
*/
|
|
976
1555
|
function* genPreorders(graph, opts) {
|
|
977
1556
|
const idx = getIndex(graph);
|
|
@@ -1017,6 +1596,25 @@ function* genPreorders(graph, opts) {
|
|
|
1017
1596
|
* Lazily yields all possible postorder (DFS finish-order) sequences.
|
|
1018
1597
|
* Different neighbor exploration orders yield different sequences.
|
|
1019
1598
|
* Use `getPostorder()` for a single canonical ordering.
|
|
1599
|
+
*
|
|
1600
|
+
* @example
|
|
1601
|
+
* ```ts
|
|
1602
|
+
* import { createGraph, genPostorders } from '@statelyai/graph';
|
|
1603
|
+
*
|
|
1604
|
+
* const graph = createGraph({
|
|
1605
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1606
|
+
* edges: [
|
|
1607
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1608
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c' },
|
|
1609
|
+
* ],
|
|
1610
|
+
* initialNodeId: 'a',
|
|
1611
|
+
* });
|
|
1612
|
+
*
|
|
1613
|
+
* for (const order of genPostorders(graph)) {
|
|
1614
|
+
* console.log(order.map(n => n.id));
|
|
1615
|
+
* // ['b', 'c', 'a'] or ['c', 'b', 'a']
|
|
1616
|
+
* }
|
|
1617
|
+
* ```
|
|
1020
1618
|
*/
|
|
1021
1619
|
function* genPostorders(graph, opts) {
|
|
1022
1620
|
const idx = getIndex(graph);
|
|
@@ -1061,6 +1659,26 @@ function* genPostorders(graph, opts) {
|
|
|
1061
1659
|
* Returns a minimum spanning tree of the graph.
|
|
1062
1660
|
* Only meaningful for connected undirected graphs (or the component reachable
|
|
1063
1661
|
* from an arbitrary start node in directed graphs).
|
|
1662
|
+
*
|
|
1663
|
+
* @example
|
|
1664
|
+
* ```ts
|
|
1665
|
+
* import { createGraph, getMinimumSpanningTree } from '@statelyai/graph';
|
|
1666
|
+
*
|
|
1667
|
+
* const graph = createGraph({
|
|
1668
|
+
* type: 'undirected',
|
|
1669
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1670
|
+
* edges: [
|
|
1671
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b', data: { weight: 1 } },
|
|
1672
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c', data: { weight: 2 } },
|
|
1673
|
+
* { id: 'ac', sourceId: 'a', targetId: 'c', data: { weight: 3 } },
|
|
1674
|
+
* ],
|
|
1675
|
+
* });
|
|
1676
|
+
*
|
|
1677
|
+
* const mst = getMinimumSpanningTree(graph, {
|
|
1678
|
+
* getWeight: (e) => e.data.weight,
|
|
1679
|
+
* });
|
|
1680
|
+
* // mst has edges 'ab' and 'bc' (total weight 3)
|
|
1681
|
+
* ```
|
|
1064
1682
|
*/
|
|
1065
1683
|
function getMinimumSpanningTree(graph, opts) {
|
|
1066
1684
|
const algorithm = opts?.algorithm ?? "prim";
|
|
@@ -1157,7 +1775,23 @@ function kruskalMST(graph, getWeight) {
|
|
|
1157
1775
|
/**
|
|
1158
1776
|
* Returns shortest paths between all pairs of nodes.
|
|
1159
1777
|
* Algorithm 'dijkstra' (default): runs getShortestPaths per source node.
|
|
1160
|
-
* Algorithm 'floyd-warshall': classic O(V
|
|
1778
|
+
* Algorithm 'floyd-warshall': classic O(V^3) dynamic programming.
|
|
1779
|
+
*
|
|
1780
|
+
* @example
|
|
1781
|
+
* ```ts
|
|
1782
|
+
* import { createGraph, getAllPairsShortestPaths } from '@statelyai/graph';
|
|
1783
|
+
*
|
|
1784
|
+
* const graph = createGraph({
|
|
1785
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1786
|
+
* edges: [
|
|
1787
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1788
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1789
|
+
* ],
|
|
1790
|
+
* });
|
|
1791
|
+
*
|
|
1792
|
+
* const allPaths = getAllPairsShortestPaths(graph);
|
|
1793
|
+
* // paths for every reachable (source, target) pair
|
|
1794
|
+
* ```
|
|
1161
1795
|
*/
|
|
1162
1796
|
function getAllPairsShortestPaths(graph, opts) {
|
|
1163
1797
|
if ((opts?.algorithm ?? "dijkstra") === "floyd-warshall") return floydWarshallAllPaths(graph, opts?.getWeight);
|
|
@@ -1264,6 +1898,25 @@ function fwReconstruct(graph, prev, idxOf, nodeIds, sourceNode, sourceIdx, targe
|
|
|
1264
1898
|
*
|
|
1265
1899
|
* Steps are concatenated: head.steps ++ tail.steps (tail already starts
|
|
1266
1900
|
* from the overlap node, so no slicing is needed).
|
|
1901
|
+
*
|
|
1902
|
+
* @example
|
|
1903
|
+
* ```ts
|
|
1904
|
+
* import { createGraph, getShortestPath, joinPaths } from '@statelyai/graph';
|
|
1905
|
+
*
|
|
1906
|
+
* const graph = createGraph({
|
|
1907
|
+
* nodes: [{ id: 'a' }, { id: 'b' }, { id: 'c' }],
|
|
1908
|
+
* edges: [
|
|
1909
|
+
* { id: 'ab', sourceId: 'a', targetId: 'b' },
|
|
1910
|
+
* { id: 'bc', sourceId: 'b', targetId: 'c' },
|
|
1911
|
+
* ],
|
|
1912
|
+
* initialNodeId: 'a',
|
|
1913
|
+
* });
|
|
1914
|
+
*
|
|
1915
|
+
* const ab = getShortestPath(graph, { to: 'b' })!;
|
|
1916
|
+
* const bc = getShortestPath(graph, { from: 'b', to: 'c' })!;
|
|
1917
|
+
* const ac = joinPaths(ab, bc);
|
|
1918
|
+
* // ac: a -> b -> c
|
|
1919
|
+
* ```
|
|
1267
1920
|
*/
|
|
1268
1921
|
function joinPaths(headPath, tailPath) {
|
|
1269
1922
|
const headEnd = headPath.steps.length > 0 ? headPath.steps[headPath.steps.length - 1].node : headPath.source;
|