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/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
|
}
|
|
@@ -1188,15 +1191,19 @@ export const edgeCount = graph => graph.edges.size;
|
|
|
1188
1191
|
* @category getters
|
|
1189
1192
|
*/
|
|
1190
1193
|
export const neighbors = (graph, nodeIndex) => {
|
|
1191
|
-
|
|
1192
|
-
if (
|
|
1194
|
+
// For undirected graphs, use the specialized helper that returns the other endpoint
|
|
1195
|
+
if (graph.type === "undirected") {
|
|
1196
|
+
return getUndirectedNeighbors(graph, nodeIndex);
|
|
1197
|
+
}
|
|
1198
|
+
const adjacencyList = graph.adjacency.get(nodeIndex);
|
|
1199
|
+
if (adjacencyList === undefined) {
|
|
1193
1200
|
return [];
|
|
1194
1201
|
}
|
|
1195
1202
|
const result = [];
|
|
1196
|
-
for (const edgeIndex of adjacencyList
|
|
1197
|
-
const edge =
|
|
1198
|
-
if (
|
|
1199
|
-
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);
|
|
1200
1207
|
}
|
|
1201
1208
|
}
|
|
1202
1209
|
return result;
|
|
@@ -1229,24 +1236,21 @@ export const neighbors = (graph, nodeIndex) => {
|
|
|
1229
1236
|
*/
|
|
1230
1237
|
export const neighborsDirected = (graph, nodeIndex, direction) => {
|
|
1231
1238
|
const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
|
|
1232
|
-
const adjacencyList =
|
|
1233
|
-
if (
|
|
1239
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1240
|
+
if (adjacencyList === undefined) {
|
|
1234
1241
|
return [];
|
|
1235
1242
|
}
|
|
1236
1243
|
const result = [];
|
|
1237
|
-
for (const edgeIndex of adjacencyList
|
|
1238
|
-
const edge =
|
|
1239
|
-
if (
|
|
1244
|
+
for (const edgeIndex of adjacencyList) {
|
|
1245
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1246
|
+
if (edge !== undefined) {
|
|
1240
1247
|
// For incoming direction, we want the source node instead of target
|
|
1241
|
-
const neighborNode = direction === "incoming" ? edge.
|
|
1248
|
+
const neighborNode = direction === "incoming" ? edge.source : edge.target;
|
|
1242
1249
|
result.push(neighborNode);
|
|
1243
1250
|
}
|
|
1244
1251
|
}
|
|
1245
1252
|
return result;
|
|
1246
1253
|
};
|
|
1247
|
-
// =============================================================================
|
|
1248
|
-
// GraphViz Export
|
|
1249
|
-
// =============================================================================
|
|
1250
1254
|
/**
|
|
1251
1255
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
1252
1256
|
*
|
|
@@ -1302,6 +1306,97 @@ export const toGraphViz = (graph, options) => {
|
|
|
1302
1306
|
lines.push("}");
|
|
1303
1307
|
return lines.join("\n");
|
|
1304
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
|
+
};
|
|
1305
1400
|
// =============================================================================
|
|
1306
1401
|
// =============================================================================
|
|
1307
1402
|
// Graph Structure Analysis Algorithms (Phase 5A)
|
|
@@ -1483,13 +1578,13 @@ export const isBipartite = graph => {
|
|
|
1483
1578
|
const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
1484
1579
|
const neighbors = new Set();
|
|
1485
1580
|
// Check edges where this node is the source
|
|
1486
|
-
const adjacencyList =
|
|
1487
|
-
if (
|
|
1488
|
-
for (const edgeIndex of adjacencyList
|
|
1489
|
-
const edge =
|
|
1490
|
-
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) {
|
|
1491
1586
|
// For undirected graphs, the neighbor is the other endpoint
|
|
1492
|
-
const otherNode = edge.
|
|
1587
|
+
const otherNode = edge.source === nodeIndex ? edge.target : edge.source;
|
|
1493
1588
|
neighbors.add(otherNode);
|
|
1494
1589
|
}
|
|
1495
1590
|
}
|
|
@@ -1623,12 +1718,12 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1623
1718
|
visited.add(node);
|
|
1624
1719
|
scc.push(node);
|
|
1625
1720
|
// Use reverse adjacency (transpose graph)
|
|
1626
|
-
const reverseAdjacency =
|
|
1627
|
-
if (
|
|
1628
|
-
for (const edgeIndex of reverseAdjacency
|
|
1629
|
-
const edge =
|
|
1630
|
-
if (
|
|
1631
|
-
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;
|
|
1632
1727
|
if (!visited.has(predecessor)) {
|
|
1633
1728
|
stack.push(predecessor);
|
|
1634
1729
|
}
|
|
@@ -1659,7 +1754,7 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1659
1754
|
* Graph.addEdge(mutable, b, c, 2)
|
|
1660
1755
|
* })
|
|
1661
1756
|
*
|
|
1662
|
-
* const result = Graph.dijkstra(graph, 0, 2, (edgeData) => edgeData)
|
|
1757
|
+
* const result = Graph.dijkstra(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
1663
1758
|
* if (Option.isSome(result)) {
|
|
1664
1759
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
1665
1760
|
* console.log(result.value.distance) // 7 - total distance
|
|
@@ -1669,20 +1764,25 @@ export const stronglyConnectedComponents = graph => {
|
|
|
1669
1764
|
* @since 3.18.0
|
|
1670
1765
|
* @category algorithms
|
|
1671
1766
|
*/
|
|
1672
|
-
export const dijkstra = (graph,
|
|
1767
|
+
export const dijkstra = (graph, config) => {
|
|
1768
|
+
const {
|
|
1769
|
+
cost,
|
|
1770
|
+
source,
|
|
1771
|
+
target
|
|
1772
|
+
} = config;
|
|
1673
1773
|
// Validate that source and target nodes exist
|
|
1674
1774
|
if (!graph.nodes.has(source)) {
|
|
1675
|
-
throw
|
|
1775
|
+
throw missingNode(source);
|
|
1676
1776
|
}
|
|
1677
1777
|
if (!graph.nodes.has(target)) {
|
|
1678
|
-
throw
|
|
1778
|
+
throw missingNode(target);
|
|
1679
1779
|
}
|
|
1680
1780
|
// Early return if source equals target
|
|
1681
1781
|
if (source === target) {
|
|
1682
1782
|
return Option.some({
|
|
1683
1783
|
path: [source],
|
|
1684
1784
|
distance: 0,
|
|
1685
|
-
|
|
1785
|
+
costs: []
|
|
1686
1786
|
});
|
|
1687
1787
|
}
|
|
1688
1788
|
// Distance tracking and priority queue simulation
|
|
@@ -1722,13 +1822,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1722
1822
|
// Get current distance
|
|
1723
1823
|
const currentDistance = distances.get(currentNode);
|
|
1724
1824
|
// Examine all outgoing edges
|
|
1725
|
-
const adjacencyList =
|
|
1726
|
-
if (
|
|
1727
|
-
for (const edgeIndex of adjacencyList
|
|
1728
|
-
const edge =
|
|
1729
|
-
if (
|
|
1730
|
-
const neighbor = edge.
|
|
1731
|
-
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);
|
|
1732
1832
|
// Validate non-negative weights
|
|
1733
1833
|
if (weight < 0) {
|
|
1734
1834
|
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -1740,7 +1840,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1740
1840
|
distances.set(neighbor, newDistance);
|
|
1741
1841
|
previous.set(neighbor, {
|
|
1742
1842
|
node: currentNode,
|
|
1743
|
-
edgeData: edge.
|
|
1843
|
+
edgeData: edge.data
|
|
1744
1844
|
});
|
|
1745
1845
|
// Add to priority queue if not visited
|
|
1746
1846
|
if (!visited.has(neighbor)) {
|
|
@@ -1761,13 +1861,13 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1761
1861
|
}
|
|
1762
1862
|
// Reconstruct path
|
|
1763
1863
|
const path = [];
|
|
1764
|
-
const
|
|
1864
|
+
const costs = [];
|
|
1765
1865
|
let currentNode = target;
|
|
1766
1866
|
while (currentNode !== null) {
|
|
1767
1867
|
path.unshift(currentNode);
|
|
1768
1868
|
const prev = previous.get(currentNode);
|
|
1769
1869
|
if (prev !== null) {
|
|
1770
|
-
|
|
1870
|
+
costs.unshift(prev.edgeData);
|
|
1771
1871
|
currentNode = prev.node;
|
|
1772
1872
|
} else {
|
|
1773
1873
|
currentNode = null;
|
|
@@ -1776,7 +1876,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1776
1876
|
return Option.some({
|
|
1777
1877
|
path,
|
|
1778
1878
|
distance: targetDistance,
|
|
1779
|
-
|
|
1879
|
+
costs
|
|
1780
1880
|
});
|
|
1781
1881
|
};
|
|
1782
1882
|
/**
|
|
@@ -1806,7 +1906,7 @@ export const dijkstra = (graph, source, target, edgeWeight) => {
|
|
|
1806
1906
|
* @since 3.18.0
|
|
1807
1907
|
* @category algorithms
|
|
1808
1908
|
*/
|
|
1809
|
-
export const floydWarshall = (graph,
|
|
1909
|
+
export const floydWarshall = (graph, cost) => {
|
|
1810
1910
|
// Get all nodes for Floyd-Warshall algorithm (needs array for nested iteration)
|
|
1811
1911
|
const allNodes = Array.from(graph.nodes.keys());
|
|
1812
1912
|
// Initialize distance matrix
|
|
@@ -1826,7 +1926,7 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1826
1926
|
}
|
|
1827
1927
|
// Set edge weights
|
|
1828
1928
|
for (const [, edgeData] of graph.edges) {
|
|
1829
|
-
const weight =
|
|
1929
|
+
const weight = cost(edgeData.data);
|
|
1830
1930
|
const i = edgeData.source;
|
|
1831
1931
|
const j = edgeData.target;
|
|
1832
1932
|
// Use minimum weight if multiple edges exist
|
|
@@ -1859,17 +1959,17 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1859
1959
|
}
|
|
1860
1960
|
// Build result paths and edge weights
|
|
1861
1961
|
const paths = new Map();
|
|
1862
|
-
const
|
|
1962
|
+
const resultCosts = new Map();
|
|
1863
1963
|
for (const i of allNodes) {
|
|
1864
1964
|
paths.set(i, new Map());
|
|
1865
|
-
|
|
1965
|
+
resultCosts.set(i, new Map());
|
|
1866
1966
|
for (const j of allNodes) {
|
|
1867
1967
|
if (i === j) {
|
|
1868
1968
|
paths.get(i).set(j, [i]);
|
|
1869
|
-
|
|
1969
|
+
resultCosts.get(i).set(j, []);
|
|
1870
1970
|
} else if (dist.get(i).get(j) === Infinity) {
|
|
1871
1971
|
paths.get(i).set(j, null);
|
|
1872
|
-
|
|
1972
|
+
resultCosts.get(i).set(j, []);
|
|
1873
1973
|
} else {
|
|
1874
1974
|
// Reconstruct path iteratively
|
|
1875
1975
|
const path = [];
|
|
@@ -1887,14 +1987,14 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1887
1987
|
path.push(current);
|
|
1888
1988
|
}
|
|
1889
1989
|
paths.get(i).set(j, path);
|
|
1890
|
-
|
|
1990
|
+
resultCosts.get(i).set(j, weights);
|
|
1891
1991
|
}
|
|
1892
1992
|
}
|
|
1893
1993
|
}
|
|
1894
1994
|
return {
|
|
1895
1995
|
distances: dist,
|
|
1896
1996
|
paths,
|
|
1897
|
-
|
|
1997
|
+
costs: resultCosts
|
|
1898
1998
|
};
|
|
1899
1999
|
};
|
|
1900
2000
|
/**
|
|
@@ -1920,7 +2020,7 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1920
2020
|
* const heuristic = (nodeData: {x: number, y: number}, targetData: {x: number, y: number}) =>
|
|
1921
2021
|
* Math.abs(nodeData.x - targetData.x) + Math.abs(nodeData.y - targetData.y)
|
|
1922
2022
|
*
|
|
1923
|
-
* const result = Graph.astar(graph, 0, 2, (edgeData) => edgeData, heuristic)
|
|
2023
|
+
* const result = Graph.astar(graph, { source: 0, target: 2, cost: (edgeData) => edgeData, heuristic })
|
|
1924
2024
|
* if (Option.isSome(result)) {
|
|
1925
2025
|
* console.log(result.value.path) // [0, 1, 2] - shortest path
|
|
1926
2026
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -1930,25 +2030,31 @@ export const floydWarshall = (graph, edgeWeight) => {
|
|
|
1930
2030
|
* @since 3.18.0
|
|
1931
2031
|
* @category algorithms
|
|
1932
2032
|
*/
|
|
1933
|
-
export const astar = (graph,
|
|
2033
|
+
export const astar = (graph, config) => {
|
|
2034
|
+
const {
|
|
2035
|
+
cost,
|
|
2036
|
+
heuristic,
|
|
2037
|
+
source,
|
|
2038
|
+
target
|
|
2039
|
+
} = config;
|
|
1934
2040
|
// Validate that source and target nodes exist
|
|
1935
2041
|
if (!graph.nodes.has(source)) {
|
|
1936
|
-
throw
|
|
2042
|
+
throw missingNode(source);
|
|
1937
2043
|
}
|
|
1938
2044
|
if (!graph.nodes.has(target)) {
|
|
1939
|
-
throw
|
|
2045
|
+
throw missingNode(target);
|
|
1940
2046
|
}
|
|
1941
2047
|
// Early return if source equals target
|
|
1942
2048
|
if (source === target) {
|
|
1943
2049
|
return Option.some({
|
|
1944
2050
|
path: [source],
|
|
1945
2051
|
distance: 0,
|
|
1946
|
-
|
|
2052
|
+
costs: []
|
|
1947
2053
|
});
|
|
1948
2054
|
}
|
|
1949
2055
|
// Get target node data for heuristic calculations
|
|
1950
|
-
const targetNodeData =
|
|
1951
|
-
if (
|
|
2056
|
+
const targetNodeData = graph.nodes.get(target);
|
|
2057
|
+
if (targetNodeData === undefined) {
|
|
1952
2058
|
throw new Error(`Target node ${target} data not found`);
|
|
1953
2059
|
}
|
|
1954
2060
|
// Distance tracking (g-score) and f-score (g + h)
|
|
@@ -1964,9 +2070,9 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
1964
2070
|
previous.set(node, null);
|
|
1965
2071
|
}
|
|
1966
2072
|
// Calculate initial f-score for source
|
|
1967
|
-
const sourceNodeData =
|
|
1968
|
-
if (
|
|
1969
|
-
const h = heuristic(sourceNodeData
|
|
2073
|
+
const sourceNodeData = graph.nodes.get(source);
|
|
2074
|
+
if (sourceNodeData !== undefined) {
|
|
2075
|
+
const h = heuristic(sourceNodeData, targetNodeData);
|
|
1970
2076
|
fScore.set(source, h);
|
|
1971
2077
|
}
|
|
1972
2078
|
// Priority queue using f-score (total estimated cost)
|
|
@@ -1996,13 +2102,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
1996
2102
|
// Get current g-score
|
|
1997
2103
|
const currentGScore = gScore.get(currentNode);
|
|
1998
2104
|
// Examine all outgoing edges
|
|
1999
|
-
const adjacencyList =
|
|
2000
|
-
if (
|
|
2001
|
-
for (const edgeIndex of adjacencyList
|
|
2002
|
-
const edge =
|
|
2003
|
-
if (
|
|
2004
|
-
const neighbor = edge.
|
|
2005
|
-
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);
|
|
2006
2112
|
// Validate non-negative weights
|
|
2007
2113
|
if (weight < 0) {
|
|
2008
2114
|
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
|
|
@@ -2015,12 +2121,12 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2015
2121
|
gScore.set(neighbor, tentativeGScore);
|
|
2016
2122
|
previous.set(neighbor, {
|
|
2017
2123
|
node: currentNode,
|
|
2018
|
-
edgeData: edge.
|
|
2124
|
+
edgeData: edge.data
|
|
2019
2125
|
});
|
|
2020
2126
|
// Calculate f-score using heuristic
|
|
2021
|
-
const neighborNodeData =
|
|
2022
|
-
if (
|
|
2023
|
-
const h = heuristic(neighborNodeData
|
|
2127
|
+
const neighborNodeData = graph.nodes.get(neighbor);
|
|
2128
|
+
if (neighborNodeData !== undefined) {
|
|
2129
|
+
const h = heuristic(neighborNodeData, targetNodeData);
|
|
2024
2130
|
const f = tentativeGScore + h;
|
|
2025
2131
|
fScore.set(neighbor, f);
|
|
2026
2132
|
// Add to open set if not visited
|
|
@@ -2043,13 +2149,13 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2043
2149
|
}
|
|
2044
2150
|
// Reconstruct path
|
|
2045
2151
|
const path = [];
|
|
2046
|
-
const
|
|
2152
|
+
const costs = [];
|
|
2047
2153
|
let currentNode = target;
|
|
2048
2154
|
while (currentNode !== null) {
|
|
2049
2155
|
path.unshift(currentNode);
|
|
2050
2156
|
const prev = previous.get(currentNode);
|
|
2051
2157
|
if (prev !== null) {
|
|
2052
|
-
|
|
2158
|
+
costs.unshift(prev.edgeData);
|
|
2053
2159
|
currentNode = prev.node;
|
|
2054
2160
|
} else {
|
|
2055
2161
|
currentNode = null;
|
|
@@ -2058,7 +2164,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2058
2164
|
return Option.some({
|
|
2059
2165
|
path,
|
|
2060
2166
|
distance: targetGScore,
|
|
2061
|
-
|
|
2167
|
+
costs
|
|
2062
2168
|
});
|
|
2063
2169
|
};
|
|
2064
2170
|
/**
|
|
@@ -2081,7 +2187,7 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2081
2187
|
* Graph.addEdge(mutable, a, c, 5)
|
|
2082
2188
|
* })
|
|
2083
2189
|
*
|
|
2084
|
-
* const result = Graph.bellmanFord(graph, 0, 2, (edgeData) => edgeData)
|
|
2190
|
+
* const result = Graph.bellmanFord(graph, { source: 0, target: 2, cost: (edgeData) => edgeData })
|
|
2085
2191
|
* if (Option.isSome(result)) {
|
|
2086
2192
|
* console.log(result.value.path) // [0, 1, 2] - shortest path A->B->C
|
|
2087
2193
|
* console.log(result.value.distance) // 2 - total distance
|
|
@@ -2091,20 +2197,25 @@ export const astar = (graph, source, target, edgeWeight, heuristic) => {
|
|
|
2091
2197
|
* @since 3.18.0
|
|
2092
2198
|
* @category algorithms
|
|
2093
2199
|
*/
|
|
2094
|
-
export const bellmanFord = (graph,
|
|
2200
|
+
export const bellmanFord = (graph, config) => {
|
|
2201
|
+
const {
|
|
2202
|
+
cost,
|
|
2203
|
+
source,
|
|
2204
|
+
target
|
|
2205
|
+
} = config;
|
|
2095
2206
|
// Validate that source and target nodes exist
|
|
2096
2207
|
if (!graph.nodes.has(source)) {
|
|
2097
|
-
throw
|
|
2208
|
+
throw missingNode(source);
|
|
2098
2209
|
}
|
|
2099
2210
|
if (!graph.nodes.has(target)) {
|
|
2100
|
-
throw
|
|
2211
|
+
throw missingNode(target);
|
|
2101
2212
|
}
|
|
2102
2213
|
// Early return if source equals target
|
|
2103
2214
|
if (source === target) {
|
|
2104
2215
|
return Option.some({
|
|
2105
2216
|
path: [source],
|
|
2106
2217
|
distance: 0,
|
|
2107
|
-
|
|
2218
|
+
costs: []
|
|
2108
2219
|
});
|
|
2109
2220
|
}
|
|
2110
2221
|
// Initialize distances and predecessors
|
|
@@ -2118,7 +2229,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2118
2229
|
// Collect all edges for relaxation
|
|
2119
2230
|
const edges = [];
|
|
2120
2231
|
for (const [, edgeData] of graph.edges) {
|
|
2121
|
-
const weight =
|
|
2232
|
+
const weight = cost(edgeData.data);
|
|
2122
2233
|
edges.push({
|
|
2123
2234
|
source: edgeData.source,
|
|
2124
2235
|
target: edgeData.target,
|
|
@@ -2161,12 +2272,12 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2161
2272
|
if (affectedNodes.has(node)) continue;
|
|
2162
2273
|
affectedNodes.add(node);
|
|
2163
2274
|
// Add all nodes reachable from this node
|
|
2164
|
-
const adjacencyList =
|
|
2165
|
-
if (
|
|
2166
|
-
for (const edgeIndex of adjacencyList
|
|
2167
|
-
const edge =
|
|
2168
|
-
if (
|
|
2169
|
-
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);
|
|
2170
2281
|
}
|
|
2171
2282
|
}
|
|
2172
2283
|
}
|
|
@@ -2184,13 +2295,13 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2184
2295
|
}
|
|
2185
2296
|
// Reconstruct path
|
|
2186
2297
|
const path = [];
|
|
2187
|
-
const
|
|
2298
|
+
const costs = [];
|
|
2188
2299
|
let currentNode = target;
|
|
2189
2300
|
while (currentNode !== null) {
|
|
2190
2301
|
path.unshift(currentNode);
|
|
2191
2302
|
const prev = previous.get(currentNode);
|
|
2192
2303
|
if (prev !== null) {
|
|
2193
|
-
|
|
2304
|
+
costs.unshift(prev.edgeData);
|
|
2194
2305
|
currentNode = prev.node;
|
|
2195
2306
|
} else {
|
|
2196
2307
|
currentNode = null;
|
|
@@ -2199,7 +2310,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2199
2310
|
return Option.some({
|
|
2200
2311
|
path,
|
|
2201
2312
|
distance: targetDistance,
|
|
2202
|
-
|
|
2313
|
+
costs
|
|
2203
2314
|
});
|
|
2204
2315
|
};
|
|
2205
2316
|
/**
|
|
@@ -2220,7 +2331,7 @@ export const bellmanFord = (graph, source, target, edgeWeight) => {
|
|
|
2220
2331
|
* })
|
|
2221
2332
|
*
|
|
2222
2333
|
* // Both traversal and element iterators return NodeWalker
|
|
2223
|
-
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, {
|
|
2334
|
+
* const dfsNodes: Graph.NodeWalker<string> = Graph.dfs(graph, { start: [0] })
|
|
2224
2335
|
* const allNodes: Graph.NodeWalker<string> = Graph.nodes(graph)
|
|
2225
2336
|
*
|
|
2226
2337
|
* // Common interface for working with node iterables
|
|
@@ -2256,7 +2367,7 @@ export class Walker {
|
|
|
2256
2367
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2257
2368
|
* })
|
|
2258
2369
|
*
|
|
2259
|
-
* const dfs = Graph.dfs(graph, {
|
|
2370
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2260
2371
|
*
|
|
2261
2372
|
* // Map to just the node data
|
|
2262
2373
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2289,7 +2400,7 @@ export class Walker {
|
|
|
2289
2400
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2290
2401
|
* })
|
|
2291
2402
|
*
|
|
2292
|
-
* const dfs = Graph.dfs(graph, {
|
|
2403
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2293
2404
|
*
|
|
2294
2405
|
* // Map to just the node data
|
|
2295
2406
|
* const values = Array.from(dfs.visit((index, data) => data))
|
|
@@ -2321,7 +2432,7 @@ export class Walker {
|
|
|
2321
2432
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2322
2433
|
* })
|
|
2323
2434
|
*
|
|
2324
|
-
* const dfs = Graph.dfs(graph, {
|
|
2435
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2325
2436
|
* const indices = Array.from(Graph.indices(dfs))
|
|
2326
2437
|
* console.log(indices) // [0, 1]
|
|
2327
2438
|
* ```
|
|
@@ -2343,7 +2454,7 @@ export const indices = walker => walker.visit((index, _) => index);
|
|
|
2343
2454
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2344
2455
|
* })
|
|
2345
2456
|
*
|
|
2346
|
-
* const dfs = Graph.dfs(graph, {
|
|
2457
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2347
2458
|
* const values = Array.from(Graph.values(dfs))
|
|
2348
2459
|
* console.log(values) // ["A", "B"]
|
|
2349
2460
|
* ```
|
|
@@ -2365,7 +2476,7 @@ export const values = walker => walker.visit((_, data) => data);
|
|
|
2365
2476
|
* Graph.addEdge(mutable, a, b, 1)
|
|
2366
2477
|
* })
|
|
2367
2478
|
*
|
|
2368
|
-
* const dfs = Graph.dfs(graph, {
|
|
2479
|
+
* const dfs = Graph.dfs(graph, { start: [0] })
|
|
2369
2480
|
* const entries = Array.from(Graph.entries(dfs))
|
|
2370
2481
|
* console.log(entries) // [[0, "A"], [1, "B"]]
|
|
2371
2482
|
* ```
|
|
@@ -2393,7 +2504,7 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2393
2504
|
* })
|
|
2394
2505
|
*
|
|
2395
2506
|
* // Start from a specific node
|
|
2396
|
-
* const dfs1 = Graph.dfs(graph, {
|
|
2507
|
+
* const dfs1 = Graph.dfs(graph, { start: [0] })
|
|
2397
2508
|
* for (const nodeIndex of Graph.indices(dfs1)) {
|
|
2398
2509
|
* console.log(nodeIndex) // Traverses in DFS order: 0, 1, 2
|
|
2399
2510
|
* }
|
|
@@ -2407,17 +2518,17 @@ export const entries = walker => walker.visit((index, data) => [index, data]);
|
|
|
2407
2518
|
* @category iterators
|
|
2408
2519
|
*/
|
|
2409
2520
|
export const dfs = (graph, config = {}) => {
|
|
2410
|
-
const
|
|
2521
|
+
const start = config.start ?? [];
|
|
2411
2522
|
const direction = config.direction ?? "outgoing";
|
|
2412
2523
|
// Validate that all start nodes exist
|
|
2413
|
-
for (const nodeIndex of
|
|
2524
|
+
for (const nodeIndex of start) {
|
|
2414
2525
|
if (!hasNode(graph, nodeIndex)) {
|
|
2415
|
-
throw
|
|
2526
|
+
throw missingNode(nodeIndex);
|
|
2416
2527
|
}
|
|
2417
2528
|
}
|
|
2418
2529
|
return new Walker(f => ({
|
|
2419
2530
|
[Symbol.iterator]: () => {
|
|
2420
|
-
const stack = [...
|
|
2531
|
+
const stack = [...start];
|
|
2421
2532
|
const discovered = new Set();
|
|
2422
2533
|
const nextMapped = () => {
|
|
2423
2534
|
while (stack.length > 0) {
|
|
@@ -2426,8 +2537,8 @@ export const dfs = (graph, config = {}) => {
|
|
|
2426
2537
|
continue;
|
|
2427
2538
|
}
|
|
2428
2539
|
discovered.add(current);
|
|
2429
|
-
const nodeDataOption =
|
|
2430
|
-
if (
|
|
2540
|
+
const nodeDataOption = graph.nodes.get(current);
|
|
2541
|
+
if (nodeDataOption === undefined) {
|
|
2431
2542
|
continue;
|
|
2432
2543
|
}
|
|
2433
2544
|
const neighbors = neighborsDirected(graph, current, direction);
|
|
@@ -2439,7 +2550,7 @@ export const dfs = (graph, config = {}) => {
|
|
|
2439
2550
|
}
|
|
2440
2551
|
return {
|
|
2441
2552
|
done: false,
|
|
2442
|
-
value: f(current, nodeDataOption
|
|
2553
|
+
value: f(current, nodeDataOption)
|
|
2443
2554
|
};
|
|
2444
2555
|
}
|
|
2445
2556
|
return {
|
|
@@ -2472,7 +2583,7 @@ export const dfs = (graph, config = {}) => {
|
|
|
2472
2583
|
* })
|
|
2473
2584
|
*
|
|
2474
2585
|
* // Start from a specific node
|
|
2475
|
-
* const bfs1 = Graph.bfs(graph, {
|
|
2586
|
+
* const bfs1 = Graph.bfs(graph, { start: [0] })
|
|
2476
2587
|
* for (const nodeIndex of Graph.indices(bfs1)) {
|
|
2477
2588
|
* console.log(nodeIndex) // Traverses in BFS order: 0, 1, 2
|
|
2478
2589
|
* }
|
|
@@ -2486,17 +2597,17 @@ export const dfs = (graph, config = {}) => {
|
|
|
2486
2597
|
* @category iterators
|
|
2487
2598
|
*/
|
|
2488
2599
|
export const bfs = (graph, config = {}) => {
|
|
2489
|
-
const
|
|
2600
|
+
const start = config.start ?? [];
|
|
2490
2601
|
const direction = config.direction ?? "outgoing";
|
|
2491
2602
|
// Validate that all start nodes exist
|
|
2492
|
-
for (const nodeIndex of
|
|
2603
|
+
for (const nodeIndex of start) {
|
|
2493
2604
|
if (!hasNode(graph, nodeIndex)) {
|
|
2494
|
-
throw
|
|
2605
|
+
throw missingNode(nodeIndex);
|
|
2495
2606
|
}
|
|
2496
2607
|
}
|
|
2497
2608
|
return new Walker(f => ({
|
|
2498
2609
|
[Symbol.iterator]: () => {
|
|
2499
|
-
const queue = [...
|
|
2610
|
+
const queue = [...start];
|
|
2500
2611
|
const discovered = new Set();
|
|
2501
2612
|
const nextMapped = () => {
|
|
2502
2613
|
while (queue.length > 0) {
|
|
@@ -2584,7 +2695,7 @@ export const topo = (graph, config = {}) => {
|
|
|
2584
2695
|
// Validate that all initial nodes exist
|
|
2585
2696
|
for (const nodeIndex of initials) {
|
|
2586
2697
|
if (!hasNode(graph, nodeIndex)) {
|
|
2587
|
-
throw
|
|
2698
|
+
throw missingNode(nodeIndex);
|
|
2588
2699
|
}
|
|
2589
2700
|
}
|
|
2590
2701
|
return new Walker(f => ({
|
|
@@ -2669,7 +2780,7 @@ export const topo = (graph, config = {}) => {
|
|
|
2669
2780
|
* })
|
|
2670
2781
|
*
|
|
2671
2782
|
* // Postorder: children before parents
|
|
2672
|
-
* const postOrder = Graph.dfsPostOrder(graph, {
|
|
2783
|
+
* const postOrder = Graph.dfsPostOrder(graph, { start: [0] })
|
|
2673
2784
|
* for (const node of postOrder) {
|
|
2674
2785
|
* console.log(node) // 1, 2, 0
|
|
2675
2786
|
* }
|
|
@@ -2679,12 +2790,12 @@ export const topo = (graph, config = {}) => {
|
|
|
2679
2790
|
* @category iterators
|
|
2680
2791
|
*/
|
|
2681
2792
|
export const dfsPostOrder = (graph, config = {}) => {
|
|
2682
|
-
const
|
|
2793
|
+
const start = config.start ?? [];
|
|
2683
2794
|
const direction = config.direction ?? "outgoing";
|
|
2684
2795
|
// Validate that all start nodes exist
|
|
2685
|
-
for (const nodeIndex of
|
|
2796
|
+
for (const nodeIndex of start) {
|
|
2686
2797
|
if (!hasNode(graph, nodeIndex)) {
|
|
2687
|
-
throw
|
|
2798
|
+
throw missingNode(nodeIndex);
|
|
2688
2799
|
}
|
|
2689
2800
|
}
|
|
2690
2801
|
return new Walker(f => ({
|
|
@@ -2693,9 +2804,9 @@ export const dfsPostOrder = (graph, config = {}) => {
|
|
|
2693
2804
|
const discovered = new Set();
|
|
2694
2805
|
const finished = new Set();
|
|
2695
2806
|
// Initialize stack with start nodes
|
|
2696
|
-
for (let i =
|
|
2807
|
+
for (let i = start.length - 1; i >= 0; i--) {
|
|
2697
2808
|
stack.push({
|
|
2698
|
-
node:
|
|
2809
|
+
node: start[i],
|
|
2699
2810
|
visitedChildren: false
|
|
2700
2811
|
});
|
|
2701
2812
|
}
|
|
@@ -2881,9 +2992,9 @@ export const externals = (graph, config = {}) => {
|
|
|
2881
2992
|
let current = nodeIterator.next();
|
|
2882
2993
|
while (!current.done) {
|
|
2883
2994
|
const [nodeIndex, nodeData] = current.value;
|
|
2884
|
-
const adjacencyList =
|
|
2995
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
2885
2996
|
// Node is external if it has no edges in the specified direction
|
|
2886
|
-
if (
|
|
2997
|
+
if (adjacencyList === undefined || adjacencyList.length === 0) {
|
|
2887
2998
|
return {
|
|
2888
2999
|
done: false,
|
|
2889
3000
|
value: f(nodeIndex, nodeData)
|