"""
A 'state tree' is (as its name implies) an object that represents a tree of
states. The tree is built by having an initial state (the 'root') and a
rule whereby child states can be reached from a parent state.
State trees are useful, for example, in solving puzzles where there are a
fixed set of moves from any given position, and a goal position which is
the solution. A couple of example puzzles are given below.
"""
from collections import deque
class StateTree(object):
"""
Representation of a tree of states.
States must be hashable (i.e., usable as dictionary keys---for example,
tuples). The initial(), reachable() and endcondtion() methods must be
subclassed. After that, the findpath() method will return a list of
states from the initial to an end state.
"""
def initial(self):
"Return the initial state."
raise NotImplementedError
def reachable(self, state):
"Yield states reachable from a given state."
raise NotImplementedError
def endcondition(self, state):
"Return whether state satisfies the end condition."
raise NotImplementedError
def findpath(self):
"Find and return shortest path from initial to end state."
# Get the initial state.
state = self.initial()
# Mapping of state to its parent, or None if initial.
parentmap = {state: None}
# Queue of states to examine.
queue = deque([state])
# Process each state in the queue.
while len(queue) > 0:
state = queue.popleft()
# Examine each new reachable state.
for child in self.reachable(state):
if child not in parentmap:
parentmap[child] = state
queue.append(child)
# If this state satisfies the end condition, return it.
if self.endcondition(state):
states = [state]
while parentmap[state] is not None:
state = parentmap[state]
states.insert(0, state)
return states
class BottlePuzzle(StateTree):
"""
There are three bottles, with capacities of 3, 5 and 8 litres. You can
pour the contents of one bottle into another until it's empty or the
other is full. How do you measure out 4 litres?
"""
# Bottle sizes.
sizes = (3, 5, 8)
# Possible pourings (from -> to).
pourings = ((0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1))
def initial(self):
return (0, 0, 8)
def reachable(self, state):
for a, b in self.pourings:
bottles = list(state)
# Get the amount poured from bottle A to bottle B.
poured = min(self.sizes[b] - bottles[b], bottles[a])
# If some was poured, yield the new state.
if poured > 0:
bottles[a] -= poured
bottles[b] += poured
yield tuple(bottles)
def endcondition(self, state):
return 4 in state
class FrogsAndToads(StateTree):
"""
The classic frogs-and-toads puzzle. An equal number of frogs and toads
face each other on a log, with a gap between them, and need to swap
places. Toads only move right, frogs only move left. A thing can move
into the gap, or jump over a different thing into the gap. How do they
do it?
"""
def initial(self):
return ('T', 'T', 'T', ' ', 'F', 'F', 'F')
def reachable(self, state):
for thing, move in (('T', 1), ('T', 2), ('F', -1), ('F', -2)):
pos = list(state)
# Find where the empty space is.
space = pos.index(' ')
# Find start position of the thing to move.
start = space - move
# If start position is out of bounds, or not of the right type,
# disallow it.
if not 0 <= start < len(pos) or pos[start] != thing:
continue
# If it's a jump, and the jumped thing isn't a different kind,
# disallow it.
if abs(move) == 2 and pos[(space + start) / 2] == thing:
continue
# Do the move and yield it.
pos[start], pos[space] = pos[space], pos[start]
yield tuple(pos)
def endcondition(self, state):
return state == ('F', 'F', 'F', ' ', 'T', 'T', 'T')
if __name__ == "__main__":
for puzzle in FrogsAndToads, BottlePuzzle:
print
print puzzle.__name__
print
for state in puzzle().findpath():
print " ", state