Skip to content

Commit 7d1e655

Browse files
miguelHxlhchavez
andauthored
Miguel 4.1 - Route Between Nodes [Python] (#88)
* Setting up graph-related data structures and operations * Implement BFS and add test case * Add type annotation for deque * Initial solution file setup * Implement route between nodes * Fix bug and update test cases * Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez <[email protected]> * Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez <[email protected]> * Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez <[email protected]> * Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez <[email protected]> * Use a set to track visited nodes instead of visited flag property on Node * Remove code unused in solution * Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez <[email protected]> * Delete graph search file * Remove usage of visited as a property on Node * Small optimization - terminate at destination Co-authored-by: lhchavez <[email protected]>
1 parent 0a9438c commit 7d1e655

File tree

1 file changed

+180
-0
lines changed

1 file changed

+180
-0
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"""Python Version 3.9.2
2+
4.1 - Route Between Nodes:
3+
Given a directed graph, design an algorithm to
4+
find out whether there is a route between two nodes.
5+
"""
6+
import unittest
7+
8+
from collections import deque
9+
from dataclasses import dataclass
10+
from typing import List, Deque, Set
11+
12+
13+
@dataclass
14+
class Graph:
15+
nodes: 'List[Node]'
16+
17+
def print_graph(self):
18+
for node in self.nodes:
19+
node.print_children()
20+
21+
22+
@dataclass
23+
class Node:
24+
id: int
25+
children: 'List[Node]'
26+
27+
def add_child(self, *nodes: 'Node'):
28+
for node in nodes:
29+
self.children.append(node)
30+
31+
def children_as_str(self) -> str:
32+
return ', '.join(str(child.id) for child in self.children)
33+
34+
def print_children(self):
35+
logging.debug('Adjacency list for node %s: %s', self.id, self.children_as_str())
36+
37+
def __str__(self):
38+
return f'Node ({self.id}), children: {self.children_as_str()}'
39+
40+
def bfs_search_exhaustive(root: Node) -> List[int]:
41+
"""Simple BFS.
42+
takes in a root, returns a list
43+
of ids of the sequence of visited
44+
nodes. Goes through entire graph.
45+
46+
Args:
47+
root (Node): starting node
48+
49+
Returns:
50+
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4])
51+
"""
52+
visited_list: List[int] = [root.id]
53+
visited: Set[int] = set([root.id])
54+
queue: Deque[Node] = deque([root])
55+
while queue:
56+
node = queue.popleft()
57+
# print(f'Visiting node ({node.id})')
58+
for n in node.children:
59+
if n.id not in visited:
60+
queue.append(n)
61+
visited_list.append(n.id)
62+
visited.add(n.id)
63+
return visited_list
64+
65+
66+
def bfs_search_for_dest(root: Node, dest: Node) -> List[int]:
67+
"""Simple BFS.
68+
takes in a root, returns a list
69+
of ids of the sequence of visited
70+
nodes. Stops at destination node
71+
72+
Args:
73+
root (Node): starting node
74+
75+
Returns:
76+
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4])
77+
"""
78+
visited_list: List[int] = [root.id]
79+
visited: Set[int] = set([root.id])
80+
queue: Deque[Node] = deque([root])
81+
while queue:
82+
node = queue.popleft()
83+
# print(f'Visiting node ({node.id})')
84+
for n in node.children:
85+
if n.id not in visited:
86+
queue.append(n)
87+
visited_list.append(n.id)
88+
visited.add(n.id)
89+
if n.id == dest.id:
90+
# done searching
91+
return visited_list
92+
return visited_list
93+
94+
def route_between_nodes(src: Node, dest: Node) -> bool:
95+
"""This function will return true if a path
96+
is found between two nodes, false otherwise.
97+
The idea is to perform a breadth first search
98+
from src to dest. After obtaining a list of
99+
nodes visited, we simply check to see if destination
100+
node id is in there.
101+
102+
Runtime Complexity:
103+
O(V + E) where V represents the number of
104+
nodes in the graph and E represents the number
105+
of edges in this graph.
106+
Space Complexity:
107+
O(V) where V represents the number of existing nodes
108+
in the graph.
109+
110+
Args:
111+
src (Node): from node
112+
dest (Node): destination node
113+
114+
Returns:
115+
bool: whether a path between src and dest exists
116+
"""
117+
ids_visited: List[int] = bfs_search_for_dest(src, dest)
118+
return dest.id in ids_visited
119+
120+
121+
class TestRouteBetweenNodes(unittest.TestCase):
122+
def test_route_between_nodes(self):
123+
n0 = Node(0, [])
124+
n1 = Node(1, [])
125+
n2 = Node(2, [])
126+
n3 = Node(3, [])
127+
n4 = Node(4, [])
128+
n5 = Node(5, [])
129+
n0.add_child(n1, n4, n5)
130+
n1.add_child(n3, n4)
131+
n2.add_child(n1)
132+
n3.add_child(n2, n4)
133+
# must remember to reset node visited properties
134+
# before each fresh run
135+
g = Graph([n0, n1, n2, n3, n4, n5])
136+
# There is a route from node 0 to node 2
137+
self.assertTrue(route_between_nodes(n0, n2))
138+
# No route between node 1 and node 0
139+
self.assertFalse(route_between_nodes(n1, n0))
140+
# There is a route from node 2 to node 3
141+
self.assertTrue(route_between_nodes(n2, n3))
142+
143+
class TestMyGraphSearch(unittest.TestCase):
144+
145+
def test_basic_graph_creation(self):
146+
n0 = Node(0, [])
147+
n1 = Node(1, [])
148+
n2 = Node(2, [])
149+
n3 = Node(3, [])
150+
n4 = Node(4, [])
151+
n5 = Node(5, [])
152+
n6 = Node(6, [])
153+
n0.add_child(n1)
154+
n1.add_child(n2)
155+
n2.add_child(n0, n3)
156+
n3.add_child(n2)
157+
n4.add_child(n6)
158+
n5.add_child(n4)
159+
n6.add_child(n5)
160+
nodes = [n0, n1, n2, n3, n4, n5, n6]
161+
g = Graph(nodes)
162+
# g.print_graph()
163+
164+
def test_basic_breadth_first_search_exhaustive(self):
165+
n0 = Node(0, [])
166+
n1 = Node(1, [])
167+
n2 = Node(2, [])
168+
n3 = Node(3, [])
169+
n4 = Node(4, [])
170+
n5 = Node(5, [])
171+
n0.add_child(n1, n4, n5)
172+
n1.add_child(n3, n4)
173+
n2.add_child(n1)
174+
n3.add_child(n2, n4)
175+
result: List[int] = bfs_search_exhaustive(n0)
176+
self.assertEqual(result, [0, 1, 4, 5, 3, 2])
177+
178+
179+
if __name__ == '__main__':
180+
unittest.main()

0 commit comments

Comments
 (0)