The card game "24" is widely used in elementary schools to help students learn arithmetic facts and problem solving skills. If you aren't familiar with the game, take a few minutes to read over the tutorial page and watch the instructional video.
Making students play this game is cruel. Why should they be forced to struggle with these cards when a computer could find the solutions almost instantly?
Your goal for this project is prevent untold suffering among elementary school students by programming a recursive 24 solver.
You must complete the implementation of the solve
method
in TwentyFourSolver.java so that it conforms to the
provided JavaDoc comments. You must also implement an application
named TwentyFour.java
that makes it possible to access
your solver from the command-line. The first argument to the
application will represent the target value and the remaining
arguments will be numbers that should be used to construct the
solution. Your application should work correctly even if the target
value is not 24. It should also work correctly regardless of
how many numbers are provided.
Here are some examples of what it should look like to interact with the completed application.
$ java TwentyFour 24 3 5 6 1 6 * 3 = 18 18 + 1 = 19 19 + 5 = 24 $
$ java TwentyFour 17 3 5 6 1 3 - 1 = 2 2 * 6 = 12 12 + 5 = 17
$ java TwentyFour 17 3 5 6 7 1 1 + 6 = 7 7 - 5 = 2 2 * 7 = 14 14 + 3 = 17
$ java TwentyFour 24 3 3 4 10 No solution exists! $
$ java TwentyFour 24 Not enough arguments. Usage: TwentyFour TARGET NUM1... $ java TwentyFour Not enough arguments. Usage: TwentyFour TARGET NUM1... $
$ java TwentyFour 24.0 3 3 4 10 Badly formatted argument. Usage: TwentyFour TARGET NUM1... $ java TwentyFour 24 q 3 4 10 Badly formatted argument. Usage: TwentyFour TARGET NUM1... $
The output of your application should exactly match the formatting in the examples above. Note that many 24 cards have multiple solutions. Where multiple solutions exist, any correct solution is acceptable.
It will be your responsibility to develop a correct recursive algorithm. The example below is designed to get you thinking in the right direction.
We can think of the 24 problem as filling in the boxes in a diagram like the following:
The goal is to fill each of the green boxes with one of the four numbers from the card, and each of the blue boxes with an arithmetic operator. In this case, the following solution would work:
There are many possible strategies for solving this problem. Let's consider the approach of working backwards from 24. Assuming we are working with the numbers 2, 2, 7, and 10, we might first try filling the last two boxes with "×" and "2". These choices may not be correct, but we can try them out to see where they takes us:
Once we've decided to end with "× 2", only one possible value will work for B: it must be 12. This leaves us in this position:
We can continue to work backwards by placing "+" and "7" in the second line:
If this is going to work, then "A" must be equal to 5:
Continuing to work backwards, let's try placing "÷" and "10":
For this to work the final box would need to contain 50, but all we have left is a 2. We must have made a mistake somewhere earlier in the process. Let's back up and try "÷" and "2" instead:
At this point, we know that the final box must contain 10, and our last number is 10. Success!
You must submit your completed code through Web-CAT by the project deadline. The scoring for this assignment will be broken into two components:
As I was writing the submission tests for this assignment, I realized I made two hasty assumptions about allowable solutions for the 24 problem. Based on the single example from the tutorial page, I assumed that:
Assumption 1 would disallow a solution like this:
Assumption 2 would disallow a solution like this:
As it turns out, the rules of the game allow for either of these solutions to be used.
Dropping assumption 1 wouldn't cause much difficulty for our "working backwards from 24" strategy: we could just try both possible operand orderings as we work our way toward a solution.
Unfortunately, dropping assumption 2 makes the problem significantly more difficult. The strategy of working backwards from 24 by picking numbers from the card doesn't work if we need to perform operations that don't use any numbers from the card.
For the purpose of this assignment, your code is only required to recognize solutions that satisfy assumptions 1 and 2 above. (If you want to take the assignment further, you are welcome to develop a more general solver.)
You may want to warm up for this assignment by completing the CodingBat exercises groupSum and groupSum6. These problems are different from 24, but they may get you thinking along the right lines.
There is a page of solutions to the 24 game that may be useful for constructing tests.
One odd aspect of this assignment is that we are working with integer values, but storing them as doubles. This is necessary because integer arithmetic in Java would lead us to invalid solutions: we don't want a solution that includes the step "5 / 2 = 2".
There are several possible approaches to this problem. The simplest solution is to use the mod operator:
if ((num % 1) == 0) { System.out.println("It's an int!"); }
If there is no remainder when we divide by 1, then we know the number must have been an integer.
Even if a double contains an integer value, by default it will be printed with a trailing ".0". This code:
double a = 3; System.out.println(a);
Will print "3.0".
Again, there are multiple ways we could address this problem. One possibility is to cast to an int before printing. Other possibilities include using System.out.printf or String.format.