1515class SearchNode (Generic [T ]):
1616 """Representation of a search node"""
1717
18- __slots__ = ("data" , "gscore" , "fscore" , "closed" , "came_from" , "in_openset" )
18+ __slots__ = ("data" , "gscore" , "fscore" , "closed" , "came_from" , "in_openset" , "cache" )
1919
2020 def __init__ (
2121 self , data : T , gscore : float = infinity , fscore : float = infinity
@@ -26,6 +26,7 @@ def __init__(
2626 self .closed = False
2727 self .in_openset = False
2828 self .came_from : Union [None , SearchNode [T ]] = None
29+ self .cache : Any = None
2930
3031 def __lt__ (self , b : "SearchNode[T]" ) -> bool :
3132 """Natural order is based on the fscore value & is used by heapq operations"""
@@ -84,29 +85,48 @@ def heuristic_cost_estimate(self, current: T, goal: T) -> float:
8485 """
8586 Computes the estimated (rough) distance between a node and the goal.
8687 The second parameter is always the goal.
88+
8789 This method must be implemented in a subclass.
8890 """
8991 raise NotImplementedError
9092
91- @abstractmethod
9293 def distance_between (self , n1 : T , n2 : T ) -> float :
9394 """
9495 Gives the real distance between two adjacent nodes n1 and n2 (i.e n2
9596 belongs to the list of n1's neighbors).
9697 n2 is guaranteed to belong to the list returned by the call to neighbors(n1).
97- This method must be implemented in a subclass.
98+
99+ This method (or "path_distance_between") must be implemented in a subclass.
98100 """
101+ raise NotImplementedError
102+
103+ def path_distance_between (self , n1 : SearchNode [T ], n2 : SearchNode [T ]) -> float :
104+ """
105+ Gives the real distance between the node n1 and its neighbor n2.
106+ n2 is guaranteed to belong to the list returned by the call to
107+ path_neighbors(n1).
108+
109+ Calls "distance_between"`by default.
110+ """
111+ return self .distance_between (n1 .data , n2 .data )
99112
100- @abstractmethod
101113 def neighbors (self , node : T ) -> Iterable [T ]:
102114 """
103115 For a given node, returns (or yields) the list of its neighbors.
104- This method must be implemented in a subclass.
116+
117+ This method (or "path_neighbors") must be implemented in a subclass.
105118 """
106119 raise NotImplementedError
107120
121+ def path_neighbors (self , node : SearchNode [T ]) -> Iterable [T ]:
122+ """
123+ For a given node, returns (or yields) the list of its reachable neighbors.
124+ Calls "neighbors" by default.
125+ """
126+ return self .neighbors (node .data )
127+
108128 def _neighbors (self , current : SearchNode [T ], search_nodes : SearchNodeDict [T ]) -> Iterable [SearchNode ]:
109- return (search_nodes [n ] for n in self .neighbors (current . data ))
129+ return (search_nodes [n ] for n in self .path_neighbors (current ))
110130
111131 def is_goal_reached (self , current : T , goal : T ) -> bool :
112132 """
@@ -153,25 +173,27 @@ def astar(
153173 if neighbor .closed :
154174 continue
155175
156- tentative_gscore = current .gscore + self .distance_between (
157- current .data , neighbor .data
158- )
176+ gscore = current .gscore + self .path_distance_between (current , neighbor )
159177
160- if tentative_gscore >= neighbor .gscore :
178+ if gscore >= neighbor .gscore :
161179 continue
162180
163- neighbor_from_openset = neighbor .in_openset
181+ fscore = gscore + self .heuristic_cost_estimate (
182+ neighbor .data , goal
183+ )
184+
185+ if neighbor .in_openset :
186+ if neighbor .fscore < fscore :
187+ # the new path to this node isn't better
188+ continue
164189
165- if neighbor_from_openset :
166190 # we have to remove the item from the heap, as its score has changed
167191 openSet .remove (neighbor )
168192
169193 # update the node
170194 neighbor .came_from = current
171- neighbor .gscore = tentative_gscore
172- neighbor .fscore = tentative_gscore + self .heuristic_cost_estimate (
173- neighbor .data , goal
174- )
195+ neighbor .gscore = gscore
196+ neighbor .fscore = fscore
175197
176198 openSet .push (neighbor )
177199
0 commit comments