Skip to content

Instantly share code, notes, and snippets.

@rcyeh
Created January 5, 2017 02:05
Show Gist options
  • Save rcyeh/57a34db57aeae7907a77ae048727a00a to your computer and use it in GitHub Desktop.
Save rcyeh/57a34db57aeae7907a77ae048727a00a to your computer and use it in GitHub Desktop.
Water Jug Solver
"""
https://www.eecis.udel.edu/~mccoy/courses/cisc4-681.10f/lec-materials/handouts/search-water-jug-handout.pdf
Consider the following problem:
A Water Jug Problem:
You are given two jugs, a 4-gallon one and a 3-gallon
one, a pump which has unlimited water which you can use
to fill the jug, and the ground on which water may be
poured. Neither jug has any measuring markings on it.
How can you get exactly 2 gallons of water in the 4-gallon jug?
State Representation and Initial State
we will represent a state of the problem as a tuple (x, y) where
x represents the amount of water in the 4-gallon jug and
y represents the amount of water in the 3-gallon jug.
Note 0 ≤ x ≤ 4, and 0 ≤ y ≤ 3.
Our initial state: (0,0)
Goal Predicate – state = (2,y) where 0 ≤ y ≤ 3.
"""
class StateMachine:
"""Encode transition verbs from one state to another"""
def __init__(self, max_capacities):
self._max = max_capacities
def start(self):
return tuple([0 for x in self._max])
def fill(self, state, bucket):
newstate = [x if i != bucket else self._max[i] for i, x in enumerate(state)]
return tuple(newstate)
def empty(self, state, bucket):
newstate = [x if i != bucket else 0 for i, x in enumerate(state)]
return tuple(newstate)
def transfer(self, state, sourcebucket, destbucket):
newstate = [x for x in state]
transfer_amount = min(state[sourcebucket],
self._max[destbucket] - state[destbucket])
newstate[sourcebucket] -= transfer_amount
newstate[destbucket] += transfer_amount
return tuple(newstate)
def breadth_first_search(self, state, steps_from_origin):
buckets = [i for i in range(len(state))]
transitions = {}
for i in buckets:
result = self.fill(state, i)
if result != state:
transitions[tuple([x for x in steps_from_origin] + [tuple(["fill", i])])] = result
result = self.empty(state, i)
if result != state:
transitions[tuple([x for x in steps_from_origin] + [tuple(["empty", i])])] = result
for j in buckets:
if j != i:
result = self.transfer(state, i, j)
if result != state:
transitions[tuple([x for x in steps_from_origin] + [tuple(["transfer", i, j])])] = result
return transitions
if __name__ == "__main__":
# calculate adjacency lists
water_jug_state = StateMachine([4, 3])
nodes_paths = {}
start = water_jug_state.start()
nodes_paths[start] = water_jug_state.breadth_first_search(start, [tuple(["start"])])
all_reached_nodes = {x: k for v in nodes_paths.values() for k, x in v.items()}
while len(nodes_paths) < len(all_reached_nodes):
for v, k in all_reached_nodes.items():
if v not in nodes_paths:
nodes_paths[v] = water_jug_state.breadth_first_search(v, k)
all_reached_nodes = {x: k for v in nodes_paths.values() for k, x in v.items()}
reverse_nodes_paths = {}
for k, v in nodes_paths.items():
for k2, v2 in v.items():
if v2 not in reverse_nodes_paths:
reverse_nodes_paths[v2] = {k: [k2]}
elif k not in reverse_nodes_paths[v2]:
reverse_nodes_paths[v2][k] = [k2]
else:
reverse_nodes_paths[v2][k].append(k2)
print("Valid solutions")
print([k for k in reverse_nodes_paths if k[0] == 2])
for solution in [k for k in reverse_nodes_paths if k[0] == 2]:
print(reverse_nodes_paths[solution])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment