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 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
- // Clear and rebuild adjacency lists with reversed directions
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 && edge.target === target) {
1169
- return true;
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
- const adjacencyList = graph.adjacency.get(nodeIndex);
1237
- if (adjacencyList === undefined) {
1238
- return [];
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
- const result = [];
1241
- for (const edgeIndex of adjacencyList) {
1242
- const edge = graph.edges.get(edgeIndex);
1243
- if (edge !== undefined) {
1244
- result.push(edge.target);
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 result;
1295
+ return getDirectedNeighbors(graph, nodeIndex, "incoming");
1248
1296
  };
1249
1297
  /**
1250
- * Get neighbors of a node in a specific direction for bidirectional traversal.
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.neighbors = neighbors;
1324
+ exports.predecessors = predecessors;
1276
1325
  const neighborsDirected = (graph, nodeIndex, direction) => {
1277
- const adjacencyMap = direction === "incoming" ? graph.reverseAdjacency : graph.adjacency;
1278
- const adjacencyList = adjacencyMap.get(nodeIndex);
1279
- if (adjacencyList === undefined) {
1280
- return [];
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 result;
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 = Array.from(neighborsDirected(graph, node, "outgoing"));
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) : neighborsDirected(graph, nodeIndex, direction);
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 = neighbors(graph, node);
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 = cost(edge.data);
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 = cost(edge.data);
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
- // Add nodes with zero in-degree to queue if no initials provided
2825
- if (initials.length === 0) {
2826
- for (const [nodeIndex, degree] of inDegree) {
2827
- if (degree === 0) {
2828
- queue.push(nodeIndex);
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 = neighborsDirected(graph, current, "outgoing");
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;