effect 3.21.5 → 3.22.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/dist/cjs/Graph.js +123 -66
- package/dist/cjs/Graph.js.map +1 -1
- package/dist/cjs/internal/version.js +1 -1
- package/dist/dts/Graph.d.ts +22 -3
- package/dist/dts/Graph.d.ts.map +1 -1
- package/dist/esm/Graph.js +116 -61
- package/dist/esm/Graph.js.map +1 -1
- package/dist/esm/internal/version.js +1 -1
- package/package.json +1 -1
- package/src/Graph.ts +149 -77
- 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.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;
|
|
6
|
+
exports.values = exports.updateNode = exports.updateEdge = exports.undirected = exports.topo = exports.toMermaid = exports.toGraphViz = exports.successors = exports.stronglyConnectedComponents = exports.reverse = exports.removeNode = exports.removeEdge = exports.predecessors = 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");
|
|
@@ -658,6 +658,24 @@ const mapEdges = (mutable, f) => {
|
|
|
658
658
|
});
|
|
659
659
|
}
|
|
660
660
|
};
|
|
661
|
+
/** @internal */
|
|
662
|
+
exports.mapEdges = mapEdges;
|
|
663
|
+
const rebuildAdjacency = mutable => {
|
|
664
|
+
mutable.adjacency.clear();
|
|
665
|
+
mutable.reverseAdjacency.clear();
|
|
666
|
+
for (const nodeIndex of mutable.nodes.keys()) {
|
|
667
|
+
mutable.adjacency.set(nodeIndex, []);
|
|
668
|
+
mutable.reverseAdjacency.set(nodeIndex, []);
|
|
669
|
+
}
|
|
670
|
+
for (const [edgeIndex, edgeData] of mutable.edges) {
|
|
671
|
+
mutable.adjacency.get(edgeData.source).push(edgeIndex);
|
|
672
|
+
mutable.reverseAdjacency.get(edgeData.target).push(edgeIndex);
|
|
673
|
+
if (mutable.type === "undirected") {
|
|
674
|
+
mutable.adjacency.get(edgeData.target).push(edgeIndex);
|
|
675
|
+
mutable.reverseAdjacency.get(edgeData.source).push(edgeIndex);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
};
|
|
661
679
|
/**
|
|
662
680
|
* Reverses all edge directions in a mutable graph by swapping source and target nodes.
|
|
663
681
|
*
|
|
@@ -681,8 +699,10 @@ const mapEdges = (mutable, f) => {
|
|
|
681
699
|
* @since 3.18.0
|
|
682
700
|
* @category transformations
|
|
683
701
|
*/
|
|
684
|
-
exports.mapEdges = mapEdges;
|
|
685
702
|
const reverse = mutable => {
|
|
703
|
+
if (mutable.type === "undirected") {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
686
706
|
// Reverse all edges by swapping source and target
|
|
687
707
|
for (const [index, edgeData] of mutable.edges) {
|
|
688
708
|
mutable.edges.set(index, {
|
|
@@ -691,20 +711,7 @@ const reverse = mutable => {
|
|
|
691
711
|
data: edgeData.data
|
|
692
712
|
});
|
|
693
713
|
}
|
|
694
|
-
|
|
695
|
-
mutable.adjacency.clear();
|
|
696
|
-
mutable.reverseAdjacency.clear();
|
|
697
|
-
// Rebuild adjacency lists with reversed directions
|
|
698
|
-
for (const [edgeIndex, edgeData] of mutable.edges) {
|
|
699
|
-
// Add to forward adjacency (source -> target)
|
|
700
|
-
const sourceEdges = mutable.adjacency.get(edgeData.source) || [];
|
|
701
|
-
sourceEdges.push(edgeIndex);
|
|
702
|
-
mutable.adjacency.set(edgeData.source, sourceEdges);
|
|
703
|
-
// Add to reverse adjacency (target <- source)
|
|
704
|
-
const targetEdges = mutable.reverseAdjacency.get(edgeData.target) || [];
|
|
705
|
-
targetEdges.push(edgeIndex);
|
|
706
|
-
mutable.reverseAdjacency.set(edgeData.target, targetEdges);
|
|
707
|
-
}
|
|
714
|
+
rebuildAdjacency(mutable);
|
|
708
715
|
// Invalidate cycle flag since edge directions changed
|
|
709
716
|
mutable.isAcyclic = Option.none();
|
|
710
717
|
};
|
|
@@ -1165,8 +1172,11 @@ const hasEdge = (graph, source, target) => {
|
|
|
1165
1172
|
// Check if any edge in the adjacency list connects to the target
|
|
1166
1173
|
for (const edgeIndex of adjacencyList) {
|
|
1167
1174
|
const edge = graph.edges.get(edgeIndex);
|
|
1168
|
-
if (edge !== undefined
|
|
1169
|
-
|
|
1175
|
+
if (edge !== undefined) {
|
|
1176
|
+
const neighbor = graph.type === "undirected" && edge.target === source ? edge.source : edge.target;
|
|
1177
|
+
if (neighbor === target) {
|
|
1178
|
+
return true;
|
|
1179
|
+
}
|
|
1170
1180
|
}
|
|
1171
1181
|
}
|
|
1172
1182
|
return false;
|
|
@@ -1198,6 +1208,22 @@ const hasEdge = (graph, source, target) => {
|
|
|
1198
1208
|
*/
|
|
1199
1209
|
exports.hasEdge = hasEdge;
|
|
1200
1210
|
const edgeCount = graph => graph.edges.size;
|
|
1211
|
+
exports.edgeCount = edgeCount;
|
|
1212
|
+
const getDirectedNeighbors = (graph, nodeIndex, direction) => {
|
|
1213
|
+
const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
|
|
1214
|
+
const adjacencyList = adjacencyMap.get(nodeIndex);
|
|
1215
|
+
if (adjacencyList === undefined) {
|
|
1216
|
+
return [];
|
|
1217
|
+
}
|
|
1218
|
+
const result = [];
|
|
1219
|
+
for (const edgeIndex of adjacencyList) {
|
|
1220
|
+
const edge = graph.edges.get(edgeIndex);
|
|
1221
|
+
if (edge !== undefined) {
|
|
1222
|
+
result.push(direction === "incoming" ? edge.source : edge.target);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
return result;
|
|
1226
|
+
};
|
|
1201
1227
|
/**
|
|
1202
1228
|
* Returns the neighboring nodes (targets of outgoing edges) for a given node.
|
|
1203
1229
|
*
|
|
@@ -1227,27 +1253,49 @@ const edgeCount = graph => graph.edges.size;
|
|
|
1227
1253
|
* @since 3.18.0
|
|
1228
1254
|
* @category getters
|
|
1229
1255
|
*/
|
|
1230
|
-
exports.edgeCount = edgeCount;
|
|
1231
1256
|
const neighbors = (graph, nodeIndex) => {
|
|
1232
1257
|
// For undirected graphs, use the specialized helper that returns the other endpoint
|
|
1233
1258
|
if (graph.type === "undirected") {
|
|
1234
1259
|
return getUndirectedNeighbors(graph, nodeIndex);
|
|
1235
1260
|
}
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1261
|
+
return getDirectedNeighbors(graph, nodeIndex, "outgoing");
|
|
1262
|
+
};
|
|
1263
|
+
/**
|
|
1264
|
+
* Returns the outgoing neighbor node indices for a node in a directed graph.
|
|
1265
|
+
*
|
|
1266
|
+
* Throws a `GraphError` when used with an undirected graph.
|
|
1267
|
+
*
|
|
1268
|
+
* @since 3.22.0
|
|
1269
|
+
* @category queries
|
|
1270
|
+
*/
|
|
1271
|
+
exports.neighbors = neighbors;
|
|
1272
|
+
const successors = (graph, nodeIndex) => {
|
|
1273
|
+
if (graph.type === "undirected") {
|
|
1274
|
+
throw new GraphError({
|
|
1275
|
+
message: "Cannot get successors of undirected graph"
|
|
1276
|
+
});
|
|
1239
1277
|
}
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1278
|
+
return getDirectedNeighbors(graph, nodeIndex, "outgoing");
|
|
1279
|
+
};
|
|
1280
|
+
/**
|
|
1281
|
+
* Returns the incoming neighbor node indices for a node in a directed graph.
|
|
1282
|
+
*
|
|
1283
|
+
* Throws a `GraphError` when used with an undirected graph.
|
|
1284
|
+
*
|
|
1285
|
+
* @since 3.22.0
|
|
1286
|
+
* @category queries
|
|
1287
|
+
*/
|
|
1288
|
+
exports.successors = successors;
|
|
1289
|
+
const predecessors = (graph, nodeIndex) => {
|
|
1290
|
+
if (graph.type === "undirected") {
|
|
1291
|
+
throw new GraphError({
|
|
1292
|
+
message: "Cannot get predecessors of undirected graph"
|
|
1293
|
+
});
|
|
1246
1294
|
}
|
|
1247
|
-
return
|
|
1295
|
+
return getDirectedNeighbors(graph, nodeIndex, "incoming");
|
|
1248
1296
|
};
|
|
1249
1297
|
/**
|
|
1250
|
-
* Get neighbors of a node in a specific direction
|
|
1298
|
+
* Get directed neighbors of a node in a specific direction.
|
|
1251
1299
|
*
|
|
1252
1300
|
* @example
|
|
1253
1301
|
* ```ts
|
|
@@ -1269,26 +1317,18 @@ const neighbors = (graph, nodeIndex) => {
|
|
|
1269
1317
|
* const incoming = Graph.neighborsDirected(graph, nodeB, "incoming")
|
|
1270
1318
|
* ```
|
|
1271
1319
|
*
|
|
1320
|
+
* @deprecated Use {@link successors} for outgoing neighbors or {@link predecessors} for incoming neighbors.
|
|
1272
1321
|
* @since 3.18.0
|
|
1273
1322
|
* @category queries
|
|
1274
1323
|
*/
|
|
1275
|
-
exports.
|
|
1324
|
+
exports.predecessors = predecessors;
|
|
1276
1325
|
const neighborsDirected = (graph, nodeIndex, direction) => {
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
}
|
|
1282
|
-
const result = [];
|
|
1283
|
-
for (const edgeIndex of adjacencyList) {
|
|
1284
|
-
const edge = graph.edges.get(edgeIndex);
|
|
1285
|
-
if (edge !== undefined) {
|
|
1286
|
-
// For incoming direction, we want the source node instead of target
|
|
1287
|
-
const neighborNode = direction === "incoming" ? edge.source : edge.target;
|
|
1288
|
-
result.push(neighborNode);
|
|
1289
|
-
}
|
|
1326
|
+
if (graph.type === "undirected") {
|
|
1327
|
+
throw new GraphError({
|
|
1328
|
+
message: "Cannot get directed neighbors of undirected graph"
|
|
1329
|
+
});
|
|
1290
1330
|
}
|
|
1291
|
-
return
|
|
1331
|
+
return getDirectedNeighbors(graph, nodeIndex, direction);
|
|
1292
1332
|
};
|
|
1293
1333
|
/**
|
|
1294
1334
|
* Exports a graph to GraphViz DOT format for visualization.
|
|
@@ -1542,7 +1582,7 @@ const isAcyclic = graph => {
|
|
|
1542
1582
|
visited.add(node);
|
|
1543
1583
|
recursionStack.add(node);
|
|
1544
1584
|
// Get neighbors for this node
|
|
1545
|
-
const nodeNeighbors =
|
|
1585
|
+
const nodeNeighbors = getDirectedNeighbors(graph, node, "outgoing");
|
|
1546
1586
|
stack[stack.length - 1] = [node, nodeNeighbors, 0, false];
|
|
1547
1587
|
continue;
|
|
1548
1588
|
}
|
|
@@ -1669,7 +1709,7 @@ const getUndirectedNeighbors = (graph, nodeIndex) => {
|
|
|
1669
1709
|
}
|
|
1670
1710
|
return Array.from(neighbors);
|
|
1671
1711
|
};
|
|
1672
|
-
const getTraversalNeighbors = (graph, nodeIndex, direction) => graph.type === "undirected" ? getUndirectedNeighbors(graph, nodeIndex) :
|
|
1712
|
+
const getTraversalNeighbors = (graph, nodeIndex, direction) => graph.type === "undirected" ? getUndirectedNeighbors(graph, nodeIndex) : getDirectedNeighbors(graph, nodeIndex, direction);
|
|
1673
1713
|
const getTraversableNeighbor = (graph, current, edge) => graph.type === "undirected" && edge.target === current ? edge.source : edge.target;
|
|
1674
1714
|
/**
|
|
1675
1715
|
* Find connected components in an undirected graph.
|
|
@@ -1748,6 +1788,11 @@ const connectedComponents = graph => {
|
|
|
1748
1788
|
*/
|
|
1749
1789
|
exports.connectedComponents = connectedComponents;
|
|
1750
1790
|
const stronglyConnectedComponents = graph => {
|
|
1791
|
+
if (graph.type === "undirected") {
|
|
1792
|
+
throw new GraphError({
|
|
1793
|
+
message: "Cannot find strongly connected components of undirected graph"
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1751
1796
|
const visited = new Set();
|
|
1752
1797
|
const finishOrder = [];
|
|
1753
1798
|
for (const startNode of graph.nodes.keys()) {
|
|
@@ -1763,7 +1808,7 @@ const stronglyConnectedComponents = graph => {
|
|
|
1763
1808
|
continue;
|
|
1764
1809
|
}
|
|
1765
1810
|
visited.add(node);
|
|
1766
|
-
const nodeNeighborsList =
|
|
1811
|
+
const nodeNeighborsList = getDirectedNeighbors(graph, node, "outgoing");
|
|
1767
1812
|
stack[stack.length - 1] = [node, nodeNeighborsList, 0, false];
|
|
1768
1813
|
continue;
|
|
1769
1814
|
}
|
|
@@ -1816,6 +1861,20 @@ const stronglyConnectedComponents = graph => {
|
|
|
1816
1861
|
}
|
|
1817
1862
|
return sccs;
|
|
1818
1863
|
};
|
|
1864
|
+
exports.stronglyConnectedComponents = stronglyConnectedComponents;
|
|
1865
|
+
const validateNonNegativeEdgeWeights = (graph, cost, algorithm) => {
|
|
1866
|
+
const edgeWeights = new Map();
|
|
1867
|
+
for (const [edgeIndex, edgeData] of graph.edges) {
|
|
1868
|
+
const weight = cost(edgeData.data);
|
|
1869
|
+
if (weight < 0 || Number.isNaN(weight)) {
|
|
1870
|
+
throw new GraphError({
|
|
1871
|
+
message: `${algorithm} requires non-negative edge weights`
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
edgeWeights.set(edgeIndex, weight);
|
|
1875
|
+
}
|
|
1876
|
+
return edgeWeights;
|
|
1877
|
+
};
|
|
1819
1878
|
/**
|
|
1820
1879
|
* Find the shortest path between two nodes using Dijkstra's algorithm.
|
|
1821
1880
|
*
|
|
@@ -1845,7 +1904,6 @@ const stronglyConnectedComponents = graph => {
|
|
|
1845
1904
|
* @since 3.18.0
|
|
1846
1905
|
* @category algorithms
|
|
1847
1906
|
*/
|
|
1848
|
-
exports.stronglyConnectedComponents = stronglyConnectedComponents;
|
|
1849
1907
|
const dijkstra = (graph, config) => {
|
|
1850
1908
|
const {
|
|
1851
1909
|
cost,
|
|
@@ -1859,6 +1917,7 @@ const dijkstra = (graph, config) => {
|
|
|
1859
1917
|
if (!graph.nodes.has(target)) {
|
|
1860
1918
|
throw missingNode(target);
|
|
1861
1919
|
}
|
|
1920
|
+
const edgeWeights = validateNonNegativeEdgeWeights(graph, cost, "Dijkstra's algorithm");
|
|
1862
1921
|
// Early return if source equals target
|
|
1863
1922
|
if (source === target) {
|
|
1864
1923
|
return Option.some({
|
|
@@ -1910,11 +1969,7 @@ const dijkstra = (graph, config) => {
|
|
|
1910
1969
|
const edge = graph.edges.get(edgeIndex);
|
|
1911
1970
|
if (edge !== undefined) {
|
|
1912
1971
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge);
|
|
1913
|
-
const weight =
|
|
1914
|
-
// Validate non-negative weights
|
|
1915
|
-
if (weight < 0) {
|
|
1916
|
-
throw new Error(`Dijkstra's algorithm requires non-negative edge weights, found ${weight}`);
|
|
1917
|
-
}
|
|
1972
|
+
const weight = edgeWeights.get(edgeIndex);
|
|
1918
1973
|
const newDistance = currentDistance + weight;
|
|
1919
1974
|
const neighborDistance = distances.get(neighbor);
|
|
1920
1975
|
// Relaxation step
|
|
@@ -2136,6 +2191,7 @@ const astar = (graph, config) => {
|
|
|
2136
2191
|
if (!graph.nodes.has(target)) {
|
|
2137
2192
|
throw missingNode(target);
|
|
2138
2193
|
}
|
|
2194
|
+
const edgeWeights = validateNonNegativeEdgeWeights(graph, cost, "A* algorithm");
|
|
2139
2195
|
// Early return if source equals target
|
|
2140
2196
|
if (source === target) {
|
|
2141
2197
|
return Option.some({
|
|
@@ -2200,11 +2256,7 @@ const astar = (graph, config) => {
|
|
|
2200
2256
|
const edge = graph.edges.get(edgeIndex);
|
|
2201
2257
|
if (edge !== undefined) {
|
|
2202
2258
|
const neighbor = getTraversableNeighbor(graph, currentNode, edge);
|
|
2203
|
-
const weight =
|
|
2204
|
-
// Validate non-negative weights
|
|
2205
|
-
if (weight < 0) {
|
|
2206
|
-
throw new Error(`A* algorithm requires non-negative edge weights, found ${weight}`);
|
|
2207
|
-
}
|
|
2259
|
+
const weight = edgeWeights.get(edgeIndex);
|
|
2208
2260
|
const tentativeGScore = currentGScore + weight;
|
|
2209
2261
|
const neighborGScore = gScore.get(neighbor);
|
|
2210
2262
|
// If this path to neighbor is better than any previous one
|
|
@@ -2810,6 +2862,7 @@ const topo = (graph, config = {}) => {
|
|
|
2810
2862
|
[Symbol.iterator]: () => {
|
|
2811
2863
|
const inDegree = new Map();
|
|
2812
2864
|
const remaining = new Set();
|
|
2865
|
+
const initialSet = new Set(initials);
|
|
2813
2866
|
const queue = [...initials];
|
|
2814
2867
|
// Initialize in-degree counts
|
|
2815
2868
|
for (const [nodeIndex] of graph.nodes) {
|
|
@@ -2821,12 +2874,16 @@ const topo = (graph, config = {}) => {
|
|
|
2821
2874
|
const currentInDegree = inDegree.get(edgeData.target) || 0;
|
|
2822
2875
|
inDegree.set(edgeData.target, currentInDegree + 1);
|
|
2823
2876
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2877
|
+
for (const nodeIndex of initials) {
|
|
2878
|
+
if (inDegree.get(nodeIndex) !== 0) {
|
|
2879
|
+
throw new GraphError({
|
|
2880
|
+
message: `Initial node ${nodeIndex} has incoming edges`
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
}
|
|
2884
|
+
for (const [nodeIndex, degree] of inDegree) {
|
|
2885
|
+
if (degree === 0 && !initialSet.has(nodeIndex)) {
|
|
2886
|
+
queue.push(nodeIndex);
|
|
2830
2887
|
}
|
|
2831
2888
|
}
|
|
2832
2889
|
const nextMapped = () => {
|
|
@@ -2835,7 +2892,7 @@ const topo = (graph, config = {}) => {
|
|
|
2835
2892
|
if (remaining.has(current)) {
|
|
2836
2893
|
remaining.delete(current);
|
|
2837
2894
|
// Process outgoing edges, reducing in-degree of targets
|
|
2838
|
-
const neighbors =
|
|
2895
|
+
const neighbors = getDirectedNeighbors(graph, current, "outgoing");
|
|
2839
2896
|
for (const neighbor of neighbors) {
|
|
2840
2897
|
if (remaining.has(neighbor)) {
|
|
2841
2898
|
const currentInDegree = inDegree.get(neighbor) || 0;
|