Writing a C++ version of the algebra game 24

前端 未结 7 1506
离开以前
离开以前 2021-01-02 12:48

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 th

相关标签:
7条回答
  • 2021-01-02 13:26

    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.

    0 讨论(0)
  • 2021-01-02 13:26

    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.

    0 讨论(0)
  • 2021-01-02 13:32

    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).

    0 讨论(0)
  • 2021-01-02 13:36

    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

    0 讨论(0)
  • 2021-01-02 13:39

    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;
      }
    }
    
    0 讨论(0)
  • 2021-01-02 13:42

    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!)

    0 讨论(0)
提交回复
热议问题