effect 3.18.4 → 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 +290 -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/JSONSchema.js +39 -8
- package/dist/cjs/JSONSchema.js.map +1 -1
- package/dist/cjs/TestClock.js +8 -8
- package/dist/cjs/TestClock.js.map +1 -1
- 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 +6 -1
- 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/JSONSchema.d.ts +3 -2
- package/dist/dts/JSONSchema.d.ts.map +1 -1
- 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 +286 -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/JSONSchema.js +35 -6
- package/dist/esm/JSONSchema.js.map +1 -1
- package/dist/esm/TestClock.js +8 -8
- package/dist/esm/TestClock.js.map +1 -1
- 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 +6 -1
- package/src/Graph.ts +415 -218
- package/src/HashRing.ts +387 -0
- package/src/JSONSchema.ts +39 -9
- package/src/TestClock.ts +9 -9
- 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
|
}
|
|
@@ -1225,15 +1229,19 @@ const edgeCount = graph => graph.edges.size;
|
|
|
1225
1229
|
*/
|
|
1226
1230
|
exports.edgeCount = edgeCount;
|
|
1227
1231
|
const neighbors = (graph, nodeIndex) => {
|
|
1228
|
-
|
|
1229
|
-
if (
|
|
1232
|
+
// For undirected graphs, use the specialized helper that returns the other endpoint
|
|
1233
|
+
if (graph.type === "undirected") {
|
|
1234
|
+
return getUndirectedNeighbors(graph, nodeIndex);
|
|
1235
|
+
}
|
|
1236
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1237
|
+
if (adjacencyList === undefined) {
|
|
1230
1238
|
return [];
|
|
1231
1239
|
}
|
|
1232
1240
|
const result = [];
|
|
1233
|
-
for (const edgeIndex of adjacencyList
|
|
1234
|
-
const edge =
|
|
1235
|
-
if (
|
|
1236
|
-
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);
|
|
1237
1245
|
}
|
|
1238
1246
|
}
|
|
1239
1247
|
return result;
|
|
@@ -1267,24 +1275,21 @@ const neighbors = (graph, nodeIndex) => {
|
|
|
1267
1275
|
exports.neighbors = neighbors;
|
|
1268
1276
|
const neighborsDirected = (graph, nodeIndex, direction) => {
|
|
1269
1277
|
const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
|
|
1270
|
-
const adjacencyList =
|
|
1271
|
-
if (
|
|
1278
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1279
|
+
if (adjacencyList === undefined) {
|
|
1272
1280
|
return [];
|
|
1273
1281
|
}
|
|
1274
1282
|
const result = [];
|
|
1275
|
-
for (const edgeIndex of adjacencyList
|
|
1276
|
-
const edge =
|
|
1277
|
-
if (
|
|
1283
|
+
for (const edgeIndex of adjacencyList) {
|
|
1284
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1285
|
+
if (edge !== undefined) {
|
|
1278
1286
|
// For incoming direction, we want the source node instead of target
|
|
1279
|
-
const neighborNode = direction === "incoming" ? edge.
|
|
1287
|
+
const neighborNode = direction === "incoming" ? edge.source : edge.target;
|
|
1280
1288
|
result.push(neighborNode);
|
|
1281
1289
|
}
|
|
1282
1290
|
}
|
|
1283
1291
|
return result;
|
|
1284
1292
|
};
|
|
1285
|
-
// =============================================================================
|
|
1286
|
-
// GraphViz Export
|
|
1287
|
-
// =============================================================================
|
|
1288
1293
|
/**
|
|
1289
1294
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
1290
1295
|
*
|
|
@@ -1341,6 +1346,98 @@ const toGraphViz = (graph, options) => {
|
|
|
1341
1346
|
lines.push("}");
|
|
1342
1347
|
return lines.join("\n");
|
|
1343
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
|
+
};
|
|
1344
1441
|
// =============================================================================
|
|
1345
1442
|
// =============================================================================
|
|
1346
1443
|
// Graph Structure Analysis Algorithms (Phase 5A)
|
|
@@ -1379,7 +1476,7 @@ const toGraphViz = (graph, options) => {
|
|
|
1379
1476
|
* @since 3.18.0
|
|
1380
1477
|
* @category algorithms
|
|
1381
1478
|
*/
|
|
1382
|
-
exports.
|
|
1479
|
+
exports.toMermaid = toMermaid;
|
|
1383
1480
|
const isAcyclic = graph => {
|
|
1384
1481
|
// Use existing cycle flag if available
|
|
1385
1482
|
if (Option.isSome(graph.isAcyclic)) {
|
|
@@ -1525,13 +1622,13 @@ exports.isBipartite = isBipartite;
|
|
|
1525
1622
|
const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
1526
1623
|
const neighbors = new Set();
|
|
1527
1624
|
// Check edges where this node is the source
|
|
1528
|
-
const adjacencyList =
|
|
1529
|
-
if (
|
|
1530
|
-
for (const edgeIndex of adjacencyList
|
|
1531
|
-
const edge =
|
|
1532
|
-
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) {
|
|
1533
1630
|
// For undirected graphs, the neighbor is the other endpoint
|
|
1534
|
-
const otherNode = edge.
|
|
1631
|
+
const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
|
|
1535
1632
|
neighbors.add(otherNode);
|
|
1536
1633
|
}
|
|
1537
1634
|
}
|
|
@@ -1666,12 +1763,12 @@ const stronglyConnectedComponents = graph => {
|
|
|
1666
1763
|
visited.add(node);
|
|
1667
1764
|
scc.push(node);
|
|
1668
1765
|
// Use reverse adjacency (transpose graph)
|
|
1669
|
-
const reverseAdjacency =
|
|
1670
|
-
if (
|
|
1671
|
-
for (const edgeIndex of reverseAdjacency
|
|
1672
|
-
const edge =
|
|
1673
|
-
if (
|
|
1674
|
-
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;
|
|
1675
1772
|
if (!visited.has(predecessor)) {
|
|
1676
1773
|
stack.push(predecessor);
|
|
1677
1774
|
}
|
|
@@ -1702,7 +1799,7 @@ const stronglyConnectedComponents = graph => {
|
|
|
1702
1799
|
* Graph.addEdge(mutable, b, c, 2)
|
|
1703
1800
|
* })
|
|
1704
1801
|
*
|
|
1705
|
-
* const result = Graph.dijkstra(graph, 0, 2, (edgeData) => edgeData)
|
|
1802
|
+
* const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
1706
1803
|
* if (Option.isSome(result)) {
|
|
1707
1804
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
1708
1805
|
* console.log(result.value.distance) // 7 - total distance
|
|
@@ -1713,20 +1810,25 @@ const stronglyConnectedComponents = graph => {
|
|
|
1713
1810
|
* @category algorithms
|
|
1714
1811
|
*/
|
|
1715
1812
|
exports.stronglyConnectedComponents = stronglyConnectedComponents;
|
|
1716
|
-
const dijkstra = (graph,
|
|
1813
|
+
const dijkstra = (graph, config) => {
|
|
1814
|
+
const {
|
|
1815
|
+
cost,
|
|
1816
|
+
source,
|
|
1817
|
+
target
|
|
1818
|
+
} = config;
|
|
1717
1819
|
// Validate that source and target nodes exist
|
|
1718
1820
|
if (!graph.nodes.has(source)) {
|
|
1719
|
-
throw
|
|
1821
|
+
throw missingNode(source);
|
|
1720
1822
|
}
|
|
1721
1823
|
if (!graph.nodes.has(target)) {
|
|
1722
|
-
throw
|
|
1824
|
+
throw missingNode(target);
|
|
1723
1825
|
}
|
|
1724
1826
|
// Early return if source equals target
|
|
1725
1827
|
if (source === target) {
|
|
1726
1828
|
return Option.some({
|
|
1727
1829
|
path: [source],
|
|
1728
1830
|
distance: 0,
|
|
1729
|
-
|
|
1831
|
+
costs: []
|
|
1730
1832
|
});
|
|
1731
1833
|
}
|
|
1732
1834
|
// Distance tracking and priority queue simulation
|
|
@@ -1766,13 +1868,13 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1766
1868
|
// Get current distance
|
|
1767
1869
|
const currentDistance = distances.get(currentNode);
|
|
1768
1870
|
// Examine all outgoing edges
|
|
1769
|
-
const adjacencyList =
|
|
1770
|
-
if (
|
|
1771
|
-
for (const edgeIndex of adjacencyList
|
|
1772
|
-
const edge =
|
|
1773
|
-
if (
|
|
1774
|
-
const neighbor = edge.
|
|
1775
|
-
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);
|
|
1776
1878
|
// Validate non-negative weights
|
|
1777
1879
|
if (weight < 0) {
|
|
1778
1880
|
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -1784,7 +1886,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1784
1886
|
distances.set(neighbor, newDistance);
|
|
1785
1887
|
previous.set(neighbor, {
|
|
1786
1888
|
node: currentNode,
|
|
1787
|
-
edgeData: edge.
|
|
1889
|
+
edgeData: edge.data
|
|
1788
1890
|
});
|
|
1789
1891
|
// Add to priority queue if not visited
|
|
1790
1892
|
if (!visited.has(neighbor)) {
|
|
@@ -1805,13 +1907,13 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1805
1907
|
}
|
|
1806
1908
|
// Reconstruct path
|
|
1807
1909
|
const path = [];
|
|
1808
|
-
const
|
|
1910
|
+
const costs = [];
|
|
1809
1911
|
let currentNode = target;
|
|
1810
1912
|
while (currentNode !== null) {
|
|
1811
1913
|
path.unshift(currentNode);
|
|
1812
1914
|
const prev = previous.get(currentNode);
|
|
1813
1915
|
if (prev !== null) {
|
|
1814
|
-
|
|
1916
|
+
costs.unshift(prev.edgeData);
|
|
1815
1917
|
currentNode = prev.node;
|
|
1816
1918
|
} else {
|
|
1817
1919
|
currentNode = null;
|
|
@@ -1820,7 +1922,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1820
1922
|
return Option.some({
|
|
1821
1923
|
path,
|
|
1822
1924
|
distance: targetDistance,
|
|
1823
|
-
|
|
1925
|
+
costs
|
|
1824
1926
|
});
|
|
1825
1927
|
};
|
|
1826
1928
|
/**
|
|
@@ -1851,7 +1953,7 @@ const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1851
1953
|
* @category algorithms
|
|
1852
1954
|
*/
|
|
1853
1955
|
exports.dijkstra = dijkstra;
|
|
1854
|
-
const floydWarshall = (graph,
|
|
1956
|
+
const floydWarshall = (graph, cost) => {
|
|
1855
1957
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
1856
1958
|
const allNodes = Array.from(graph.nodes.keys());
|
|
1857
1959
|
// Initialize distance matrix
|
|
@@ -1871,7 +1973,7 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1871
1973
|
}
|
|
1872
1974
|
// Set edge weights
|
|
1873
1975
|
for (const [, edgeData] of graph.edges) {
|
|
1874
|
-
const weight =
|
|
1976
|
+
const weight = cost(edgeData.data);
|
|
1875
1977
|
const i = edgeData.source;
|
|
1876
1978
|
const j = edgeData.target;
|
|
1877
1979
|
// Use minimum weight if multiple edges exist
|
|
@@ -1904,17 +2006,17 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1904
2006
|
}
|
|
1905
2007
|
// Build result paths and edge weights
|
|
1906
2008
|
const paths = new Map();
|
|
1907
|
-
const
|
|
2009
|
+
const resultCosts = new Map();
|
|
1908
2010
|
for (const i of allNodes) {
|
|
1909
2011
|
paths.set(i, new Map());
|
|
1910
|
-
|
|
2012
|
+
resultCosts.set(i, new Map());
|
|
1911
2013
|
for (const j of allNodes) {
|
|
1912
2014
|
if (i === j) {
|
|
1913
2015
|
paths.get(i).set(j, [i]);
|
|
1914
|
-
|
|
2016
|
+
resultCosts.get(i).set(j, []);
|
|
1915
2017
|
} else if (dist.get(i).get(j) === Infinity) {
|
|
1916
2018
|
paths.get(i).set(j, null);
|
|
1917
|
-
|
|
2019
|
+
resultCosts.get(i).set(j, []);
|
|
1918
2020
|
} else {
|
|
1919
2021
|
// Reconstruct path iteratively
|
|
1920
2022
|
const path = [];
|
|
@@ -1932,14 +2034,14 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1932
2034
|
path.push(current);
|
|
1933
2035
|
}
|
|
1934
2036
|
paths.get(i).set(j, path);
|
|
1935
|
-
|
|
2037
|
+
resultCosts.get(i).set(j, weights);
|
|
1936
2038
|
}
|
|
1937
2039
|
}
|
|
1938
2040
|
}
|
|
1939
2041
|
return {
|
|
1940
2042
|
distances: dist,
|
|
1941
2043
|
paths,
|
|
1942
|
-
|
|
2044
|
+
costs: resultCosts
|
|
1943
2045
|
};
|
|
1944
2046
|
};
|
|
1945
2047
|
/**
|
|
@@ -1965,7 +2067,7 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1965
2067
|
* const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
|
|
1966
2068
|
* Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
|
|
1967
2069
|
*
|
|
1968
|
-
* const result = Graph.astar(graph, 0, 2, (edgeData) => edgeData, heuristic)
|
|
2070
|
+
* const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
|
|
1969
2071
|
* if (Option.isSome(result)) {
|
|
1970
2072
|
* console.log(result.value.path) // [0, 1, 2] - shortest path
|
|
1971
2073
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -1976,25 +2078,31 @@ const floydWarshall = (graph, edgeWeight) => {
|
|
|
1976
2078
|
* @category algorithms
|
|
1977
2079
|
*/
|
|
1978
2080
|
exports.floydWarshall = floydWarshall;
|
|
1979
|
-
const astar = (graph,
|
|
2081
|
+
const astar = (graph, config) => {
|
|
2082
|
+
const {
|
|
2083
|
+
cost,
|
|
2084
|
+
heuristic,
|
|
2085
|
+
source,
|
|
2086
|
+
target
|
|
2087
|
+
} = config;
|
|
1980
2088
|
// Validate that source and target nodes exist
|
|
1981
2089
|
if (!graph.nodes.has(source)) {
|
|
1982
|
-
throw
|
|
2090
|
+
throw missingNode(source);
|
|
1983
2091
|
}
|
|
1984
2092
|
if (!graph.nodes.has(target)) {
|
|
1985
|
-
throw
|
|
2093
|
+
throw missingNode(target);
|
|
1986
2094
|
}
|
|
1987
2095
|
// Early return if source equals target
|
|
1988
2096
|
if (source === target) {
|
|
1989
2097
|
return Option.some({
|
|
1990
2098
|
path: [source],
|
|
1991
2099
|
distance: 0,
|
|
1992
|
-
|
|
2100
|
+
costs: []
|
|
1993
2101
|
});
|
|
1994
2102
|
}
|
|
1995
2103
|
// Get target node data for heuristic calculations
|
|
1996
|
-
const targetNodeData =
|
|
1997
|
-
if (
|
|
2104
|
+
const targetNodeData = graph.nodes.get(target);
|
|
2105
|
+
if (targetNodeData === undefined) {
|
|
1998
2106
|
throw new Error(`Target node ${target} data not found`);
|
|
1999
2107
|
}
|
|
2000
2108
|
// Distance tracking (g-score) and f-score (g + h)
|
|
@@ -2010,9 +2118,9 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2010
2118
|
previous.set(node, null);
|
|
2011
2119
|
}
|
|
2012
2120
|
// Calculate initial f-score for source
|
|
2013
|
-
const sourceNodeData =
|
|
2014
|
-
if (
|
|
2015
|
-
const h = heuristic(sourceNodeData
|
|
2121
|
+
const sourceNodeData = graph.nodes.get(source);
|
|
2122
|
+
if (sourceNodeData !== undefined) {
|
|
2123
|
+
const h = heuristic(sourceNodeData, targetNodeData);
|
|
2016
2124
|
fScore.set(source, h);
|
|
2017
2125
|
}
|
|
2018
2126
|
// Priority queue using f-score (total estimated cost)
|
|
@@ -2042,13 +2150,13 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2042
2150
|
// Get current g-score
|
|
2043
2151
|
const currentGScore = gScore.get(currentNode);
|
|
2044
2152
|
// Examine all outgoing edges
|
|
2045
|
-
const adjacencyList =
|
|
2046
|
-
if (
|
|
2047
|
-
for (const edgeIndex of adjacencyList
|
|
2048
|
-
const edge =
|
|
2049
|
-
if (
|
|
2050
|
-
const neighbor = edge.
|
|
2051
|
-
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);
|
|
2052
2160
|
// Validate non-negative weights
|
|
2053
2161
|
if (weight < 0) {
|
|
2054
2162
|
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -2061,12 +2169,12 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2061
2169
|
gScore.set(neighbor, tentativeGScore);
|
|
2062
2170
|
previous.set(neighbor, {
|
|
2063
2171
|
node: currentNode,
|
|
2064
|
-
edgeData: edge.
|
|
2172
|
+
edgeData: edge.data
|
|
2065
2173
|
});
|
|
2066
2174
|
// Calculate f-score using heuristic
|
|
2067
|
-
const neighborNodeData =
|
|
2068
|
-
if (
|
|
2069
|
-
const h = heuristic(neighborNodeData
|
|
2175
|
+
const neighborNodeData = graph.nodes.get(neighbor);
|
|
2176
|
+
if (neighborNodeData !== undefined) {
|
|
2177
|
+
const h = heuristic(neighborNodeData, targetNodeData);
|
|
2070
2178
|
const f = tentativeGScore + h;
|
|
2071
2179
|
fScore.set(neighbor, f);
|
|
2072
2180
|
// Add to open set if not visited
|
|
@@ -2089,13 +2197,13 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2089
2197
|
}
|
|
2090
2198
|
// Reconstruct path
|
|
2091
2199
|
const path = [];
|
|
2092
|
-
const
|
|
2200
|
+
const costs = [];
|
|
2093
2201
|
let currentNode = target;
|
|
2094
2202
|
while (currentNode !== null) {
|
|
2095
2203
|
path.unshift(currentNode);
|
|
2096
2204
|
const prev = previous.get(currentNode);
|
|
2097
2205
|
if (prev !== null) {
|
|
2098
|
-
|
|
2206
|
+
costs.unshift(prev.edgeData);
|
|
2099
2207
|
currentNode = prev.node;
|
|
2100
2208
|
} else {
|
|
2101
2209
|
currentNode = null;
|
|
@@ -2104,7 +2212,7 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2104
2212
|
return Option.some({
|
|
2105
2213
|
path,
|
|
2106
2214
|
distance: targetGScore,
|
|
2107
|
-
|
|
2215
|
+
costs
|
|
2108
2216
|
});
|
|
2109
2217
|
};
|
|
2110
2218
|
/**
|
|
@@ -2127,7 +2235,7 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2127
2235
|
* Graph.addEdge(mutable, a, c, 5)
|
|
2128
2236
|
* })
|
|
2129
2237
|
*
|
|
2130
|
-
* const result = Graph.bellmanFord(graph, 0, 2, (edgeData) => edgeData)
|
|
2238
|
+
* const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
2131
2239
|
* if (Option.isSome(result)) {
|
|
2132
2240
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
2133
2241
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -2138,20 +2246,25 @@ const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2138
2246
|
* @category algorithms
|
|
2139
2247
|
*/
|
|
2140
2248
|
exports.astar = astar;
|
|
2141
|
-
const bellmanFord = (graph,
|
|
2249
|
+
const bellmanFord = (graph, config) => {
|
|
2250
|
+
const {
|
|
2251
|
+
cost,
|
|
2252
|
+
source,
|
|
2253
|
+
target
|
|
2254
|
+
} = config;
|
|
2142
2255
|
// Validate that source and target nodes exist
|
|
2143
2256
|
if (!graph.nodes.has(source)) {
|
|
2144
|
-
throw
|
|
2257
|
+
throw missingNode(source);
|
|
2145
2258
|
}
|
|
2146
2259
|
if (!graph.nodes.has(target)) {
|
|
2147
|
-
throw
|
|
2260
|
+
throw missingNode(target);
|
|
2148
2261
|
}
|
|
2149
2262
|
// Early return if source equals target
|
|
2150
2263
|
if (source === target) {
|
|
2151
2264
|
return Option.some({
|
|
2152
2265
|
path: [source],
|
|
2153
2266
|
distance: 0,
|
|
2154
|
-
|
|
2267
|
+
costs: []
|
|
2155
2268
|
});
|
|
2156
2269
|
}
|
|
2157
2270
|
// Initialize distances and predecessors
|
|
@@ -2165,7 +2278,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2165
2278
|
// Collect all edges for relaxation
|
|
2166
2279
|
const edges = [];
|
|
2167
2280
|
for (const [, edgeData] of graph.edges) {
|
|
2168
|
-
const weight =
|
|
2281
|
+
const weight = cost(edgeData.data);
|
|
2169
2282
|
edges.push({
|
|
2170
2283
|
source: edgeData.source,
|
|
2171
2284
|
target: edgeData.target,
|
|
@@ -2208,12 +2321,12 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2208
2321
|
if (affectedNodes.has(node)) continue;
|
|
2209
2322
|
affectedNodes.add(node);
|
|
2210
2323
|
// Add all nodes reachable from this node
|
|
2211
|
-
const adjacencyList =
|
|
2212
|
-
if (
|
|
2213
|
-
for (const edgeIndex of adjacencyList
|
|
2214
|
-
const edge =
|
|
2215
|
-
if (
|
|
2216
|
-
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);
|
|
2217
2330
|
}
|
|
2218
2331
|
}
|
|
2219
2332
|
}
|
|
@@ -2231,13 +2344,13 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2231
2344
|
}
|
|
2232
2345
|
// Reconstruct path
|
|
2233
2346
|
const path = [];
|
|
2234
|
-
const
|
|
2347
|
+
const costs = [];
|
|
2235
2348
|
let currentNode = target;
|
|
2236
2349
|
while (currentNode !== null) {
|
|
2237
2350
|
path.unshift(currentNode);
|
|
2238
2351
|
const prev = previous.get(currentNode);
|
|
2239
2352
|
if (prev !== null) {
|
|
2240
|
-
|
|
2353
|
+
costs.unshift(prev.edgeData);
|
|
2241
2354
|
currentNode = prev.node;
|
|
2242
2355
|
} else {
|
|
2243
2356
|
currentNode = null;
|
|
@@ -2246,7 +2359,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2246
2359
|
return Option.some({
|
|
2247
2360
|
path,
|
|
2248
2361
|
distance: targetDistance,
|
|
2249
|
-
|
|
2362
|
+
costs
|
|
2250
2363
|
});
|
|
2251
2364
|
};
|
|
2252
2365
|
/**
|
|
@@ -2267,7 +2380,7 @@ const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2267
2380
|
* })
|
|
2268
2381
|
*
|
|
2269
2382
|
* // Both traversal and element iterators return NodeWalker
|
|
2270
|
-
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, {
|
|
2383
|
+
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
|
|
2271
2384
|
* const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
|
|
2272
2385
|
*
|
|
2273
2386
|
* // Common interface for working with node iterables
|
|
@@ -2304,7 +2417,7 @@ class Walker {
|
|
|
2304
2417
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2305
2418
|
* })
|
|
2306
2419
|
*
|
|
2307
|
-
* const dfs = Graph.dfs(graph, {
|
|
2420
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2308
2421
|
*
|
|
2309
2422
|
* // Map to just the node data
|
|
2310
2423
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2337,7 +2450,7 @@ class Walker {
|
|
|
2337
2450
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2338
2451
|
* })
|
|
2339
2452
|
*
|
|
2340
|
-
* const dfs = Graph.dfs(graph, {
|
|
2453
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2341
2454
|
*
|
|
2342
2455
|
* // Map to just the node data
|
|
2343
2456
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2369,7 +2482,7 @@ class Walker {
|
|
|
2369
2482
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2370
2483
|
* })
|
|
2371
2484
|
*
|
|
2372
|
-
* const dfs = Graph.dfs(graph, {
|
|
2485
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2373
2486
|
* const indices = Array.from(Graph.indices(dfs))
|
|
2374
2487
|
* console.log(indices) // [0, 1]
|
|
2375
2488
|
* ```
|
|
@@ -2392,7 +2505,7 @@ const indices = walker => walker.visit((index, _) => index);
|
|
|
2392
2505
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2393
2506
|
* })
|
|
2394
2507
|
*
|
|
2395
|
-
* const dfs = Graph.dfs(graph, {
|
|
2508
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2396
2509
|
* const values = Array.from(Graph.values(dfs))
|
|
2397
2510
|
* console.log(values) // ["A", "B"]
|
|
2398
2511
|
* ```
|
|
@@ -2415,7 +2528,7 @@ const values = walker => walker.visit((_, data) => data);
|
|
|
2415
2528
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2416
2529
|
* })
|
|
2417
2530
|
*
|
|
2418
|
-
* const dfs = Graph.dfs(graph, {
|
|
2531
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2419
2532
|
* const entries = Array.from(Graph.entries(dfs))
|
|
2420
2533
|
* console.log(entries) // [[0, "A"], [1, "B"]]
|
|
2421
2534
|
* ```
|
|
@@ -2444,7 +2557,7 @@ const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2444
2557
|
* })
|
|
2445
2558
|
*
|
|
2446
2559
|
* // Start from a specific node
|
|
2447
|
-
* const dfs1 = Graph.dfs(graph, {
|
|
2560
|
+
* const dfs1 = Graph.dfs(graph, { start: [0] })
|
|
2448
2561
|
* for (const nodeIndex of Graph.indices(dfs1)) {
|
|
2449
2562
|
* console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
|
|
2450
2563
|
* }
|
|
@@ -2459,17 +2572,17 @@ const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2459
2572
|
*/
|
|
2460
2573
|
exports.entries = entries;
|
|
2461
2574
|
const dfs = (graph, config = {}) => {
|
|
2462
|
-
const
|
|
2575
|
+
const start = config.start ?? [];
|
|
2463
2576
|
const direction = config.direction ?? "outgoing";
|
|
2464
2577
|
// Validate that all start nodes exist
|
|
2465
|
-
for (const nodeIndex of
|
|
2578
|
+
for (const nodeIndex of start) {
|
|
2466
2579
|
if (!hasNode(graph, nodeIndex)) {
|
|
2467
|
-
throw
|
|
2580
|
+
throw missingNode(nodeIndex);
|
|
2468
2581
|
}
|
|
2469
2582
|
}
|
|
2470
2583
|
return new Walker(f => ({
|
|
2471
2584
|
[Symbol.iterator]: () => {
|
|
2472
|
-
const stack = [...
|
|
2585
|
+
const stack = [...start];
|
|
2473
2586
|
const discovered = new Set();
|
|
2474
2587
|
const nextMapped = () => {
|
|
2475
2588
|
while (stack.length > 0) {
|
|
@@ -2478,8 +2591,8 @@ const dfs = (graph, config = {}) => {
|
|
|
2478
2591
|
continue;
|
|
2479
2592
|
}
|
|
2480
2593
|
discovered.add(current);
|
|
2481
|
-
const nodeDataOption =
|
|
2482
|
-
if (
|
|
2594
|
+
const nodeDataOption = graph.nodes.get(current);
|
|
2595
|
+
if (nodeDataOption === undefined) {
|
|
2483
2596
|
continue;
|
|
2484
2597
|
}
|
|
2485
2598
|
const neighbors = neighborsDirected(graph, current, direction);
|
|
@@ -2491,7 +2604,7 @@ const dfs = (graph, config = {}) => {
|
|
|
2491
2604
|
}
|
|
2492
2605
|
return {
|
|
2493
2606
|
done: false,
|
|
2494
|
-
value: f(current, nodeDataOption
|
|
2607
|
+
value: f(current, nodeDataOption)
|
|
2495
2608
|
};
|
|
2496
2609
|
}
|
|
2497
2610
|
return {
|
|
@@ -2524,7 +2637,7 @@ const dfs = (graph, config = {}) => {
|
|
|
2524
2637
|
* })
|
|
2525
2638
|
*
|
|
2526
2639
|
* // Start from a specific node
|
|
2527
|
-
* const bfs1 = Graph.bfs(graph, {
|
|
2640
|
+
* const bfs1 = Graph.bfs(graph, { start: [0] })
|
|
2528
2641
|
* for (const nodeIndex of Graph.indices(bfs1)) {
|
|
2529
2642
|
* console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
|
|
2530
2643
|
* }
|
|
@@ -2539,17 +2652,17 @@ const dfs = (graph, config = {}) => {
|
|
|
2539
2652
|
*/
|
|
2540
2653
|
exports.dfs = dfs;
|
|
2541
2654
|
const bfs = (graph, config = {}) => {
|
|
2542
|
-
const
|
|
2655
|
+
const start = config.start ?? [];
|
|
2543
2656
|
const direction = config.direction ?? "outgoing";
|
|
2544
2657
|
// Validate that all start nodes exist
|
|
2545
|
-
for (const nodeIndex of
|
|
2658
|
+
for (const nodeIndex of start) {
|
|
2546
2659
|
if (!hasNode(graph, nodeIndex)) {
|
|
2547
|
-
throw
|
|
2660
|
+
throw missingNode(nodeIndex);
|
|
2548
2661
|
}
|
|
2549
2662
|
}
|
|
2550
2663
|
return new Walker(f => ({
|
|
2551
2664
|
[Symbol.iterator]: () => {
|
|
2552
|
-
const queue = [...
|
|
2665
|
+
const queue = [...start];
|
|
2553
2666
|
const discovered = new Set();
|
|
2554
2667
|
const nextMapped = () => {
|
|
2555
2668
|
while (queue.length > 0) {
|
|
@@ -2638,7 +2751,7 @@ const topo = (graph, config = {}) => {
|
|
|
2638
2751
|
// Validate that all initial nodes exist
|
|
2639
2752
|
for (const nodeIndex of initials) {
|
|
2640
2753
|
if (!hasNode(graph, nodeIndex)) {
|
|
2641
|
-
throw
|
|
2754
|
+
throw missingNode(nodeIndex);
|
|
2642
2755
|
}
|
|
2643
2756
|
}
|
|
2644
2757
|
return new Walker(f => ({
|
|
@@ -2723,7 +2836,7 @@ const topo = (graph, config = {}) => {
|
|
|
2723
2836
|
* })
|
|
2724
2837
|
*
|
|
2725
2838
|
* // Postorder: children before parents
|
|
2726
|
-
* const postOrder = Graph.dfsPostOrder(graph, {
|
|
2839
|
+
* const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
|
|
2727
2840
|
* for (const node of postOrder) {
|
|
2728
2841
|
* console.log(node) // 1, 2, 0
|
|
2729
2842
|
* }
|
|
@@ -2734,12 +2847,12 @@ const topo = (graph, config = {}) => {
|
|
|
2734
2847
|
*/
|
|
2735
2848
|
exports.topo = topo;
|
|
2736
2849
|
const dfsPostOrder = (graph, config = {}) => {
|
|
2737
|
-
const
|
|
2850
|
+
const start = config.start ?? [];
|
|
2738
2851
|
const direction = config.direction ?? "outgoing";
|
|
2739
2852
|
// Validate that all start nodes exist
|
|
2740
|
-
for (const nodeIndex of
|
|
2853
|
+
for (const nodeIndex of start) {
|
|
2741
2854
|
if (!hasNode(graph, nodeIndex)) {
|
|
2742
|
-
throw
|
|
2855
|
+
throw missingNode(nodeIndex);
|
|
2743
2856
|
}
|
|
2744
2857
|
}
|
|
2745
2858
|
return new Walker(f => ({
|
|
@@ -2748,9 +2861,9 @@ const dfsPostOrder = (graph, config = {}) => {
|
|
|
2748
2861
|
const discovered = new Set();
|
|
2749
2862
|
const finished = new Set();
|
|
2750
2863
|
// Initialize stack with start nodes
|
|
2751
|
-
for (let i =
|
|
2864
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
2752
2865
|
stack.push({
|
|
2753
|
-
node:
|
|
2866
|
+
node: start[i],
|
|
2754
2867
|
visitedChildren: false
|
|
2755
2868
|
});
|
|
2756
2869
|
}
|
|
@@ -2939,9 +3052,9 @@ const externals = (graph, config = {}) => {
|
|
|
2939
3052
|
let current = nodeIterator.next();
|
|
2940
3053
|
while (!current.done) {
|
|
2941
3054
|
const [nodeIndex, nodeData] = current.value;
|
|
2942
|
-
const adjacencyList =
|
|
3055
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
2943
3056
|
// Node is external if it has no edges in the specified direction
|
|
2944
|
-
if (
|
|
3057
|
+
if (adjacencyList === undefined || adjacencyList.length === 0) {
|
|
2945
3058
|
return {
|
|
2946
3059
|
done: false,
|
|
2947
3060
|
value: f(nodeIndex, nodeData)
|