Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 16563c3

Browse files
committed
search.py converted to 3rd edition.
1 parent 5eb77e6 commit 16563c3

File tree

4 files changed

+139
-91
lines changed

4 files changed

+139
-91
lines changed

csp.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class CSP(search.Problem):
3636
curr_domains[var] Slot: remaining consistent values for var
3737
Used by constraint propagation routines.
3838
The following methods are used only by graph_search and tree_search:
39-
successor(state) Return a list of (action, state) pairs
39+
actions(state) Return a list of actions
40+
result(state, action) Return a successor of state
4041
goal_test(state) Return true if all constraints satisfied
4142
The following are just for debugging purposes:
4243
nassigns Slot: tracks the number of assignments made
@@ -78,18 +79,22 @@ def display(self, assignment):
7879
# Subclasses can print in a prettier way, or display with a GUI
7980
print 'CSP:', self, 'with assignment:', assignment
8081

81-
## These methods are for the tree and graph search interface:
82+
## These methods are for the tree- and graph-search interface:
8283

83-
def successor(self, state):
84-
"Return a list of (action, state) pairs."
84+
def actions(self, state):
85+
"""Return a list of applicable actions: nonconflicting
86+
assignments to an unassigned variable."""
8587
if len(state) == len(self.vars):
8688
return []
8789
else:
8890
assignment = dict(state)
8991
var = find_if(lambda v: v not in assignment, self.vars)
90-
return [((var, val), state + ((var, val),))
91-
for val in self.domains[var]
92+
return [(var, val) for val in self.domains[var]
9293
if self.nconflicts(var, val, assignment) == 0]
94+
95+
def result(self, state, (var, val)):
96+
"Perform an action and return the new state."
97+
return state + ((var, val),)
9398

9499
def goal_test(self, state):
95100
"The goal is to assign all vars, with all constraints satisfied."

search.py

Lines changed: 117 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,35 @@
44
then create problem instances and solve them with calls to the various search
55
functions."""
66

7-
# (Written for the second edition of AIMA; expect some discrepanciecs
8-
# from the third edition until this gets reviewed.)
9-
107
from utils import *
118
import agents
129
import math, random, sys, time, bisect, string
1310

1411
#______________________________________________________________________________
1512

1613
class Problem(object):
17-
"""The abstract class for a formal problem. You should subclass this and
18-
implement the method successor, and possibly __init__, goal_test, and
19-
path_cost. Then you will create instances of your subclass and solve them
20-
with the various search functions."""
14+
"""The abstract class for a formal problem. You should subclass
15+
this and implement the methods actions and result, and possibly
16+
__init__, goal_test, and path_cost. Then you will create instances
17+
of your subclass and solve them with the various search functions."""
2118

2219
def __init__(self, initial, goal=None):
2320
"""The constructor specifies the initial state, and possibly a goal
2421
state, if there is a unique goal. Your subclass's constructor can add
2522
other arguments."""
2623
self.initial = initial; self.goal = goal
2724

28-
def successor(self, state):
29-
"""Given a state, return a sequence of (action, state) pairs reachable
30-
from this state. If there are many successors, consider an iterator
31-
that yields the successors one at a time, rather than building them
32-
all at once. Iterators will work fine within the framework."""
25+
def actions(self, state):
26+
"""Return the actions that can be executed in the given
27+
state. The result would typically be a list, but if there are
28+
many actions, consider yielding them one at a time in an
29+
iterator, rather than building them all at once."""
30+
abstract
31+
32+
def result(self, state, action):
33+
"""Return the state that results from executing the given
34+
action in the given state. The action must be one of
35+
self.actions(state)."""
3336
abstract
3437

3538
def goal_test(self, state):
@@ -72,20 +75,29 @@ def __init__(self, state, parent=None, action=None, path_cost=0):
7275
def __repr__(self):
7376
return "<Node %s>" % (self.state,)
7477

78+
def expand(self, problem):
79+
"List the nodes reachable in one step from this node."
80+
return [self.child_node(problem, action)
81+
for action in problem.actions(self.state)]
82+
83+
def child_node(self, problem, action):
84+
"Fig. 3.10"
85+
next = problem.result(self.state, action)
86+
return Node(next, self, action,
87+
problem.path_cost(self.path_cost, self.state, action, next))
88+
89+
def solution(self):
90+
"Return the sequence of actions to go from the root to this node."
91+
return [node.action for node in self.path()]
92+
7593
def path(self):
76-
"""Create a list of nodes from the root to this node."""
94+
"Return a list of nodes forming the path from the root to this node."
7795
node, path_back = self, []
7896
while node:
7997
path_back.append(node)
8098
node = node.parent
8199
return list(reversed(path_back))
82100

83-
def expand(self, problem):
84-
"Return a list of nodes reachable from this node. [Fig. 3.8]"
85-
return [Node(next, self, act,
86-
problem.path_cost(self.path_cost, self.state, act, next))
87-
for (act, next) in problem.successor(self.state)]
88-
89101
#______________________________________________________________________________
90102

91103
class SimpleProblemSolvingAgentProgram:
@@ -117,51 +129,60 @@ def search(self, problem):
117129
#______________________________________________________________________________
118130
## Uninformed Search algorithms
119131

120-
def tree_search(problem, fringe):
132+
def tree_search(problem, frontier):
121133
"""Search through the successors of a problem to find a goal.
122-
The argument fringe should be an empty queue.
123-
Don't worry about repeated paths to a state. [Fig. 3.8]"""
124-
fringe.append(Node(problem.initial))
125-
while fringe:
126-
node = fringe.pop()
134+
The argument frontier should be an empty queue.
135+
Don't worry about repeated paths to a state. [Fig. 3.7]"""
136+
frontier.append(Node(problem.initial))
137+
while frontier:
138+
node = frontier.pop()
127139
if problem.goal_test(node.state):
128140
return node
129-
fringe.extend(node.expand(problem))
141+
frontier.extend(node.expand(problem))
142+
return None
143+
144+
def graph_search(problem, frontier):
145+
"""Search through the successors of a problem to find a goal.
146+
The argument frontier should be an empty queue.
147+
If two paths reach a state, only use the best one. [Fig. 3.7]"""
148+
frontier.append(Node(problem.initial))
149+
explored = set()
150+
while frontier:
151+
node = frontier.pop()
152+
if problem.goal_test(node.state):
153+
return node
154+
explored.add(node.state)
155+
frontier.extend(successor for successor in node.expand(problem)
156+
if successor.state not in explored
157+
and successor.state not in frontier)
130158
return None
131159

132160
def breadth_first_tree_search(problem):
133-
"Search the shallowest nodes in the search tree first. [p 74]"
161+
"Search the shallowest nodes in the search tree first."
134162
return tree_search(problem, FIFOQueue())
135163

136164
def depth_first_tree_search(problem):
137-
"Search the deepest nodes in the search tree first. [p 74]"
165+
"Search the deepest nodes in the search tree first."
138166
return tree_search(problem, Stack())
139167

140-
def graph_search(problem, fringe):
141-
"""Search through the successors of a problem to find a goal.
142-
The argument fringe should be an empty queue.
143-
If two paths reach a state, only use the best one. [Fig. 3.18]"""
144-
closed = {}
145-
fringe.append(Node(problem.initial))
146-
while fringe:
147-
node = fringe.pop()
148-
if problem.goal_test(node.state):
149-
return node
150-
if node.state not in closed:
151-
closed[node.state] = True
152-
fringe.extend(node.expand(problem))
153-
return None
154-
155168
def breadth_first_graph_search(problem):
156-
"Search the shallowest nodes in the search tree first. [p 74]"
169+
"Search the shallowest nodes in the search tree first."
157170
return graph_search(problem, FIFOQueue())
158171

159172
def depth_first_graph_search(problem):
160-
"Search the deepest nodes in the search tree first. [p 74]"
173+
"Search the deepest nodes in the search tree first."
161174
return graph_search(problem, Stack())
162175

176+
def breadth_first_search(problem):
177+
"Fig. 3.11"
178+
NotImplemented
179+
180+
def uniform_cost_search(problem):
181+
"Fig. 3.14"
182+
NotImplemented
183+
163184
def depth_limited_search(problem, limit=50):
164-
"[Fig. 3.12]"
185+
"[Fig. 3.17]"
165186
def recursive_dls(node, problem, limit):
166187
if problem.goal_test(node.state):
167188
return node
@@ -181,7 +202,7 @@ def recursive_dls(node, problem, limit):
181202
return recursive_dls(Node(problem.initial), problem, limit)
182203

183204
def iterative_deepening_search(problem):
184-
"[Fig. 3.13]"
205+
"[Fig. 3.18]"
185206
for depth in xrange(sys.maxint):
186207
result = depth_limited_search(problem, depth)
187208
if result is not 'cutoff':
@@ -217,7 +238,7 @@ def f(n):
217238
## Other search algorithms
218239

219240
def recursive_best_first_search(problem, h=None):
220-
"[Fig. 4.5]"
241+
"[Fig. 3.26]"
221242
h = h or problem.h
222243

223244
def RBFS(problem, node, flimit):
@@ -248,7 +269,7 @@ def RBFS(problem, node, flimit):
248269

249270
def hill_climbing(problem):
250271
"""From the initial node, keep choosing the neighbor with highest value,
251-
stopping when no neighbor is better. [Fig. 4.11]"""
272+
stopping when no neighbor is better. [Fig. 4.2]"""
252273
current = Node(problem.initial)
253274
while True:
254275
neighbors = current.expand(problem)
@@ -280,28 +301,32 @@ def simulated_annealing(problem, schedule=exp_schedule()):
280301
if delta_e > 0 or probability(math.exp(delta_e/T)):
281302
current = next
282303

283-
def online_dfs_agent(a):
284-
"[Fig. 4.12]"
285-
pass #### more
304+
def and_or_graph_search(problem):
305+
"[Fig. 4.11]"
306+
NotImplemented
307+
308+
def online_dfs_agent(s1):
309+
"[Fig. 4.21]"
310+
NotImplemented
286311

287-
def lrta_star_agent(a):
288-
"[Fig. 4.12]"
289-
pass #### more
312+
def lrta_star_agent(s1):
313+
"[Fig. 4.24]"
314+
NotImplemented
290315

291316
#______________________________________________________________________________
292317
# Genetic Algorithm
293318

294319
def genetic_search(problem, fitness_fn, ngen=1000, pmut=0.1, n=20):
295320
"""Call genetic_algorithm on the appropriate parts of a problem.
296-
This requires that the problem has a successor function that
297-
generates states that can mate and mutate, and that it has a value
298-
method that scores states."""
299-
states = [s for (a, s) in problem.successor(problem.initial_state)]
321+
This requires the problem to have states that can mate and mutate,
322+
plus a value method that scores states."""
323+
s = problem.initial_state
324+
states = [problem.result(s, a) for a in problem.actions(s)]
300325
random.shuffle(states)
301326
return genetic_algorithm(states[:n], problem.value, ngen, pmut)
302327

303328
def genetic_algorithm(population, fitness_fn, ngen=1000, pmut=0.1):
304-
"""[Fig. 4.7]"""
329+
"[Fig. 4.8]"
305330
for i in range(ngen):
306331
new_population = []
307332
for i in len(population):
@@ -314,7 +339,7 @@ def genetic_algorithm(population, fitness_fn, ngen=1000, pmut=0.1):
314339
return argmax(population, fitness_fn)
315340

316341
class GAState:
317-
"Abstract class for individuals in a genetic algorithm."
342+
"Abstract class for individuals in a genetic search."
318343
def __init__(self, genes):
319344
self.genes = genes
320345

@@ -464,9 +489,13 @@ def __init__(self, initial, goal, graph):
464489
Problem.__init__(self, initial, goal)
465490
self.graph = graph
466491

467-
def successor(self, A):
468-
"Return a list of (action, result) pairs."
469-
return [(B, B) for B in self.graph.get(A).keys()]
492+
def actions(self, A):
493+
"The actions at a graph node are just its neighbors."
494+
return self.graph.get(A).keys()
495+
496+
def result(self, state, action):
497+
"The result of going to a neighbor is just that neighbor."
498+
return action
470499

471500
def path_cost(self, cost_so_far, A, action, B):
472501
return cost_so_far + (self.graph.get(A,B) or infinity)
@@ -486,24 +515,30 @@ class NQueensProblem(Problem):
486515
each other. A state is represented as an N-element array, where
487516
a value of r in the c-th entry means there is a queen at column c,
488517
row r, and a value of None means that the c-th column has not been
489-
filled in yet. We fill in columns left to right."""
518+
filled in yet. We fill in columns left to right.
519+
>>> depth_first_tree_search(NQueensProblem(8))
520+
<Node [7, 3, 0, 2, 5, 1, 6, 4]>
521+
"""
490522
def __init__(self, N):
491523
self.N = N
492524
self.initial = [None] * N
493525

494-
def successor(self, state):
526+
def actions(self, state):
495527
"In the leftmost empty column, try all non-conflicting rows."
496528
if state[-1] is not None:
497529
return [] # All columns filled; no successors
498530
else:
499-
def place(col, row):
500-
new = state[:]
501-
new[col] = row
502-
return new
503531
col = state.index(None)
504-
return [(row, place(col, row)) for row in range(self.N)
532+
return [row for row in range(self.N)
505533
if not self.conflicted(state, row, col)]
506534

535+
def result(self, state, row):
536+
"Place the next queen at the given row."
537+
col = state.index(None)
538+
new = state[:]
539+
new[col] = row
540+
return new
541+
507542
def conflicted(self, state, row, col):
508543
"Would placing a queen at (row, col) conflict with anything?"
509544
for c in range(col):
@@ -723,14 +758,15 @@ def __init__(self, problem):
723758
self.succs = self.goal_tests = self.states = 0
724759
self.found = None
725760

726-
def successor(self, state):
727-
"Return a list of (action, state) pairs reachable from this state."
728-
result = self.problem.successor(state)
729-
self.succs += 1; self.states += len(result)
730-
return result
761+
def actions(self, state):
762+
self.succs += 1
763+
return self.problem.actions(state)
764+
765+
def result(self, state, action):
766+
self.states += 1
767+
return self.problem.result(state, action)
731768

732769
def goal_test(self, state):
733-
"Return true if the state is a goal."
734770
self.goal_tests += 1
735771
result = self.problem.goal_test(state)
736772
if result:
@@ -742,7 +778,7 @@ def __getattr__(self, attr):
742778

743779
def __repr__(self):
744780
return '<%4d/%4d/%4d/%s>' % (self.succs, self.goal_tests,
745-
self.states, str(self.found)[0:4])
781+
self.states, str(self.found)[:4])
746782

747783
def compare_searchers(problems, header, searchers=[breadth_first_tree_search,
748784
breadth_first_graph_search, depth_first_graph_search,
@@ -760,11 +796,11 @@ def compare_graph_searchers():
760796
>>> compare_graph_searchers()
761797
Searcher Romania(A, B) Romania(O, N) Australia
762798
breadth_first_tree_search < 21/ 22/ 59/B> <1158/1159/3288/N> < 7/ 8/ 22/WA>
763-
breadth_first_graph_search < 10/ 19/ 26/B> < 19/ 45/ 45/N> < 5/ 8/ 16/WA>
764-
depth_first_graph_search < 9/ 15/ 23/B> < 16/ 27/ 39/N> < 4/ 7/ 13/WA>
799+
breadth_first_graph_search < 11/ 12/ 28/B> < 33/ 34/ 76/N> < 6/ 7/ 19/WA>
800+
depth_first_graph_search < 9/ 10/ 23/B> < 16/ 17/ 39/N> < 4/ 5/ 13/WA>
765801
iterative_deepening_search < 11/ 33/ 31/B> < 656/1815/1812/N> < 3/ 11/ 11/WA>
766802
depth_limited_search < 54/ 65/ 185/B> < 387/1012/1125/N> < 50/ 54/ 200/WA>
767-
astar_search < 3/ 4/ 9/B> < 8/ 10/ 22/N> < 2/ 3/ 6/WA>
803+
astar_search < 3/ 4/ 9/B> < 8/ 9/ 22/N> < 2/ 3/ 6/WA>
768804
recursive_best_first_search < 200/ 201/ 601/B> < 71/ 72/ 213/N> < 11/ 12/ 43/WA> """
769805
compare_searchers(problems=[GraphProblem('A', 'B', romania),
770806
GraphProblem('O', 'N', romania),

0 commit comments

Comments
 (0)