views:

357

answers:

7

I am trying to write a C++ program that works like the game 24. For those who don't know how it is played, basically you try to find any way that 4 numbers can total 24 through the four algebraic operators of +, -, /, *, and parenthesis.

As an example, say someone inputs 2,3,1,5 ((2+3)*5) - 1 = 24

It was relatively simple to code the function to determine if three numbers can make 24 because of the limited number of positions for parenthesis, but I can not figure how code it efficiently when four variables are entered.

UPDATE:
I have some permutations working now but I still cannot enumerate all cases because I don't know how to code for the cases where the operations are the same.

Also, what is the easiest way to calculate the RPN? I came across many pages such as this one: http://www.dreamincode.net/forums/index.php?showtopic=15406 but as a beginner, I am not sure how to implement it.

Thanks Again!

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;





bool MakeSum(int num1, int num2, int num3, int num4)
{

  vector<int> vi;
  vi.push_back(num1);
  vi.push_back(num2);
  vi.push_back(num3);
  vi.push_back(num4);

  sort(vi.begin(),vi.end());


  char a1 = '+';
  char a2 = '-';
  char a3 = '*';
  char a4 = '/';
  vector<char> va;
  va.push_back(a1);
  va.push_back(a2);
  va.push_back(a3);
  va.push_back(a4);

  sort(va.begin(),va.end());
  while(next_permutation(vi.begin(),vi.end()))
    {

      while(next_permutation(va.begin(),va.end()))
    {

      cout<<vi[0]<<vi[1]<<vi[2]<<vi[3]<< va[0]<<va[1]<<va[2]<<endl;

      cout<<vi[0]<<vi[1]<<vi[2]<<va[0]<< vi[3]<<va[1]<<va[2]<<endl;

      cout<<vi[0]<<vi[1]<<vi[2]<<va[0]<< va[1]<<vi[3]<<va[2]<<endl;

      cout<<vi[0]<<vi[1]<<va[0]<<vi[2]<< vi[3]<<va[1]<<va[2]<<endl;

      cout<<vi[0]<<vi[1]<<va[0]<<vi[2]<< va[1]<<vi[3]<<va[2]<<endl; 


    }

    }

  return 0;

}

int main()
{

  MakeSum(5,7,2,1);
  return 0;
}
A: 

i wrote something like this before. You need a recursive evaluator. Call evaluate, when you hit "(" call evaluate again otherwise run along with digits and operators till you hit ")", now return the result of the -+*/ operations the the evaluate instance above you

pm100
I think (s)he's trying to generate all possible results... I'm not sure.
Ninefingers
I don't need to know all possible totals, just if one of them equals 24.
hahuang65
@ ninefingers can i have my down vote back then :-(
pm100
@pm100: The problem is generating all possible expressions. The problem is not how to evaluate a particular expression. I am also giving this a -1.
Moron
yup - i do indeed suck. I thought we wanted to evaluate a guess supplied by a user.
pm100
A: 

Look up the Knapsack problem (here's a link to get you started: http://en.wikipedia.org/wiki/Knapsack_problem), this problem is pretty close to that, just a little harder (and the Knapsack problem is NP-complete!)

miked
-1: Seems totally irrelevant.
Moron
Did you even go to the link? From the wikipedia page: "The knapsack problem or rucksack problem is a problem in combinatorial optimization: Given a set of items, each with a weight and a value, determine the number of each item to include in a collection so that the total weight is less than a given limit and the total value is as large as possible."I could re write that as "Given a set of numbers, each with a value, determine the algebraic operation and order of operations to make the result equal to 24."Those problems are almost the same!
miked
Generic knapsack solves for weights, and variable amounts of each item, which make it significantly more complicated than this game.
Tanzelax
A: 

One thing that might make this faster than normal is parallelisation. Check out OpenMP. Using this, more than one check is carried out at once (your "alg" function) thus if you have a dual/quad core cpu, your program should be faster.

That said, if as suggested above the problem is NP-complete, it'll be faster, not necessarily fast.

Ninefingers
+5  A: 

You can just use Reverse Polish Notation to generate the possible expressions, which should remove the need for parantheses.

An absolutely naive way to do this would be to generate all possible strings of 4 digits and 3 operators (paying no heed to validity as an RPN), assume it is in RPN and try to evaluate it. You will hit some error cases (as in invalid RPN strings). The total number of possibilities (if I calculated correctly) is ~50,000.

A more clever way should get it down to ~7500 I believe (64*24*5 to be exact): Generate a permutation of the digits (24 ways), generate a triplet of 3 operators (4^3 = 64 ways) and now place the operators among the digits to make it valid RPN(there are 5 ways, see Omnifarious' answer).

You should be able to find permutation generators and RPN calculators easily on the web.

Hope that helps!

PS: Just FYI: RPN is nothing but the postorder traversal of the corresponding expression tree, and for d digits, the number is d! * 4^(d-1) * Choose(2(d-1), (d-1))/d. (The last term is a catalan number).

Moron
This is basically a nice and simple way of managing my idea of a tree. I like it. The rule is that the number of digits must always exceed the number of operators as the expression is read from left to right. So with that rule you might be able to avoid generating any error cases.
Omnifarious
+1  A: 

Edited: The solution below is wrong. We also need to consider the numbers makeable with just x_2 and x_4, and with just x_1 and x_4. This approach can still work, but it's going to be rather more complex (and even less efficient). Sorry...


Suppose we have four numbers x_1, x_2, x_3, x_4. Write

S = { all numbers we can make just using x_3, x_4 },

Then we can rewrite the set we're interested in, which I'll call

T = { all numbers we can make using x_1, x_2, x_3, x_4 }

as

T = { all numbers we can make using x_1, x_2 and some s from S }.

So an algorithm is to generate all possible numbers in S, then use each number s in S in turn to generate part of T. (This will generalise fairly easily to n numbers instead of just 4).

Here's a rough, untested code example:

#include <set> // we can use std::set to store integers without duplication
#include <vector> // we might want duplication in the inputs

// the 2-number special case
std::set<int> all_combinations_from_pair(int a, int b)
{
  std::set results;

  // here we just use brute force
  results.insert(a+b);  // = b+a
  results.insert(a-b);
  results.insert(b-a);
  results.insert(a*b);  // = b*a
  // need to make sure it divides exactly
  if (a%b==0) results.insert(a/b);
  if (b%a==0) results.insert(b/a);

  return results;   
}

// the general case
std::set<int> all_combinations_from(std::vector<int> inputs)
{
  if (inputs.size() == 2) 
  {
    return all_combinations_from_pair(inputs[0], inputs[1]);
  }
  else
  {
    std::set<int> S = all_combinations_from_pair(inputs[0], inputs[1]);
    std::set<int> T;
    std::set<int> rest = S;
    rest.remove(rest.begin());
    rest.remove(rest.begin()); // gets rid of first two

    for (std::set<int>.iterator i = S.begin(); i < S.end(); i++)
    {
      std::set<int> new_inputs = S;
      new_inputs.insert(*i);
      std::set<int> new_outputs = all_combinations_from(new_inputs);

      for (std::set<int>.iterator j = new_outputs.begin(); j < new_outputs.end(); j++)
        T.insert(*j); // I'm sure you can do this with set_union()
    }

    return T;
  }
}
Tom Smith
The tree/RPN approaches by Moron and Omnifarious are a much more sensible way to go.
Tom Smith
+6  A: 

So, the simple way is to permute through all possible combinations. This is slightly tricky, the order of the numbers can be important, and certainly the order of operations is.

One observation is that you are trying to generate all possible expression trees with certain properties. One property is that the tree will always have exactly 4 leaves. This means the tree will also always have exactly 3 internal nodes. There are only 3 possible shapes for such a tree:

  A
 / \
 N  A
   / \      (and the mirror image)
  N   A
     / \
    N   N

  A
 / \
N   A
   / \
  A   N   (and the mirror image)
 / \
N   N

     A
   /` `\
  A     A
 / \   / \
N  N  N  N

In each spot for A you can have any one of the 4 operations. In each spot for N you can have any one of the numbers. But each number can only appear for one N.

Coding this as a brute force search shouldn't be too hard, and I think that after you have things done this way it will become easier to think about optimizations.

For example, + and * are commutative. This means that mirrors that flip the left and right children of those operations will have no effect. It might be possible to cut down searching through all such flips.

Someone else mentioned RPN notation. The trees directly map to this. Here is a list of all possible trees in RPN:

N N N N A A A
N N N A N A A
N N N A A N A
N N A N N A A
N N A N A N A

That's 4*3*2 = 24 possibilities for numbers, 4*4*4 = 64 possibilities for operations, 24 * 64 * 5 = 7680 total possibilities for a given set of 4 numbers. Easily countable and can be evaluated in a tiny fraction of a second on a modern system. Heck, even in basic on my old Atari 8 bit I bet this problem would only take minutes for a given group of 4 numbers.

Omnifarious
+1: for enumerating RPN possibilites and mapping to tree.
Moron
Alright, I think I'll start working with this suggestion since it was well explained and a few other people seem to like the RPN approach. This is only my second homework assignment in my Intro to Software engineering class, so its taking me some time to catch up with what everyone's been suggesting. :)
hahuang65
@hahuang65: I have code I just wrote based on this approach, and it works for several cases I tried. :-)
Omnifarious
I'm trying to write the code but I am wondering if you used some sort of function to calculate RPN, if you somehow generated a bunch of strings and then evaluated them, or if you structured your loops such that a new value is generated and tested each time. Thanks so much for what you have already helped me with!
hahuang65
@hahuang65: I generated the RPN and calculated it. My code is kind of ugly. :-)
Omnifarious
Alright I'll keep at this. I was just wondering because in class today my professor said his solution was only about 20 lines of code so I want to make sure I'm not killing myself here. But it is a learning experience after all.
hahuang65
@hahuang65, it strikes me as one of those kinds of problems that once you have some critical insight it's trivial. My code is about 150 lines or so.
Omnifarious
So if I'm thinking of this correctly I'll have 6 main loops that test each of those possible RPN trees. Inside those I'll the loops that run through the permutations for the number positions, and inside those the operator positions? And then somehow calculate 24 using RPN math? I've had enough of this problem for today though so maybe I'll have more luck tomorrow morning. Thanks again for all your help and continuing to respond to me!
hahuang65
@hahuang65: If you have switching among the RPN trees at the top level, you then have 5 main loops as there are only 5 different trees.
Omnifarious
@Omnifarious: Can you take a look at the code that I posted above and give me some feedback? Is this the right track?
hahuang65
+1  A: 

If you are allowed to use the same operator twice, you probably don't want to mix the operators into the numbers. Instead, perhaps use three 0's as a placeholder for where operations will occur (none of the 4 numbers are 0, right?) and use another structure to determine which operations will be used.

The second structure could be a vector<int> initialized with three 1's followed by three 0's. The 0's correspond to the 0's in the number vector. If a 0 is preceded by zero 1's, the corresponding operation is +, if preceded by one 1, it's -, etc. For example:

6807900 <= equation of form ( 6 @ 8 ) @ ( 7 @ 9 )
100110 <= replace @'s with (-,-,/)
possibility is (6-8)-(7/9)

Advance through the operation possibilities using next_permutation in an inner loop.

By the way, you can also return early if the number-permutation is an invalid postfix expression. All permutations of the above example less than 6708090 are invalid, and all greater are valid, so you could start with 9876000 and work your way down with prev_permutation.

Potatoswatter