# Notes from Wednesday, November 8
# A general purpose program for heuristic state-space search
#-----------------------------------------------------------------------------
# The Node class is used to represent nodes of the search tree. Each node
# contains a state representation, a reference to the node's parent node, a
# string describing the action that generated the node's state from the parent
# state, the number of steps from the start node to this node, and the heuristic
# estimate of the distance remaining from this node to the nearest goal.
class Node:
def __init__(self, state, parent, action):
self.state = state
self.parent = parent
self.action = action
self.steps = 0
self.estimate = 0
def cost(self):
return self.steps + self.estimate
def expand(self):
successors = []
for (action, newState) in self.state.applyOperators():
newNode = Node(newState, self, action)
successors.append(newNode)
return successors
#-----------------------------------------------------------------------------
# search keeps track of all unexamined nodes in an ordered list called the
# fringe. It repeatedly picks the next node with the lowest estimated cost
# (i.e., distance from start node + estimated distance remaining to the goal),
# and checks to see if it represents the goal. If so, it prints the path from
# the start node to the goal node and stops. Otherwise, it adds all of the
# successors of the node to the fringe, remembers the state just examined in
# order to avoid examining it again, and repeats the cycle. The search fails
# if it runs out of nodes to examine before reaching a goal.
def search(initialState, goalState, heuristic):
fringe = PriorityQueue()
start = Node(initialState, None, None)
start.estimate = heuristic(initialState, goalState)
fringe.insert(start)
oldStates = []
while not fringe.empty():
current = fringe.remove()
if current.state == goalState:
print 'Found a solution...'
showSolution(current)
return
elif current.state not in oldStates:
for successor in current.expand():
successor.steps = current.steps + 1
successor.estimate = heuristic(successor.state, goalState)
fringe.insert(successor)
oldStates.append(current.state)
print 'Search failed'
# showSolution prints out the sequence of states and actions used to reach
# the goal, by following the chain of parent references backwards from the
# goal node to the start node.
def showSolution(node):
numSteps = node.steps
path = []
# build up path in reverse
while node is not None:
path.insert(0, node)
node = node.parent
print path[0].state
for n in path[1:]:
print '%s\n%s' % (n.action, n.state)
print 'Solution took %d steps' % numSteps
#-----------------------------------------------------------------------------
# A simple implementation of a priority queue as an ordered list. Elements in
# a priority queue are stored in ascending order by their cost value.
# q.empty() returns True if q is empty
# q.insert(x) inserts x into q according to the cost of x
# q.remove() removes and returns the lowest-cost element from q
class PriorityQueue:
def __init__(self):
self.contents = []
def __str__(self):
return '%s' % self.contents
def empty(self):
return len(self.contents) == 0
def insert(self, new):
for i in range(len(self.contents)):
if new.cost() < self.contents[i].cost():
# insert element at position i
self.contents.insert(i, new)
return
# all elements are bigger, so add it to the end
self.contents.append(new)
def remove(self):
if self.empty():
return None
else:
# remove element at position 0
return self.contents.pop(0)