@@ -97,6 +97,17 @@ def path(self):
9797 node = node .parent
9898 return list (reversed (path_back ))
9999
100+ # We want for a queue of nodes in breadth_first_search or
101+ # astar_search to have no duplicated states, so we treat nodes
102+ # with the same state as equal. [Problem: this may not be what you
103+ # want in other contexts.]
104+
105+ def __eq__ (self , other ):
106+ return isinstance (other , Node ) and self .state == other .state
107+
108+ def __hash__ (self ):
109+ return hash (self .state )
110+
100111#______________________________________________________________________________
101112
102113class SimpleProblemSolvingAgentProgram :
@@ -143,7 +154,7 @@ def tree_search(problem, frontier):
143154def graph_search (problem , frontier ):
144155 """Search through the successors of a problem to find a goal.
145156 The argument frontier should be an empty queue.
146- If two paths reach a state, only use the best one. [Fig. 3.7]"""
157+ If two paths reach a state, only use the first one. [Fig. 3.7]"""
147158 frontier .append (Node (problem .initial ))
148159 explored = set ()
149160 while frontier :
@@ -153,7 +164,7 @@ def graph_search(problem, frontier):
153164 explored .add (node .state )
154165 frontier .extend (child for child in node .expand (problem )
155166 if child .state not in explored
156- and child . state not in frontier )
167+ and child not in frontier )
157168 return None
158169
159170def breadth_first_tree_search (problem ):
@@ -164,21 +175,61 @@ def depth_first_tree_search(problem):
164175 "Search the deepest nodes in the search tree first."
165176 return tree_search (problem , Stack ())
166177
167- def breadth_first_graph_search (problem ):
168- "Search the shallowest nodes in the search tree first."
169- return graph_search (problem , FIFOQueue ())
170-
171178def depth_first_graph_search (problem ):
172179 "Search the deepest nodes in the search tree first."
173180 return graph_search (problem , Stack ())
174181
175182def breadth_first_search (problem ):
176- "Fig. 3.11"
177- unimplemented ()
183+ "[Fig. 3.11]"
184+ node = Node (problem .initial )
185+ if problem .goal_test (node .state ):
186+ return node
187+ frontier = FIFOQueue ()
188+ frontier .append (node )
189+ explored = set ()
190+ while frontier :
191+ node = frontier .pop ()
192+ explored .add (node .state )
193+ for child in node .expand (problem ):
194+ if child .state not in explored and child not in frontier :
195+ if problem .goal_test (child .state ):
196+ return child
197+ frontier .append (child )
198+ return None
199+
200+ def best_first_graph_search (problem , f ):
201+ """Search the nodes with the lowest f scores first.
202+ You specify the function f(node) that you want to minimize; for example,
203+ if f is a heuristic estimate to the goal, then we have greedy best
204+ first search; if f is node.depth then we have breadth-first search.
205+ There is a subtlety: the line "f = memoize(f, 'f')" means that the f
206+ values will be cached on the nodes as they are computed. So after doing
207+ a best first search you can examine the f values of the path returned."""
208+ f = memoize (f , 'f' )
209+ node = Node (problem .initial )
210+ if problem .goal_test (node .state ):
211+ return node
212+ frontier = PriorityQueue (min , f )
213+ frontier .append (node )
214+ explored = set ()
215+ while frontier :
216+ node = frontier .pop ()
217+ if problem .goal_test (node .state ):
218+ return node
219+ explored .add (node .state )
220+ for child in node .expand (problem ):
221+ if child .state not in explored and child not in frontier :
222+ frontier .append (child )
223+ elif child in frontier :
224+ incumbent = frontier [child ]
225+ if f (child ) < f (incumbent ):
226+ del frontier [incumbent ]
227+ frontier .append (child )
228+ return None
178229
179230def uniform_cost_search (problem ):
180- "Fig. 3.14"
181- unimplemented ( )
231+ "[ Fig. 3.14] "
232+ return best_first_graph_search ( problem , lambda node : node . path_cost )
182233
183234def depth_limited_search (problem , limit = 50 ):
184235 "[Fig. 3.17]"
@@ -210,35 +261,22 @@ def iterative_deepening_search(problem):
210261#______________________________________________________________________________
211262# Informed (Heuristic) Search
212263
213- def best_first_graph_search (problem , f ):
214- """Search the nodes with the lowest f scores first.
215- You specify the function f(node) that you want to minimize; for example,
216- if f is a heuristic estimate to the goal, then we have greedy best
217- first search; if f is node.depth then we have breadth-first search.
218- There is a subtlety: the line "f = memoize(f, 'f')" means that the f
219- values will be cached on the nodes as they are computed. So after doing
220- a best first search you can examine the f values of the path returned."""
221- f = memoize (f , 'f' )
222- return graph_search (problem , PriorityQueue (min , f ))
223-
224264greedy_best_first_graph_search = best_first_graph_search
225265 # Greedy best-first search is accomplished by specifying f(n) = h(n).
226266
227267def astar_search (problem , h = None ):
228268 """A* search is best-first graph search with f(n) = g(n)+h(n).
229- You need to specify the h function when you call astar_search.
230- Uses the pathmax trick: f(n) = max(f(n), g(n)+h(n))."""
231- h = h or problem .h
232- def f (n ):
233- return max (getattr (n , 'f' , - infinity ), n .path_cost + h (n ))
234- return best_first_graph_search (problem , f )
269+ You need to specify the h function when you call astar_search, or
270+ else in your Problem subclass."""
271+ h = memoize (h or problem .h , 'h' )
272+ return best_first_graph_search (problem , lambda n : n .path_cost + h (n ))
235273
236274#______________________________________________________________________________
237275# Other search algorithms
238276
239277def recursive_best_first_search (problem , h = None ):
240278 "[Fig. 3.26]"
241- h = h or problem .h
279+ h = memoize ( h or problem .h , 'h' )
242280
243281 def RBFS (problem , node , flimit ):
244282 if problem .goal_test (node .state ):
@@ -768,17 +806,25 @@ def goal_test(self, state):
768806 self .found = state
769807 return result
770808
809+ def path_cost (self , c , state1 , action , state2 ):
810+ return self .problem .path_cost (c , state1 , action , state2 )
811+
812+ def value (self , state ):
813+ return self .problem .value (state )
814+
771815 def __getattr__ (self , attr ):
772816 return getattr (self .problem , attr )
773817
774818 def __repr__ (self ):
775819 return '<%4d/%4d/%4d/%s>' % (self .succs , self .goal_tests ,
776820 self .states , str (self .found )[:4 ])
777821
778- def compare_searchers (problems , header , searchers = [breadth_first_tree_search ,
779- breadth_first_graph_search , depth_first_graph_search ,
780- iterative_deepening_search , depth_limited_search ,
781- astar_search , recursive_best_first_search ]):
822+ def compare_searchers (problems , header ,
823+ searchers = [breadth_first_tree_search ,
824+ breadth_first_search , depth_first_graph_search ,
825+ iterative_deepening_search ,
826+ depth_limited_search , astar_search ,
827+ recursive_best_first_search ]):
782828 def do (searcher , problem ):
783829 p = InstrumentedProblem (problem )
784830 searcher (p )
@@ -789,14 +835,14 @@ def do(searcher, problem):
789835def compare_graph_searchers ():
790836 """Prints a table of results like this:
791837>>> compare_graph_searchers()
792- Searcher Romania(A, B) Romania(O, N) Australia
793- breadth_first_tree_search < 21/ 22/ 59/B> <1158/1159/3288/N> < 7/ 8/ 22/WA>
794- breadth_first_graph_search < 11 / 12 / 28 /B> < 33 / 34 / 76 /N> < 6 / 7 / 19 /WA>
795- depth_first_graph_search < 9 / 10 / 23 /B> < 16/ 17/ 39 /N> < 4/ 5/ 13 /WA>
796- iterative_deepening_search < 11/ 33/ 31/B> < 656/1815/1812/N> < 3/ 11/ 11/WA>
797- depth_limited_search < 54/ 65/ 185/B> < 387/1012/1125/N> < 50/ 54/ 200/WA>
798- astar_search < 3 / 4 / 9 /B> < 8 / 9 / 22 /N> < 2/ 3 / 6/WA>
799- recursive_best_first_search < 200/ 201/ 601 /B> < 71/ 72/ 213 /N> < 11/ 12/ 43/WA>"""
838+ Searcher Romania(A, B) Romania(O, N) Australia
839+ breadth_first_tree_search < 21/ 22/ 59/B> <1158/1159/3288/N> < 7/ 8/ 22/WA>
840+ breadth_first_search < 7 / 11 / 18 /B> < 19 / 20 / 45 /N> < 2 / 6 / 8 /WA>
841+ depth_first_graph_search < 8 / 9 / 20 /B> < 16/ 17/ 38 /N> < 4/ 5/ 11 /WA>
842+ iterative_deepening_search < 11/ 33/ 31/B> < 656/1815/1812/N> < 3/ 11/ 11/WA>
843+ depth_limited_search < 54/ 65/ 185/B> < 387/1012/1125/N> < 50/ 54/ 200/WA>
844+ astar_search < 5 / 7 / 15 /B> < 16 / 18 / 40 /N> < 2/ 4 / 6/WA>
845+ recursive_best_first_search < 5/ 6/ 15 /B> <5887/5888/16532 /N> < 11/ 12/ 43/WA>"""
800846 compare_searchers (problems = [GraphProblem ('A' , 'B' , romania ),
801847 GraphProblem ('O' , 'N' , romania ),
802848 GraphProblem ('Q' , 'WA' , australia )],
@@ -808,10 +854,12 @@ def compare_graph_searchers():
808854>>> ab = GraphProblem('A', 'B', romania)
809855>>> breadth_first_tree_search(ab).solution()
810856['S', 'F', 'B']
811- >>> breadth_first_graph_search (ab).solution()
857+ >>> breadth_first_search (ab).solution()
812858['S', 'F', 'B']
859+ >>> uniform_cost_search(ab).solution()
860+ ['S', 'R', 'P', 'B']
813861>>> depth_first_graph_search(ab).solution()
814- ['T', 'L', 'M', 'D', 'C', 'R', 'S', 'F ', 'B']
862+ ['T', 'L', 'M', 'D', 'C', 'P ', 'B']
815863>>> iterative_deepening_search(ab).solution()
816864['S', 'F', 'B']
817865>>> len(depth_limited_search(ab).solution())
0 commit comments