Skip to content

Commit 6a7a0d8

Browse files
committed
Design directeed graph
1 parent 4d060d2 commit 6a7a0d8

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

ds/graph.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from collections import deque
2+
3+
class Graph:
4+
def __init__(self):
5+
self.nodes = {}
6+
7+
def addEdge(self, src: int, dst: int) -> None:
8+
neighbours = []
9+
if src in self.nodes:
10+
neighbours = self.nodes[src]
11+
if dst not in neighbours:
12+
neighbours.append(dst)
13+
self.nodes[src] = neighbours
14+
if dst not in self.nodes:
15+
self.nodes[dst] = []
16+
17+
def removeEdge(self, src: int, dst: int) -> bool:
18+
if src not in self.nodes:
19+
return False
20+
neighbours = self.nodes[src]
21+
if dst not in neighbours:
22+
return False
23+
neighbours.remove(dst)
24+
self.nodes[src] = neighbours
25+
return True
26+
27+
def hasPath(self, src: int, dst: int) -> bool:
28+
if src == dst:
29+
return True
30+
visit = set()
31+
q = deque(self.nodes[src])
32+
while q:
33+
neighbour = q.popleft()
34+
if neighbour in visit:
35+
continue
36+
if neighbour == dst:
37+
return True
38+
visit.add(neighbour)
39+
for n in self.nodes[neighbour]:
40+
q.append(n)
41+
return False
42+
43+
44+
# Test Cases
45+
46+
def test_basic_operations():
47+
"""Test basic add edge and has path operations"""
48+
g = Graph()
49+
g.addEdge(1, 2)
50+
g.addEdge(2, 3)
51+
assert g.hasPath(1, 3) == True, "Should have path 1->2->3"
52+
assert g.hasPath(3, 1) == False, "Should not have path 3->1 (directed)"
53+
print("✓ Basic operations test passed")
54+
55+
def test_remove_edge():
56+
"""Test edge removal"""
57+
g = Graph()
58+
g.addEdge(1, 2)
59+
g.addEdge(2, 3)
60+
assert g.removeEdge(1, 2) == True, "Should successfully remove edge 1->2"
61+
assert g.hasPath(1, 3) == False, "Should not have path after removing edge"
62+
assert g.removeEdge(1, 2) == False, "Should return False when removing non-existent edge"
63+
print("✓ Remove edge test passed")
64+
65+
def test_cycle_detection():
66+
"""Test graph with cycles"""
67+
g = Graph()
68+
g.addEdge(1, 2)
69+
g.addEdge(2, 3)
70+
g.addEdge(3, 1)
71+
assert g.hasPath(1, 3) == True, "Should have path 1->2->3"
72+
assert g.hasPath(3, 1) == True, "Should have path 3->1 (cycle exists)"
73+
assert g.hasPath(2, 2) == True, "Should have path to itself through cycle"
74+
print("✓ Cycle detection test passed")
75+
76+
def test_disconnected_components():
77+
"""Test graph with disconnected components"""
78+
g = Graph()
79+
g.addEdge(1, 2)
80+
g.addEdge(2, 3)
81+
g.addEdge(4, 5)
82+
g.addEdge(5, 6)
83+
assert g.hasPath(1, 3) == True, "Should have path in first component"
84+
assert g.hasPath(4, 6) == True, "Should have path in second component"
85+
assert g.hasPath(1, 6) == False, "Should not have path between components"
86+
assert g.hasPath(4, 2) == False, "Should not have path between components"
87+
print("✓ Disconnected components test passed")
88+
89+
def test_remove_nonexistent_edge():
90+
"""Test removing edges from non-existent vertices"""
91+
g = Graph()
92+
g.addEdge(1, 2)
93+
assert g.removeEdge(3, 4) == False, "Should return False for non-existent vertices"
94+
assert g.removeEdge(1, 3) == False, "Should return False when dst doesn't exist"
95+
assert g.removeEdge(3, 2) == False, "Should return False when src doesn't exist"
96+
print("✓ Remove nonexistent edge test passed")
97+
98+
def test_duplicate_edge():
99+
"""Test adding duplicate edges"""
100+
g = Graph()
101+
g.addEdge(1, 2)
102+
g.addEdge(1, 2) # Duplicate - should not create multiple edges
103+
g.addEdge(1, 2) # Another duplicate
104+
assert g.hasPath(1, 2) == True, "Should have path"
105+
assert g.removeEdge(1, 2) == True, "Should remove edge"
106+
assert g.hasPath(1, 2) == False, "Should not have path after single removal"
107+
print("✓ Duplicate edge test passed")
108+
109+
def test_single_vertex():
110+
"""Test operations with single vertex"""
111+
g = Graph()
112+
g.addEdge(1, 2)
113+
g.removeEdge(1, 2)
114+
# After removal, vertices still exist but no edges
115+
# hasPath assumes both vertices exist
116+
print("✓ Single vertex test passed")
117+
118+
def test_complex_graph():
119+
"""Test more complex graph structure"""
120+
g = Graph()
121+
# Create a diamond shape: 1->2, 1->3, 2->4, 3->4
122+
g.addEdge(1, 2)
123+
g.addEdge(1, 3)
124+
g.addEdge(2, 4)
125+
g.addEdge(3, 4)
126+
assert g.hasPath(1, 4) == True, "Should have path 1->2->4"
127+
g.removeEdge(2, 4)
128+
assert g.hasPath(1, 4) == True, "Should still have path 1->3->4"
129+
g.removeEdge(3, 4)
130+
assert g.hasPath(1, 4) == False, "Should not have path after removing both edges to 4"
131+
print("✓ Complex graph test passed")
132+
133+
def test_long_path():
134+
"""Test longer paths"""
135+
g = Graph()
136+
# Create chain: 1->2->3->4->5->6->7->8->9->10
137+
for i in range(1, 10):
138+
g.addEdge(i, i + 1)
139+
assert g.hasPath(1, 10) == True, "Should have long path"
140+
assert g.hasPath(10, 1) == False, "Should not have reverse path"
141+
g.removeEdge(5, 6) # Break the chain
142+
assert g.hasPath(1, 10) == False, "Should not have path after breaking chain"
143+
assert g.hasPath(1, 5) == True, "Should have path to middle"
144+
assert g.hasPath(6, 10) == True, "Should have path from middle to end"
145+
print("✓ Long path test passed")
146+
147+
def test_empty_graph():
148+
"""Test operations on empty graph"""
149+
g = Graph()
150+
assert g.removeEdge(1, 2) == False, "Should return False on empty graph"
151+
print("✓ Empty graph test passed")
152+
153+
def test_bidirectional_edges():
154+
"""Test that directed edges work correctly"""
155+
g = Graph()
156+
g.addEdge(1, 2)
157+
g.addEdge(2, 1) # Add reverse edge
158+
assert g.hasPath(1, 2) == True, "Should have path 1->2"
159+
assert g.hasPath(2, 1) == True, "Should have path 2->1"
160+
g.removeEdge(1, 2)
161+
assert g.hasPath(1, 2) == False, "Should not have path 1->2 after removal"
162+
assert g.hasPath(2, 1) == True, "Should still have path 2->1"
163+
print("✓ Bidirectional edges test passed")
164+
165+
# Run all tests
166+
if __name__ == "__main__":
167+
test_basic_operations()
168+
test_remove_edge()
169+
test_cycle_detection()
170+
test_disconnected_components()
171+
test_remove_nonexistent_edge()
172+
test_duplicate_edge()
173+
test_single_vertex()
174+
test_complex_graph()
175+
test_long_path()
176+
test_empty_graph()
177+
test_bidirectional_edges()
178+
print("\n✅ All tests passed!")

0 commit comments

Comments
 (0)