Books · The Fiddler: Solutions
Chapter 102
Can You Pour the Water?
You have two empty pitchers, one holding litres (pitcher ) and one holding litres (pitcher ). The allowed moves are to fill a pitcher to the brim from the tap, empty a pitcher completely, or pour from one pitcher into the other until either the first is empty or the second is full. Each move counts as one step. What is the fewest steps to make one pitcher hold exactly litres?
The Fiddler, Zach Wissner-Gross, March 29, 2024(original post)
Solution
A state of the system is the pair of current volumes, and every move sends one state to another, so the fewest steps to reach a is the length of the shortest path from through this graph. One route reaches it by repeatedly filling the small pitcher and tipping it into the large one, draining the large one once it overflows: which is twelve moves. That nothing shorter works is what the search settles: every state reachable in eleven moves or fewer is enumerated below, and none holds a . So the answer is steps. (Since every whole number of litres is eventually reachable; simply sits twelve moves deep.)
The computation
Run a breadth-first search from : from each state generate the six moves (fill either pitcher, empty either, pour either way) and record the first time each new state is seen. The first distance at which a state shows a is the answer.
from collections import deque
def shortest(CA, CB, target):
start = (0, 0); dist = {start: 0}; q = deque([start])
while q:
a, b = q.popleft(); d = dist[(a, b)]
t1 = min(a, CB - b); t2 = min(b, CA - a)
for s in [(CA, b), (a, CB), (0, b), (a, 0), (a - t1, b + t1), (a + t2, b - t2)]:
if s not in dist: dist[s] = d + 1; q.append(s)
return min(v for (a, b), v in dist.items() if a == target or b == target)
print(shortest(10, 3, 5)) # 12
Extra Credit
Now the pitchers hold and litres. If is the fewest steps to obtain exactly litres in one pitcher, what is the largest , and at which ?
Solution
There is no tidy formula here: pouring between a - and a -litre pitcher runs the Euclidean algorithm in slow motion, and how deep a given volume sits depends on the whole chain of subtractions. So the honest tool is the same search, now carried over every reachable state, recording for each volume the first step at which it appears and taking the worst. The deepest volume turns out to be the middling litres, exactly half the large pitcher, and it sits
The computation
The same breadth-first search as above, run to exhaustion over all states, gives the first-reached step for every volume; the maximum is , at .
from collections import deque
def all_dist(CA, CB):
dist = {(0, 0): 0}; q = deque([(0, 0)])
while q:
a, b = q.popleft(); d = dist[(a, b)]
t1 = min(a, CB - b); t2 = min(b, CA - a)
for s in [(CA, b), (a, CB), (0, b), (a, 0), (a - t1, b + t1), (a + t2, b - t2)]:
if s not in dist: dist[s] = d + 1; q.append(s)
return dist
d = all_dist(100, 93)
best = {}
for (a, b), v in d.items():
for amt in (a, b):
if amt not in best or v < best[amt]: best[amt] = v
N = max(best, key=best.get)
print(best[N], N) # 190 50