Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to design an algorithm to calculate countdown style maths number puzzle

I have always wanted to do this but every time I start thinking about the problem it blows my mind because of its exponential nature.

The problem solver I want to be able to understand and code is for the countdown maths problem:

Given set of number X1 to X5 calculate how they can be combined using mathematical operations to make Y. You can apply multiplication, division, addition and subtraction.

So how does 1,3,7,6,8,3 make 348?

Answer: (((8 * 7) + 3) -1) *6 = 348.

How to write an algorithm that can solve this problem? Where do you begin when trying to solve a problem like this? What important considerations do you have to think about when designing such an algorithm?

like image 457
drlobo Avatar asked Mar 08 '13 11:03

drlobo


People also ask

How does countdown math work?

The contestants have 30 seconds to work out a sequence of calculations with the numbers whose final result is as close to the target number as possible. They may use only the four basic operations of addition, subtraction, multiplication and division, and do not have to use all six numbers.

How many points is a countdown number game?

10 points are given for an exact answer, 7 points for a non-exact solution up to 5 from the target, and 5 points for a solution between 6 and 10 from the target. If neither contestant can get within 10, no points are awarded.


2 Answers

Very quick and dirty solution in Java:

public class JavaApplication1 {      public static void main(String[] args)     {         List<Integer> list = Arrays.asList(1, 3, 7, 6, 8, 3);         for (Integer integer : list) {             List<Integer> runList = new ArrayList<>(list);             runList.remove(integer);             Result result = getOperations(runList, integer, 348);             if (result.success) {                 System.out.println(integer + result.output);                 return;             }         }     }      public static class Result     {          public String output;         public boolean success;     }      public static Result getOperations(List<Integer> numbers, int midNumber, int target)     {         Result midResult = new Result();         if (midNumber == target) {             midResult.success = true;             midResult.output = "";             return midResult;         }         for (Integer number : numbers) {             List<Integer> newList = new ArrayList<Integer>(numbers);             newList.remove(number);             if (newList.isEmpty()) {                 if (midNumber - number == target) {                     midResult.success = true;                     midResult.output = "-" + number;                     return midResult;                 }                 if (midNumber + number == target) {                     midResult.success = true;                     midResult.output = "+" + number;                     return midResult;                 }                 if (midNumber * number == target) {                     midResult.success = true;                     midResult.output = "*" + number;                     return midResult;                 }                 if (midNumber / number == target) {                     midResult.success = true;                     midResult.output = "/" + number;                     return midResult;                 }                 midResult.success = false;                 midResult.output = "f" + number;                 return midResult;             } else {                 midResult = getOperations(newList, midNumber - number, target);                 if (midResult.success) {                     midResult.output = "-" + number + midResult.output;                     return midResult;                 }                 midResult = getOperations(newList, midNumber + number, target);                 if (midResult.success) {                     midResult.output = "+" + number + midResult.output;                     return midResult;                 }                 midResult = getOperations(newList, midNumber * number, target);                 if (midResult.success) {                     midResult.output = "*" + number + midResult.output;                     return midResult;                 }                 midResult = getOperations(newList, midNumber / number, target);                 if (midResult.success) {                     midResult.output = "/" + number + midResult.output;                     return midResult                 }             }          }         return midResult;     } } 

UPDATE

It's basically just simple brute force algorithm with exponential complexity. However you can gain some improvemens by leveraging some heuristic function which will help you to order sequence of numbers or(and) operations you will process in each level of getOperatiosn() function recursion.

Example of such heuristic function is for example difference between mid result and total target result.

This way however only best-case and average-case complexities get improved. Worst case complexity remains untouched.

Worst case complexity can be improved by some kind of branch cutting. I'm not sure if it's possible in this case.

like image 166
Ondrej Bozek Avatar answered Sep 19 '22 16:09

Ondrej Bozek


Sure it's exponential but it's tiny so a good (enough) naive implementation would be a good start. I suggest you drop the usual infix notation with bracketing, and use postfix, it's easier to program. You can always prettify the outputs as a separate stage.

Start by listing and evaluating all the (valid) sequences of numbers and operators. For example (in postfix):

1 3 7 6 8 3 + + + + + -> 28 1 3 7 6 8 3 + + + + - -> 26 

My Java is laughable, I don't come here to be laughed at so I'll leave coding this up to you.

To all the smart people reading this: yes, I know that for even a small problem like this there are smarter approaches which are likely to be faster, I'm just pointing OP towards an initial working solution. Someone else can write the answer with the smarter solution(s).

So, to answer your questions:

  • I begin with an algorithm that I think will lead me quickly to a working solution. In this case the obvious (to me) choice is exhaustive enumeration and testing of all possible calculations.
  • If the obvious algorithm looks unappealing for performance reasons I'll start thinking more deeply about it, recalling other algorithms that I know about which are likely to deliver better performance. I may start coding one of those first instead.
  • If I stick with the exhaustive algorithm and find that the run-time is, in practice, too long, then I might go back to the previous step and code again. But it has to be worth my while, there's a cost/benefit assessment to be made -- as long as my code can outperform Rachel Riley I'd be satisfied.
  • Important considerations include my time vs computer time, mine costs a helluva lot more.
like image 37
High Performance Mark Avatar answered Sep 19 '22 16:09

High Performance Mark