effect 3.18.5 → 3.19.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/HashRing/package.json +6 -0
- package/dist/cjs/Array.js.map +1 -1
- package/dist/cjs/Effect.js.map +1 -1
- package/dist/cjs/Graph.js +286 -177
- package/dist/cjs/Graph.js.map +1 -1
- package/dist/cjs/HashRing.js +257 -0
- package/dist/cjs/HashRing.js.map +1 -0
- package/dist/cjs/index.js +4 -2
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Array.d.ts +3 -3
- package/dist/dts/Array.d.ts.map +1 -1
- package/dist/dts/Effect.d.ts +5 -0
- package/dist/dts/Effect.d.ts.map +1 -1
- package/dist/dts/Graph.d.ts +147 -49
- package/dist/dts/Graph.d.ts.map +1 -1
- package/dist/dts/HashRing.d.ts +158 -0
- package/dist/dts/HashRing.d.ts.map +1 -0
- package/dist/dts/Types.d.ts +1 -1
- package/dist/dts/Types.d.ts.map +1 -1
- package/dist/dts/index.d.ts +5 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/esm/Array.js.map +1 -1
- package/dist/esm/Effect.js.map +1 -1
- package/dist/esm/Graph.js +282 -175
- package/dist/esm/Graph.js.map +1 -1
- package/dist/esm/HashRing.js +245 -0
- package/dist/esm/HashRing.js.map +1 -0
- package/dist/esm/index.js +5 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +9 -1
- package/src/Array.ts +4 -4
- package/src/Effect.ts +5 -0
- package/src/Graph.ts +410 -218
- package/src/HashRing.ts +387 -0
- package/src/Types.ts +3 -1
- package/src/index.ts +6 -0
- package/src/internal/version.ts +1 -1
package/dist/cjs/Graph.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.values = exports.updateNode = exports.updateEdge = exports.undirected = exports.topo = exports.toGraphViz = exports.stronglyConnectedComponents = exports.reverse = exports.removeNode = exports.removeEdge = exports.nodes = exports.nodeCount = exports.neighborsDirected = exports.neighbors = exports.mutate = exports.mapNodes = exports.mapEdges = exports.isGraph = exports.isBipartite = exports.isAcyclic = exports.indices = exports.hasNode = exports.hasEdge = exports.getNode = exports.getEdge = exports.floydWarshall = exports.findNodes = exports.findNode = exports.findEdges = exports.findEdge = exports.filterNodes = exports.filterMapNodes = exports.filterMapEdges = exports.filterEdges = exports.externals = exports.entries = exports.endMutation = exports.edges = exports.edgeCount = exports.directed = exports.dijkstra = exports.dfsPostOrder = exports.dfs = exports.connectedComponents = exports.bfs = exports.bellmanFord = exports.beginMutation = exports.astar = exports.addNode = exports.addEdge = exports.Walker = exports.TypeId = exports.Edge = void 0;
|
|
6
|
+
exports.values = exports.updateNode = exports.updateEdge = exports.undirected = exports.topo = exports.toMermaid = exports.toGraphViz = exports.stronglyConnectedComponents = exports.reverse = exports.removeNode = exports.removeEdge = exports.nodes = exports.nodeCount = exports.neighborsDirected = exports.neighbors = exports.mutate = exports.mapNodes = exports.mapEdges = exports.isGraph = exports.isBipartite = exports.isAcyclic = exports.indices = exports.hasNode = exports.hasEdge = exports.getNode = exports.getEdge = exports.floydWarshall = exports.findNodes = exports.findNode = exports.findEdges = exports.findEdge = exports.filterNodes = exports.filterMapNodes = exports.filterMapEdges = exports.filterEdges = exports.externals = exports.entries = exports.endMutation = exports.edges = exports.edgeCount = exports.directed = exports.dijkstra = exports.dfsPostOrder = exports.dfs = exports.connectedComponents = exports.bfs = exports.bellmanFord = exports.beginMutation = exports.astar = exports.addNode = exports.addEdge = exports.Walker = exports.TypeId = exports.GraphError = exports.Edge = void 0;
|
|
7
7
|
var Data = _interopRequireWildcard(require("./Data.js"));
|
|
8
8
|
var Equal = _interopRequireWildcard(require("./Equal.js"));
|
|
9
9
|
var _Function = require("./Function.js");
|
|
@@ -17,17 +17,6 @@ function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r
|
|
|
17
17
|
* @since 3.18.0
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
* Safely get a value from a Map, returning an Option.
|
|
22
|
-
* Uses explicit key presence check with map.has() for better safety.
|
|
23
|
-
* @internal
|
|
24
|
-
*/
|
|
25
|
-
const getMapSafe = (map, key) => {
|
|
26
|
-
if (map.has(key)) {
|
|
27
|
-
return Option.some(map.get(key));
|
|
28
|
-
}
|
|
29
|
-
return Option.none();
|
|
30
|
-
};
|
|
31
20
|
/**
|
|
32
21
|
* Unique identifier for Graph instances.
|
|
33
22
|
*
|
|
@@ -113,6 +102,21 @@ const ProtoGraph = {
|
|
|
113
102
|
}
|
|
114
103
|
};
|
|
115
104
|
// =============================================================================
|
|
105
|
+
// Errors
|
|
106
|
+
// =============================================================================
|
|
107
|
+
/**
|
|
108
|
+
* Error thrown when a graph operation fails.
|
|
109
|
+
*
|
|
110
|
+
* @since 3.18.0
|
|
111
|
+
* @category errors
|
|
112
|
+
*/
|
|
113
|
+
class GraphError extends /*#__PURE__*/Data.TaggedError("GraphError") {}
|
|
114
|
+
/** @internal */
|
|
115
|
+
exports.GraphError = GraphError;
|
|
116
|
+
const missingNode = node => new GraphError({
|
|
117
|
+
message: `Node ${node} does not exist`
|
|
118
|
+
});
|
|
119
|
+
// =============================================================================
|
|
116
120
|
// Constructors
|
|
117
121
|
// =============================================================================
|
|
118
122
|
/** @internal */
|
|
@@ -344,7 +348,7 @@ const addNode = (mutable, data) => {
|
|
|
344
348
|
* @category getters
|
|
345
349
|
*/
|
|
346
350
|
exports.addNode = addNode;
|
|
347
|
-
const getNode = (graph, nodeIndex) =>
|
|
351
|
+
const getNode = (graph, nodeIndex) => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)) : Option.none();
|
|
348
352
|
/**
|
|
349
353
|
* Checks if a node with the given index exists in the graph.
|
|
350
354
|
*
|
|
@@ -923,10 +927,10 @@ const invalidateCycleFlagOnAddition = mutable => {
|
|
|
923
927
|
const addEdge = (mutable, source, target, data) => {
|
|
924
928
|
// Validate that both nodes exist
|
|
925
929
|
if (!mutable.nodes.has(source)) {
|
|
926
|
-
throw
|
|
930
|
+
throw missingNode(source);
|
|
927
931
|
}
|
|
928
932
|
if (!mutable.nodes.has(target)) {
|
|
929
|
-
throw
|
|
933
|
+
throw missingNode(target);
|
|
930
934
|
}
|
|
931
935
|
const edgeIndex = mutable.nextEdgeIndex;
|
|
932
936
|
// Create edge data
|
|
@@ -937,23 +941,23 @@ const addEdge = (mutable, source, target, data) => {
|
|
|
937
941
|
});
|
|
938
942
|
mutable.edges.set(edgeIndex, edgeData);
|
|
939
943
|
// Update adjacency lists
|
|
940
|
-
const sourceAdjacency =
|
|
941
|
-
if (
|
|
942
|
-
sourceAdjacency.
|
|
944
|
+
const sourceAdjacency = mutable.adjacency.get(source);
|
|
945
|
+
if (sourceAdjacency !== undefined) {
|
|
946
|
+
sourceAdjacency.push(edgeIndex);
|
|
943
947
|
}
|
|
944
|
-
const targetReverseAdjacency =
|
|
945
|
-
if (
|
|
946
|
-
targetReverseAdjacency.
|
|
948
|
+
const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
|
|
949
|
+
if (targetReverseAdjacency !== undefined) {
|
|
950
|
+
targetReverseAdjacency.push(edgeIndex);
|
|
947
951
|
}
|
|
948
952
|
// For undirected graphs, add reverse connections
|
|
949
953
|
if (mutable.type === "undirected") {
|
|
950
|
-
const targetAdjacency =
|
|
951
|
-
if (
|
|
952
|
-
targetAdjacency.
|
|
954
|
+
const targetAdjacency = mutable.adjacency.get(target);
|
|
955
|
+
if (targetAdjacency !== undefined) {
|
|
956
|
+
targetAdjacency.push(edgeIndex);
|
|
953
957
|
}
|
|
954
|
-
const sourceReverseAdjacency =
|
|
955
|
-
if (
|
|
956
|
-
sourceReverseAdjacency.
|
|
958
|
+
const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
|
|
959
|
+
if (sourceReverseAdjacency !== undefined) {
|
|
960
|
+
sourceReverseAdjacency.push(edgeIndex);
|
|
957
961
|
}
|
|
958
962
|
}
|
|
959
963
|
// Update allocators
|
|
@@ -992,16 +996,16 @@ const removeNode = (mutable, nodeIndex) => {
|
|
|
992
996
|
// Collect all incident edges for removal
|
|
993
997
|
const edgesToRemove = [];
|
|
994
998
|
// Get outgoing edges
|
|
995
|
-
const outgoingEdges =
|
|
996
|
-
if (
|
|
997
|
-
for (const edge of outgoingEdges
|
|
999
|
+
const outgoingEdges = mutable.adjacency.get(nodeIndex);
|
|
1000
|
+
if (outgoingEdges !== undefined) {
|
|
1001
|
+
for (const edge of outgoingEdges) {
|
|
998
1002
|
edgesToRemove.push(edge);
|
|
999
1003
|
}
|
|
1000
1004
|
}
|
|
1001
1005
|
// Get incoming edges
|
|
1002
|
-
const incomingEdges =
|
|
1003
|
-
if (
|
|
1004
|
-
for (const edge of incomingEdges
|
|
1006
|
+
const incomingEdges = mutable.reverseAdjacency.get(nodeIndex);
|
|
1007
|
+
if (incomingEdges !== undefined) {
|
|
1008
|
+
for (const edge of incomingEdges) {
|
|
1005
1009
|
edgesToRemove.push(edge);
|
|
1006
1010
|
}
|
|
1007
1011
|
}
|
|
@@ -1050,43 +1054,43 @@ const removeEdge = (mutable, edgeIndex) => {
|
|
|
1050
1054
|
exports.removeEdge = removeEdge;
|
|
1051
1055
|
const removeEdgeInternal = (mutable, edgeIndex) => {
|
|
1052
1056
|
// Get edge data
|
|
1053
|
-
const edge =
|
|
1054
|
-
if (
|
|
1057
|
+
const edge = mutable.edges.get(edgeIndex);
|
|
1058
|
+
if (edge === undefined) {
|
|
1055
1059
|
return false; // Edge doesn't exist, no mutation occurred
|
|
1056
1060
|
}
|
|
1057
1061
|
const {
|
|
1058
1062
|
source,
|
|
1059
1063
|
target
|
|
1060
|
-
} = edge
|
|
1064
|
+
} = edge;
|
|
1061
1065
|
// Remove from adjacency lists
|
|
1062
|
-
const sourceAdjacency =
|
|
1063
|
-
if (
|
|
1064
|
-
const index = sourceAdjacency.
|
|
1066
|
+
const sourceAdjacency = mutable.adjacency.get(source);
|
|
1067
|
+
if (sourceAdjacency !== undefined) {
|
|
1068
|
+
const index = sourceAdjacency.indexOf(edgeIndex);
|
|
1065
1069
|
if (index !== -1) {
|
|
1066
|
-
sourceAdjacency.
|
|
1070
|
+
sourceAdjacency.splice(index, 1);
|
|
1067
1071
|
}
|
|
1068
1072
|
}
|
|
1069
|
-
const targetReverseAdjacency =
|
|
1070
|
-
if (
|
|
1071
|
-
const index = targetReverseAdjacency.
|
|
1073
|
+
const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
|
|
1074
|
+
if (targetReverseAdjacency !== undefined) {
|
|
1075
|
+
const index = targetReverseAdjacency.indexOf(edgeIndex);
|
|
1072
1076
|
if (index !== -1) {
|
|
1073
|
-
targetReverseAdjacency.
|
|
1077
|
+
targetReverseAdjacency.splice(index, 1);
|
|
1074
1078
|
}
|
|
1075
1079
|
}
|
|
1076
1080
|
// For undirected graphs, remove reverse connections
|
|
1077
1081
|
if (mutable.type === "undirected") {
|
|
1078
|
-
const targetAdjacency =
|
|
1079
|
-
if (
|
|
1080
|
-
const index = targetAdjacency.
|
|
1082
|
+
const targetAdjacency = mutable.adjacency.get(target);
|
|
1083
|
+
if (targetAdjacency !== undefined) {
|
|
1084
|
+
const index = targetAdjacency.indexOf(edgeIndex);
|
|
1081
1085
|
if (index !== -1) {
|
|
1082
|
-
targetAdjacency.
|
|
1086
|
+
targetAdjacency.splice(index, 1);
|
|
1083
1087
|
}
|
|
1084
1088
|
}
|
|
1085
|
-
const sourceReverseAdjacency =
|
|
1086
|
-
if (
|
|
1087
|
-
const index = sourceReverseAdjacency.
|
|
1089
|
+
const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
|
|
1090
|
+
if (sourceReverseAdjacency !== undefined) {
|
|
1091
|
+
const index = sourceReverseAdjacency.indexOf(edgeIndex);
|
|
1088
1092
|
if (index !== -1) {
|
|
1089
|
-
sourceReverseAdjacency.
|
|
1093
|
+
sourceReverseAdjacency.splice(index, 1);
|
|
1090
1094
|
}
|
|
1091
1095
|
}
|
|
1092
1096
|
}
|
|
@@ -1123,7 +1127,7 @@ const removeEdgeInternal = (mutable, edgeIndex) => {
|
|
|
1123
1127
|
* @since 3.18.0
|
|
1124
1128
|
* @category getters
|
|
1125
1129
|
*/
|
|
1126
|
-
const getEdge = (graph, edgeIndex) =>
|
|
1130
|
+
const getEdge = (graph, edgeIndex) => graph.edges.has(edgeIndex) ? Option.some(graph.edges.get(edgeIndex)) : Option.none();
|
|
1127
1131
|
/**
|
|
1128
1132
|
* Checks if an edge exists between two nodes in the graph.
|
|
1129
1133
|
*
|
|
@@ -1154,14 +1158,14 @@ const getEdge = (graph, edgeIndex) => getMapSafe(graph.edges, edgeIndex);
|
|
|
1154
1158
|
*/
|
|
1155
1159
|
exports.getEdge = getEdge;
|
|
1156
1160
|
const hasEdge = (graph, source, target) => {
|
|
1157
|
-
const adjacencyList =
|
|
1158
|
-
if (
|
|
1161
|
+
const adjacencyList = graph.adjacency.get(source);
|
|
1162
|
+
if (adjacencyList === undefined) {
|
|
1159
1163
|
return false;
|
|
1160
1164
|
}
|
|
1161
1165
|
// Check if any edge in the adjacency list connects to the target
|
|
1162
|
-
for (const edgeIndex of adjacencyList
|
|
1163
|
-
const edge =
|
|
1164
|
-
if (
|
|
1166
|
+
for (const edgeIndex of adjacencyList) {
|
|
1167
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1168
|
+
if (edge !== undefined && edge.target === target) {
|
|
1165
1169
|
return true;
|
|
1166
1170
|
}
|
|
1167
1171
|
}
|
|
@@ -1229,15 +1233,15 @@ const neighbors = (graph, nodeIndex) => {
|
|
|
1229
1233
|
if (graph.type === "undirected") {
|
|
1230
1234
|
return getUndirectedNeighbors(graph, nodeIndex);
|
|
1231
1235
|
}
|
|
1232
|
-
const adjacencyList =
|
|
1233
|
-
if (
|
|
1236
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1237
|
+
if (adjacencyList === undefined) {
|
|
1234
1238
|
return [];
|
|
1235
1239
|
}
|
|
1236
1240
|
const result = [];
|
|
1237
|
-
for (const edgeIndex of adjacencyList
|
|
1238
|
-
const edge =
|
|
1239
|
-
if (
|
|
1240
|
-
result.push(edge.
|
|
1241
|
+
for (const edgeIndex of adjacencyList) {
|
|
1242
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1243
|
+
if (edge !== undefined) {
|
|
1244
|
+
result.push(edge.target);
|
|
1241
1245
|
}
|
|
1242
1246
|
}
|
|
1243
1247
|
return result;
|
|
@@ -1271,24 +1275,21 @@ const neighbors = (graph, nodeIndex) => {
|
|
|
1271
1275
|
exports.neighbors = neighbors;
|
|
1272
1276
|
const neighborsDirected = (graph, nodeIndex, direction) => {
|
|
1273
1277
|
const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
|
|
1274
|
-
const adjacencyList =
|
|
1275
|
-
if (
|
|
1278
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1279
|
+
if (adjacencyList === undefined) {
|
|
1276
1280
|
return [];
|
|
1277
1281
|
}
|
|
1278
1282
|
const result = [];
|
|
1279
|
-
for (const edgeIndex of adjacencyList
|
|
1280
|
-
const edge =
|
|
1281
|
-
if (
|
|
1283
|
+
for (const edgeIndex of adjacencyList) {
|
|
1284
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1285
|
+
if (edge !== undefined) {
|
|
1282
1286
|
// For incoming direction, we want the source node instead of target
|
|
1283
|
-
const neighborNode = direction === "incoming" ? edge.
|
|
1287
|
+
const neighborNode = direction === "incoming" ? edge.source : edge.target;
|
|
1284
1288
|
result.push(neighborNode);
|
|
1285
1289
|
}
|
|
1286
1290
|
}
|
|
1287
1291
|
return result;
|
|
1288
1292
|
};
|
|
1289
|
-
// =============================================================================
|
|
1290
|
-
// GraphViz Export
|
|
1291
|
-
// =============================================================================
|
|
1292
1293
|
/**
|
|
1293
1294
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
1294
1295
|
*
|
|
@@ -1345,6 +1346,98 @@ const toGraphViz = (graph, options) => {
|
|
|
1345
1346
|
lines.push("}");
|
|
1346
1347
|
return lines.join("\n");
|
|
1347
1348
|
};
|
|
1349
|
+
/** @internal */
|
|
1350
|
+
exports.toGraphViz = toGraphViz;
|
|
1351
|
+
const escapeMermaidLabel = label => {
|
|
1352
|
+
// Escape special characters for Mermaid using HTML entity codes
|
|
1353
|
+
// According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax
|
|
1354
|
+
return label.replace(/#/g, "#35;").replace(/"/g, "#quot;").replace(/</g, "#lt;").replace(/>/g, "#gt;").replace(/&/g, "#amp;").replace(/\[/g, "#91;").replace(/\]/g, "#93;").replace(/\{/g, "#123;").replace(/\}/g, "#125;").replace(/\(/g, "#40;").replace(/\)/g, "#41;").replace(/\|/g, "#124;").replace(/\\/g, "#92;").replace(/\n/g, "<br/>");
|
|
1355
|
+
};
|
|
1356
|
+
/** @internal */
|
|
1357
|
+
const formatMermaidNode = (nodeId, label, shape) => {
|
|
1358
|
+
switch (shape) {
|
|
1359
|
+
case "rectangle":
|
|
1360
|
+
return `${nodeId}["${label}"]`;
|
|
1361
|
+
case "rounded":
|
|
1362
|
+
return `${nodeId}("${label}")`;
|
|
1363
|
+
case "circle":
|
|
1364
|
+
return `${nodeId}(("${label}"))`;
|
|
1365
|
+
case "diamond":
|
|
1366
|
+
return `${nodeId}{"${label}"}`;
|
|
1367
|
+
case "hexagon":
|
|
1368
|
+
return `${nodeId}{{"${label}"}}`;
|
|
1369
|
+
case "stadium":
|
|
1370
|
+
return `${nodeId}(["${label}"])`;
|
|
1371
|
+
case "subroutine":
|
|
1372
|
+
return `${nodeId}[["${label}"]]`;
|
|
1373
|
+
case "cylindrical":
|
|
1374
|
+
return `${nodeId}[("${label}")]`;
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
/**
|
|
1378
|
+
* Exports a graph to Mermaid diagram format for visualization.
|
|
1379
|
+
*
|
|
1380
|
+
* @example
|
|
1381
|
+
* ```ts
|
|
1382
|
+
* import { Graph } from "effect"
|
|
1383
|
+
*
|
|
1384
|
+
* const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
|
|
1385
|
+
* const app = Graph.addNode(mutable, "App")
|
|
1386
|
+
* const db = Graph.addNode(mutable, "Database")
|
|
1387
|
+
* const cache = Graph.addNode(mutable, "Cache")
|
|
1388
|
+
* Graph.addEdge(mutable, app, db, 1)
|
|
1389
|
+
* Graph.addEdge(mutable, app, cache, 2)
|
|
1390
|
+
* })
|
|
1391
|
+
*
|
|
1392
|
+
* const mermaid = Graph.toMermaid(graph)
|
|
1393
|
+
* console.log(mermaid)
|
|
1394
|
+
* // flowchart TD
|
|
1395
|
+
* // 0["App"]
|
|
1396
|
+
* // 1["Database"]
|
|
1397
|
+
* // 2["Cache"]
|
|
1398
|
+
* // 0 -->|"1"| 1
|
|
1399
|
+
* // 0 -->|"2"| 2
|
|
1400
|
+
* ```
|
|
1401
|
+
*
|
|
1402
|
+
* @since 3.18.0
|
|
1403
|
+
* @category utils
|
|
1404
|
+
*/
|
|
1405
|
+
const toMermaid = (graph, options) => {
|
|
1406
|
+
// Extract and validate options with defaults
|
|
1407
|
+
const {
|
|
1408
|
+
diagramType,
|
|
1409
|
+
direction = "TD",
|
|
1410
|
+
edgeLabel = data => String(data),
|
|
1411
|
+
nodeLabel = data => String(data),
|
|
1412
|
+
nodeShape = () => "rectangle"
|
|
1413
|
+
} = options ?? {};
|
|
1414
|
+
// Auto-detect diagram type if not specified
|
|
1415
|
+
const finalDiagramType = diagramType ?? (graph.type === "directed" ? "flowchart" : "graph");
|
|
1416
|
+
// Generate diagram header
|
|
1417
|
+
const lines = [];
|
|
1418
|
+
lines.push(`${finalDiagramType} ${direction}`);
|
|
1419
|
+
// Add nodes
|
|
1420
|
+
for (const [nodeIndex, nodeData] of graph.nodes) {
|
|
1421
|
+
const nodeId = String(nodeIndex);
|
|
1422
|
+
const label = escapeMermaidLabel(nodeLabel(nodeData));
|
|
1423
|
+
const shape = nodeShape(nodeData);
|
|
1424
|
+
const formattedNode = formatMermaidNode(nodeId, label, shape);
|
|
1425
|
+
lines.push(` ${formattedNode}`);
|
|
1426
|
+
}
|
|
1427
|
+
// Add edges
|
|
1428
|
+
const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---";
|
|
1429
|
+
for (const [, edgeData] of graph.edges) {
|
|
1430
|
+
const sourceId = String(edgeData.source);
|
|
1431
|
+
const targetId = String(edgeData.target);
|
|
1432
|
+
const label = escapeMermaidLabel(edgeLabel(edgeData.data));
|
|
1433
|
+
if (label) {
|
|
1434
|
+
lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`);
|
|
1435
|
+
} else {
|
|
1436
|
+
lines.push(` ${sourceId} ${edgeOperator} ${targetId}`);
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return lines.join("\n");
|
|
1440
|
+
};
|
|
1348
1441
|
// =============================================================================
|
|
1349
1442
|
// =============================================================================
|
|
1350
1443
|
// Graph Structure Analysis Algorithms (Phase 5A)
|
|
@@ -1383,7 +1476,7 @@ const toGraphViz = (graph, options) => {
|
|
|
1383
1476
|
* @since 3.18.0
|
|
1384
1477
|
* @category algorithms
|
|
1385
1478
|
*/
|
|
1386
|
-
exports.
|
|
1479
|
+
exports.toMermaid = toMermaid;
|
|
1387
1480
|
const isAcyclic = graph => {
|
|
1388
1481
|
// Use existing cycle flag if available
|
|
1389
1482
|
if (Option.isSome(graph.isAcyclic)) {
|
|
@@ -1529,13 +1622,13 @@ exports.isBipartite = isBipartite;
|
|
|
1529
1622
|
const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
1530
1623
|
const neighbors = new Set();
|
|
1531
1624
|
// Check edges where this node is the source
|
|
1532
|
-
const adjacencyList =
|
|
1533
|
-
if (
|
|
1534
|
-
for (const edgeIndex of adjacencyList
|
|
1535
|
-
const edge =
|
|
1536
|
-
if (
|
|
1625
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1626
|
+
if (adjacencyList !== undefined) {
|
|
1627
|
+
for (const edgeIndex of adjacencyList) {
|
|
1628
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1629
|
+
if (edge !== undefined) {
|
|
1537
1630
|
// For undirected graphs, the neighbor is the other endpoint
|
|
1538
|
-
const otherNode = edge.
|
|
1631
|
+
const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
|
|
1539
1632
|
neighbors.add(otherNode);
|
|
1540
1633
|
}
|
|
1541
1634
|
}
|
|
@@ -1670,12 +1763,12 @@ const stronglyConnectedComponents = graph => {
|
|
|
1670
1763
|
visited.add(node);
|
|
1671
1764
|
scc.push(node);
|
|
1672
1765
|
// Use reverse adjacency (transpose graph)
|
|
1673
|
-
const reverseAdjacency =
|
|
1674
|
-
if (
|
|
1675
|
-
for (const edgeIndex of reverseAdjacency
|
|
1676
|
-
const edge =
|
|
1677
|
-
if (
|
|
1678
|
-
const predecessor = edge.
|
|
1766
|
+
const reverseAdjacency = graph.reverseAdjacency.get(node);
|
|
1767
|
+
if (reverseAdjacency !== undefined) {
|
|
1768
|
+
for (const edgeIndex of reverseAdjacency) {
|
|
1769
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1770
|
+
if (edge !== undefined) {
|
|
1771
|
+
const predecessor = edge.source;
|
|
1679
1772
|
if (!visited.has(predecessor)) {
|
|
1680
1773
|
stack.push(predecessor);
|
|
1681
1774
|
}
|
|
@@ -1706,7 +1799,7 @@ const stronglyConnectedComponents = graph => {
|
|
|
1706
1799
|
* Graph.addEdge(mutable, b, c, 2)
|
|
1707
1800
|
* })
|
|
1708
1801
|
*
|
|
1709
|
-
* const result = Graph.dijkstra(graph, 0, 2, (edgeData) => edgeData)
|
|
1802
|
+
* const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
1710
1803
|
* if (Option.isSome(result)) {
|
|
1711
1804
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
1712
1805
|
* console.log(result.value.distance) // 7 - total distance
|
|
@@ -1717,20 +1810,25 @@ const stronglyConnectedComponents = graph => {
|
|
|
1717
1810
|
* @category algorithms
|
|
1718
1811
|
*/
|
|
1719
1812
|
exports.stronglyConnectedComponents = stronglyConnectedComponents;
|
|
1720
|
-
const dijkstra = (graph,
|
|
1813
|
+
const dijkstra = (graph, config) => {
|
|
1814
|
+
const {
|
|
1815
|
+
cost,
|
|
1816
|
+
source,
|
|
1817
|
+
target
|
|
1818
|
+
} = config;
|
|
1721
1819
|
// Validate that source and target nodes exist
|
|
1722
1820
|
if (!graph.nodes.has(source)) {
|
|
1723
|
-
throw
|
|
1821
|
+
throw missingNode(source);
|
|
1724
1822
|
}
|
|
1725
1823
|
if (!graph.nodes.has(target)) {
|
|
1726
|
-
throw
|
|
1824
|
+
throw missingNode(target);
|
|
1727
1825
|
}
|
|
1728
1826
|
// Early return if source equals target
|
|
1729
1827
|
if (source === target) {
|
|
1730
1828
|
return Option.some({
|
|
1731
1829
|
path: [source],
|
|
1732
1830
|
distance: 0,
|
|
1733
|
-
|
|
1831
|
+
costs: []
|
|
1734
1832
|
});
|
|
1735
1833
|
}
|
|
1736
1834
|
// Distance tracking and priority queue simulation
|
|
@@ -1770,13 +1868,13 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1770
1868
|
// Get current distance
|
|
1771
1869
|
const currentDistance = distances.get(currentNode);
|
|
1772
1870
|
// Examine all outgoing edges
|
|
1773
|
-
const adjacencyList =
|
|
1774
|
-
if (
|
|
1775
|
-
for (const edgeIndex of adjacencyList
|
|
1776
|
-
const edge =
|
|
1777
|
-
if (
|
|
1778
|
-
const neighbor = edge.
|
|
1779
|
-
const weight =
|
|
1871
|
+
const adjacencyList = graph.adjacency.get(currentNode);
|
|
1872
|
+
if (adjacencyList !== undefined) {
|
|
1873
|
+
for (const edgeIndex of adjacencyList) {
|
|
1874
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1875
|
+
if (edge !== undefined) {
|
|
1876
|
+
const neighbor = edge.target;
|
|
1877
|
+
const weight = cost(edge.data);
|
|
1780
1878
|
// Validate non-negative weights
|
|
1781
1879
|
if (weight < 0) {
|
|
1782
1880
|
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -1788,7 +1886,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1788
1886
|
distances.set(neighbor, newDistance);
|
|
1789
1887
|
previous.set(neighbor, {
|
|
1790
1888
|
node: currentNode,
|
|
1791
|
-
edgeData: edge.
|
|
1889
|
+
edgeData: edge.data
|
|
1792
1890
|
});
|
|
1793
1891
|
// Add to priority queue if not visited
|
|
1794
1892
|
if (!visited.has(neighbor)) {
|
|
@@ -1809,13 +1907,13 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1809
1907
|
}
|
|
1810
1908
|
// Reconstruct path
|
|
1811
1909
|
const path = [];
|
|
1812
|
-
const
|
|
1910
|
+
const costs = [];
|
|
1813
1911
|
let currentNode = target;
|
|
1814
1912
|
while (currentNode !== null) {
|
|
1815
1913
|
path.unshift(currentNode);
|
|
1816
1914
|
const prev = previous.get(currentNode);
|
|
1817
1915
|
if (prev !== null) {
|
|
1818
|
-
|
|
1916
|
+
costs.unshift(prev.edgeData);
|
|
1819
1917
|
currentNode = prev.node;
|
|
1820
1918
|
} else {
|
|
1821
1919
|
currentNode = null;
|
|
@@ -1824,7 +1922,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1824
1922
|
return Option.some({
|
|
1825
1923
|
path,
|
|
1826
1924
|
distance: targetDistance,
|
|
1827
|
-
|
|
1925
|
+
costs
|
|
1828
1926
|
});
|
|
1829
1927
|
};
|
|
1830
1928
|
/**
|
|
@@ -1855,7 +1953,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1855
1953
|
* @category algorithms
|
|
1856
1954
|
*/
|
|
1857
1955
|
exports.dijkstra = dijkstra;
|
|
1858
|
-
const floydWarshall = (graph,
|
|
1956
|
+
const floydWarshall = (graph, cost) => {
|
|
1859
1957
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
1860
1958
|
const allNodes = Array.from(graph.nodes.keys());
|
|
1861
1959
|
// Initialize distance matrix
|
|
@@ -1875,7 +1973,7 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1875
1973
|
}
|
|
1876
1974
|
// Set edge weights
|
|
1877
1975
|
for (const [, edgeData] of graph.edges) {
|
|
1878
|
-
const weight =
|
|
1976
|
+
const weight = cost(edgeData.data);
|
|
1879
1977
|
const i = edgeData.source;
|
|
1880
1978
|
const j = edgeData.target;
|
|
1881
1979
|
// Use minimum weight if multiple edges exist
|
|
@@ -1908,17 +2006,17 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1908
2006
|
}
|
|
1909
2007
|
// Build result paths and edge weights
|
|
1910
2008
|
const paths = new Map();
|
|
1911
|
-
const
|
|
2009
|
+
const resultCosts = new Map();
|
|
1912
2010
|
for (const i of allNodes) {
|
|
1913
2011
|
paths.set(i, new Map());
|
|
1914
|
-
|
|
2012
|
+
resultCosts.set(i, new Map());
|
|
1915
2013
|
for (const j of allNodes) {
|
|
1916
2014
|
if (i === j) {
|
|
1917
2015
|
paths.get(i).set(j, [i]);
|
|
1918
|
-
|
|
2016
|
+
resultCosts.get(i).set(j, []);
|
|
1919
2017
|
} else if (dist.get(i).get(j) === Infinity) {
|
|
1920
2018
|
paths.get(i).set(j, null);
|
|
1921
|
-
|
|
2019
|
+
resultCosts.get(i).set(j, []);
|
|
1922
2020
|
} else {
|
|
1923
2021
|
// Reconstruct path iteratively
|
|
1924
2022
|
const path = [];
|
|
@@ -1936,14 +2034,14 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1936
2034
|
path.push(current);
|
|
1937
2035
|
}
|
|
1938
2036
|
paths.get(i).set(j, path);
|
|
1939
|
-
|
|
2037
|
+
resultCosts.get(i).set(j, weights);
|
|
1940
2038
|
}
|
|
1941
2039
|
}
|
|
1942
2040
|
}
|
|
1943
2041
|
return {
|
|
1944
2042
|
distances: dist,
|
|
1945
2043
|
paths,
|
|
1946
|
-
|
|
2044
|
+
costs: resultCosts
|
|
1947
2045
|
};
|
|
1948
2046
|
};
|
|
1949
2047
|
/**
|
|
@@ -1969,7 +2067,7 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1969
2067
|
* const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
|
|
1970
2068
|
* Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
|
|
1971
2069
|
*
|
|
1972
|
-
* const result = Graph.astar(graph, 0, 2, (edgeData) => edgeData, heuristic)
|
|
2070
|
+
* const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
|
|
1973
2071
|
* if (Option.isSome(result)) {
|
|
1974
2072
|
* console.log(result.value.path) // [0, 1, 2] - shortest path
|
|
1975
2073
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -1980,25 +2078,31 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1980
2078
|
* @category algorithms
|
|
1981
2079
|
*/
|
|
1982
2080
|
exports.floydWarshall = floydWarshall;
|
|
1983
|
-
const astar = (graph,
|
|
2081
|
+
const astar = (graph, config) => {
|
|
2082
|
+
const {
|
|
2083
|
+
cost,
|
|
2084
|
+
heuristic,
|
|
2085
|
+
source,
|
|
2086
|
+
target
|
|
2087
|
+
} = config;
|
|
1984
2088
|
// Validate that source and target nodes exist
|
|
1985
2089
|
if (!graph.nodes.has(source)) {
|
|
1986
|
-
throw
|
|
2090
|
+
throw missingNode(source);
|
|
1987
2091
|
}
|
|
1988
2092
|
if (!graph.nodes.has(target)) {
|
|
1989
|
-
throw
|
|
2093
|
+
throw missingNode(target);
|
|
1990
2094
|
}
|
|
1991
2095
|
// Early return if source equals target
|
|
1992
2096
|
if (source === target) {
|
|
1993
2097
|
return Option.some({
|
|
1994
2098
|
path: [source],
|
|
1995
2099
|
distance: 0,
|
|
1996
|
-
|
|
2100
|
+
costs: []
|
|
1997
2101
|
});
|
|
1998
2102
|
}
|
|
1999
2103
|
// Get target node data for heuristic calculations
|
|
2000
|
-
const targetNodeData =
|
|
2001
|
-
if (
|
|
2104
|
+
const targetNodeData = graph.nodes.get(target);
|
|
2105
|
+
if (targetNodeData === undefined) {
|
|
2002
2106
|
throw new Error(`Target node ${target} data not found`);
|
|
2003
2107
|
}
|
|
2004
2108
|
// Distance tracking (g-score) and f-score (g + h)
|
|
@@ -2014,9 +2118,9 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2014
2118
|
previous.set(node, null);
|
|
2015
2119
|
}
|
|
2016
2120
|
// Calculate initial f-score for source
|
|
2017
|
-
const sourceNodeData =
|
|
2018
|
-
if (
|
|
2019
|
-
const h = heuristic(sourceNodeData
|
|
2121
|
+
const sourceNodeData = graph.nodes.get(source);
|
|
2122
|
+
if (sourceNodeData !== undefined) {
|
|
2123
|
+
const h = heuristic(sourceNodeData, targetNodeData);
|
|
2020
2124
|
fScore.set(source, h);
|
|
2021
2125
|
}
|
|
2022
2126
|
// Priority queue using f-score (total estimated cost)
|
|
@@ -2046,13 +2150,13 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2046
2150
|
// Get current g-score
|
|
2047
2151
|
const currentGScore = gScore.get(currentNode);
|
|
2048
2152
|
// Examine all outgoing edges
|
|
2049
|
-
const adjacencyList =
|
|
2050
|
-
if (
|
|
2051
|
-
for (const edgeIndex of adjacencyList
|
|
2052
|
-
const edge =
|
|
2053
|
-
if (
|
|
2054
|
-
const neighbor = edge.
|
|
2055
|
-
const weight =
|
|
2153
|
+
const adjacencyList = graph.adjacency.get(currentNode);
|
|
2154
|
+
if (adjacencyList !== undefined) {
|
|
2155
|
+
for (const edgeIndex of adjacencyList) {
|
|
2156
|
+
const edge = graph.edges.get(edgeIndex);
|
|
2157
|
+
if (edge !== undefined) {
|
|
2158
|
+
const neighbor = edge.target;
|
|
2159
|
+
const weight = cost(edge.data);
|
|
2056
2160
|
// Validate non-negative weights
|
|
2057
2161
|
if (weight < 0) {
|
|
2058
2162
|
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -2065,12 +2169,12 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2065
2169
|
gScore.set(neighbor, tentativeGScore);
|
|
2066
2170
|
previous.set(neighbor, {
|
|
2067
2171
|
node: currentNode,
|
|
2068
|
-
edgeData: edge.
|
|
2172
|
+
edgeData: edge.data
|
|
2069
2173
|
});
|
|
2070
2174
|
// Calculate f-score using heuristic
|
|
2071
|
-
const neighborNodeData =
|
|
2072
|
-
if (
|
|
2073
|
-
const h = heuristic(neighborNodeData
|
|
2175
|
+
const neighborNodeData = graph.nodes.get(neighbor);
|
|
2176
|
+
if (neighborNodeData !== undefined) {
|
|
2177
|
+
const h = heuristic(neighborNodeData, targetNodeData);
|
|
2074
2178
|
const f = tentativeGScore + h;
|
|
2075
2179
|
fScore.set(neighbor, f);
|
|
2076
2180
|
// Add to open set if not visited
|
|
@@ -2093,13 +2197,13 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2093
2197
|
}
|
|
2094
2198
|
// Reconstruct path
|
|
2095
2199
|
const path = [];
|
|
2096
|
-
const
|
|
2200
|
+
const costs = [];
|
|
2097
2201
|
let currentNode = target;
|
|
2098
2202
|
while (currentNode !== null) {
|
|
2099
2203
|
path.unshift(currentNode);
|
|
2100
2204
|
const prev = previous.get(currentNode);
|
|
2101
2205
|
if (prev !== null) {
|
|
2102
|
-
|
|
2206
|
+
costs.unshift(prev.edgeData);
|
|
2103
2207
|
currentNode = prev.node;
|
|
2104
2208
|
} else {
|
|
2105
2209
|
currentNode = null;
|
|
@@ -2108,7 +2212,7 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2108
2212
|
return Option.some({
|
|
2109
2213
|
path,
|
|
2110
2214
|
distance: targetGScore,
|
|
2111
|
-
|
|
2215
|
+
costs
|
|
2112
2216
|
});
|
|
2113
2217
|
};
|
|
2114
2218
|
/**
|
|
@@ -2131,7 +2235,7 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2131
2235
|
* Graph.addEdge(mutable, a, c, 5)
|
|
2132
2236
|
* })
|
|
2133
2237
|
*
|
|
2134
|
-
* const result = Graph.bellmanFord(graph, 0, 2, (edgeData) => edgeData)
|
|
2238
|
+
* const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
2135
2239
|
* if (Option.isSome(result)) {
|
|
2136
2240
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
2137
2241
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -2142,20 +2246,25 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2142
2246
|
* @category algorithms
|
|
2143
2247
|
*/
|
|
2144
2248
|
exports.astar = astar;
|
|
2145
|
-
const bellmanFord = (graph,
|
|
2249
|
+
const bellmanFord = (graph, config) => {
|
|
2250
|
+
const {
|
|
2251
|
+
cost,
|
|
2252
|
+
source,
|
|
2253
|
+
target
|
|
2254
|
+
} = config;
|
|
2146
2255
|
// Validate that source and target nodes exist
|
|
2147
2256
|
if (!graph.nodes.has(source)) {
|
|
2148
|
-
throw
|
|
2257
|
+
throw missingNode(source);
|
|
2149
2258
|
}
|
|
2150
2259
|
if (!graph.nodes.has(target)) {
|
|
2151
|
-
throw
|
|
2260
|
+
throw missingNode(target);
|
|
2152
2261
|
}
|
|
2153
2262
|
// Early return if source equals target
|
|
2154
2263
|
if (source === target) {
|
|
2155
2264
|
return Option.some({
|
|
2156
2265
|
path: [source],
|
|
2157
2266
|
distance: 0,
|
|
2158
|
-
|
|
2267
|
+
costs: []
|
|
2159
2268
|
});
|
|
2160
2269
|
}
|
|
2161
2270
|
// Initialize distances and predecessors
|
|
@@ -2169,7 +2278,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2169
2278
|
// Collect all edges for relaxation
|
|
2170
2279
|
const edges = [];
|
|
2171
2280
|
for (const [, edgeData] of graph.edges) {
|
|
2172
|
-
const weight =
|
|
2281
|
+
const weight = cost(edgeData.data);
|
|
2173
2282
|
edges.push({
|
|
2174
2283
|
source: edgeData.source,
|
|
2175
2284
|
target: edgeData.target,
|
|
@@ -2212,12 +2321,12 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2212
2321
|
if (affectedNodes.has(node)) continue;
|
|
2213
2322
|
affectedNodes.add(node);
|
|
2214
2323
|
// Add all nodes reachable from this node
|
|
2215
|
-
const adjacencyList =
|
|
2216
|
-
if (
|
|
2217
|
-
for (const edgeIndex of adjacencyList
|
|
2218
|
-
const edge =
|
|
2219
|
-
if (
|
|
2220
|
-
queue.push(edge.
|
|
2324
|
+
const adjacencyList = graph.adjacency.get(node);
|
|
2325
|
+
if (adjacencyList !== undefined) {
|
|
2326
|
+
for (const edgeIndex of adjacencyList) {
|
|
2327
|
+
const edge = graph.edges.get(edgeIndex);
|
|
2328
|
+
if (edge !== undefined) {
|
|
2329
|
+
queue.push(edge.target);
|
|
2221
2330
|
}
|
|
2222
2331
|
}
|
|
2223
2332
|
}
|
|
@@ -2235,13 +2344,13 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2235
2344
|
}
|
|
2236
2345
|
// Reconstruct path
|
|
2237
2346
|
const path = [];
|
|
2238
|
-
const
|
|
2347
|
+
const costs = [];
|
|
2239
2348
|
let currentNode = target;
|
|
2240
2349
|
while (currentNode !== null) {
|
|
2241
2350
|
path.unshift(currentNode);
|
|
2242
2351
|
const prev = previous.get(currentNode);
|
|
2243
2352
|
if (prev !== null) {
|
|
2244
|
-
|
|
2353
|
+
costs.unshift(prev.edgeData);
|
|
2245
2354
|
currentNode = prev.node;
|
|
2246
2355
|
} else {
|
|
2247
2356
|
currentNode = null;
|
|
@@ -2250,7 +2359,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2250
2359
|
return Option.some({
|
|
2251
2360
|
path,
|
|
2252
2361
|
distance: targetDistance,
|
|
2253
|
-
|
|
2362
|
+
costs
|
|
2254
2363
|
});
|
|
2255
2364
|
};
|
|
2256
2365
|
/**
|
|
@@ -2271,7 +2380,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2271
2380
|
* })
|
|
2272
2381
|
*
|
|
2273
2382
|
* // Both traversal and element iterators return NodeWalker
|
|
2274
|
-
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, {
|
|
2383
|
+
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
|
|
2275
2384
|
* const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
|
|
2276
2385
|
*
|
|
2277
2386
|
* // Common interface for working with node iterables
|
|
@@ -2308,7 +2417,7 @@ class Walker {
|
|
|
2308
2417
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2309
2418
|
* })
|
|
2310
2419
|
*
|
|
2311
|
-
* const dfs = Graph.dfs(graph, {
|
|
2420
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2312
2421
|
*
|
|
2313
2422
|
* // Map to just the node data
|
|
2314
2423
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2341,7 +2450,7 @@ class Walker {
|
|
|
2341
2450
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2342
2451
|
* })
|
|
2343
2452
|
*
|
|
2344
|
-
* const dfs = Graph.dfs(graph, {
|
|
2453
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2345
2454
|
*
|
|
2346
2455
|
* // Map to just the node data
|
|
2347
2456
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2373,7 +2482,7 @@ class Walker {
|
|
|
2373
2482
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2374
2483
|
* })
|
|
2375
2484
|
*
|
|
2376
|
-
* const dfs = Graph.dfs(graph, {
|
|
2485
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2377
2486
|
* const indices = Array.from(Graph.indices(dfs))
|
|
2378
2487
|
* console.log(indices) // [0, 1]
|
|
2379
2488
|
* ```
|
|
@@ -2396,7 +2505,7 @@ const indices = walker => walker.visit((index, _) => index);
|
|
|
2396
2505
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2397
2506
|
* })
|
|
2398
2507
|
*
|
|
2399
|
-
* const dfs = Graph.dfs(graph, {
|
|
2508
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2400
2509
|
* const values = Array.from(Graph.values(dfs))
|
|
2401
2510
|
* console.log(values) // ["A", "B"]
|
|
2402
2511
|
* ```
|
|
@@ -2419,7 +2528,7 @@ const values = walker => walker.visit((_, data) => data);
|
|
|
2419
2528
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2420
2529
|
* })
|
|
2421
2530
|
*
|
|
2422
|
-
* const dfs = Graph.dfs(graph, {
|
|
2531
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2423
2532
|
* const entries = Array.from(Graph.entries(dfs))
|
|
2424
2533
|
* console.log(entries) // [[0, "A"], [1, "B"]]
|
|
2425
2534
|
* ```
|
|
@@ -2448,7 +2557,7 @@ const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2448
2557
|
* })
|
|
2449
2558
|
*
|
|
2450
2559
|
* // Start from a specific node
|
|
2451
|
-
* const dfs1 = Graph.dfs(graph, {
|
|
2560
|
+
* const dfs1 = Graph.dfs(graph, { start: [0] })
|
|
2452
2561
|
* for (const nodeIndex of Graph.indices(dfs1)) {
|
|
2453
2562
|
* console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
|
|
2454
2563
|
* }
|
|
@@ -2463,17 +2572,17 @@ const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2463
2572
|
*/
|
|
2464
2573
|
exports.entries = entries;
|
|
2465
2574
|
const dfs = (graph, config = {}) => {
|
|
2466
|
-
const
|
|
2575
|
+
const start = config.start ?? [];
|
|
2467
2576
|
const direction = config.direction ?? "outgoing";
|
|
2468
2577
|
// Validate that all start nodes exist
|
|
2469
|
-
for (const nodeIndex of
|
|
2578
|
+
for (const nodeIndex of start) {
|
|
2470
2579
|
if (!hasNode(graph, nodeIndex)) {
|
|
2471
|
-
throw
|
|
2580
|
+
throw missingNode(nodeIndex);
|
|
2472
2581
|
}
|
|
2473
2582
|
}
|
|
2474
2583
|
return new Walker(f => ({
|
|
2475
2584
|
[Symbol.iterator]: () => {
|
|
2476
|
-
const stack = [...
|
|
2585
|
+
const stack = [...start];
|
|
2477
2586
|
const discovered = new Set();
|
|
2478
2587
|
const nextMapped = () => {
|
|
2479
2588
|
while (stack.length > 0) {
|
|
@@ -2482,8 +2591,8 @@ const dfs = (graph, config = {}) => {
|
|
|
2482
2591
|
continue;
|
|
2483
2592
|
}
|
|
2484
2593
|
discovered.add(current);
|
|
2485
|
-
const nodeDataOption =
|
|
2486
|
-
if (
|
|
2594
|
+
const nodeDataOption = graph.nodes.get(current);
|
|
2595
|
+
if (nodeDataOption === undefined) {
|
|
2487
2596
|
continue;
|
|
2488
2597
|
}
|
|
2489
2598
|
const neighbors = neighborsDirected(graph, current, direction);
|
|
@@ -2495,7 +2604,7 @@ const dfs = (graph, config = {}) => {
|
|
|
2495
2604
|
}
|
|
2496
2605
|
return {
|
|
2497
2606
|
done: false,
|
|
2498
|
-
value: f(current, nodeDataOption
|
|
2607
|
+
value: f(current, nodeDataOption)
|
|
2499
2608
|
};
|
|
2500
2609
|
}
|
|
2501
2610
|
return {
|
|
@@ -2528,7 +2637,7 @@ const dfs = (graph, config = {}) => {
|
|
|
2528
2637
|
* })
|
|
2529
2638
|
*
|
|
2530
2639
|
* // Start from a specific node
|
|
2531
|
-
* const bfs1 = Graph.bfs(graph, {
|
|
2640
|
+
* const bfs1 = Graph.bfs(graph, { start: [0] })
|
|
2532
2641
|
* for (const nodeIndex of Graph.indices(bfs1)) {
|
|
2533
2642
|
* console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
|
|
2534
2643
|
* }
|
|
@@ -2543,17 +2652,17 @@ const dfs = (graph, config = {}) => {
|
|
|
2543
2652
|
*/
|
|
2544
2653
|
exports.dfs = dfs;
|
|
2545
2654
|
const bfs = (graph, config = {}) => {
|
|
2546
|
-
const
|
|
2655
|
+
const start = config.start ?? [];
|
|
2547
2656
|
const direction = config.direction ?? "outgoing";
|
|
2548
2657
|
// Validate that all start nodes exist
|
|
2549
|
-
for (const nodeIndex of
|
|
2658
|
+
for (const nodeIndex of start) {
|
|
2550
2659
|
if (!hasNode(graph, nodeIndex)) {
|
|
2551
|
-
throw
|
|
2660
|
+
throw missingNode(nodeIndex);
|
|
2552
2661
|
}
|
|
2553
2662
|
}
|
|
2554
2663
|
return new Walker(f => ({
|
|
2555
2664
|
[Symbol.iterator]: () => {
|
|
2556
|
-
const queue = [...
|
|
2665
|
+
const queue = [...start];
|
|
2557
2666
|
const discovered = new Set();
|
|
2558
2667
|
const nextMapped = () => {
|
|
2559
2668
|
while (queue.length > 0) {
|
|
@@ -2642,7 +2751,7 @@ const topo = (graph, config = {}) => {
|
|
|
2642
2751
|
// Validate that all initial nodes exist
|
|
2643
2752
|
for (const nodeIndex of initials) {
|
|
2644
2753
|
if (!hasNode(graph, nodeIndex)) {
|
|
2645
|
-
throw
|
|
2754
|
+
throw missingNode(nodeIndex);
|
|
2646
2755
|
}
|
|
2647
2756
|
}
|
|
2648
2757
|
return new Walker(f => ({
|
|
@@ -2727,7 +2836,7 @@ const topo = (graph, config = {}) => {
|
|
|
2727
2836
|
* })
|
|
2728
2837
|
*
|
|
2729
2838
|
* // Postorder: children before parents
|
|
2730
|
-
* const postOrder = Graph.dfsPostOrder(graph, {
|
|
2839
|
+
* const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
|
|
2731
2840
|
* for (const node of postOrder) {
|
|
2732
2841
|
* console.log(node) // 1, 2, 0
|
|
2733
2842
|
* }
|
|
@@ -2738,12 +2847,12 @@ const topo = (graph, config = {}) => {
|
|
|
2738
2847
|
*/
|
|
2739
2848
|
exports.topo = topo;
|
|
2740
2849
|
const dfsPostOrder = (graph, config = {}) => {
|
|
2741
|
-
const
|
|
2850
|
+
const start = config.start ?? [];
|
|
2742
2851
|
const direction = config.direction ?? "outgoing";
|
|
2743
2852
|
// Validate that all start nodes exist
|
|
2744
|
-
for (const nodeIndex of
|
|
2853
|
+
for (const nodeIndex of start) {
|
|
2745
2854
|
if (!hasNode(graph, nodeIndex)) {
|
|
2746
|
-
throw
|
|
2855
|
+
throw missingNode(nodeIndex);
|
|
2747
2856
|
}
|
|
2748
2857
|
}
|
|
2749
2858
|
return new Walker(f => ({
|
|
@@ -2752,9 +2861,9 @@ const dfsPostOrder = (graph, config = {}) => {
|
|
|
2752
2861
|
const discovered = new Set();
|
|
2753
2862
|
const finished = new Set();
|
|
2754
2863
|
// Initialize stack with start nodes
|
|
2755
|
-
for (let i =
|
|
2864
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
2756
2865
|
stack.push({
|
|
2757
|
-
node:
|
|
2866
|
+
node: start[i],
|
|
2758
2867
|
visitedChildren: false
|
|
2759
2868
|
});
|
|
2760
2869
|
}
|
|
@@ -2943,9 +3052,9 @@ const externals = (graph, config = {}) => {
|
|
|
2943
3052
|
let current = nodeIterator.next();
|
|
2944
3053
|
while (!current.done) {
|
|
2945
3054
|
const [nodeIndex, nodeData] = current.value;
|
|
2946
|
-
const adjacencyList =
|
|
3055
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
2947
3056
|
// Node is external if it has no edges in the specified direction
|
|
2948
|
-
if (
|
|
3057
|
+
if (adjacencyList === undefined || adjacencyList.length === 0) {
|
|
2949
3058
|
return {
|
|
2950
3059
|
done: false,
|
|
2951
3060
|
value: f(nodeIndex, nodeData)
|