Skip to content

Commit 0f52fba

Browse files
committed
Recompute graph edge key after reverse to reflect new direction
1 parent dc12f1f commit 0f52fba

4 files changed

Lines changed: 76 additions & 0 deletions

File tree

src/algorithms/graph/strongly-connected-components/__test__/stronglyConnectedComponents.test.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,31 @@ describe('stronglyConnectedComponents', () => {
9999
expect(components[3][1].getKey()).toBe(vertexF.getKey());
100100
expect(components[3][2].getKey()).toBe(vertexE.getKey());
101101
});
102+
103+
it('should not break on edges in opposite directions (issue #873)', () => {
104+
const vertexA = new GraphVertex('A');
105+
const vertexB = new GraphVertex('B');
106+
const vertexC = new GraphVertex('C');
107+
const vertexD = new GraphVertex('D');
108+
109+
const edgeAB = new GraphEdge(vertexA, vertexB);
110+
const edgeBA = new GraphEdge(vertexB, vertexA);
111+
const edgeBC = new GraphEdge(vertexB, vertexC);
112+
const edgeCA = new GraphEdge(vertexC, vertexA);
113+
const edgeCD = new GraphEdge(vertexC, vertexD);
114+
115+
const graph = new Graph(true);
116+
graph
117+
.addEdge(edgeAB)
118+
.addEdge(edgeBA)
119+
.addEdge(edgeBC)
120+
.addEdge(edgeCA)
121+
.addEdge(edgeCD);
122+
123+
const components = stronglyConnectedComponents(graph);
124+
125+
expect(components.length).toBe(2);
126+
expect(components[0].map((vertex) => vertex.getKey()).sort()).toEqual(['A', 'B', 'C']);
127+
expect(components[1].map((vertex) => vertex.getKey())).toEqual(['D']);
128+
});
102129
});

src/data-structures/graph/GraphEdge.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ export default class GraphEdge {
99
this.startVertex = startVertex;
1010
this.endVertex = endVertex;
1111
this.weight = weight;
12+
// A custom key (if provided) is a stable identity that is kept on reverse.
13+
// Auto-generated keys are recomputed so they always reflect the direction.
14+
this.customKey = key;
1215
this.key = key;
1316
}
1417

@@ -33,6 +36,12 @@ export default class GraphEdge {
3336
this.startVertex = this.endVertex;
3437
this.endVertex = tmp;
3538

39+
// Invalidate the auto-generated key so getKey() recomputes it for the new
40+
// direction. A custom key represents a stable identity and is preserved.
41+
if (this.customKey === null) {
42+
this.key = null;
43+
}
44+
3645
return this;
3746
}
3847

src/data-structures/graph/__test__/Graph.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,26 @@ describe('Graph', () => {
347347
expect(graph.getNeighbors(vertexB)[0].getKey()).toBe(vertexA.getKey());
348348
});
349349

350+
it('should keep edge keys consistent after reverse', () => {
351+
const vertexA = new GraphVertex('A');
352+
const vertexB = new GraphVertex('B');
353+
354+
const edgeAB = new GraphEdge(vertexA, vertexB);
355+
356+
const graph = new Graph(true);
357+
graph.addEdge(edgeAB);
358+
359+
graph.reverse();
360+
361+
// After reverse the edge goes B->A, so its key must reflect the new direction.
362+
expect(edgeAB.getKey()).toBe('B_A');
363+
364+
// Re-adding a fresh edge in the original A->B direction must not collide
365+
// with a stale key left over from the reversed edge.
366+
expect(() => graph.addEdge(new GraphEdge(vertexA, vertexB))).not.toThrow();
367+
expect(graph.getAllEdges().length).toBe(2);
368+
});
369+
350370
it('should return vertices indices', () => {
351371
const vertexA = new GraphVertex('A');
352372
const vertexB = new GraphVertex('B');

src/data-structures/graph/__test__/GraphEdge.test.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,24 @@ describe('GraphEdge', () => {
6262
expect(edge.getKey()).toEqual(customKey);
6363
expect(edge.toString()).toEqual('custom_key');
6464
});
65+
66+
it('should recompute auto-generated key to reflect direction after reverse', () => {
67+
const edge = new GraphEdge(new GraphVertex('A'), new GraphVertex('B'));
68+
69+
expect(edge.getKey()).toBe('A_B');
70+
71+
edge.reverse();
72+
73+
expect(edge.getKey()).toBe('B_A');
74+
});
75+
76+
it('should preserve custom key after reverse', () => {
77+
const edge = new GraphEdge(new GraphVertex('A'), new GraphVertex('B'), 0, 'custom_key');
78+
79+
expect(edge.getKey()).toBe('custom_key');
80+
81+
edge.reverse();
82+
83+
expect(edge.getKey()).toBe('custom_key');
84+
});
6585
});

0 commit comments

Comments
 (0)