-
Notifications
You must be signed in to change notification settings - Fork 95
Miguel 4.1 - Route Between Nodes [Python] #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
lhchavez
merged 16 commits into
techqueria:master
from
miguelHx:miguel_4.1_route_between_nodes
Sep 8, 2021
Merged
Changes from 6 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
1e0ba79
Setting up graph-related data structures and operations
miguelHx 9934242
Implement BFS and add test case
miguelHx 77f5991
Add type annotation for deque
miguelHx 5355108
Initial solution file setup
miguelHx f01b2a2
Implement route between nodes
miguelHx 097fc80
Fix bug and update test cases
miguelHx 421fc49
Update Python/chapter04/p01_route_between_nodes/miguelHx.py
miguelHx 3905562
Update Python/chapter04/p01_route_between_nodes/miguelHx.py
miguelHx 1c4bb6e
Update Python/chapter04/p01_route_between_nodes/miguelHx.py
miguelHx e3a3932
Update Python/chapter04/p01_route_between_nodes/miguelHx.py
miguelHx 265cf29
Use a set to track visited nodes instead of visited flag property on …
miguelHx 39c084c
Remove code unused in solution
miguelHx 2ef8112
Update Python/chapter04/p01_route_between_nodes/miguelHx.py
miguelHx 59bceee
Delete graph search file
miguelHx 75c59a4
Remove usage of visited as a property on Node
miguelHx e5f27c8
Small optimization - terminate at destination
miguelHx File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import unittest | ||
|
||
from collections import deque | ||
from dataclasses import dataclass | ||
from typing import List, Deque | ||
|
||
|
||
@dataclass | ||
class Graph: | ||
nodes: 'List[Node]' | ||
|
||
def print_graph(self): | ||
for node in self.nodes: | ||
node.print_children() | ||
|
||
def reset_visited(self): | ||
for node in self.nodes: | ||
node.visited = False | ||
|
||
|
||
@dataclass | ||
class Node: | ||
id: int | ||
children: 'List[Node]' | ||
visited: bool = False | ||
|
||
def add_child(self, *nodes: 'Node'): | ||
for node in nodes: | ||
self.children.append(node) | ||
|
||
def print_children(self): | ||
message = f'Adjacency list for node ({self.id}): ' | ||
for child in self.children: | ||
message += '{}, '.format(child.id) | ||
print(message) | ||
|
||
def __str__(self): | ||
return f'Node ({self.id}), visited: {self.visited}' | ||
|
||
|
||
def dfs_search(root: Node) -> List[int]: | ||
"""Simple DFS. | ||
takes in a root, returns a list | ||
of ids of the sequence of visited | ||
nodes. | ||
|
||
Args: | ||
root (Node): starting node | ||
|
||
Returns: | ||
List[int]: list of node IDs (i.e. [0, 1, 3]) | ||
""" | ||
output = [] | ||
if root is None: | ||
return | ||
# print(f'Visiting node ({root.id})') | ||
root.visited = True | ||
# print(root.children) | ||
output.append(root.id) | ||
for node in root.children: | ||
if not node.visited: | ||
output.extend(dfs_search(node)) | ||
return output | ||
|
||
def bfs_search(root: Node) -> List[int]: | ||
"""Simple BFS. | ||
takes in a root, returns a list | ||
of ids of the sequence of visited | ||
nodes. | ||
|
||
Args: | ||
root (Node): starting node | ||
|
||
Returns: | ||
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4]) | ||
""" | ||
output = [] | ||
output.append(root.id) | ||
queue: Deque[Node] = deque() | ||
root.visited = True | ||
queue.append(root) | ||
while len(queue) >= 1: | ||
node = queue.popleft() | ||
node.visited = True | ||
# print(f'Visiting node ({node.id})') | ||
for n in node.children: | ||
if not n.visited: | ||
n.visited = True | ||
queue.append(n) | ||
output.append(n.id) | ||
return output | ||
|
||
|
||
class TestMyGraphSearch(unittest.TestCase): | ||
|
||
def test_basic_graph_creation(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n6 = Node(6, []) | ||
n0.add_child(n1) | ||
n1.add_child(n2) | ||
n2.add_child(n0, n3) | ||
n3.add_child(n2) | ||
n4.add_child(n6) | ||
n5.add_child(n4) | ||
n6.add_child(n5) | ||
nodes = [n0, n1, n2, n3, n4, n5, n6] | ||
g = Graph(nodes) | ||
# g.print_graph() | ||
|
||
def test_basic_depth_first_search(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n0.add_child(n1, n4, n5) | ||
n1.add_child(n3, n4) | ||
n3.add_child(n2, n4) | ||
result: List[int] = dfs_search(n0) | ||
self.assertEqual(result, [0, 1, 3, 2, 4, 5]) | ||
|
||
def test_basic_breadth_first_search(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n0.add_child(n1, n4, n5) | ||
n1.add_child(n3, n4) | ||
n3.add_child(n2, n4) | ||
result: List[int] = bfs_search(n0) | ||
self.assertEqual(result, [0, 1, 4, 5, 3, 2]) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
"""Python Version 3.9.2 | ||
4.1 - Route Between Nodes: | ||
Given a directed graph, design an algorithm to | ||
find out whether there is a route between two nodes. | ||
""" | ||
import unittest | ||
|
||
from collections import deque | ||
from dataclasses import dataclass | ||
from typing import List, Deque | ||
|
||
|
||
@dataclass | ||
class Graph: | ||
nodes: 'List[Node]' | ||
|
||
def print_graph(self): | ||
for node in self.nodes: | ||
node.print_children() | ||
|
||
def reset_visited(self): | ||
for node in self.nodes: | ||
node.visited = False | ||
lhchavez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@dataclass | ||
class Node: | ||
id: int | ||
children: 'List[Node]' | ||
visited: bool = False | ||
|
||
def add_child(self, *nodes: 'Node'): | ||
for node in nodes: | ||
self.children.append(node) | ||
|
||
def print_children(self): | ||
message = f'Adjacency list for node ({self.id}): ' | ||
for child in self.children: | ||
message += '{}, '.format(child.id) | ||
print(message) | ||
miguelHx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def __str__(self): | ||
return f'Node ({self.id}), visited: {self.visited}' | ||
|
||
|
||
def dfs_search(root: Node) -> List[int]: | ||
lhchavez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Simple DFS. | ||
takes in a root, returns a list | ||
of ids of the sequence of visited | ||
nodes. | ||
|
||
Args: | ||
root (Node): starting node | ||
|
||
Returns: | ||
List[int]: list of node IDs (i.e. [0, 1, 3]) | ||
""" | ||
output = [] | ||
if root is None: | ||
return | ||
# print(f'Visiting node ({root.id})') | ||
root.visited = True | ||
# print(root.children) | ||
output.append(root.id) | ||
for node in root.children: | ||
if not node.visited: | ||
output.extend(dfs_search(node)) | ||
return output | ||
|
||
def bfs_search(root: Node) -> List[int]: | ||
"""Simple BFS. | ||
takes in a root, returns a list | ||
of ids of the sequence of visited | ||
nodes. | ||
|
||
Args: | ||
root (Node): starting node | ||
|
||
Returns: | ||
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4]) | ||
""" | ||
visited_nodes: List[Node] = [] | ||
visited_nodes.append(root) | ||
lhchavez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
queue: Deque[Node] = deque() | ||
root.visited = True | ||
queue.append(root) | ||
miguelHx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
while len(queue) >= 1: | ||
miguelHx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node = queue.popleft() | ||
node.visited = True | ||
miguelHx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# print(f'Visiting node ({node.id})') | ||
for n in node.children: | ||
if not n.visited: | ||
n.visited = True | ||
queue.append(n) | ||
visited_nodes.append(n) | ||
# reset visited state | ||
g = Graph(visited_nodes) | ||
g.reset_visited() | ||
return list(map(lambda n: n.id, visited_nodes)) | ||
|
||
|
||
def route_between_nodes(src: Node, dest: Node) -> bool: | ||
"""This function will return true if a path | ||
is found between two nodes, false otherwise. | ||
The idea is to perform a breadth first search | ||
from src to dest. After obtaining a list of | ||
nodes visited, we simply check to see if destination | ||
node id is in there. | ||
|
||
Runtime Complexity: | ||
O(V + E) where V represents the number of | ||
nodes in the graph and E represents the number | ||
of edges in this graph. | ||
Space Complexity: | ||
O(V) where V represents the number of existing nodes | ||
in the graph. | ||
|
||
Args: | ||
src (Node): from node | ||
dest (Node): destination node | ||
|
||
Returns: | ||
bool: whether a path between src and dest exists | ||
""" | ||
ids_visited: List[int] = bfs_search(src) | ||
lhchavez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return True if dest.id in ids_visited else False | ||
miguelHx marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
class TestRouteBetweenNodes(unittest.TestCase): | ||
def test_route_between_nodes(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n0.add_child(n1, n4, n5) | ||
n1.add_child(n3, n4) | ||
n2.add_child(n1) | ||
n3.add_child(n2, n4) | ||
# must remember to reset node visited properties | ||
# before each fresh run | ||
g = Graph([n0, n1, n2, n3, n4, n5]) | ||
# There is a route from node 0 to node 2 | ||
self.assertTrue(route_between_nodes(n0, n2)) | ||
# No route between node 1 and node 0 | ||
self.assertFalse(route_between_nodes(n1, n0)) | ||
# There is a route from node 2 to node 3 | ||
self.assertTrue(route_between_nodes(n2, n3)) | ||
|
||
class TestMyGraphSearch(unittest.TestCase): | ||
|
||
def test_basic_graph_creation(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n6 = Node(6, []) | ||
n0.add_child(n1) | ||
n1.add_child(n2) | ||
n2.add_child(n0, n3) | ||
n3.add_child(n2) | ||
n4.add_child(n6) | ||
n5.add_child(n4) | ||
n6.add_child(n5) | ||
nodes = [n0, n1, n2, n3, n4, n5, n6] | ||
g = Graph(nodes) | ||
# g.print_graph() | ||
|
||
def test_basic_depth_first_search(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n0.add_child(n1, n4, n5) | ||
n1.add_child(n3, n4) | ||
n2.add_child(n1) | ||
n3.add_child(n2, n4) | ||
result: List[int] = dfs_search(n0) | ||
self.assertEqual(result, [0, 1, 3, 2, 4, 5]) | ||
|
||
def test_basic_breadth_first_search(self): | ||
n0 = Node(0, []) | ||
n1 = Node(1, []) | ||
n2 = Node(2, []) | ||
n3 = Node(3, []) | ||
n4 = Node(4, []) | ||
n5 = Node(5, []) | ||
n0.add_child(n1, n4, n5) | ||
n1.add_child(n3, n4) | ||
n2.add_child(n1) | ||
n3.add_child(n2, n4) | ||
result: List[int] = bfs_search(n0) | ||
self.assertEqual(result, [0, 1, 4, 5, 3, 2]) | ||
|
||
|
||
if __name__ == '__main__': | ||
unittest.main() |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.