From a3fdc9218560c3b737d48ef7dd70b3b67938c096 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 20:39:11 -0700 Subject: [PATCH 1/4] Add reference code for graph search --- Python/chapter04/lib/graph_search.py | 143 +++++++++++++++++++++++++++ 1 file changed, 143 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..61179a50 --- /dev/null +++ b/Python/chapter04/lib/graph_search.py @@ -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() From eba55b77d4778647e8bf23983556a03cfee4e608 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 20:49:16 -0700 Subject: [PATCH 2/4] Add improvements to bfs_search --- Python/chapter04/lib/graph_search.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py index 61179a50..894e268d 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, Deque +from typing import List, Deque, Set @dataclass @@ -29,10 +29,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}' @@ -74,21 +71,18 @@ 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) - queue: Deque[Node] = deque() - root.visited = True - queue.append(root) - while len(queue) >= 1: + visited_list: List[int] = [root.id] + visited: Set[int] = set([root.id]) + queue: Deque[Node] = deque([root]) + while queue: node = queue.popleft() - node.visited = True # 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) - output.append(n.id) - return output + visited_list.append(n.id) + visited.add(n.id) + return visited_list class TestMyGraphSearch(unittest.TestCase): From 52e87b2b427adadf97cf3fd8e84d65f293b1f53e Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 21:03:35 -0700 Subject: [PATCH 3/4] Improvements to dfs_search --- Python/chapter04/lib/graph_search.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py index 894e268d..3ecd8751 100644 --- a/Python/chapter04/lib/graph_search.py +++ b/Python/chapter04/lib/graph_search.py @@ -35,7 +35,7 @@ def __str__(self): return f'Node ({self.id}), visited: {self.visited}' -def dfs_search(root: Node) -> List[int]: +def dfs_search(root: Node, visited: Set[int] = set()) -> List[int]: """Simple DFS. takes in a root, returns a list of ids of the sequence of visited @@ -47,17 +47,18 @@ def dfs_search(root: Node) -> List[int]: Returns: List[int]: list of node IDs (i.e. [0, 1, 3]) """ - output = [] if root is None: - return + raise TypeError + visited_list: List[int] = [root.id] + if not visited: + visited.add(root.id) # 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 + if node.id not in visited: + visited.add(node.id) + visited_list.extend(dfs_search(node, visited)) + return visited_list def bfs_search(root: Node) -> List[int]: """Simple BFS. From cdd5d1b49790d7d930dbb578d3d2fe2c4be2c283 Mon Sep 17 00:00:00 2001 From: miguelhx Date: Tue, 7 Sep 2021 21:14:54 -0700 Subject: [PATCH 4/4] Remove unneeded if statement --- Python/chapter04/lib/graph_search.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Python/chapter04/lib/graph_search.py b/Python/chapter04/lib/graph_search.py index 3ecd8751..d7279a9d 100644 --- a/Python/chapter04/lib/graph_search.py +++ b/Python/chapter04/lib/graph_search.py @@ -50,8 +50,12 @@ def dfs_search(root: Node, visited: Set[int] = set()) -> List[int]: if root is None: raise TypeError visited_list: List[int] = [root.id] - if not visited: - visited.add(root.id) + # if already added, won't add to set. + # line 55 is mainly for initial empty set. + # future .adds will attempt to add value that + # already exists in the set. + # result will be a no-op + visited.add(root.id) # print(f'Visiting node ({root.id})') # print(root.children) for node in root.children: