1+ """
2+ A*
3+
4+ Author: Timothy Moore
5+
6+ A* is an informed search algorithm, or a best-first search,
7+ meaning that it solves problems by searching among all possible
8+ paths to the solution (goal) for the one that incurs the smallest
9+ cost (least distance travelled, shortest time, etc.), and among
10+ these paths it first considers the ones that appear to lead most
11+ quickly to the solution.
12+ https://en.wikipedia.org/wiki/A*_search_algorithm
13+ """
114import heapq
15+ import inspect
16+
217
318class OneDirectionalAStar (object ):
419 """AStar object
@@ -28,7 +43,7 @@ class OneDirectionalAStar(object):
2843 #
2944 # You would expand your side of center and find the top and bottom at
3045 # (5 + 12) - WORSE than just going to them. This is the case where we
31- # would NOT add the path base->center->top to the open list because
46+ # would NOT add the path base->center->top to the _open list because
3247 # (for these weights) it will never be better than base->top.
3348 #
3449 # You would also add the new node (55 + 0) or the destination.
@@ -37,14 +52,15 @@ class OneDirectionalAStar(object):
3752 # river with a cost of (18 + 12).
3853 #
3954 # You expand the top node on the other side of the river next and find
40- # one of the neighbors is already on the open list (the destination)
55+ # one of the neighbors is already on the _open list (the destination)
4156 # at a score of (55 + 0), but your cost to get there is (30 + 0). This
4257 # is where you would REPLACE the old path with yourself.
4358
4459 def __init__ (self ):
4560 pass
46-
47- def reverse_path (self , node ):
61+
62+ @staticmethod
63+ def reverse_path (node ):
4864 """
4965 Walks backward from an end node to the start
5066 node and reconstructs a path. Meant for internal
@@ -75,7 +91,7 @@ def find_path(self, graph, start, end, heuristic_fn):
7591 - The distance between nodes are small
7692 - There are too many nodes for an exhaustive search
7793 to ever be feasible.
78- - The world is mostly open (ie there are many paths
94+ - The world is mostly _open (ie there are many paths
7995 from the start to the end that are acceptable)
8096 - Execution speed is more important than accuracy.
8197 The best way to do this is to make the heuristic slightly
@@ -94,21 +110,21 @@ def find_path(self, graph, start, end, heuristic_fn):
94110 """
95111
96112 # It starts off very similiar to Dijkstra. However, we will need to lookup
97- # nodes in the open list before. There can be thousands of nodes in the open
113+ # nodes in the _open list before. There can be thousands of nodes in the _open
98114 # list and any unordered search is too expensive, so we trade some memory usage for
99115 # more consistent performance by maintaining a dictionary (O(1) lookup) between
100116 # vertices and their nodes.
101- open_lookup = {}
102- open = []
117+ _open_lookup = {}
118+ _open = []
103119 closed = set ()
104120
105121 # We require a bit more information on each node than Dijkstra
106122 # and we do slightly more calculation, so the heuristic must
107123 # prune enough nodes to offset those costs. In practice this
108- # is almost always the case if their are any large open areas
124+ # is almost always the case if their are any large _open areas
109125 # (nodes with many connected nodes).
110126
111- # Rather than simply expanding nodes that are on the open list
127+ # Rather than simply expanding nodes that are on the _open list
112128 # based on how close they are to the start, we will expand based
113129 # on how much distance we predict is between the start and end
114130 # node IF we go through that parent. That is a combination of
@@ -120,15 +136,19 @@ def find_path(self, graph, start, end, heuristic_fn):
120136
121137 counter = 0
122138 heur = heuristic_fn (graph , start , end )
123- open_lookup [start ] = { 'vertex' : start , 'dist_start_to_here' : 0 , 'pred_dist_here_to_end' : heur , 'pred_total_dist' : heur , 'parent' : None }
124- heapq .heappush (open , (heur , counter , start ))
139+ _open_lookup [start ] = {'vertex' : start ,
140+ 'dist_start_to_here' : 0 ,
141+ 'pred_dist_here_to_end' : heur ,
142+ 'pred_total_dist' : heur ,
143+ 'parent' : None }
144+ heapq .heappush (_open , (heur , counter , start ))
125145 counter += 1
126146
127- while len (open ) > 0 :
128- current = heapq .heappop (open )
147+ while len (_open ) > 0 :
148+ current = heapq .heappop (_open )
129149 current_vertex = current [2 ]
130- current_dict = open_lookup [current_vertex ]
131- del open_lookup [current_vertex ]
150+ current_dict = _open_lookup [current_vertex ]
151+ del _open_lookup [current_vertex ]
132152 closed .update (current_vertex )
133153
134154 if current_vertex == end :
@@ -142,8 +162,10 @@ def find_path(self, graph, start, end, heuristic_fn):
142162 # node first.
143163 continue
144164
145- cost_start_to_neighbor = current_dict ['dist_start_to_here' ] + graph .get_edge_weight (current_vertex , neighbor )
146- neighbor_from_lookup = open_lookup .get (neighbor , None ) # avoid searching twice
165+ cost_start_to_neighbor = current_dict ['dist_start_to_here' ] \
166+ + graph .get_edge_weight (current_vertex , neighbor )
167+ # avoid searching twice
168+ neighbor_from_lookup = _open_lookup .get (neighbor , None )
147169 if neighbor_from_lookup is not None :
148170 # If our heuristic is NOT consistent or the grid is NOT uniform,
149171 # it is possible that there is a better path to a neighbor of a
@@ -158,50 +180,49 @@ def find_path(self, graph, start, end, heuristic_fn):
158180 if cost_start_to_neighbor < old_dist_start_to_neighbor :
159181 pred_dist_neighbor_to_end = neighbor_from_lookup ['pred_dist_here_to_end' ]
160182 pred_total_dist_through_neighbor_to_end = cost_start_to_neighbor + pred_dist_neighbor_to_end
161- # Note, we've already shown that neighbor (the vector) is already in the open list,
183+ # Note, we've already shown that neighbor (the vector) is already in the _open list,
162184 # but unfortunately we don't know where and we have to do a slow lookup to fix the
163185 # key its sorting by to the new predicted total distance.
164186
165187 # In case we're using a fancy debugger we want to search in user-code so when
166188 # this lookup freezes we can see how much longer its going to take.
167189 found = None
168- for i in range (0 , len (open )):
169- if open [i ][2 ] == neighbor :
190+ for i in range (0 , len (_open )):
191+ if _open [i ][2 ] == neighbor :
170192 found = i
171193 break
172194 if found is None :
173- raise Exception ('A vertex is in the open lookup but not in open. This is impossible, please submit an issue + include the graph!' )
174- # todo I'm not certain about the performance characteristics of doing this with heapq, nor if
175- # it would be better to delete heapify and push or rather than replace
176- open [i ] = (pred_total_dist_through_neighbor_to_end , counter , neighbor )
195+ raise Exception ('A vertex is in the _open lookup but not in _open. '
196+ 'This is impossible, please submit an issue + include the graph!' )
197+ # TODO: I'm not certain about the performance characteristics of doing this with heapq, nor if
198+ # TODO: it would be better to delete heapify and push or rather than replace
199+
200+ # TODO: Local variable 'i' could be referenced before assignment
201+ _open [i ] = (pred_total_dist_through_neighbor_to_end , counter , neighbor )
177202 counter += 1
178- heapq .heapify (open )
179- open_lookup [neighbor ] = { 'vertex' : neighbor ,
203+ heapq .heapify (_open )
204+ _open_lookup [neighbor ] = {'vertex' : neighbor ,
180205 'dist_start_to_here' : cost_start_to_neighbor ,
181206 'pred_dist_here_to_end' : pred_dist_neighbor_to_end ,
182207 'pred_total_dist' : pred_total_dist_through_neighbor_to_end ,
183- 'parent' : current_dict }
208+ 'parent' : current_dict }
184209 continue
185-
186-
210+
187211 # We've found the first possible way to the path!
188212 pred_dist_neighbor_to_end = heuristic_fn (graph , neighbor , end )
189213 pred_total_dist_through_neighbor_to_end = cost_start_to_neighbor + pred_dist_neighbor_to_end
190- heapq .heappush (open , (pred_total_dist_through_neighbor_to_end , counter , neighbor ))
191- open_lookup [neighbor ] = { 'vertex' : neighbor ,
214+ heapq .heappush (_open , (pred_total_dist_through_neighbor_to_end , counter , neighbor ))
215+ _open_lookup [neighbor ] = {'vertex' : neighbor ,
192216 'dist_start_to_here' : cost_start_to_neighbor ,
193217 'pred_dist_here_to_end' : pred_dist_neighbor_to_end ,
194218 'pred_total_dist' : pred_total_dist_through_neighbor_to_end ,
195- 'parent' : current_dict }
219+ 'parent' : current_dict }
196220
197221 return None
198222
199223 @staticmethod
200- def get_code (self ):
224+ def get_code ():
201225 """
202226 returns the code for the current class
203227 """
204228 return inspect .getsource (OneDirectionalAStar )
205-
206-
207-
0 commit comments