2

I am trying to create a bear game where the user enters a number (n), and the program will see if it can reach the number 42 by doing some specified steps. If it can't, then it notifies the user that number can't reach the goal.

These are the rules:

  1. If n is even, then you may give back exactly n/2 bears.
  2. If n is divisible by 3 or 4, then you may multiply the last two digits of n and give back this many bears.
  3. If n is divisible by 5, then you may give back exactly 42 bears.

Here's an example:

  • Start with 250 bears
  • Since 250 is divisible by 5, you may return 42 of the bears, leaving you with 208 bears.
  • Since 208 is even, you may return half of the bears, leaving you with 104 bears.
  • Since 104 is even, you may return half of the bears, leaving you with 52 bears.
  • Since 52 is divisible by 4, you may multiply the last two digits (resulting in 10) and return these 10 bears. This leaves you with 42 bears.
  • You have reached the goal!

Here's what I have so far:

#include <iostream>

using namespace std;

bool bears(int n);

int main(){
    int number;

    do{
        cout<<"enter the amount of bears (press 0 to stop the program): ";
        cin>>number;
        if (bears(number)){
            cout<<"you have reached the goal!"<<endl;
        }

        else{
            cout<<"sorry, you have not reached the goal."<<endl;
        }

    }while(number != 0);
}

bool bears(int n){
    if (n < 42){
        return false;
    }

    else if (n == 42){
        return true;
    }

    else{
        if (n % 5 == 0){
            return bears(n - 42);
        }

        else if(n % 2 == 0){
            return bears(n / 2);
        }

         else if(n % 4 == 0|| n % 3 == 0)
        {
            int one;
            int two;
            one=n%10;
            two=(n%100)/10;
            return bears(n - one * two);
        }  
    }
}

My program has the basic rules, but when I type in 250 bears it says it can't reach the goal. I understand what's happening in the code and why it can't reach the specified goal, but how do I make it universal so it'll work not just for the number 250, but for other numbers like: 84.

esam
  • 580
  • 3
  • 13
user2896120
  • 3,180
  • 4
  • 40
  • 100
  • 1
    All the recursive calls need to be `return bears(...);`. – Barmar Nov 06 '16 at 21:17
  • 1
    @Barmar My question is nothing like that question that you have pointed to. Also, returning does not fix the problem. The problem is the same even with the return statement added. – user2896120 Nov 06 '16 at 21:21
  • 1
    Read the question and you will know this is not an exact duplicate.... – user2896120 Nov 06 '16 at 21:24
  • Step through the program in the debugger, that should pinpoint the problem. – Barmar Nov 06 '16 at 21:25
  • That was definitely the problem in the code you originally posted. Now that you've edited it, I've voted to reopen. – Barmar Nov 06 '16 at 21:26
  • @Barmar As I have stated in the question, I know what the problem is. I know why it's saying it can't reach the goal. What I am saying is how can I make it more versatile, so that it knows which rule to go to. For example, even though 52 is even, instead of going through the even else if statement, I divide by 4 instead. – user2896120 Nov 06 '16 at 21:28
  • Just change the order of the tests, so you check `if (n % 4 == 0)` before you check `if (n % 2 == 0)` – Barmar Nov 06 '16 at 21:29
  • @Barmar But if I do that, then when it's 208, it's divisible by 4 so when you multiply 0 and 8, 0 bears are returned, causing an infinite loop. But I could skip that else if statement if the last two numbers are 0. If they are, I could go to the even else if statement and that should work – user2896120 Nov 06 '16 at 21:35
  • The mod 4 in your final else if statement is useless because any number that would be divisible by 4 would also be divisible by 2, therefore being caught in the previous condition – Nick Zuber Nov 06 '16 at 22:06
  • @user2896120 That seems like a problem with the rules. It doesn't say what to do when multiple criteria match, like multiples of 10 fit both rule 1 and rule 2. – Barmar Nov 06 '16 at 22:08
  • Like you already pointed out, the issue is that you are only considering the first matching rule instead of all of the fitting ones. To change that you can introduce a vector with all the possible number of bears after applying a fitting rule. Then you iterate over that vector doing the same. In the end you return true if you find at least one sequence leading to 42 bears. – Corristo Nov 06 '16 at 22:21
  • 1
    This is potentially a complicated problem. You need an intelligent way of choosing the rules at each step. May be for a given number if you apply rule 1 in the first step, you go to a path that you can never get to 42, but if you start with rule 3 there is a path. I would suggest add the label algorithm to this and discuss it there. This is an algorithm design problem not a C++ or recursion question. – esam Nov 06 '16 at 22:24
  • I added the label. It is pending peer review. – esam Nov 06 '16 at 22:29

2 Answers2

5

@Corristo's answer is good, but a similar depth first search algorithm can be used with minimal changes to your code. I've removed the redundant ifs and elses (since we're returning in every condition).

What this function does is instead of using a greedy approach like your solution does, it tests all cases till it finds an answer. Your code tests the second condition only if the first condition (divisibility by 5) is not satisfied, and so on.

bool bears(int n){
    if (n < 42)
        return false;

    if (n == 42)
        return true;

    // Moves on if condition isn't satisfied
    if ((n % 5 == 0) && bears(n - 42)) return true;

    if ((n % 2 == 0) && bears(n / 2)) return true;

    if(n % 4 == 0|| n % 3 == 0)
    {
        int one;
        int two;
        one=n%10;
        two=(n%100)/10;
        return one * two != 0 && bears(n - one * two);
    }  

    return false;
}

You can try optimizing it further by using a cache (std::map) where you store the results and return the stored result if you have computed it before, but it'll only help in very few cases.

EvilTak
  • 7,091
  • 27
  • 36
1

Since we don't have any a priori knowledge about which rule we need to choose for a particular number, the easiest way is to try all of them by exploring the entire state space. You can think of it as a reachability problem in an implicitly given graph.

One way to solve such a reachability problem is by using breath-first or depth-first search, which can be implemented recursively.

For your game this can be done by modifying your bears function to take a std::vector of integers for which it checks if it contains at least one number from which 42 can be reached.

With a depth-first search it might look as follows:

bool bears(std::vector<int> ns) {
    // first remove all numbers < 42 since the goal cannot be reached from them
    ns.erase(std::remove_if(std::begin(ns), std::end(ns),
                            [] (auto const& n) { return n < 42; }),
             std::end(ns));

    if (ns.empty()) {
        return false;
    } else  if (std::any_of(std::cbegin(ns),
                            std::cend(ns),
                            [] (auto const& n) { return n == 42; })) {
        return true;
    } else {
       auto possible_number_of_bears = std::vector<std::vector<int>>{};
       std::transform(std::cbegin(ns),
                      std::cend(ns),
                      std::back_inserter(possible_number_of_bears),
                      [] (auto const& n) {
                            auto after_possible_rules = std::vector<int>{};
                            if (n % 5 == 0) {
                                after_possible_rules.emplace_back(n - 42);
                            }

                            if (n % 2 == 0) {
                                after_possible_rules.emplace_back(n / 2);
                            }

                            if (n % 4 == 0 || n % 3 == 0) {
                                int one;
                                int two;
                                one = n % 10;
                                two = (n % 100) / 10;
                                // avoid infinite recursion by only adding
                                // the new number if it is different from n
                                if (one * two != 0) {
                                    after_possible_rules.emplace_back(n - one * two);
                                }
                            }
                            return after_possible_rules; });
        return std::any_of(std::cbegin(possible_number_of_bears),
                           std::cend(possible_number_of_bears),
                           bears);
    }
}

Now you only need to adjust the calling code to

 if (bears({number})) {
     std::cout << "you have reached the goal!" << std::endl;
 } else {
     std::cout << "sorry, you have not reached the goal." << std::endl;
 }

and modify the forward declaration of bears accordingly.

Corristo
  • 4,911
  • 1
  • 20
  • 36
  • What's the reason for making `bears` take a vector? It seems to me that the code would be a *lot* simpler if it took a single number instead. – interjay Nov 07 '16 at 00:18
  • @interjay Maybe that is because I come from a functional programming background, but I tend to model non-determinism by using vectors. The only other alternative that comes to my mind is to implement a "real" DFS/BFS which would need a stack/queue, I don't think that is much simpler. So if you have an implementation that takes a single number *and* is simpler in mind, please go ahead and post it as an answer. – Corristo Nov 07 '16 at 06:35
  • If you think of the problem in terms of mathematical sets of reachable numbers, I think this is actually quite intuitive. You start with a set containing the single element the user provided, and then in each iteration a single number in the set of numbers you already found to be reachable gets mapped to the set of possible successors (hence you end up with a set of sets, or a vector of vectors). Since `std::vector` is superior to `std::set` performancewise in almost all cases I use `vector` when I didn't do additional measurements. – Corristo Nov 07 '16 at 06:48
  • Using a vector to model non-determinism is fine, but it can be a lot simpler. Just have the function take a single `int` parameter, create your `after_possible_rules` vector based on it, and then call `std::any_of` on that. Adding an additional vector on top of that significantly complicates the code for no benefit, and makes it more difficult to add memoization. – interjay Nov 07 '16 at 10:29
  • @interjay If you do that you get a BFS instead of a DFS :) Having seen the solution of EvilTak I agree that mine is overly complicated, but I'll leave it here as it is easily modifyable to search breadth-first which might be useful for other similar cases, e.g. if you also want to output a shortest certificate for the found solution. – Corristo Nov 07 '16 at 10:39
  • It would still be a DFS. For a BFS you need a FIFO queue, so it can't be implemented recursively like DFS (of course you can implement a loop using tail recursion and pass in the queue as a parameter, but that's not really using the power of recursion like DFS). – interjay Nov 07 '16 at 12:19