From 72c63481ab001c63b74da642eafc482e27d2bf71 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Tue, 19 May 2020 15:11:35 +0200 Subject: [PATCH 1/5] implement greedy best first --- graphs/greedy_best_first.py | 163 ++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 graphs/greedy_best_first.py diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py new file mode 100644 index 000000000000..3760b1bbdf2f --- /dev/null +++ b/graphs/greedy_best_first.py @@ -0,0 +1,163 @@ +""" +https://en.wikipedia.org/wiki/Best-first_search#Greedy_BFS +""" + +import time +from typing import List, Tuple + +grid = [ + [0, 0, 0, 0, 0, 0, 0], + [0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 1, 0, 0, 0, 0], + [1, 0, 1, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 1, 0, 0], +] + +delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # up, left, down, right + + +class Node: + """ + >>> k = Node(0, 0, 4, 5, 0, None) + >>> k.calculate_heuristic() + 9 + >>> n = Node(1, 4, 3, 4, 2, None) + >>> n.calculate_heuristic() + 2 + >>> l = [k, n] + >>> n == l[0] + False + >>> l.sort() + >>> n == l[0] + True + """ + + def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent): + self.pos_x = pos_x + self.pos_y = pos_y + self.pos = (pos_y, pos_x) + self.goal_x = goal_x + self.goal_y = goal_y + self.g_cost = g_cost + self.parent = parent + self.f_cost = self.calculate_heuristic() + + def calculate_heuristic(self) -> float: + """ + The heuristic here is the Manhattan Distance + Could elaborate to offer more than one choice + """ + dy = abs(self.pos_x - self.goal_x) + dx = abs(self.pos_y - self.goal_y) + return dx + dy + + def __lt__(self, other): + return self.f_cost < other.f_cost + + +class GreedyBestFirst: + def __init__(self, start, goal): + self.start = Node(start[1], start[0], goal[1], goal[0], 0, None) + self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None) + + self.open_nodes = [self.start] + self.closed_nodes = [] + + self.reached = False + + self.path = [(self.start.pos_y, self.start.pos_x)] + self.costs = [0] + + def search(self): + while self.open_nodes: + # Open Nodes are sorted using __lt__ + self.open_nodes.sort() + current_node = self.open_nodes.pop(0) + + if current_node.pos == self.target.pos: + self.reached = True + self.path = self.retrace_path(current_node) + break + + self.closed_nodes.append(current_node) + successors = self.get_successors(current_node) + + for child_node in successors: + if child_node in self.closed_nodes: + continue + + if child_node not in self.open_nodes: + self.open_nodes.append(child_node) + else: + # retrieve the best current path + better_node = self.open_nodes.pop(self.open_nodes.index(child_node)) + + if child_node.g_cost < better_node.g_cost: + self.open_nodes.append(child_node) + else: + self.open_nodes.append(better_node) + + if not (self.reached): + print("No path found") + + def get_successors(self, parent: Node) -> List[Node]: + """ + Returns a list of successors (both in the grid and free spaces) + """ + successors = [] + for action in delta: + pos_x = parent.pos_x + action[1] + pos_y = parent.pos_y + action[0] + if ( + pos_x < 0 + or pos_x > len(grid[0]) - 1 + or pos_y < 0 + or pos_y > len(grid) - 1 + ): + continue + + if grid[pos_y][pos_x] != 0: + continue + + node_ = Node( + pos_x, + pos_y, + self.target.pos_y, + self.target.pos_x, + parent.g_cost + 1, + parent, + ) + successors.append(node_) + return successors + + def retrace_path(self, node: Node) -> List[Tuple[int]]: + """ + Retrace the path from parents to parents until start node + """ + current_node = node + path = [] + while current_node is not None: + path.append((current_node.pos_y, current_node.pos_x)) + current_node = current_node.parent + path.reverse() + return path + + +# all coordinates are given in format [y,x] +init = (0, 0) +goal = (len(grid) - 1, len(grid[0]) - 1) +for elem in grid: + print(elem) + +print("------") + +greedy_bf = GreedyBestFirst(init, goal) +greedy_bf.search() + +for elem in greedy_bf.path: + grid[elem[0]][elem[1]] = 2 + +for elem in grid: + print(elem) From 1073d7da74db86d6230665af604c4e1786f3c630 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Tue, 19 May 2020 15:13:11 +0200 Subject: [PATCH 2/5] implement Greedy Best First Search --- graphs/greedy_best_first.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 3760b1bbdf2f..51992a906f39 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -2,7 +2,6 @@ https://en.wikipedia.org/wiki/Best-first_search#Greedy_BFS """ -import time from typing import List, Tuple grid = [ @@ -160,4 +159,4 @@ def retrace_path(self, node: Node) -> List[Tuple[int]]: grid[elem[0]][elem[1]] = 2 for elem in grid: - print(elem) + print(elem) \ No newline at end of file From d6a096c091c196df857c1cdf9357391048c835b1 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 20 May 2020 15:03:39 +0200 Subject: [PATCH 3/5] review changes --- graphs/greedy_best_first.py | 74 +++++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 32 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 51992a906f39..97a6e10d36e2 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -57,6 +57,18 @@ def __lt__(self, other): class GreedyBestFirst: + """ + >>> gbf = GreedyBestFirst((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> (gbf.start.pos_y + delta[3][0], gbf.start.pos_x + delta[3][1]) + (0, 1) + >>> (gbf.start.pos_y + delta[2][0], gbf.start.pos_x + delta[2][1]) + (1, 0) + >>> gbf.retrace_path(gbf.start) + [(0, 0)] + >>> gbf.search() + [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (6, 1), (6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] + """ + def __init__(self, start, goal): self.start = Node(start[1], start[0], goal[1], goal[0], 0, None) self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None) @@ -66,10 +78,11 @@ def __init__(self, start, goal): self.reached = False - self.path = [(self.start.pos_y, self.start.pos_x)] - self.costs = [0] - - def search(self): + def search(self) -> List[Tuple[int]]: + """ + Search for the path, + if a path is not found, only the starting position is returned + """ while self.open_nodes: # Open Nodes are sorted using __lt__ self.open_nodes.sort() @@ -77,8 +90,7 @@ def search(self): if current_node.pos == self.target.pos: self.reached = True - self.path = self.retrace_path(current_node) - break + return self.retrace_path(current_node) self.closed_nodes.append(current_node) successors = self.get_successors(current_node) @@ -100,6 +112,7 @@ def search(self): if not (self.reached): print("No path found") + return [self.start.pos] def get_successors(self, parent: Node) -> List[Node]: """ @@ -109,26 +122,23 @@ def get_successors(self, parent: Node) -> List[Node]: for action in delta: pos_x = parent.pos_x + action[1] pos_y = parent.pos_y + action[0] - if ( - pos_x < 0 - or pos_x > len(grid[0]) - 1 - or pos_y < 0 - or pos_y > len(grid) - 1 - ): + + if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1): continue if grid[pos_y][pos_x] != 0: continue - node_ = Node( - pos_x, - pos_y, - self.target.pos_y, - self.target.pos_x, - parent.g_cost + 1, - parent, + successors.append( + Node( + pos_x, + pos_y, + self.target.pos_y, + self.target.pos_x, + parent.g_cost + 1, + parent, + ) ) - successors.append(node_) return successors def retrace_path(self, node: Node) -> List[Tuple[int]]: @@ -144,19 +154,19 @@ def retrace_path(self, node: Node) -> List[Tuple[int]]: return path -# all coordinates are given in format [y,x] -init = (0, 0) -goal = (len(grid) - 1, len(grid[0]) - 1) -for elem in grid: - print(elem) +if __name__ == "__main__": + init = (0, 0) + goal = (len(grid) - 1, len(grid[0]) - 1) + for elem in grid: + print(elem) -print("------") + print("------") -greedy_bf = GreedyBestFirst(init, goal) -greedy_bf.search() + greedy_bf = GreedyBestFirst(init, goal) + path = greedy_bf.search() -for elem in greedy_bf.path: - grid[elem[0]][elem[1]] = 2 + for elem in path: + grid[elem[0]][elem[1]] = 2 -for elem in grid: - print(elem) \ No newline at end of file + for elem in grid: + print(elem) From 690ef648c4ab716d4b858d430d05ab5d4044f263 Mon Sep 17 00:00:00 2001 From: Erwin Lejeune Date: Wed, 20 May 2020 16:49:39 +0200 Subject: [PATCH 4/5] add doctests --- graphs/greedy_best_first.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index 97a6e10d36e2..fcc4b6e6ef77 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -59,6 +59,8 @@ def __lt__(self, other): class GreedyBestFirst: """ >>> gbf = GreedyBestFirst((0, 0), (len(grid) - 1, len(grid[0]) - 1)) + >>> [x.pos for x in gbf.get_successors(gbf.start)] + [(1, 0), (0, 1)] >>> (gbf.start.pos_y + delta[3][0], gbf.start.pos_x + delta[3][1]) (0, 1) >>> (gbf.start.pos_y + delta[2][0], gbf.start.pos_x + delta[2][1]) From fe733f2fcb2923d9f7bdbc47e985064270bac03b Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Wed, 20 May 2020 23:10:56 +0200 Subject: [PATCH 5/5] >>> gbf.search() # doctest: +NORMALIZE_WHITESPACE --- graphs/greedy_best_first.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graphs/greedy_best_first.py b/graphs/greedy_best_first.py index fcc4b6e6ef77..2e63a50ce30a 100644 --- a/graphs/greedy_best_first.py +++ b/graphs/greedy_best_first.py @@ -14,7 +14,7 @@ [0, 0, 0, 0, 1, 0, 0], ] -delta = [[-1, 0], [0, -1], [1, 0], [0, 1]] # up, left, down, right +delta = ([-1, 0], [0, -1], [1, 0], [0, 1]) # up, left, down, right class Node: @@ -52,7 +52,7 @@ def calculate_heuristic(self) -> float: dx = abs(self.pos_y - self.goal_y) return dx + dy - def __lt__(self, other): + def __lt__(self, other) -> bool: return self.f_cost < other.f_cost @@ -67,8 +67,9 @@ class GreedyBestFirst: (1, 0) >>> gbf.retrace_path(gbf.start) [(0, 0)] - >>> gbf.search() - [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (6, 1), (6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] + >>> gbf.search() # doctest: +NORMALIZE_WHITESPACE + [(0, 0), (1, 0), (2, 0), (3, 0), (3, 1), (4, 1), (5, 1), (6, 1), + (6, 2), (6, 3), (5, 3), (5, 4), (5, 5), (6, 5), (6, 6)] """ def __init__(self, start, goal): @@ -113,7 +114,6 @@ def search(self) -> List[Tuple[int]]: self.open_nodes.append(better_node) if not (self.reached): - print("No path found") return [self.start.pos] def get_successors(self, parent: Node) -> List[Node]: