Problem:

Sim’eon Denis Poisson (1781–1840), a famous French mathematician and physicist, is said to have become interested in mathematics after encountering some version of the following old puzzle: Given an 8-gallon jug full of water and two empty jugs of 5- and 3-gallon capacity, get exactly 4 gallon of water in one of the jugs by completely filling up and/or emptying jugs into others.
We will generalize this problem to work with 3 jugs of varying capacity. Let’s say that we have 3 jugs, namely A, B, and C. Jug C will always start out completely full. Furthermore, we will define a goal state where jug A will contain a gallons, B will contain b gallons, and C will contain c gallons upon completion of the search. You need to find the minimum number of moves to go from the initial state to the goal state.

Display:

$ ./waterjugpuzzle
Usage: ./waterjugpuzzle <cap A> <cap B> <cap C> <goal A> <goal B> <goal C>
$ ./waterjugpuzzle 3 5 8 0 2 6
Initial state. (0, 0, 8)
Pour 5 gallons from C to B. (0, 5, 3)
Pour 3 gallons from B to A. (3, 2, 3)
Pour 3 gallons from A to C. (0, 2, 6)
$ ./waterjugpuzzle 5 7 10 3 3 4
No solution.

Solution:

  • In this solution, we use tree data structure and BFS (breadth-first search) to solve the problem.
  • Root node is initial state.
  • Each node in the tree corresponds to the state after pouring water and has only 1 parent
  • From each node, append maximum of 6 children representing 6 different ways to pour water.
  • When a new state is created, it’s marked as visited. We also enqueue it into a queue.
  • Dequeue each node in queue and continue to append tree from that node.
  • When the goal state reached, find the path from this node to root. The path representing the steps for filling the jugs required by input.

Example:

Take a look at this example:

$ ./waterjugpuzzle 3 5 8 0 2 6
Initial state. (0, 0, 8)
Pour 5 gallons from C to B. (0, 5, 3)
Pour 3 gallons from B to A. (3, 2, 3)
Pour 3 gallons from A to C. (0, 2, 6)
  • Capacity in 3 jugs A, B, C is 3, 5, 8 gallons respectively.
  • Our goal is get exactly 2 gallons in jug B and 6 gallons in jug C.
  • We start with initial state (0, 0, 8), as root, that means
    • Jug A and jub B are empty
    • Jug C contains 8 gallons
  • Append 2 children as 2 ways to pour water. We also enqueue them:
                  Tree                        Queue
                  (0, 0, 8)                   |0, 5, 3| <
                  /       \                   |3, 0, 5|
                 /         \
          (0, 5, 3)       (3, 0, 5)
    
  • Dequeue and take (0, 5, 3) state. Keep append children from this node and enqueue new states:
                              (0, 0, 8)
                              /       \
                             /         \
                     (0, 5, 3)         (3, 0, 5)
                     /       \
                    /         \
            (3, 2, 3)         (3, 5, 0)
    
  • Keep going, and we’ll have this:
                              (0, 0, 8)
                              /       \
                             /         \
                     (0, 5, 3)         (3, 0, 5)
                     /       \                 \
                    /         \                 \
            (3, 2, 3)         (3, 5, 0)         (0, 3, 5)
             /                                  /
            /                                  /
    (0, 2, 6)                          (3, 3, 2)
    
  • We can easily see the path from root (0, 0, 8) to goal state (0, 2, 6). Those are the steps to solve the problem.

Pseudo code:

bool BFS(int a, int b, int c) 

    enqueue new State(a, b, c) 
    dequeue a state from the Queue
    
    while (queue is not empty)
        if current_state equals goal_state
            print the backtracked solution
            return true

        if current_state has already been seen
            continue

        mark current as having been visited
        try 6 ways to pour water, enqueue new states to queue
    
    return false

Note: Source code available here. Give me ⭐ if you enjoy it!