@@ -97,6 +97,17 @@ def path(self):
97
97
node = node .parent
98
98
return list (reversed (path_back ))
99
99
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
+
100
111
#______________________________________________________________________________
101
112
102
113
class SimpleProblemSolvingAgentProgram :
@@ -143,7 +154,7 @@ def tree_search(problem, frontier):
143
154
def graph_search (problem , frontier ):
144
155
"""Search through the successors of a problem to find a goal.
145
156
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]"""
147
158
frontier .append (Node (problem .initial ))
148
159
explored = set ()
149
160
while frontier :
@@ -153,7 +164,7 @@ def graph_search(problem, frontier):
153
164
explored .add (node .state )
154
165
frontier .extend (child for child in node .expand (problem )
155
166
if child .state not in explored
156
- and child . state not in frontier )
167
+ and child not in frontier )
157
168
return None
158
169
159
170
def breadth_first_tree_search (problem ):
@@ -164,21 +175,61 @@ def depth_first_tree_search(problem):
164
175
"Search the deepest nodes in the search tree first."
165
176
return tree_search (problem , Stack ())
166
177
167
- def breadth_first_graph_search (problem ):
168
- "Search the shallowest nodes in the search tree first."
169
- return graph_search (problem , FIFOQueue ())
170
-
171
178
def depth_first_graph_search (problem ):
172
179
"Search the deepest nodes in the search tree first."
173
180
return graph_search (problem , Stack ())
174
181
175
182
def 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
178
229
179
230
def 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 )
182
233
183
234
def depth_limited_search (problem , limit = 50 ):
184
235
"[Fig. 3.17]"
@@ -210,35 +261,22 @@ def iterative_deepening_search(problem):
210
261
#______________________________________________________________________________
211
262
# Informed (Heuristic) Search
212
263
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
-
224
264
greedy_best_first_graph_search = best_first_graph_search
225
265
# Greedy best-first search is accomplished by specifying f(n) = h(n).
226
266
227
267
def astar_search (problem , h = None ):
228
268
"""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 ))
235
273
236
274
#______________________________________________________________________________
237
275
# Other search algorithms
238
276
239
277
def recursive_best_first_search (problem , h = None ):
240
278
"[Fig. 3.26]"
241
- h = h or problem .h
279
+ h = memoize ( h or problem .h , 'h' )
242
280
243
281
def RBFS (problem , node , flimit ):
244
282
if problem .goal_test (node .state ):
@@ -768,17 +806,25 @@ def goal_test(self, state):
768
806
self .found = state
769
807
return result
770
808
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
+
771
815
def __getattr__ (self , attr ):
772
816
return getattr (self .problem , attr )
773
817
774
818
def __repr__ (self ):
775
819
return '<%4d/%4d/%4d/%s>' % (self .succs , self .goal_tests ,
776
820
self .states , str (self .found )[:4 ])
777
821
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 ]):
782
828
def do (searcher , problem ):
783
829
p = InstrumentedProblem (problem )
784
830
searcher (p )
@@ -789,14 +835,14 @@ def do(searcher, problem):
789
835
def compare_graph_searchers ():
790
836
"""Prints a table of results like this:
791
837
>>> 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>"""
800
846
compare_searchers (problems = [GraphProblem ('A' , 'B' , romania ),
801
847
GraphProblem ('O' , 'N' , romania ),
802
848
GraphProblem ('Q' , 'WA' , australia )],
@@ -808,10 +854,12 @@ def compare_graph_searchers():
808
854
>>> ab = GraphProblem('A', 'B', romania)
809
855
>>> breadth_first_tree_search(ab).solution()
810
856
['S', 'F', 'B']
811
- >>> breadth_first_graph_search (ab).solution()
857
+ >>> breadth_first_search (ab).solution()
812
858
['S', 'F', 'B']
859
+ >>> uniform_cost_search(ab).solution()
860
+ ['S', 'R', 'P', 'B']
813
861
>>> depth_first_graph_search(ab).solution()
814
- ['T', 'L', 'M', 'D', 'C', 'R', 'S', 'F ', 'B']
862
+ ['T', 'L', 'M', 'D', 'C', 'P ', 'B']
815
863
>>> iterative_deepening_search(ab).solution()
816
864
['S', 'F', 'B']
817
865
>>> len(depth_limited_search(ab).solution())
0 commit comments