Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

Challenge: N Jugs Problem

by Limbic~Region (Chancellor)
on Apr 14, 2009 at 18:22 UTC ( [id://757452]=perlquestion: print w/replies, xml ) Need Help??

Limbic~Region has asked for the wisdom of the Perl Monks concerning the following question:

All,
There is a common puzzle called the 3 jug problem. You have 3 jugs (X, Y and Z) and a sufficiently large supply of water (infinite). Jugs X and Y hold a fixed whole amount of water. Jug Z is sufficiently large to hold an arbitrary amount of water (infinite). The usual task is to get a target amount (T) of water in jug Z without any measuring device other than jugs X and Y.

T must be a multiple of GCD(X, Y). For GCD(X, Y) = 1, any value of T can be obtained - which is typical in such puzzles. This is all a non-straight forward way of saying that the usual problem is simple and boring.

This challenge puts further constraints on the problem and complicates it - to answer the following questions:

  • Is there a general solution for all values of X, Y and T that minimize the number of steps?*
  • Is there a general solution for all values of X, Y and T that minimize the amount of water needed?*
  • If such a general solution exists, does it still work if instead of being a 3 Jug Problem, it is extended to N jugs?

While I understand this is more of a math problem, there was sufficient interest in the chatterbox to post it. Ideas ranged from brute force, dynamic programming to towers of hanoi. Since the description in the first two paragraphs might not have made how this works clear, here is an example (which may not be optimal):

X = 3, Y = 5, T = 4 GCD(X, Y) = 1 so we know it is possible 0. Starting state (X=0, Y=0, Z=0) 1. Fill X with 3 units of water (X=3, Y=0, Z=0) 2. Pour X into Z (X=0, Y=0, Z=3) 3. Fill X with 3 units of water (X=3, Y=0, Z=3) 4. Pour X into Y (X=0, Y=3, Z=3) 5. Fill X with 3 units of water (X=3, Y=3, Z=3) 6. Pour X into Y until Y is full (X=1, Y=5, Z=3) 7. Pour X into Z (X=0, Y=5, Z=4)

* Each "step" is defined as filling a jug or pouring a jug. For the example above, there are 7 steps. The amount of water was 9. If, in a more complicated problem, water needs to be poured on to the ground - that water would need to be accounted for as well. Since I don't know the answer to the questions above, it is possible that the optimal solution for steps is not the same as for water.

Update: Second paragraph was re-worded as blokhead pointed out via /msg that I had misunderstood and mistated something

Cheers - L~R

Replies are listed 'Best First'.
Re: Challenge: N Jugs Problem
by blokhead (Monsignor) on Apr 14, 2009 at 18:58 UTC
    In fact, the amounts which are possible are exactly the multiples of gcd(X,Y). Here's a proof: Since gcd(X,Y) divides both numbers, you'll see that all of the allowed operations preserve the invariant that each jug contains an amount which is divisible by gcd(X,Y). For the other direction, if you can get gcd(X,Y) into the big jug, then you can get c*gcd(X,Y) into the big jug, by repeating the same steps c times (of course this will be far from optimal, but it demonstrates that you can at least get this amount somehow). So it suffices to show that you can get gcd(X,Y). That part uses the fact that there exist integers a,b, such that aX + bY = gcd(X,Y), and uses a and b to determine how many times to fill the X and Y jugs. This argument should generalize easily to the more general problem you mentioned.

    Update: elaboration on the above paragraph. Suppose aX + bY = gcd(X,Y). Then one of {a,b} is positive and one is non-positive. Suppose a is positive. Then here is how you get gcd(X,Y) units in the X-unit jug:

    • Do this "a" times: Fill the X-jug and dump as much as you can into the Y-unit jug.
    • If the Y-jug fills during this step, and you have dumped the Y-jug less than |b| times, then dump the Y-jug on the ground and continue to empty the X-jug into the Y-jug.
    • Once you have dumped the Y-jug |b| times, there will be aX - |b|Y = aX + bY = gcd(X,Y) left in the X-jug, which you can pour into the target jug
    </update>

    BTW, the greedy approach won't necessarily work either. The greedy approach is (assuming X>Y): fill in increments of X as many times as you can, then fill in increments of Y as many times as you can, then somehow obtain the remaining amount to be filled, if any.

    For instance, with X=5, Y=3, and target = 6, the best you can do is fill the 3-unit jug twice. The greedy algorithm would first put 5 units in the target jug, then try to get 1 unit into the target jug, which will certainly take more steps.

    In fact, if you restrict the target amount to something that is expressible as a positive linear combination of X and Y, you have an equivalent of the change-making problem, where it is known that the greedy algorithm may not always work (for the generalized problem with more than 2 jugs/denominations).

    blokhead

      blokhead,
      BTW, the greedy approach won't necessarily work either.

      There is a reason I left that on my scratch and not in this post :-) I knew if I said it was my hunch, I would immediately be proven wrong. Perhaps DP is the way to go.

      Cheers - L~R

Re: Challenge: N Jugs Problem
by kennethk (Abbot) on Apr 14, 2009 at 18:51 UTC
    <nitpick>

    If GCD(X, Y) <> 1, then any value of T can be obtained provided that T > X*Y - X - Y

    I think your condition is not stated correctly. If X = 2 and Y = 4, then no odd T is achievable. Perhaps the condition should be any T where T is a multiple of GCD(X, Y)? </nitpick> And on to the list.

    It seems like dynamic programming is the way to go for either optimization. Essentially I see it in two stages: determining algorithms for obtaining all water amounts less than the bigger jug and then application of the Knapsack problem. I'm gonna go off with a pen and paper to think about how to achieve that first part. Don't tell my boss.

      kennethk,
      Not a nitpick, it was outright wrong. I have modified the second paragraph accordingly as blokhead has also pointed this out in a /msg. I misunderstood this which talks about GCD(X, Y) = 1 but where the only operation permitted is addition.

      Cheers - L~R

Re: Challenge: N Jugs Problem
by JavaFan (Canon) on Apr 14, 2009 at 19:25 UTC
    You could consider the problem as a state machine, with an infinite amount of nodes. From each node, there are only a finite number of transitions: fill X from supply, fill Y from supply, pour X in Y, pour X in Z, pour Y in X, pour Y in Z, pour Z in X, pour Z in Y, empty X and empty Y.

    So, I'd apply Dijkstra's algorithm. Start with a queue containing a single state (X = Y = Z = 0). Now, loop and do the following:

    • Pop a state from the queue.
    • For each transition, apply the transition to the popped state, creating a new state. For each such state:
      • If the new state satisfies the condition, you're done.
      • Else, if we've seen this state before, try next transition.
      • Otherwise, put the state at the back of the queue.
    This will garantee you find a solution with the minimal number of steps needed.

    The drawback is that the algorithm will not terminate if there is no solution. Nor is it necessarely efficient.

    You can easily change the algorithm to find the solution using the smallest amount of water to keep track of the amount of water used, and keeping the queue partially ordered (a heap will do) on water usage.

      I saw your node when I was about to post a solution that uses the technique you mentioned, so I'll post it here. It saves me from explaining it :)

      Such as solution for the first problem:

      7 steps: S->X X->Z S->X X->Z Z->Y S->X X->Z

      And for the second problem

      Required supply: 6 7 steps: S->X X->Z S->X X->Z Z->Y Y->X X->Z

      Being the brute-force approach, I wrote is as a baseline for verification and benchmarking.

Re: Challenge: N Jugs Problem
by kyle (Abbot) on Apr 14, 2009 at 21:05 UTC
    • This is the same algorithm that JavaFan described (brute force).
    • It tracks water usage, if you want to optimize a solution that way, but that's commented out right now. I suspect that the shortest path solution is always the least water used solution.
    • Call it with a series of numbers on the command line. The first is the target number, and all subsequent numbers are jug capacities.
    • It can handle any number of jugs.
    • I wrote a jug object with Moose, mostly for fun.
    • Formatting courtesy of Perl::Tidy because Yours::Truly wrote some long lines.
    • I waste CPU and memory as if I'm writing in Perl or something.

    Update: After some work last night refactoring this into more objects and other fun stuff, it now can really find least water solutions, but it takes a really long time to do it.

      It tracks water usage, if you want to optimize a solution that way, but that's commented out right now. I suspect that the shortest path solution is always the least water used solution.
      I think you are wrong. I wrote my own solution (which I won't post, as several have now been posted) and found that for jugs with sizes 3 and 7, and target 11, there's a solution that requires 5 moves (fill Y, pour Y in X, pour Y in Z, fill Y, pour Y in Z), but that uses 14 water. There's a solution that requires just 11 water, but that requires 6 moves (fill Y, pour Y in X, pour Y in Z, pour X in Y, fill Y, pour Y in Z).

      Of course, my program may be wrong, and I might have missed a 5 move solution that uses just 11 water.

        My solution agrees with your findings:

        $ perl min_steps.pl 3 7 11 5 steps: S->Y Y->Z Z->X S->Y Y->Z $ perl min_supply.pl 3 7 11 Required supply: 11 6 steps: S->Y Y->Z Z->X X->S S->Y Y->Z

        I wrote my own solution (which I won't post, as several have now been posted) ...

        I'd be interested to see that.

        I did some more work on mine, and I got to where I thought it should find a minimum water solution, but it ran overnight without finishing.

Re: Challenge: N Jugs Problem
by JavaFan (Canon) on Apr 15, 2009 at 09:58 UTC
    Eric Weisstein says the problem is solvable using trilinear coordinates.

    Note that the problem he describes is somewhat more restrictive: either you have 3 jugs with certain amounts of liquid, or you have just 2 jugs and an infinite supply of liquid (so, no arbitrary large jug).

    Here is some math describing the 3 jugs problem where all jugs have finite (integer) size, and no water is to be spilled (and no infinite supply available).

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://757452]
Approved by linuxer
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (5)
As of 2024-04-25 18:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found