Skip to content

Commit a537469

Browse files
authored
fix Graph.neighbors for undirected graphs (#5669)
1 parent 979d1c1 commit a537469

File tree

3 files changed

+71
-0
lines changed

3 files changed

+71
-0
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"effect": patch
3+
---
4+
5+
Fix Graph.neighbors() returning self-loops in undirected graphs.
6+
7+
Graph.neighbors() now correctly returns the other endpoint for undirected graphs instead of always returning edge.target, which caused nodes to appear as their own neighbors when queried from the target side of an edge.

packages/effect/src/Graph.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1488,6 +1488,11 @@ export const neighbors = <N, E, T extends Kind = "directed">(
14881488
graph: Graph<N, E, T> | MutableGraph<N, E, T>,
14891489
nodeIndex: NodeIndex
14901490
): Array<NodeIndex> => {
1491+
// For undirected graphs, use the specialized helper that returns the other endpoint
1492+
if (graph.type === "undirected") {
1493+
return getUndirectedNeighbors(graph as any, nodeIndex)
1494+
}
1495+
14911496
const adjacencyList = getMapSafe(graph.adjacency, nodeIndex)
14921497
if (Option.isNone(adjacencyList)) {
14931498
return []

packages/effect/test/Graph.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,6 +1497,65 @@ describe("Graph", () => {
14971497
})
14981498
})
14991499

1500+
describe("neighbors with undirected graphs", () => {
1501+
it("should return correct neighbors for single edge", () => {
1502+
const graph = Graph.undirected<number, void>((mutable) => {
1503+
Graph.addNode(mutable, 0)
1504+
Graph.addNode(mutable, 1)
1505+
Graph.addEdge(mutable, 0, 1, undefined)
1506+
})
1507+
1508+
expect(Graph.neighbors(graph, 0)).toEqual([1])
1509+
expect(Graph.neighbors(graph, 1)).toEqual([0])
1510+
})
1511+
1512+
it("should return correct neighbors for linear graph", () => {
1513+
const graph = Graph.undirected<number, void>((mutable) => {
1514+
Graph.addNode(mutable, 0)
1515+
Graph.addNode(mutable, 1)
1516+
Graph.addNode(mutable, 2)
1517+
Graph.addEdge(mutable, 0, 1, undefined)
1518+
Graph.addEdge(mutable, 1, 2, undefined)
1519+
})
1520+
1521+
expect(Graph.neighbors(graph, 0)).toEqual([1])
1522+
expect(Graph.neighbors(graph, 1).sort()).toEqual([0, 2])
1523+
expect(Graph.neighbors(graph, 2)).toEqual([1])
1524+
})
1525+
1526+
it("should handle multiple edges between same nodes", () => {
1527+
const graph = Graph.undirected<number, void>((mutable) => {
1528+
Graph.addNode(mutable, 0)
1529+
Graph.addNode(mutable, 1)
1530+
Graph.addEdge(mutable, 0, 1, undefined)
1531+
Graph.addEdge(mutable, 0, 1, undefined)
1532+
})
1533+
1534+
// Should deduplicate neighbors
1535+
expect(Graph.neighbors(graph, 0)).toEqual([1])
1536+
expect(Graph.neighbors(graph, 1)).toEqual([0])
1537+
})
1538+
1539+
it("should handle self-loops", () => {
1540+
const graph = Graph.undirected<number, void>((mutable) => {
1541+
Graph.addNode(mutable, 0)
1542+
Graph.addEdge(mutable, 0, 0, undefined)
1543+
})
1544+
1545+
expect(Graph.neighbors(graph, 0)).toEqual([0])
1546+
})
1547+
1548+
it("should handle node with no neighbors", () => {
1549+
const graph = Graph.undirected<number, void>((mutable) => {
1550+
Graph.addNode(mutable, 0)
1551+
Graph.addNode(mutable, 1)
1552+
})
1553+
1554+
expect(Graph.neighbors(graph, 0)).toEqual([])
1555+
expect(Graph.neighbors(graph, 1)).toEqual([])
1556+
})
1557+
})
1558+
15001559
describe("neighborsDirected", () => {
15011560
it("should return incoming neighbors", () => {
15021561
let nodeA: Graph.NodeIndex

0 commit comments

Comments
 (0)