Skip to content
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

Miguel 4.1 - Route Between Nodes [Python] #88

Merged
merged 16 commits into from
Sep 8, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 180 additions & 0 deletions Python/chapter04/p01_route_between_nodes/miguelHx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""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, Set


@dataclass
class Graph:
nodes: 'List[Node]'

def print_graph(self):
for node in self.nodes:
node.print_children()


@dataclass
class Node:
id: int
children: 'List[Node]'

def add_child(self, *nodes: 'Node'):
for node in nodes:
self.children.append(node)

def children_as_str(self) -> str:
return ', '.join(str(child.id) for child in self.children)

def print_children(self):
logging.debug('Adjacency list for node %s: %s', self.id, self.children_as_str())

def __str__(self):
return f'Node ({self.id}), children: {self.children_as_str()}'

def bfs_search_exhaustive(root: Node) -> List[int]:
"""Simple BFS.
takes in a root, returns a list
of ids of the sequence of visited
nodes. Goes through entire graph.

Args:
root (Node): starting node

Returns:
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4])
"""
visited_list: List[int] = [root.id]
visited: Set[int] = set([root.id])
queue: Deque[Node] = deque([root])
while queue:
node = queue.popleft()
# print(f'Visiting node ({node.id})')
for n in node.children:
if n.id not in visited:
queue.append(n)
visited_list.append(n.id)
visited.add(n.id)
return visited_list


def bfs_search_for_dest(root: Node, dest: Node) -> List[int]:
"""Simple BFS.
takes in a root, returns a list
of ids of the sequence of visited
nodes. Stops at destination node

Args:
root (Node): starting node

Returns:
List[int]: List[int]: list of node IDs (i.e. [0, 1, 4])
"""
visited_list: List[int] = [root.id]
visited: Set[int] = set([root.id])
queue: Deque[Node] = deque([root])
while queue:
node = queue.popleft()
# print(f'Visiting node ({node.id})')
for n in node.children:
if n.id not in visited:
queue.append(n)
visited_list.append(n.id)
visited.add(n.id)
if n.id == dest.id:
# done searching
return visited_list
return visited_list

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_for_dest(src, dest)
return dest.id in ids_visited


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_breadth_first_search_exhaustive(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_exhaustive(n0)
self.assertEqual(result, [0, 1, 4, 5, 3, 2])


if __name__ == '__main__':
unittest.main()