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/esm/Graph.js
CHANGED
|
@@ -9,17 +9,6 @@ import * as Hash from "./Hash.js";
|
|
|
9
9
|
import { format, NodeInspectSymbol } from "./Inspectable.js";
|
|
10
10
|
import * as Option from "./Option.js";
|
|
11
11
|
import { pipeArguments } from "./Pipeable.js";
|
|
12
|
-
/**
|
|
13
|
-
* Safely get a value from a Map, returning an Option.
|
|
14
|
-
* Uses explicit key presence check with map.has() for better safety.
|
|
15
|
-
* @internal
|
|
16
|
-
*/
|
|
17
|
-
const getMapSafe = (map, key) => {
|
|
18
|
-
if (map.has(key)) {
|
|
19
|
-
return Option.some(map.get(key));
|
|
20
|
-
}
|
|
21
|
-
return Option.none();
|
|
22
|
-
};
|
|
23
12
|
/**
|
|
24
13
|
* Unique identifier for Graph instances.
|
|
25
14
|
*
|
|
@@ -104,6 +93,20 @@ const ProtoGraph = {
|
|
|
104
93
|
}
|
|
105
94
|
};
|
|
106
95
|
// =============================================================================
|
|
96
|
+
// Errors
|
|
97
|
+
// =============================================================================
|
|
98
|
+
/**
|
|
99
|
+
* Error thrown when a graph operation fails.
|
|
100
|
+
*
|
|
101
|
+
* @since 3.18.0
|
|
102
|
+
* @category errors
|
|
103
|
+
*/
|
|
104
|
+
export class GraphError extends /*#__PURE__*/Data.TaggedError("GraphError") {}
|
|
105
|
+
/** @internal */
|
|
106
|
+
const missingNode = node => new GraphError({
|
|
107
|
+
message: `Node ${node} does not exist`
|
|
108
|
+
});
|
|
109
|
+
// =============================================================================
|
|
107
110
|
// Constructors
|
|
108
111
|
// =============================================================================
|
|
109
112
|
/** @internal */
|
|
@@ -329,7 +332,7 @@ export const addNode = (mutable, data) => {
|
|
|
329
332
|
* @since 3.18.0
|
|
330
333
|
* @category getters
|
|
331
334
|
*/
|
|
332
|
-
export const getNode = (graph, nodeIndex) =>
|
|
335
|
+
export const getNode = (graph, nodeIndex) => graph.nodes.has(nodeIndex) ? Option.some(graph.nodes.get(nodeIndex)) : Option.none();
|
|
333
336
|
/**
|
|
334
337
|
* Checks if a node with the given index exists in the graph.
|
|
335
338
|
*
|
|
@@ -892,10 +895,10 @@ const invalidateCycleFlagOnAddition = mutable => {
|
|
|
892
895
|
export const addEdge = (mutable, source, target, data) => {
|
|
893
896
|
// Validate that both nodes exist
|
|
894
897
|
if (!mutable.nodes.has(source)) {
|
|
895
|
-
throw
|
|
898
|
+
throw missingNode(source);
|
|
896
899
|
}
|
|
897
900
|
if (!mutable.nodes.has(target)) {
|
|
898
|
-
throw
|
|
901
|
+
throw missingNode(target);
|
|
899
902
|
}
|
|
900
903
|
const edgeIndex = mutable.nextEdgeIndex;
|
|
901
904
|
// Create edge data
|
|
@@ -906,23 +909,23 @@ export const addEdge = (mutable, source, target, data) => {
|
|
|
906
909
|
});
|
|
907
910
|
mutable.edges.set(edgeIndex, edgeData);
|
|
908
911
|
// Update adjacency lists
|
|
909
|
-
const sourceAdjacency =
|
|
910
|
-
if (
|
|
911
|
-
sourceAdjacency.
|
|
912
|
+
const sourceAdjacency = mutable.adjacency.get(source);
|
|
913
|
+
if (sourceAdjacency !== undefined) {
|
|
914
|
+
sourceAdjacency.push(edgeIndex);
|
|
912
915
|
}
|
|
913
|
-
const targetReverseAdjacency =
|
|
914
|
-
if (
|
|
915
|
-
targetReverseAdjacency.
|
|
916
|
+
const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
|
|
917
|
+
if (targetReverseAdjacency !== undefined) {
|
|
918
|
+
targetReverseAdjacency.push(edgeIndex);
|
|
916
919
|
}
|
|
917
920
|
// For undirected graphs, add reverse connections
|
|
918
921
|
if (mutable.type === "undirected") {
|
|
919
|
-
const targetAdjacency =
|
|
920
|
-
if (
|
|
921
|
-
targetAdjacency.
|
|
922
|
+
const targetAdjacency = mutable.adjacency.get(target);
|
|
923
|
+
if (targetAdjacency !== undefined) {
|
|
924
|
+
targetAdjacency.push(edgeIndex);
|
|
922
925
|
}
|
|
923
|
-
const sourceReverseAdjacency =
|
|
924
|
-
if (
|
|
925
|
-
sourceReverseAdjacency.
|
|
926
|
+
const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
|
|
927
|
+
if (sourceReverseAdjacency !== undefined) {
|
|
928
|
+
sourceReverseAdjacency.push(edgeIndex);
|
|
926
929
|
}
|
|
927
930
|
}
|
|
928
931
|
// Update allocators
|
|
@@ -960,16 +963,16 @@ export const removeNode = (mutable, nodeIndex) => {
|
|
|
960
963
|
// Collect all incident edges for removal
|
|
961
964
|
const edgesToRemove = [];
|
|
962
965
|
// Get outgoing edges
|
|
963
|
-
const outgoingEdges =
|
|
964
|
-
if (
|
|
965
|
-
for (const edge of outgoingEdges
|
|
966
|
+
const outgoingEdges = mutable.adjacency.get(nodeIndex);
|
|
967
|
+
if (outgoingEdges !== undefined) {
|
|
968
|
+
for (const edge of outgoingEdges) {
|
|
966
969
|
edgesToRemove.push(edge);
|
|
967
970
|
}
|
|
968
971
|
}
|
|
969
972
|
// Get incoming edges
|
|
970
|
-
const incomingEdges =
|
|
971
|
-
if (
|
|
972
|
-
for (const edge of incomingEdges
|
|
973
|
+
const incomingEdges = mutable.reverseAdjacency.get(nodeIndex);
|
|
974
|
+
if (incomingEdges !== undefined) {
|
|
975
|
+
for (const edge of incomingEdges) {
|
|
973
976
|
edgesToRemove.push(edge);
|
|
974
977
|
}
|
|
975
978
|
}
|
|
@@ -1016,43 +1019,43 @@ export const removeEdge = (mutable, edgeIndex) => {
|
|
|
1016
1019
|
/** @internal */
|
|
1017
1020
|
const removeEdgeInternal = (mutable, edgeIndex) => {
|
|
1018
1021
|
// Get edge data
|
|
1019
|
-
const edge =
|
|
1020
|
-
if (
|
|
1022
|
+
const edge = mutable.edges.get(edgeIndex);
|
|
1023
|
+
if (edge === undefined) {
|
|
1021
1024
|
return false; // Edge doesn't exist, no mutation occurred
|
|
1022
1025
|
}
|
|
1023
1026
|
const {
|
|
1024
1027
|
source,
|
|
1025
1028
|
target
|
|
1026
|
-
} = edge
|
|
1029
|
+
} = edge;
|
|
1027
1030
|
// Remove from adjacency lists
|
|
1028
|
-
const sourceAdjacency =
|
|
1029
|
-
if (
|
|
1030
|
-
const index = sourceAdjacency.
|
|
1031
|
+
const sourceAdjacency = mutable.adjacency.get(source);
|
|
1032
|
+
if (sourceAdjacency !== undefined) {
|
|
1033
|
+
const index = sourceAdjacency.indexOf(edgeIndex);
|
|
1031
1034
|
if (index !== -1) {
|
|
1032
|
-
sourceAdjacency.
|
|
1035
|
+
sourceAdjacency.splice(index, 1);
|
|
1033
1036
|
}
|
|
1034
1037
|
}
|
|
1035
|
-
const targetReverseAdjacency =
|
|
1036
|
-
if (
|
|
1037
|
-
const index = targetReverseAdjacency.
|
|
1038
|
+
const targetReverseAdjacency = mutable.reverseAdjacency.get(target);
|
|
1039
|
+
if (targetReverseAdjacency !== undefined) {
|
|
1040
|
+
const index = targetReverseAdjacency.indexOf(edgeIndex);
|
|
1038
1041
|
if (index !== -1) {
|
|
1039
|
-
targetReverseAdjacency.
|
|
1042
|
+
targetReverseAdjacency.splice(index, 1);
|
|
1040
1043
|
}
|
|
1041
1044
|
}
|
|
1042
1045
|
// For undirected graphs, remove reverse connections
|
|
1043
1046
|
if (mutable.type === "undirected") {
|
|
1044
|
-
const targetAdjacency =
|
|
1045
|
-
if (
|
|
1046
|
-
const index = targetAdjacency.
|
|
1047
|
+
const targetAdjacency = mutable.adjacency.get(target);
|
|
1048
|
+
if (targetAdjacency !== undefined) {
|
|
1049
|
+
const index = targetAdjacency.indexOf(edgeIndex);
|
|
1047
1050
|
if (index !== -1) {
|
|
1048
|
-
targetAdjacency.
|
|
1051
|
+
targetAdjacency.splice(index, 1);
|
|
1049
1052
|
}
|
|
1050
1053
|
}
|
|
1051
|
-
const sourceReverseAdjacency =
|
|
1052
|
-
if (
|
|
1053
|
-
const index = sourceReverseAdjacency.
|
|
1054
|
+
const sourceReverseAdjacency = mutable.reverseAdjacency.get(source);
|
|
1055
|
+
if (sourceReverseAdjacency !== undefined) {
|
|
1056
|
+
const index = sourceReverseAdjacency.indexOf(edgeIndex);
|
|
1054
1057
|
if (index !== -1) {
|
|
1055
|
-
sourceReverseAdjacency.
|
|
1058
|
+
sourceReverseAdjacency.splice(index, 1);
|
|
1056
1059
|
}
|
|
1057
1060
|
}
|
|
1058
1061
|
}
|
|
@@ -1089,7 +1092,7 @@ const removeEdgeInternal = (mutable, edgeIndex) => {
|
|
|
1089
1092
|
* @since 3.18.0
|
|
1090
1093
|
* @category getters
|
|
1091
1094
|
*/
|
|
1092
|
-
export const getEdge = (graph, edgeIndex) =>
|
|
1095
|
+
export const getEdge = (graph, edgeIndex) => graph.edges.has(edgeIndex) ? Option.some(graph.edges.get(edgeIndex)) : Option.none();
|
|
1093
1096
|
/**
|
|
1094
1097
|
* Checks if an edge exists between two nodes in the graph.
|
|
1095
1098
|
*
|
|
@@ -1119,14 +1122,14 @@ export const getEdge = (graph, edgeIndex) => getMapSafe(graph.edges, edgeIndex);
|
|
|
1119
1122
|
* @category getters
|
|
1120
1123
|
*/
|
|
1121
1124
|
export const hasEdge = (graph, source, target) => {
|
|
1122
|
-
const adjacencyList =
|
|
1123
|
-
if (
|
|
1125
|
+
const adjacencyList = graph.adjacency.get(source);
|
|
1126
|
+
if (adjacencyList === undefined) {
|
|
1124
1127
|
return false;
|
|
1125
1128
|
}
|
|
1126
1129
|
// Check if any edge in the adjacency list connects to the target
|
|
1127
|
-
for (const edgeIndex of adjacencyList
|
|
1128
|
-
const edge =
|
|
1129
|
-
if (
|
|
1130
|
+
for (const edgeIndex of adjacencyList) {
|
|
1131
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1132
|
+
if (edge !== undefined && edge.target === target) {
|
|
1130
1133
|
return true;
|
|
1131
1134
|
}
|
|
1132
1135
|
}
|
|
@@ -1192,15 +1195,15 @@ export const neighbors = (graph, nodeIndex) => {
|
|
|
1192
1195
|
if (graph.type === "undirected") {
|
|
1193
1196
|
return getUndirectedNeighbors(graph, nodeIndex);
|
|
1194
1197
|
}
|
|
1195
|
-
const adjacencyList =
|
|
1196
|
-
if (
|
|
1198
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1199
|
+
if (adjacencyList === undefined) {
|
|
1197
1200
|
return [];
|
|
1198
1201
|
}
|
|
1199
1202
|
const result = [];
|
|
1200
|
-
for (const edgeIndex of adjacencyList
|
|
1201
|
-
const edge =
|
|
1202
|
-
if (
|
|
1203
|
-
result.push(edge.
|
|
1203
|
+
for (const edgeIndex of adjacencyList) {
|
|
1204
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1205
|
+
if (edge !== undefined) {
|
|
1206
|
+
result.push(edge.target);
|
|
1204
1207
|
}
|
|
1205
1208
|
}
|
|
1206
1209
|
return result;
|
|
@@ -1233,24 +1236,21 @@ export const neighbors = (graph, nodeIndex) => {
|
|
|
1233
1236
|
*/
|
|
1234
1237
|
export const neighborsDirected = (graph, nodeIndex, direction) => {
|
|
1235
1238
|
const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
|
|
1236
|
-
const adjacencyList =
|
|
1237
|
-
if (
|
|
1239
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1240
|
+
if (adjacencyList === undefined) {
|
|
1238
1241
|
return [];
|
|
1239
1242
|
}
|
|
1240
1243
|
const result = [];
|
|
1241
|
-
for (const edgeIndex of adjacencyList
|
|
1242
|
-
const edge =
|
|
1243
|
-
if (
|
|
1244
|
+
for (const edgeIndex of adjacencyList) {
|
|
1245
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1246
|
+
if (edge !== undefined) {
|
|
1244
1247
|
// For incoming direction, we want the source node instead of target
|
|
1245
|
-
const neighborNode = direction === "incoming" ? edge.
|
|
1248
|
+
const neighborNode = direction === "incoming" ? edge.source : edge.target;
|
|
1246
1249
|
result.push(neighborNode);
|
|
1247
1250
|
}
|
|
1248
1251
|
}
|
|
1249
1252
|
return result;
|
|
1250
1253
|
};
|
|
1251
|
-
// =============================================================================
|
|
1252
|
-
// GraphViz Export
|
|
1253
|
-
// =============================================================================
|
|
1254
1254
|
/**
|
|
1255
1255
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
1256
1256
|
*
|
|
@@ -1306,6 +1306,97 @@ export const toGraphViz = (graph, options) => {
|
|
|
1306
1306
|
lines.push("}");
|
|
1307
1307
|
return lines.join("\n");
|
|
1308
1308
|
};
|
|
1309
|
+
/** @internal */
|
|
1310
|
+
const escapeMermaidLabel = label => {
|
|
1311
|
+
// Escape special characters for Mermaid using HTML entity codes
|
|
1312
|
+
// According to: https://mermaid.js.org/syntax/flowchart.html#special-characters-that-break-syntax
|
|
1313
|
+
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/>");
|
|
1314
|
+
};
|
|
1315
|
+
/** @internal */
|
|
1316
|
+
const formatMermaidNode = (nodeId, label, shape) => {
|
|
1317
|
+
switch (shape) {
|
|
1318
|
+
case "rectangle":
|
|
1319
|
+
return `${nodeId}["${label}"]`;
|
|
1320
|
+
case "rounded":
|
|
1321
|
+
return `${nodeId}("${label}")`;
|
|
1322
|
+
case "circle":
|
|
1323
|
+
return `${nodeId}(("${label}"))`;
|
|
1324
|
+
case "diamond":
|
|
1325
|
+
return `${nodeId}{"${label}"}`;
|
|
1326
|
+
case "hexagon":
|
|
1327
|
+
return `${nodeId}{{"${label}"}}`;
|
|
1328
|
+
case "stadium":
|
|
1329
|
+
return `${nodeId}(["${label}"])`;
|
|
1330
|
+
case "subroutine":
|
|
1331
|
+
return `${nodeId}[["${label}"]]`;
|
|
1332
|
+
case "cylindrical":
|
|
1333
|
+
return `${nodeId}[("${label}")]`;
|
|
1334
|
+
}
|
|
1335
|
+
};
|
|
1336
|
+
/**
|
|
1337
|
+
* Exports a graph to Mermaid diagram format for visualization.
|
|
1338
|
+
*
|
|
1339
|
+
* @example
|
|
1340
|
+
* ```ts
|
|
1341
|
+
* import { Graph } from "effect"
|
|
1342
|
+
*
|
|
1343
|
+
* const graph = Graph.mutate(Graph.directed<string, number>(), (mutable) => {
|
|
1344
|
+
* const app = Graph.addNode(mutable, "App")
|
|
1345
|
+
* const db = Graph.addNode(mutable, "Database")
|
|
1346
|
+
* const cache = Graph.addNode(mutable, "Cache")
|
|
1347
|
+
* Graph.addEdge(mutable, app, db, 1)
|
|
1348
|
+
* Graph.addEdge(mutable, app, cache, 2)
|
|
1349
|
+
* })
|
|
1350
|
+
*
|
|
1351
|
+
* const mermaid = Graph.toMermaid(graph)
|
|
1352
|
+
* console.log(mermaid)
|
|
1353
|
+
* // flowchart TD
|
|
1354
|
+
* // 0["App"]
|
|
1355
|
+
* // 1["Database"]
|
|
1356
|
+
* // 2["Cache"]
|
|
1357
|
+
* // 0 -->|"1"| 1
|
|
1358
|
+
* // 0 -->|"2"| 2
|
|
1359
|
+
* ```
|
|
1360
|
+
*
|
|
1361
|
+
* @since 3.18.0
|
|
1362
|
+
* @category utils
|
|
1363
|
+
*/
|
|
1364
|
+
export const toMermaid = (graph, options) => {
|
|
1365
|
+
// Extract and validate options with defaults
|
|
1366
|
+
const {
|
|
1367
|
+
diagramType,
|
|
1368
|
+
direction = "TD",
|
|
1369
|
+
edgeLabel = data => String(data),
|
|
1370
|
+
nodeLabel = data => String(data),
|
|
1371
|
+
nodeShape = () => "rectangle"
|
|
1372
|
+
} = options ?? {};
|
|
1373
|
+
// Auto-detect diagram type if not specified
|
|
1374
|
+
const finalDiagramType = diagramType ?? (graph.type === "directed" ? "flowchart" : "graph");
|
|
1375
|
+
// Generate diagram header
|
|
1376
|
+
const lines = [];
|
|
1377
|
+
lines.push(`${finalDiagramType} ${direction}`);
|
|
1378
|
+
// Add nodes
|
|
1379
|
+
for (const [nodeIndex, nodeData] of graph.nodes) {
|
|
1380
|
+
const nodeId = String(nodeIndex);
|
|
1381
|
+
const label = escapeMermaidLabel(nodeLabel(nodeData));
|
|
1382
|
+
const shape = nodeShape(nodeData);
|
|
1383
|
+
const formattedNode = formatMermaidNode(nodeId, label, shape);
|
|
1384
|
+
lines.push(` ${formattedNode}`);
|
|
1385
|
+
}
|
|
1386
|
+
// Add edges
|
|
1387
|
+
const edgeOperator = finalDiagramType === "flowchart" ? "-->" : "---";
|
|
1388
|
+
for (const [, edgeData] of graph.edges) {
|
|
1389
|
+
const sourceId = String(edgeData.source);
|
|
1390
|
+
const targetId = String(edgeData.target);
|
|
1391
|
+
const label = escapeMermaidLabel(edgeLabel(edgeData.data));
|
|
1392
|
+
if (label) {
|
|
1393
|
+
lines.push(` ${sourceId} ${edgeOperator}|"${label}"| ${targetId}`);
|
|
1394
|
+
} else {
|
|
1395
|
+
lines.push(` ${sourceId} ${edgeOperator} ${targetId}`);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
return lines.join("\n");
|
|
1399
|
+
};
|
|
1309
1400
|
// =============================================================================
|
|
1310
1401
|
// =============================================================================
|
|
1311
1402
|
// Graph Structure Analysis Algorithms (Phase 5A)
|
|
@@ -1487,13 +1578,13 @@ export const isBipartite = graph => {
|
|
|
1487
1578
|
const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
1488
1579
|
const neighbors = new Set();
|
|
1489
1580
|
// Check edges where this node is the source
|
|
1490
|
-
const adjacencyList =
|
|
1491
|
-
if (
|
|
1492
|
-
for (const edgeIndex of adjacencyList
|
|
1493
|
-
const edge =
|
|
1494
|
-
if (
|
|
1581
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1582
|
+
if (adjacencyList !== undefined) {
|
|
1583
|
+
for (const edgeIndex of adjacencyList) {
|
|
1584
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1585
|
+
if (edge !== undefined) {
|
|
1495
1586
|
// For undirected graphs, the neighbor is the other endpoint
|
|
1496
|
-
const otherNode = edge.
|
|
1587
|
+
const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
|
|
1497
1588
|
neighbors.add(otherNode);
|
|
1498
1589
|
}
|
|
1499
1590
|
}
|
|
@@ -1627,12 +1718,12 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1627
1718
|
visited.add(node);
|
|
1628
1719
|
scc.push(node);
|
|
1629
1720
|
// Use reverse adjacency (transpose graph)
|
|
1630
|
-
const reverseAdjacency =
|
|
1631
|
-
if (
|
|
1632
|
-
for (const edgeIndex of reverseAdjacency
|
|
1633
|
-
const edge =
|
|
1634
|
-
if (
|
|
1635
|
-
const predecessor = edge.
|
|
1721
|
+
const reverseAdjacency = graph.reverseAdjacency.get(node);
|
|
1722
|
+
if (reverseAdjacency !== undefined) {
|
|
1723
|
+
for (const edgeIndex of reverseAdjacency) {
|
|
1724
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1725
|
+
if (edge !== undefined) {
|
|
1726
|
+
const predecessor = edge.source;
|
|
1636
1727
|
if (!visited.has(predecessor)) {
|
|
1637
1728
|
stack.push(predecessor);
|
|
1638
1729
|
}
|
|
@@ -1663,7 +1754,7 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1663
1754
|
* Graph.addEdge(mutable, b, c, 2)
|
|
1664
1755
|
* })
|
|
1665
1756
|
*
|
|
1666
|
-
* const result = Graph.dijkstra(graph, 0, 2, (edgeData) => edgeData)
|
|
1757
|
+
* const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
1667
1758
|
* if (Option.isSome(result)) {
|
|
1668
1759
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
1669
1760
|
* console.log(result.value.distance) // 7 - total distance
|
|
@@ -1673,20 +1764,25 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1673
1764
|
* @since 3.18.0
|
|
1674
1765
|
* @category algorithms
|
|
1675
1766
|
*/
|
|
1676
|
-
export const dijkstra = (graph,
|
|
1767
|
+
export const dijkstra = (graph, config) => {
|
|
1768
|
+
const {
|
|
1769
|
+
cost,
|
|
1770
|
+
source,
|
|
1771
|
+
target
|
|
1772
|
+
} = config;
|
|
1677
1773
|
// Validate that source and target nodes exist
|
|
1678
1774
|
if (!graph.nodes.has(source)) {
|
|
1679
|
-
throw
|
|
1775
|
+
throw missingNode(source);
|
|
1680
1776
|
}
|
|
1681
1777
|
if (!graph.nodes.has(target)) {
|
|
1682
|
-
throw
|
|
1778
|
+
throw missingNode(target);
|
|
1683
1779
|
}
|
|
1684
1780
|
// Early return if source equals target
|
|
1685
1781
|
if (source === target) {
|
|
1686
1782
|
return Option.some({
|
|
1687
1783
|
path: [source],
|
|
1688
1784
|
distance: 0,
|
|
1689
|
-
|
|
1785
|
+
costs: []
|
|
1690
1786
|
});
|
|
1691
1787
|
}
|
|
1692
1788
|
// Distance tracking and priority queue simulation
|
|
@@ -1726,13 +1822,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1726
1822
|
// Get current distance
|
|
1727
1823
|
const currentDistance = distances.get(currentNode);
|
|
1728
1824
|
// Examine all outgoing edges
|
|
1729
|
-
const adjacencyList =
|
|
1730
|
-
if (
|
|
1731
|
-
for (const edgeIndex of adjacencyList
|
|
1732
|
-
const edge =
|
|
1733
|
-
if (
|
|
1734
|
-
const neighbor = edge.
|
|
1735
|
-
const weight =
|
|
1825
|
+
const adjacencyList = graph.adjacency.get(currentNode);
|
|
1826
|
+
if (adjacencyList !== undefined) {
|
|
1827
|
+
for (const edgeIndex of adjacencyList) {
|
|
1828
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1829
|
+
if (edge !== undefined) {
|
|
1830
|
+
const neighbor = edge.target;
|
|
1831
|
+
const weight = cost(edge.data);
|
|
1736
1832
|
// Validate non-negative weights
|
|
1737
1833
|
if (weight < 0) {
|
|
1738
1834
|
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -1744,7 +1840,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1744
1840
|
distances.set(neighbor, newDistance);
|
|
1745
1841
|
previous.set(neighbor, {
|
|
1746
1842
|
node: currentNode,
|
|
1747
|
-
edgeData: edge.
|
|
1843
|
+
edgeData: edge.data
|
|
1748
1844
|
});
|
|
1749
1845
|
// Add to priority queue if not visited
|
|
1750
1846
|
if (!visited.has(neighbor)) {
|
|
@@ -1765,13 +1861,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1765
1861
|
}
|
|
1766
1862
|
// Reconstruct path
|
|
1767
1863
|
const path = [];
|
|
1768
|
-
const
|
|
1864
|
+
const costs = [];
|
|
1769
1865
|
let currentNode = target;
|
|
1770
1866
|
while (currentNode !== null) {
|
|
1771
1867
|
path.unshift(currentNode);
|
|
1772
1868
|
const prev = previous.get(currentNode);
|
|
1773
1869
|
if (prev !== null) {
|
|
1774
|
-
|
|
1870
|
+
costs.unshift(prev.edgeData);
|
|
1775
1871
|
currentNode = prev.node;
|
|
1776
1872
|
} else {
|
|
1777
1873
|
currentNode = null;
|
|
@@ -1780,7 +1876,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1780
1876
|
return Option.some({
|
|
1781
1877
|
path,
|
|
1782
1878
|
distance: targetDistance,
|
|
1783
|
-
|
|
1879
|
+
costs
|
|
1784
1880
|
});
|
|
1785
1881
|
};
|
|
1786
1882
|
/**
|
|
@@ -1810,7 +1906,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1810
1906
|
* @since 3.18.0
|
|
1811
1907
|
* @category algorithms
|
|
1812
1908
|
*/
|
|
1813
|
-
export const floydWarshall = (graph,
|
|
1909
|
+
export const floydWarshall = (graph, cost) => {
|
|
1814
1910
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
1815
1911
|
const allNodes = Array.from(graph.nodes.keys());
|
|
1816
1912
|
// Initialize distance matrix
|
|
@@ -1830,7 +1926,7 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1830
1926
|
}
|
|
1831
1927
|
// Set edge weights
|
|
1832
1928
|
for (const [, edgeData] of graph.edges) {
|
|
1833
|
-
const weight =
|
|
1929
|
+
const weight = cost(edgeData.data);
|
|
1834
1930
|
const i = edgeData.source;
|
|
1835
1931
|
const j = edgeData.target;
|
|
1836
1932
|
// Use minimum weight if multiple edges exist
|
|
@@ -1863,17 +1959,17 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1863
1959
|
}
|
|
1864
1960
|
// Build result paths and edge weights
|
|
1865
1961
|
const paths = new Map();
|
|
1866
|
-
const
|
|
1962
|
+
const resultCosts = new Map();
|
|
1867
1963
|
for (const i of allNodes) {
|
|
1868
1964
|
paths.set(i, new Map());
|
|
1869
|
-
|
|
1965
|
+
resultCosts.set(i, new Map());
|
|
1870
1966
|
for (const j of allNodes) {
|
|
1871
1967
|
if (i === j) {
|
|
1872
1968
|
paths.get(i).set(j, [i]);
|
|
1873
|
-
|
|
1969
|
+
resultCosts.get(i).set(j, []);
|
|
1874
1970
|
} else if (dist.get(i).get(j) === Infinity) {
|
|
1875
1971
|
paths.get(i).set(j, null);
|
|
1876
|
-
|
|
1972
|
+
resultCosts.get(i).set(j, []);
|
|
1877
1973
|
} else {
|
|
1878
1974
|
// Reconstruct path iteratively
|
|
1879
1975
|
const path = [];
|
|
@@ -1891,14 +1987,14 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1891
1987
|
path.push(current);
|
|
1892
1988
|
}
|
|
1893
1989
|
paths.get(i).set(j, path);
|
|
1894
|
-
|
|
1990
|
+
resultCosts.get(i).set(j, weights);
|
|
1895
1991
|
}
|
|
1896
1992
|
}
|
|
1897
1993
|
}
|
|
1898
1994
|
return {
|
|
1899
1995
|
distances: dist,
|
|
1900
1996
|
paths,
|
|
1901
|
-
|
|
1997
|
+
costs: resultCosts
|
|
1902
1998
|
};
|
|
1903
1999
|
};
|
|
1904
2000
|
/**
|
|
@@ -1924,7 +2020,7 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1924
2020
|
* const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
|
|
1925
2021
|
* Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
|
|
1926
2022
|
*
|
|
1927
|
-
* const result = Graph.astar(graph, 0, 2, (edgeData) => edgeData, heuristic)
|
|
2023
|
+
* const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
|
|
1928
2024
|
* if (Option.isSome(result)) {
|
|
1929
2025
|
* console.log(result.value.path) // [0, 1, 2] - shortest path
|
|
1930
2026
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -1934,25 +2030,31 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1934
2030
|
* @since 3.18.0
|
|
1935
2031
|
* @category algorithms
|
|
1936
2032
|
*/
|
|
1937
|
-
export const astar = (graph,
|
|
2033
|
+
export const astar = (graph, config) => {
|
|
2034
|
+
const {
|
|
2035
|
+
cost,
|
|
2036
|
+
heuristic,
|
|
2037
|
+
source,
|
|
2038
|
+
target
|
|
2039
|
+
} = config;
|
|
1938
2040
|
// Validate that source and target nodes exist
|
|
1939
2041
|
if (!graph.nodes.has(source)) {
|
|
1940
|
-
throw
|
|
2042
|
+
throw missingNode(source);
|
|
1941
2043
|
}
|
|
1942
2044
|
if (!graph.nodes.has(target)) {
|
|
1943
|
-
throw
|
|
2045
|
+
throw missingNode(target);
|
|
1944
2046
|
}
|
|
1945
2047
|
// Early return if source equals target
|
|
1946
2048
|
if (source === target) {
|
|
1947
2049
|
return Option.some({
|
|
1948
2050
|
path: [source],
|
|
1949
2051
|
distance: 0,
|
|
1950
|
-
|
|
2052
|
+
costs: []
|
|
1951
2053
|
});
|
|
1952
2054
|
}
|
|
1953
2055
|
// Get target node data for heuristic calculations
|
|
1954
|
-
const targetNodeData =
|
|
1955
|
-
if (
|
|
2056
|
+
const targetNodeData = graph.nodes.get(target);
|
|
2057
|
+
if (targetNodeData === undefined) {
|
|
1956
2058
|
throw new Error(`Target node ${target} data not found`);
|
|
1957
2059
|
}
|
|
1958
2060
|
// Distance tracking (g-score) and f-score (g + h)
|
|
@@ -1968,9 +2070,9 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
1968
2070
|
previous.set(node, null);
|
|
1969
2071
|
}
|
|
1970
2072
|
// Calculate initial f-score for source
|
|
1971
|
-
const sourceNodeData =
|
|
1972
|
-
if (
|
|
1973
|
-
const h = heuristic(sourceNodeData
|
|
2073
|
+
const sourceNodeData = graph.nodes.get(source);
|
|
2074
|
+
if (sourceNodeData !== undefined) {
|
|
2075
|
+
const h = heuristic(sourceNodeData, targetNodeData);
|
|
1974
2076
|
fScore.set(source, h);
|
|
1975
2077
|
}
|
|
1976
2078
|
// Priority queue using f-score (total estimated cost)
|
|
@@ -2000,13 +2102,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2000
2102
|
// Get current g-score
|
|
2001
2103
|
const currentGScore = gScore.get(currentNode);
|
|
2002
2104
|
// Examine all outgoing edges
|
|
2003
|
-
const adjacencyList =
|
|
2004
|
-
if (
|
|
2005
|
-
for (const edgeIndex of adjacencyList
|
|
2006
|
-
const edge =
|
|
2007
|
-
if (
|
|
2008
|
-
const neighbor = edge.
|
|
2009
|
-
const weight =
|
|
2105
|
+
const adjacencyList = graph.adjacency.get(currentNode);
|
|
2106
|
+
if (adjacencyList !== undefined) {
|
|
2107
|
+
for (const edgeIndex of adjacencyList) {
|
|
2108
|
+
const edge = graph.edges.get(edgeIndex);
|
|
2109
|
+
if (edge !== undefined) {
|
|
2110
|
+
const neighbor = edge.target;
|
|
2111
|
+
const weight = cost(edge.data);
|
|
2010
2112
|
// Validate non-negative weights
|
|
2011
2113
|
if (weight < 0) {
|
|
2012
2114
|
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -2019,12 +2121,12 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2019
2121
|
gScore.set(neighbor, tentativeGScore);
|
|
2020
2122
|
previous.set(neighbor, {
|
|
2021
2123
|
node: currentNode,
|
|
2022
|
-
edgeData: edge.
|
|
2124
|
+
edgeData: edge.data
|
|
2023
2125
|
});
|
|
2024
2126
|
// Calculate f-score using heuristic
|
|
2025
|
-
const neighborNodeData =
|
|
2026
|
-
if (
|
|
2027
|
-
const h = heuristic(neighborNodeData
|
|
2127
|
+
const neighborNodeData = graph.nodes.get(neighbor);
|
|
2128
|
+
if (neighborNodeData !== undefined) {
|
|
2129
|
+
const h = heuristic(neighborNodeData, targetNodeData);
|
|
2028
2130
|
const f = tentativeGScore + h;
|
|
2029
2131
|
fScore.set(neighbor, f);
|
|
2030
2132
|
// Add to open set if not visited
|
|
@@ -2047,13 +2149,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2047
2149
|
}
|
|
2048
2150
|
// Reconstruct path
|
|
2049
2151
|
const path = [];
|
|
2050
|
-
const
|
|
2152
|
+
const costs = [];
|
|
2051
2153
|
let currentNode = target;
|
|
2052
2154
|
while (currentNode !== null) {
|
|
2053
2155
|
path.unshift(currentNode);
|
|
2054
2156
|
const prev = previous.get(currentNode);
|
|
2055
2157
|
if (prev !== null) {
|
|
2056
|
-
|
|
2158
|
+
costs.unshift(prev.edgeData);
|
|
2057
2159
|
currentNode = prev.node;
|
|
2058
2160
|
} else {
|
|
2059
2161
|
currentNode = null;
|
|
@@ -2062,7 +2164,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2062
2164
|
return Option.some({
|
|
2063
2165
|
path,
|
|
2064
2166
|
distance: targetGScore,
|
|
2065
|
-
|
|
2167
|
+
costs
|
|
2066
2168
|
});
|
|
2067
2169
|
};
|
|
2068
2170
|
/**
|
|
@@ -2085,7 +2187,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2085
2187
|
* Graph.addEdge(mutable, a, c, 5)
|
|
2086
2188
|
* })
|
|
2087
2189
|
*
|
|
2088
|
-
* const result = Graph.bellmanFord(graph, 0, 2, (edgeData) => edgeData)
|
|
2190
|
+
* const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
2089
2191
|
* if (Option.isSome(result)) {
|
|
2090
2192
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
2091
2193
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -2095,20 +2197,25 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2095
2197
|
* @since 3.18.0
|
|
2096
2198
|
* @category algorithms
|
|
2097
2199
|
*/
|
|
2098
|
-
export const bellmanFord = (graph,
|
|
2200
|
+
export const bellmanFord = (graph, config) => {
|
|
2201
|
+
const {
|
|
2202
|
+
cost,
|
|
2203
|
+
source,
|
|
2204
|
+
target
|
|
2205
|
+
} = config;
|
|
2099
2206
|
// Validate that source and target nodes exist
|
|
2100
2207
|
if (!graph.nodes.has(source)) {
|
|
2101
|
-
throw
|
|
2208
|
+
throw missingNode(source);
|
|
2102
2209
|
}
|
|
2103
2210
|
if (!graph.nodes.has(target)) {
|
|
2104
|
-
throw
|
|
2211
|
+
throw missingNode(target);
|
|
2105
2212
|
}
|
|
2106
2213
|
// Early return if source equals target
|
|
2107
2214
|
if (source === target) {
|
|
2108
2215
|
return Option.some({
|
|
2109
2216
|
path: [source],
|
|
2110
2217
|
distance: 0,
|
|
2111
|
-
|
|
2218
|
+
costs: []
|
|
2112
2219
|
});
|
|
2113
2220
|
}
|
|
2114
2221
|
// Initialize distances and predecessors
|
|
@@ -2122,7 +2229,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2122
2229
|
// Collect all edges for relaxation
|
|
2123
2230
|
const edges = [];
|
|
2124
2231
|
for (const [, edgeData] of graph.edges) {
|
|
2125
|
-
const weight =
|
|
2232
|
+
const weight = cost(edgeData.data);
|
|
2126
2233
|
edges.push({
|
|
2127
2234
|
source: edgeData.source,
|
|
2128
2235
|
target: edgeData.target,
|
|
@@ -2165,12 +2272,12 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2165
2272
|
if (affectedNodes.has(node)) continue;
|
|
2166
2273
|
affectedNodes.add(node);
|
|
2167
2274
|
// Add all nodes reachable from this node
|
|
2168
|
-
const adjacencyList =
|
|
2169
|
-
if (
|
|
2170
|
-
for (const edgeIndex of adjacencyList
|
|
2171
|
-
const edge =
|
|
2172
|
-
if (
|
|
2173
|
-
queue.push(edge.
|
|
2275
|
+
const adjacencyList = graph.adjacency.get(node);
|
|
2276
|
+
if (adjacencyList !== undefined) {
|
|
2277
|
+
for (const edgeIndex of adjacencyList) {
|
|
2278
|
+
const edge = graph.edges.get(edgeIndex);
|
|
2279
|
+
if (edge !== undefined) {
|
|
2280
|
+
queue.push(edge.target);
|
|
2174
2281
|
}
|
|
2175
2282
|
}
|
|
2176
2283
|
}
|
|
@@ -2188,13 +2295,13 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2188
2295
|
}
|
|
2189
2296
|
// Reconstruct path
|
|
2190
2297
|
const path = [];
|
|
2191
|
-
const
|
|
2298
|
+
const costs = [];
|
|
2192
2299
|
let currentNode = target;
|
|
2193
2300
|
while (currentNode !== null) {
|
|
2194
2301
|
path.unshift(currentNode);
|
|
2195
2302
|
const prev = previous.get(currentNode);
|
|
2196
2303
|
if (prev !== null) {
|
|
2197
|
-
|
|
2304
|
+
costs.unshift(prev.edgeData);
|
|
2198
2305
|
currentNode = prev.node;
|
|
2199
2306
|
} else {
|
|
2200
2307
|
currentNode = null;
|
|
@@ -2203,7 +2310,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2203
2310
|
return Option.some({
|
|
2204
2311
|
path,
|
|
2205
2312
|
distance: targetDistance,
|
|
2206
|
-
|
|
2313
|
+
costs
|
|
2207
2314
|
});
|
|
2208
2315
|
};
|
|
2209
2316
|
/**
|
|
@@ -2224,7 +2331,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2224
2331
|
* })
|
|
2225
2332
|
*
|
|
2226
2333
|
* // Both traversal and element iterators return NodeWalker
|
|
2227
|
-
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, {
|
|
2334
|
+
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
|
|
2228
2335
|
* const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
|
|
2229
2336
|
*
|
|
2230
2337
|
* // Common interface for working with node iterables
|
|
@@ -2260,7 +2367,7 @@ export class Walker {
|
|
|
2260
2367
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2261
2368
|
* })
|
|
2262
2369
|
*
|
|
2263
|
-
* const dfs = Graph.dfs(graph, {
|
|
2370
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2264
2371
|
*
|
|
2265
2372
|
* // Map to just the node data
|
|
2266
2373
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2293,7 +2400,7 @@ export class Walker {
|
|
|
2293
2400
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2294
2401
|
* })
|
|
2295
2402
|
*
|
|
2296
|
-
* const dfs = Graph.dfs(graph, {
|
|
2403
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2297
2404
|
*
|
|
2298
2405
|
* // Map to just the node data
|
|
2299
2406
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2325,7 +2432,7 @@ export class Walker {
|
|
|
2325
2432
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2326
2433
|
* })
|
|
2327
2434
|
*
|
|
2328
|
-
* const dfs = Graph.dfs(graph, {
|
|
2435
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2329
2436
|
* const indices = Array.from(Graph.indices(dfs))
|
|
2330
2437
|
* console.log(indices) // [0, 1]
|
|
2331
2438
|
* ```
|
|
@@ -2347,7 +2454,7 @@ export const indices = walker => walker.visit((index, _) => index);
|
|
|
2347
2454
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2348
2455
|
* })
|
|
2349
2456
|
*
|
|
2350
|
-
* const dfs = Graph.dfs(graph, {
|
|
2457
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2351
2458
|
* const values = Array.from(Graph.values(dfs))
|
|
2352
2459
|
* console.log(values) // ["A", "B"]
|
|
2353
2460
|
* ```
|
|
@@ -2369,7 +2476,7 @@ export const values = walker => walker.visit((_, data) => data);
|
|
|
2369
2476
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2370
2477
|
* })
|
|
2371
2478
|
*
|
|
2372
|
-
* const dfs = Graph.dfs(graph, {
|
|
2479
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2373
2480
|
* const entries = Array.from(Graph.entries(dfs))
|
|
2374
2481
|
* console.log(entries) // [[0, "A"], [1, "B"]]
|
|
2375
2482
|
* ```
|
|
@@ -2397,7 +2504,7 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2397
2504
|
* })
|
|
2398
2505
|
*
|
|
2399
2506
|
* // Start from a specific node
|
|
2400
|
-
* const dfs1 = Graph.dfs(graph, {
|
|
2507
|
+
* const dfs1 = Graph.dfs(graph, { start: [0] })
|
|
2401
2508
|
* for (const nodeIndex of Graph.indices(dfs1)) {
|
|
2402
2509
|
* console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
|
|
2403
2510
|
* }
|
|
@@ -2411,17 +2518,17 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2411
2518
|
* @category iterators
|
|
2412
2519
|
*/
|
|
2413
2520
|
export const dfs = (graph, config = {}) => {
|
|
2414
|
-
const
|
|
2521
|
+
const start = config.start ?? [];
|
|
2415
2522
|
const direction = config.direction ?? "outgoing";
|
|
2416
2523
|
// Validate that all start nodes exist
|
|
2417
|
-
for (const nodeIndex of
|
|
2524
|
+
for (const nodeIndex of start) {
|
|
2418
2525
|
if (!hasNode(graph, nodeIndex)) {
|
|
2419
|
-
throw
|
|
2526
|
+
throw missingNode(nodeIndex);
|
|
2420
2527
|
}
|
|
2421
2528
|
}
|
|
2422
2529
|
return new Walker(f => ({
|
|
2423
2530
|
[Symbol.iterator]: () => {
|
|
2424
|
-
const stack = [...
|
|
2531
|
+
const stack = [...start];
|
|
2425
2532
|
const discovered = new Set();
|
|
2426
2533
|
const nextMapped = () => {
|
|
2427
2534
|
while (stack.length > 0) {
|
|
@@ -2430,8 +2537,8 @@ export const dfs = (graph, config = {}) => {
|
|
|
2430
2537
|
continue;
|
|
2431
2538
|
}
|
|
2432
2539
|
discovered.add(current);
|
|
2433
|
-
const nodeDataOption =
|
|
2434
|
-
if (
|
|
2540
|
+
const nodeDataOption = graph.nodes.get(current);
|
|
2541
|
+
if (nodeDataOption === undefined) {
|
|
2435
2542
|
continue;
|
|
2436
2543
|
}
|
|
2437
2544
|
const neighbors = neighborsDirected(graph, current, direction);
|
|
@@ -2443,7 +2550,7 @@ export const dfs = (graph, config = {}) => {
|
|
|
2443
2550
|
}
|
|
2444
2551
|
return {
|
|
2445
2552
|
done: false,
|
|
2446
|
-
value: f(current, nodeDataOption
|
|
2553
|
+
value: f(current, nodeDataOption)
|
|
2447
2554
|
};
|
|
2448
2555
|
}
|
|
2449
2556
|
return {
|
|
@@ -2476,7 +2583,7 @@ export const dfs = (graph, config = {}) => {
|
|
|
2476
2583
|
* })
|
|
2477
2584
|
*
|
|
2478
2585
|
* // Start from a specific node
|
|
2479
|
-
* const bfs1 = Graph.bfs(graph, {
|
|
2586
|
+
* const bfs1 = Graph.bfs(graph, { start: [0] })
|
|
2480
2587
|
* for (const nodeIndex of Graph.indices(bfs1)) {
|
|
2481
2588
|
* console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
|
|
2482
2589
|
* }
|
|
@@ -2490,17 +2597,17 @@ export const dfs = (graph, config = {}) => {
|
|
|
2490
2597
|
* @category iterators
|
|
2491
2598
|
*/
|
|
2492
2599
|
export const bfs = (graph, config = {}) => {
|
|
2493
|
-
const
|
|
2600
|
+
const start = config.start ?? [];
|
|
2494
2601
|
const direction = config.direction ?? "outgoing";
|
|
2495
2602
|
// Validate that all start nodes exist
|
|
2496
|
-
for (const nodeIndex of
|
|
2603
|
+
for (const nodeIndex of start) {
|
|
2497
2604
|
if (!hasNode(graph, nodeIndex)) {
|
|
2498
|
-
throw
|
|
2605
|
+
throw missingNode(nodeIndex);
|
|
2499
2606
|
}
|
|
2500
2607
|
}
|
|
2501
2608
|
return new Walker(f => ({
|
|
2502
2609
|
[Symbol.iterator]: () => {
|
|
2503
|
-
const queue = [...
|
|
2610
|
+
const queue = [...start];
|
|
2504
2611
|
const discovered = new Set();
|
|
2505
2612
|
const nextMapped = () => {
|
|
2506
2613
|
while (queue.length > 0) {
|
|
@@ -2588,7 +2695,7 @@ export const topo = (graph, config = {}) => {
|
|
|
2588
2695
|
// Validate that all initial nodes exist
|
|
2589
2696
|
for (const nodeIndex of initials) {
|
|
2590
2697
|
if (!hasNode(graph, nodeIndex)) {
|
|
2591
|
-
throw
|
|
2698
|
+
throw missingNode(nodeIndex);
|
|
2592
2699
|
}
|
|
2593
2700
|
}
|
|
2594
2701
|
return new Walker(f => ({
|
|
@@ -2673,7 +2780,7 @@ export const topo = (graph, config = {}) => {
|
|
|
2673
2780
|
* })
|
|
2674
2781
|
*
|
|
2675
2782
|
* // Postorder: children before parents
|
|
2676
|
-
* const postOrder = Graph.dfsPostOrder(graph, {
|
|
2783
|
+
* const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
|
|
2677
2784
|
* for (const node of postOrder) {
|
|
2678
2785
|
* console.log(node) // 1, 2, 0
|
|
2679
2786
|
* }
|
|
@@ -2683,12 +2790,12 @@ export const topo = (graph, config = {}) => {
|
|
|
2683
2790
|
* @category iterators
|
|
2684
2791
|
*/
|
|
2685
2792
|
export const dfsPostOrder = (graph, config = {}) => {
|
|
2686
|
-
const
|
|
2793
|
+
const start = config.start ?? [];
|
|
2687
2794
|
const direction = config.direction ?? "outgoing";
|
|
2688
2795
|
// Validate that all start nodes exist
|
|
2689
|
-
for (const nodeIndex of
|
|
2796
|
+
for (const nodeIndex of start) {
|
|
2690
2797
|
if (!hasNode(graph, nodeIndex)) {
|
|
2691
|
-
throw
|
|
2798
|
+
throw missingNode(nodeIndex);
|
|
2692
2799
|
}
|
|
2693
2800
|
}
|
|
2694
2801
|
return new Walker(f => ({
|
|
@@ -2697,9 +2804,9 @@ export const dfsPostOrder = (graph, config = {}) => {
|
|
|
2697
2804
|
const discovered = new Set();
|
|
2698
2805
|
const finished = new Set();
|
|
2699
2806
|
// Initialize stack with start nodes
|
|
2700
|
-
for (let i =
|
|
2807
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
2701
2808
|
stack.push({
|
|
2702
|
-
node:
|
|
2809
|
+
node: start[i],
|
|
2703
2810
|
visitedChildren: false
|
|
2704
2811
|
});
|
|
2705
2812
|
}
|
|
@@ -2885,9 +2992,9 @@ export const externals = (graph, config = {}) => {
|
|
|
2885
2992
|
let current = nodeIterator.next();
|
|
2886
2993
|
while (!current.done) {
|
|
2887
2994
|
const [nodeIndex, nodeData] = current.value;
|
|
2888
|
-
const adjacencyList =
|
|
2995
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
2889
2996
|
// Node is external if it has no edges in the specified direction
|
|
2890
|
-
if (
|
|
2997
|
+
if (adjacencyList === undefined || adjacencyList.length === 0) {
|
|
2891
2998
|
return {
|
|
2892
2999
|
done: false,
|
|
2893
3000
|
value: f(nodeIndex, nodeData)
|