Skip to content

Commit 21ed896

Browse files
guilyxpoyeacclauss
authored
Fixes in Bidirectional A* (TheAlgorithms#2020)
* implement bidirectional astar * add type hints * add wikipedia url * format with black * changes from review * fix collision check * Add testmod() * # doctest: +NORMALIZE_WHITESPACE * Codespell: euclidean * Codespell: coordinates * Codespell: traversal * Codespell: remaining Co-authored-by: John Law <[email protected]> Co-authored-by: Christian Clauss <[email protected]>
1 parent dc596d2 commit 21ed896

File tree

4 files changed

+87
-49
lines changed

4 files changed

+87
-49
lines changed

graphs/bidirectional_a_star.py

+84-46
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
"""
44

55
import time
6+
from math import sqrt
67
from typing import List, Tuple
78

9+
# 1 for manhattan, 0 for euclidean
10+
HEURISTIC = 0
11+
812
grid = [
913
[0, 0, 0, 0, 0, 0, 0],
1014
[0, 1, 0, 0, 0, 0, 0], # 0 are free path whereas 1's are obstacles
@@ -20,12 +24,12 @@
2024

2125
class Node:
2226
"""
23-
>>> k = Node(0, 0, 4, 5, 0, None)
27+
>>> k = Node(0, 0, 4, 3, 0, None)
2428
>>> k.calculate_heuristic()
25-
9
29+
5.0
2630
>>> n = Node(1, 4, 3, 4, 2, None)
2731
>>> n.calculate_heuristic()
28-
2
32+
2.0
2933
>>> l = [k, n]
3034
>>> n == l[0]
3135
False
@@ -47,18 +51,35 @@ def __init__(self, pos_x, pos_y, goal_x, goal_y, g_cost, parent):
4751

4852
def calculate_heuristic(self) -> float:
4953
"""
50-
The heuristic here is the Manhattan Distance
51-
Could elaborate to offer more than one choice
54+
Heuristic for the A*
5255
"""
53-
dy = abs(self.pos_x - self.goal_x)
54-
dx = abs(self.pos_y - self.goal_y)
55-
return dx + dy
56-
57-
def __lt__(self, other):
56+
dy = self.pos_x - self.goal_x
57+
dx = self.pos_y - self.goal_y
58+
if HEURISTIC == 1:
59+
return abs(dx) + abs(dy)
60+
else:
61+
return sqrt(dy ** 2 + dx ** 2)
62+
63+
def __lt__(self, other) -> bool:
5864
return self.f_cost < other.f_cost
5965

6066

6167
class AStar:
68+
"""
69+
>>> astar = AStar((0, 0), (len(grid) - 1, len(grid[0]) - 1))
70+
>>> (astar.start.pos_y + delta[3][0], astar.start.pos_x + delta[3][1])
71+
(0, 1)
72+
>>> [x.pos for x in astar.get_successors(astar.start)]
73+
[(1, 0), (0, 1)]
74+
>>> (astar.start.pos_y + delta[2][0], astar.start.pos_x + delta[2][1])
75+
(1, 0)
76+
>>> astar.retrace_path(astar.start)
77+
[(0, 0)]
78+
>>> astar.search() # doctest: +NORMALIZE_WHITESPACE
79+
[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2), (2, 3), (3, 3),
80+
(4, 3), (4, 4), (5, 4), (5, 5), (6, 5), (6, 6)]
81+
"""
82+
6283
def __init__(self, start, goal):
6384
self.start = Node(start[1], start[0], goal[1], goal[0], 0, None)
6485
self.target = Node(goal[1], goal[0], goal[1], goal[0], 99999, None)
@@ -68,19 +89,15 @@ def __init__(self, start, goal):
6889

6990
self.reached = False
7091

71-
self.path = [(self.start.pos_y, self.start.pos_x)]
72-
self.costs = [0]
73-
74-
def search(self):
92+
def search(self) -> List[Tuple[int]]:
7593
while self.open_nodes:
7694
# Open Nodes are sorted using __lt__
7795
self.open_nodes.sort()
7896
current_node = self.open_nodes.pop(0)
7997

8098
if current_node.pos == self.target.pos:
8199
self.reached = True
82-
self.path = self.retrace_path(current_node)
83-
break
100+
return self.retrace_path(current_node)
84101

85102
self.closed_nodes.append(current_node)
86103
successors = self.get_successors(current_node)
@@ -101,7 +118,7 @@ def search(self):
101118
self.open_nodes.append(better_node)
102119

103120
if not (self.reached):
104-
print("No path found")
121+
return [(self.start.pos)]
105122

106123
def get_successors(self, parent: Node) -> List[Node]:
107124
"""
@@ -111,21 +128,22 @@ def get_successors(self, parent: Node) -> List[Node]:
111128
for action in delta:
112129
pos_x = parent.pos_x + action[1]
113130
pos_y = parent.pos_y + action[0]
114-
if not (0 < pos_x < len(grid[0]) - 1 and 0 < pos_y < len(grid) - 1):
131+
if not (0 <= pos_x <= len(grid[0]) - 1 and 0 <= pos_y <= len(grid) - 1):
115132
continue
116133

117134
if grid[pos_y][pos_x] != 0:
118135
continue
119136

120-
node_ = Node(
121-
pos_x,
122-
pos_y,
123-
self.target.pos_y,
124-
self.target.pos_x,
125-
parent.g_cost + 1,
126-
parent,
137+
successors.append(
138+
Node(
139+
pos_x,
140+
pos_y,
141+
self.target.pos_y,
142+
self.target.pos_x,
143+
parent.g_cost + 1,
144+
parent,
145+
)
127146
)
128-
successors.append(node_)
129147
return successors
130148

131149
def retrace_path(self, node: Node) -> List[Tuple[int]]:
@@ -142,13 +160,24 @@ def retrace_path(self, node: Node) -> List[Tuple[int]]:
142160

143161

144162
class BidirectionalAStar:
163+
"""
164+
>>> bd_astar = BidirectionalAStar((0, 0), (len(grid) - 1, len(grid[0]) - 1))
165+
>>> bd_astar.fwd_astar.start.pos == bd_astar.bwd_astar.target.pos
166+
True
167+
>>> bd_astar.retrace_bidirectional_path(bd_astar.fwd_astar.start,
168+
... bd_astar.bwd_astar.start)
169+
[(0, 0)]
170+
>>> bd_astar.search() # doctest: +NORMALIZE_WHITESPACE
171+
[(0, 0), (0, 1), (0, 2), (1, 2), (1, 3), (2, 3), (2, 4),
172+
(2, 5), (3, 5), (4, 5), (5, 5), (5, 6), (6, 6)]
173+
"""
174+
145175
def __init__(self, start, goal):
146176
self.fwd_astar = AStar(start, goal)
147177
self.bwd_astar = AStar(goal, start)
148178
self.reached = False
149-
self.path = self.fwd_astar.path
150179

151-
def search(self):
180+
def search(self) -> List[Tuple[int]]:
152181
while self.fwd_astar.open_nodes or self.bwd_astar.open_nodes:
153182
self.fwd_astar.open_nodes.sort()
154183
self.bwd_astar.open_nodes.sort()
@@ -157,8 +186,9 @@ def search(self):
157186

158187
if current_bwd_node.pos == current_fwd_node.pos:
159188
self.reached = True
160-
self.retrace_bidirectional_path(current_fwd_node, current_bwd_node)
161-
break
189+
return self.retrace_bidirectional_path(
190+
current_fwd_node, current_bwd_node
191+
)
162192

163193
self.fwd_astar.closed_nodes.append(current_fwd_node)
164194
self.bwd_astar.closed_nodes.append(current_bwd_node)
@@ -189,30 +219,38 @@ def search(self):
189219
else:
190220
astar.open_nodes.append(better_node)
191221

222+
if not self.reached:
223+
return [self.fwd_astar.start.pos]
224+
192225
def retrace_bidirectional_path(
193226
self, fwd_node: Node, bwd_node: Node
194227
) -> List[Tuple[int]]:
195228
fwd_path = self.fwd_astar.retrace_path(fwd_node)
196229
bwd_path = self.bwd_astar.retrace_path(bwd_node)
197-
fwd_path.reverse()
230+
bwd_path.pop()
231+
bwd_path.reverse()
198232
path = fwd_path + bwd_path
199233
return path
200234

201235

202-
# all coordinates are given in format [y,x]
203-
init = (0, 0)
204-
goal = (len(grid) - 1, len(grid[0]) - 1)
205-
for elem in grid:
206-
print(elem)
236+
if __name__ == "__main__":
237+
# all coordinates are given in format [y,x]
238+
import doctest
239+
240+
doctest.testmod()
241+
init = (0, 0)
242+
goal = (len(grid) - 1, len(grid[0]) - 1)
243+
for elem in grid:
244+
print(elem)
207245

208-
start_time = time.time()
209-
a_star = AStar(init, goal)
210-
a_star.search()
211-
end_time = time.time() - start_time
212-
print(f"AStar execution time = {end_time:f} seconds")
246+
start_time = time.time()
247+
a_star = AStar(init, goal)
248+
path = a_star.search()
249+
end_time = time.time() - start_time
250+
print(f"AStar execution time = {end_time:f} seconds")
213251

214-
bd_start_time = time.time()
215-
bidir_astar = BidirectionalAStar(init, goal)
216-
bidir_astar.search()
217-
bd_end_time = time.time() - bd_start_time
218-
print(f"BidirectionalAStar execution time = {bd_end_time:f} seconds")
252+
bd_start_time = time.time()
253+
bidir_astar = BidirectionalAStar(init, goal)
254+
path = bidir_astar.search()
255+
bd_end_time = time.time() - bd_start_time
256+
print(f"BidirectionalAStar execution time = {bd_end_time:f} seconds")

machine_learning/astar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Cell(object):
1717
"""
1818
Class cell represents a cell in the world which have the property
1919
position : The position of the represented by tupleof x and y
20-
co-ordinates initially set to (0,0)
20+
coordinates initially set to (0,0)
2121
parent : This contains the parent cell object which we visited
2222
before arrinving this cell
2323
g,h,f : The parameters for constructing the heuristic function

scheduling/shortest_job_first_algorithm.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Shortest job remainig first
2+
Shortest job remaining first
33
Please note arrival time and burst
44
Please use spaces to separate times entered.
55
"""

sorts/tree_sort.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def insert(self, val):
2929

3030

3131
def inorder(root, res):
32-
# Recursive travesal
32+
# Recursive traversal
3333
if root:
3434
inorder(root.left, res)
3535
res.append(root.val)

0 commit comments

Comments
 (0)