From 1e0ba79b29a28f17cc5dc2e6c8829f0ae2292660 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Fri, 27 Aug 2021 16:53:03 -0700 Subject: [PATCH 01/16] Setting up graph-related data structures and operations --- Python/chapter04/lib/graph_search.py | 127 +++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 Python/chapter04/lib/graph_search.py diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py new file mode 100644 index 00000000..c5c23d1f --- /dev/null +++ b/Python/chapter04/lib/graph_search.py @@ -0,0 +1,127 @@ +import unittest + +from dataclasses import dataclass +from typing import List + + +@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 = [] + 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) + self.assertEqual(dfs_search(n0), [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) + + +if __name__ == '__main__': + unittest.main() From 993424204b74e555dd1bbd8f2b83adab8a4a0163 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Sat, 28 Aug 2021 16:33:44 -0700 Subject: [PATCH 02/16] Implement BFS and add test case --- Python/chapter04/lib/graph_search.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py index c5c23d1f..6e67001e 100644 --- a/Python/chapter04/lib/graph_search.py +++ b/Python/chapter04/lib/graph_search.py @@ -1,5 +1,6 @@ import unittest +from collections import deque from dataclasses import dataclass from typing import List @@ -52,7 +53,7 @@ def dfs_search(root: Node) -> List[int]: output = [] if root is None: return - print(f'Visiting node ({root.id})') + # print(f'Visiting node ({root.id})') root.visited = True # print(root.children) output.append(root.id) @@ -74,10 +75,22 @@ def bfs_search(root: Node) -> List[int]: List[int]: List[int]: list of node IDs (i.e. [0, 1, 4]) """ output = [] + output.append(root.id) + queue = 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): @@ -109,7 +122,8 @@ def test_basic_depth_first_search(self): n0.add_child(n1, n4, n5) n1.add_child(n3, n4) n3.add_child(n2, n4) - self.assertEqual(dfs_search(n0), [0, 1, 3, 2, 4, 5]) + 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, []) @@ -121,6 +135,8 @@ def test_basic_breadth_first_search(self): 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__': From 77f5991df696e884f6dc2ed00d690bfa1d52577a Mon Sep 17 00:00:00 2001 From: miguelhx Date: Sat, 28 Aug 2021 16:44:56 -0700 Subject: [PATCH 03/16] Add type annotation for deque --- Python/chapter04/lib/graph_search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py index 6e67001e..61179a50 100644 --- a/Python/chapter04/lib/graph_search.py +++ b/Python/chapter04/lib/graph_search.py @@ -2,7 +2,7 @@ from collections import deque from dataclasses import dataclass -from typing import List +from typing import List, Deque @dataclass @@ -76,7 +76,7 @@ def bfs_search(root: Node) -> List[int]: """ output = [] output.append(root.id) - queue = deque() + queue: Deque[Node] = deque() root.visited = True queue.append(root) while len(queue) >= 1: From 535510842c2fcff086b4dfee48152cfa56ad20a7 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Sat, 28 Aug 2021 16:45:43 -0700 Subject: [PATCH 04/16] Initial solution file setup --- .../p01_route_between_nodes/miguelHx.py | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 Python/chapter04/p01_route_between_nodes/miguelHx.py diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py new file mode 100644 index 00000000..8f498947 --- /dev/null +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -0,0 +1,150 @@ +"""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 + + +@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() From f01b2a2d7fe49dc6689cf765b801fbc0e941432f Mon Sep 17 00:00:00 2001 From: miguelhx Date: Sat, 28 Aug 2021 17:42:48 -0700 Subject: [PATCH 05/16] Implement route between nodes --- .../p01_route_between_nodes/miguelHx.py | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 8f498947..e8671127 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -3,8 +3,6 @@ Given a directed graph, design an algorithm to find out whether there is a route between two nodes. """ - - import unittest from collections import deque @@ -98,6 +96,51 @@ def bfs_search(root: Node) -> List[int]: return output +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) + return True if dest.id in ids_visited else False + + +class TestRouteBetweenNodes: + 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) + n3.add_child(n2, n4) + # 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): From 097fc800c5ab82319f62de60e49d5e1b64d17b66 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Sat, 28 Aug 2021 18:06:21 -0700 Subject: [PATCH 06/16] Fix bug and update test cases --- .../p01_route_between_nodes/miguelHx.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index e8671127..127a3d36 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -79,8 +79,8 @@ def bfs_search(root: Node) -> List[int]: Returns: List[int]: List[int]: list of node IDs (i.e. [0, 1, 4]) """ - output = [] - output.append(root.id) + visited_nodes: List[Node] = [] + visited_nodes.append(root) queue: Deque[Node] = deque() root.visited = True queue.append(root) @@ -92,8 +92,11 @@ def bfs_search(root: Node) -> List[int]: if not n.visited: n.visited = True queue.append(n) - output.append(n.id) - return output + 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: @@ -123,7 +126,7 @@ def route_between_nodes(src: Node, dest: Node) -> bool: return True if dest.id in ids_visited else False -class TestRouteBetweenNodes: +class TestRouteBetweenNodes(unittest.TestCase): def test_route_between_nodes(self): n0 = Node(0, []) n1 = Node(1, []) @@ -133,7 +136,11 @@ def test_route_between_nodes(self): 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 @@ -171,6 +178,7 @@ def test_basic_depth_first_search(self): 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]) @@ -184,6 +192,7 @@ def test_basic_breadth_first_search(self): 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]) From 421fc499a68a179c06de6df6c592fd96780361f6 Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Tue, 7 Sep 2021 20:10:37 -0700 Subject: [PATCH 07/16] Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 127a3d36..35d4d687 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -84,7 +84,7 @@ def bfs_search(root: Node) -> List[int]: queue: Deque[Node] = deque() root.visited = True queue.append(root) - while len(queue) >= 1: + while queue: node = queue.popleft() node.visited = True # print(f'Visiting node ({node.id})') From 39055624b0027646b6c76f270783220b6854e13c Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Tue, 7 Sep 2021 20:12:13 -0700 Subject: [PATCH 08/16] Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 35d4d687..264634be 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -86,7 +86,6 @@ def bfs_search(root: Node) -> List[int]: queue.append(root) while queue: node = queue.popleft() - node.visited = True # print(f'Visiting node ({node.id})') for n in node.children: if not n.visited: From 1c4bb6e03ce9e94d7e78487aba5a4242b052c4bf Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Tue, 7 Sep 2021 20:13:12 -0700 Subject: [PATCH 09/16] Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 264634be..4f51505c 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -122,7 +122,7 @@ def route_between_nodes(src: Node, dest: Node) -> bool: bool: whether a path between src and dest exists """ ids_visited: List[int] = bfs_search(src) - return True if dest.id in ids_visited else False + return dest.id in ids_visited class TestRouteBetweenNodes(unittest.TestCase): From e3a39321964b7665854b5bd5c1829f40a605a055 Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Tue, 7 Sep 2021 20:15:26 -0700 Subject: [PATCH 10/16] Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 4f51505c..ac0c1825 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -81,9 +81,7 @@ def bfs_search(root: Node) -> List[int]: """ visited_nodes: List[Node] = [] visited_nodes.append(root) - queue: Deque[Node] = deque() - root.visited = True - queue.append(root) + queue: Deque[Node] = deque([root]) while queue: node = queue.popleft() # print(f'Visiting node ({node.id})') From 265cf29b04bfc1e75ac795c3422ee831d68fe02c Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 20:23:35 -0700 Subject: [PATCH 11/16] Use a set to track visited nodes instead of visited flag property on Node --- .../p01_route_between_nodes/miguelHx.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index ac0c1825..d661e01a 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -7,7 +7,7 @@ from collections import deque from dataclasses import dataclass -from typing import List, Deque +from typing import List, Deque, Set @dataclass @@ -79,22 +79,18 @@ def bfs_search(root: Node) -> List[int]: Returns: List[int]: List[int]: list of node IDs (i.e. [0, 1, 4]) """ - visited_nodes: List[Node] = [] - visited_nodes.append(root) + 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 not n.visited: - n.visited = True + if n.id not in visited: 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)) - + visited_list.append(n.id) + visited.add(n.id) + return visited_list def route_between_nodes(src: Node, dest: Node) -> bool: """This function will return true if a path From 39c084ce4086aee21900712cd5fd9e7574e0ddd0 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 20:30:54 -0700 Subject: [PATCH 12/16] Remove code unused in solution --- .../p01_route_between_nodes/miguelHx.py | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index d661e01a..059aa4fa 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -42,31 +42,6 @@ def print_children(self): 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 @@ -162,20 +137,6 @@ def test_basic_graph_creation(self): 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, []) From 2ef8112845a08a2c0c804b221b286f423c786197 Mon Sep 17 00:00:00 2001 From: Miguel Hernandez Date: Tue, 7 Sep 2021 20:32:39 -0700 Subject: [PATCH 13/16] Update Python/chapter04/p01_route_between_nodes/miguelHx.py Co-authored-by: lhchavez --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 059aa4fa..f10f1cd8 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -34,10 +34,7 @@ def add_child(self, *nodes: 'Node'): 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) + logging.debug('Adjacency list for node %s: %s', self.id, ', '.join(str(child.id) for child in self.children)) def __str__(self): return f'Node ({self.id}), visited: {self.visited}' From 59bceeefc2449efb37ef2aa5a948e234e2b8ec66 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 21:16:35 -0700 Subject: [PATCH 14/16] Delete graph search file --- Python/chapter04/lib/graph_search.py | 143 --------------------------- 1 file changed, 143 deletions(-) delete mode 100644 Python/chapter04/lib/graph_search.py diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py deleted file mode 100644 index 61179a50..00000000 --- a/Python/chapter04/lib/graph_search.py +++ /dev/null @@ -1,143 +0,0 @@ -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() From 75c59a4fbe9b2495af76d09a46092afd5e1da4f8 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 21:25:43 -0700 Subject: [PATCH 15/16] Remove usage of visited as a property on Node --- Python/chapter04/p01_route_between_nodes/miguelHx.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index f10f1cd8..404f89f3 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -18,26 +18,24 @@ 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 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, ', '.join(str(child.id) for child in self.children)) + logging.debug('Adjacency list for node %s: %s', self.id, self.children_as_str()) def __str__(self): - return f'Node ({self.id}), visited: {self.visited}' + return f'Node ({self.id}), children: {self.children_as_str()}' def bfs_search(root: Node) -> List[int]: """Simple BFS. From e5f27c89ce8531ba096309714f2a91dff75de95b Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 21:34:52 -0700 Subject: [PATCH 16/16] Small optimization - terminate at destination --- .../p01_route_between_nodes/miguelHx.py | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/Python/chapter04/p01_route_between_nodes/miguelHx.py b/Python/chapter04/p01_route_between_nodes/miguelHx.py index 404f89f3..4ded0104 100644 --- a/Python/chapter04/p01_route_between_nodes/miguelHx.py +++ b/Python/chapter04/p01_route_between_nodes/miguelHx.py @@ -37,11 +37,11 @@ def print_children(self): def __str__(self): return f'Node ({self.id}), children: {self.children_as_str()}' -def bfs_search(root: Node) -> List[int]: +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. + nodes. Goes through entire graph. Args: root (Node): starting node @@ -62,6 +62,35 @@ def bfs_search(root: Node) -> List[int]: 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. @@ -85,7 +114,7 @@ def route_between_nodes(src: Node, dest: Node) -> bool: Returns: bool: whether a path between src and dest exists """ - ids_visited: List[int] = bfs_search(src) + ids_visited: List[int] = bfs_search_for_dest(src, dest) return dest.id in ids_visited @@ -132,7 +161,7 @@ def test_basic_graph_creation(self): g = Graph(nodes) # g.print_graph() - def test_basic_breadth_first_search(self): + def test_basic_breadth_first_search_exhaustive(self): n0 = Node(0, []) n1 = Node(1, []) n2 = Node(2, []) @@ -143,7 +172,7 @@ def test_basic_breadth_first_search(self): n1.add_child(n3, n4) n2.add_child(n1) n3.add_child(n2, n4) - result: List[int] = bfs_search(n0) + result: List[int] = bfs_search_exhaustive(n0) self.assertEqual(result, [0, 1, 4, 5, 3, 2])