header

Water Jars

//-------------------------------------------------------------
// Water Jars
//
// There are three water jars with capacities of 3, 5, and
// 8 gallons. There are no markings of any kind on the jars
// to indicate the amount of water in the jar. Initially, the
// 8-gallon jar is full and the other two are empty. This
// program determines a sequence of transfers that will result
// in exactly 4 gallons in each of the two larger jars. A
// transfer consists of pouring the contents of one jar into
// another until the first jar is empty or the second jar is
// full.
//
// D. Searls
// Apr 2000
// Asbury College
//-------------------------------------------------------------

# include <iostream>
# include <iomanip>
# include <fstream>

using namespace std;

const int MAXSIZE = 100;

struct Tstate {
    int jar[3];        // Jar contents
};

const Tstate CAPACITY = {{8,5,3}};  // The maximum capacity of each of the three jars
const Tstate GOAL = {{4,4,0}};      // The goal state

struct Tstate_list {
    int num;               // Number of recorded states in the sequence of states
    int src[MAXSIZE];      // Source jar leading to corresponding state
    int dst[MAXSIZE];      // Destination jar in corresponding state
    Tstate state[MAXSIZE]; // Current sequence of states (where each state consists
                           // of the number of gallons in each jar)
};

// In this program it makes sense to define the following variables as globals.
// We could pass each of these as an in/out parameter to our recursive function
// but it would only make the routine less efficient; both in regard to speed
// and in regard to memory usage.

ofstream outfile;      // The output file
Tstate_list history;   // Basically, the list of states in the current path
int numSolutions;      // The number of solutions

//---------------------------------------------------------
// Equality for Tstate variables
//
// Return true if the corresponding jars in the Tstate
// structures j1 and j2 have the same contents.
//
// In Parameters: j1, j2
//---------------------------------------------------------
bool operator == (const Tstate& j1, const Tstate& j2)
{
    return (j1.jar[0] == j2.jar[0] && j1.jar[1] == j2.jar[1] && j1.jar[2] == j2.jar[2]);
}

//---------------------------------------------------------
// duplicateState
//
// This function determines whether the contents of the
// jars matches the contents of the jars at some earlier
// point in the current history. If so, this function
// returns true. Otherwise, this function returns false.
//
// In parameter: contents
//
// Global Access: history
//---------------------------------------------------------
bool duplicateState(const Tstate& contents)
{
    bool isDuplicate = false;
    int i = 0;
    while (i < history.num && !isDuplicate)
    {
        isDuplicate = (history.state[i] == contents);
        i++;
    }
    
    return isDuplicate;
}

//---------------------------------------------------------
// writeHistory
//
// Write the current history to the output file. Typically,
// this routine is invoked when a solution has been found.
//
// Global Access: history, outfile, numSolutions
//---------------------------------------------------------
void writeHistory()
{
    outfile << "SOLUTION " << numSolutions << endl << endl;
    for (int i = 0; i < history.num; i++)
    {
        if (i == 0)
        {
            outfile << "        Initial State:";
        }
        else
        {
            outfile << "Pour jar " << history.src[i]+1
                    << " into jar " << history.dst[i]+1 << ':';
        }
        outfile << setw(4) << history.state[i].jar[0]
                << setw(4) << history.state[i].jar[1]
                << setw(4) << history.state[i].jar[2]
                << endl;
    }
    outfile << endl;
    outfile << "Number of transfers: " << history.num-1 << endl;
    outfile << "   Number of states: " << history.num << endl << endl;
}

//---------------------------------------------------------
// transfer
//
// This routine pours the contents of the source jar into
// the destination jar. If that results in a state that has
// already been encountered in the current sequence of
// states, then the routine terminates. If this new state
// is the goal state then this routine will write the
// current solution by writing the sequence of states
// that led to the desired goal state. Otherwise, from this
// new state, an attempt is made to pour the contents of
// each jar into each of the other two jars to see if that
// leads to a solution. This routine is an example of a
// depth-first search algorithm. However, instead of
// quitting after finding the first solution, it goes on to
// find every solution.
//
// In parameters: contents, src, dst
//
// Global Access: outfile, history, numSolutions
//---------------------------------------------------------
void transfer(Tstate contents, int src, int dst)
{
    
    // Pour contents of src jar into dst jar
    
    if (CAPACITY.jar[dst]-contents.jar[dst] > contents.jar[src])
    {
        contents.jar[dst] = contents.jar[dst]+contents.jar[src];
        contents.jar[src] = 0;
    }
    else
    {
        contents.jar[src] = contents.jar[src] - (CAPACITY.jar[dst]-contents.jar[dst]);
        contents.jar[dst] = CAPACITY.jar[dst];
    }
    
    // If this is not a duplicate then proceed
    
    if (!duplicateState(contents))
    {
        
        // Add new state to history list
        
        history.src[history.num] = src;
        history.dst[history.num] = dst;
        for (int i = 0; i < 3; i++)
        {
            history.state[history.num].jar[i] = contents.jar[i];
        }
        history.num++;
               
        // If we have reached the termination condition, write the history
        
        if (contents == GOAL)
        {
            numSolutions++;
            writeHistory();
            cout << "Solution " << numSolutions << endl;
        }
        
        // Otherwise, keep pouring
        
        else
        {
            transfer(contents, 0, 1);
            transfer(contents, 0, 2);
            transfer(contents, 1, 0);
            transfer(contents, 1, 2);
            transfer(contents, 2, 0);
            transfer(contents, 2, 1);
        }
        
        // We've tried every possible move from the current state so
        // back up to the previous state.
        
        history.num--;
    }
}

//**************************************
//         M A I N   D R I V E R        
//**************************************

int main()
{
    Tstate contents = {{8, 0, 0}};  // Initial contents of the jars
    history.num = 0;
    numSolutions = 0;
    
    outfile.open("output.txt");
    if (!outfile.fail())
    {
        transfer(contents, 0, 0);
        outfile.close();
        cout << "Output can be found in \"output.txt\"\n\n";
    }
    else
    {
        cout << "Could not open output file!\n\n";
    }
    
    return 0;
}