0

Problem:

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 use breadth-first search to find the minimum number of moves to go from the initial state to the goal state.

My pseudo code:

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

    add new State(a, b, c) to Queue
    pop 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, pushing new states to queue
    
    return false

My solution in C++:

#include <iostream>
#include <sstream>
#include <string>
#include <regex>
#include <queue>
#include <map>
#include <new>

using namespace std;

// Struct to represent state of water in the jugs.
struct State
{
    int a, b, c;
    int toPour;
    char from;
    char to;
    State *parent;

    State(int _a, int _b, int _c, State *_parent) : a(_a), b(_b), c(_c), toPour(0), parent(_parent) {}
    State(int _a, int _b, int _c, int _toPour, char _from, char _to, State *_parent) : a(_a), b(_b), c(_c), toPour(_toPour), from(_from), to(_to), parent(_parent) {}

    // String representation of state in tuple form.
    void to_string()
    {
        if (toPour > 0)
        {
            cout << "Pour " << toPour;
            if (toPour == 1)
            {
                cout << " gallon";
            } else
            {
                cout << " gallons";
            }
            cout << " from " << from << " to " << to;
        }
        else
        {
            cout << "Initial state";
        }
        
        cout << ". (" << a << ", " << b << ", " << c << ")" << endl;
        
        
    }
};

void demonstrate(State state) {
    if (state.parent != nullptr)
    {
        demonstrate(*(state.parent));
    }
    state.to_string();
    return;
    
}

bool BFS(int a, int b, int c, int capacity[], int goal[])
{
    map<pair<pair<int, int>, int>, int> m;
    queue<State> q;

    q.push(*(new State(a, b, c, nullptr)));

    int toPour;

    while (!q.empty())
    {

        if (q.front().a == goal[0] && q.front().b == goal[1] && q.front().c == goal[2])
        {

            demonstrate(q.front());

            return true;
        }

        if (m[make_pair(make_pair(q.front().a, q.front().b), q.front().c)] == 1)
        {
            q.pop();
            continue;
        }
        m[make_pair(make_pair(q.front().a, q.front().b), q.front().c)] = 1;

        // a -> b
        if (q.front().a > 0 && q.front().b < capacity[1])
        {
            toPour = q.front().a > (capacity[1] - q.front().b) ? (capacity[1] - q.front().b) : q.front().a;

            q.push(*(new State(q.front().a - toPour, q.front().b + toPour, q.front().c, toPour, 'A', 'B', &q.front())));
        }
        // a -> c
        if (q.front().a > 0 && q.front().c < capacity[2])
        {
            toPour = q.front().a > (capacity[2] - q.front().c) ? (capacity[2] - q.front().c) : q.front().a;

            q.push(*(new State(q.front().a - toPour, q.front().b, q.front().c + toPour, toPour, 'A', 'C', &q.front())));
        }
        // b -> c
        if (q.front().b > 0 && q.front().c < capacity[2])
        {
            toPour = q.front().b > (capacity[2] - q.front().c) ? (capacity[2] - q.front().c) : q.front().b;

            q.push(*(new State(q.front().a, q.front().b - toPour, q.front().c + toPour, toPour, 'B', 'C', &q.front())));
        }
        // b -> a
        if (q.front().b > 0 && q.front().a < capacity[0])
        {
            toPour = q.front().b > (capacity[0] - q.front().a) ? (capacity[0] - q.front().a) : q.front().b;

            q.push(*(new State(q.front().a + toPour, q.front().b - toPour, q.front().c, toPour, 'B', 'A', &q.front())));
        }
        // c -> a
        if (q.front().c > 0 && q.front().a < capacity[0])
        {
            toPour = q.front().c > (capacity[0] - q.front().a) ? (capacity[0] - q.front().a) : q.front().c;

            q.push(*(new State(q.front().a + toPour, q.front().b, q.front().c - toPour, toPour, 'C', 'A', &q.front())));
        }
        // c -> b
        if (q.front().c > 0 && q.front().b < capacity[1])
        {
            toPour = q.front().c > (capacity[1] - q.front().b) ? (capacity[1] - q.front().b) : q.front().c;

            q.push(*(new State(q.front().a, q.front().b + toPour, q.front().c - toPour, toPour, 'C', 'B', &q.front())));
        }
        q.pop();
    }

    return false;
}

int main(int argc, char **argv)
{

    if (argc != 7)
    {
        cout << "Usage: ./waterjugpuzzle <cap A> <cap B> <cap C> <goal A> <goal B> <goal C>" << endl;
        exit(0);
    }

    regex digits("\\d+");
    int *capacity = new int[3];

    for (int i = 1; i < 4; i++)
    {
        if (!regex_match(argv[i], digits) || stoi(argv[i]) == 0)
        {
            cout << "Error: Invalid capacity '" << argv[i] << "' for jug " << (char)('A' + i - 1) << "." << endl;
            exit(1);
        }
        capacity[i - 1] = stoi(argv[i]);
    }

    int *goal = new int[3];
    int totalGoal = 0;

    for (int i = 4; i < 7; i++)
    {
        if (!regex_match(argv[i], digits))
        {
            cout << "Error: Invalid goal '" << argv[i] << "' for jug " << (char)('A' + i - 4) << "." << endl;
            exit(1);
        }
        goal[i - 4] = stoi(argv[i]);

        totalGoal += stoi(argv[i]);
    }

    for (int i = 0; i < 3; i++)
    {
        if (goal[i] > capacity[i])
        {
            cout << "Error: Goal cannot exceed capacity of jug " << (char)('A' + i) << "." << endl;
            exit(2);
        }
    }

    if (totalGoal != capacity[2])
    {
        cout << "Error: Total gallons in goal state must be equal to the capacity of jug C." << endl;
        exit(3);
    }

    if (!BFS(0, 0, capacity[2], capacity, goal))
    {
        cout << "No solution." << endl;
    }

    return 0;
}

Usage: ./waterjugpuzzle <capacity A> <capacity B> <capacity C> <goal A> <goal B> <goal C>
Example:

./waterjugpuzzle 1 3 4 0 2 2
Initial state. (0, 0, 4)
Pour 3 gallons from C to B. (0, 3, 1)
Pour 1 gallon from B to A. (1, 2, 1)
Pour 1 gallon from A to C. (0, 2, 2)

Error: This solution work perfectly on MacOS, but on Ubuntu, when I try some inputs like 8 17 20 0 10 10 or 4 7 10 0 5 5, the program return Segmentation fault (core dumped) after it finds the solution and starts printing.

Ha Do Manh
  • 76
  • 8
  • 1
    If the crash is consistent with the given input, gdb should help you chew this up in a few minutes. – user4581301 Oct 17 '20 at 06:18
  • 1
    Unrelated: `q.push(*(new State(a, b, c, nullptr)));` leaks memory. Without some reference to the allocated `State` it's next to impossible to free it. – user4581301 Oct 17 '20 at 06:22
  • Looks like a `State` is freed prematurely. Probably the queue resized and invalidated the pointers to `parent`.. – user4581301 Oct 17 '20 at 06:24
  • 1
    Different behavior on different platforms suggests undefined behavior somewhere. Please concoct a [mre] so that there is less code to wade through. *(One quick simplification: don't rely on user input. Instead, set `totalGoal` and the elements of `capacity[]` and `goal[]` to hardcoded values that demonstrate the problem.)* – JaMiT Oct 17 '20 at 06:28
  • the `queue` should be built around a `deque`. Easy to invalidate iterators by adding to `deque`, but hard to invalidate pointers. it more work needed to pinpoint exactly hwere the pointer gets invalidated. – user4581301 Oct 17 '20 at 06:35
  • I get a stack overflow with `demonstrate` calling itself forever. There are only 23 states ever created but the callstack has thousands of recursive calls. Seems there's a loop in the `state.parent` list. – Retired Ninja Oct 17 '20 at 06:38
  • 3
    The raw pointers to contained elements of a dynamic container, specifically the State queue, are definitely bonkers. After refactoring that containment to use `std::shared_pointer` in both the `State::parent` and the actual queue I cannot reproduce your bug. I suggest you perhaps consider doing the same. – WhozCraig Oct 17 '20 at 06:55
  • My program has run successfully after changing to use `shared_ptr` according to @WhozCraig. But `shared_ptr` only supports **c++11** and above versions. Can you suggest me another solution? – Ha Do Manh Oct 20 '20 at 17:38
  • Nope. I really can't. That is *the* solution for proper reference counting of dynamic content. [RAII](https://en.cppreference.com/w/cpp/language/raii) is the proper modus-operandi here, and should be embraced. If you can't use `std::shared_ptr` then use `boost::shared_ptr` as an alternative. If you can't use that either you must be using a toolchain that is literally from the prior century, and I seriously suggest a technology uptick. Of course, you could always debug your raw-pointer solution as well. I just didn't take that kind of time out of my day. – WhozCraig Oct 20 '20 at 17:44

0 Answers0