Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to brute force arithmetic puzzle?

Tags:

algorithm

math

A friend shared this puzzle:

How to make 21 from the numbers 1, 5, 6, 7?

You can use the operations of addition, subtraction, multiplication and division, as well as brackets. You must use each number once.

I eventually solved it on paper—two days later. No doubt a computer could brute force all solutions in a second.

How though? I tried generating all strings a.b.c.d inserting the numbers for letters and operations for dots, but it missed my solution.


Bonus puzzles:

  • How to make 11 from 1,5,6,7?
  • How to make 16 from 1,5,6,7?
like image 400
Colonel Panic Avatar asked Mar 30 '15 18:03

Colonel Panic


2 Answers

An obvious approach would be the folllwing:

  1. You start with a vector S of four numbers: S = ( 1, 5, 6, 7 )
  2. Pick any two numbers a and b from S, remove them from S
  3. Apply an arbitrary arithmetic operation to a and b, thus obtaining a new number c (take care to avoid division by zero and verify exact division, if the problem requires it)
  4. Include c into S, thus obtaining S'
  5. Continue from step 1, now using S' in place of S

Brute-force branching is performed at step 2 (selecting two numbers), and step 3 (selecting operation). The cycle should be repeated until S reduces to only 1 element, which is the result for this particular brute-force branch.

Brackets are not used explicitly, but they are present implicitly.

If one branch ends with 21 in S, you have a solution, at which point you can either terminate, or continue branching to search for other solutions.

like image 183
AnT Avatar answered Nov 11 '22 05:11

AnT


Here's the "pop two, combine, recurse" algorithm as suggested by AnT, coded in Python. The hardest part was assembling the expressions after the recursion. I used find-and-replace.

#!python
import operator
import itertools
from fractions import Fraction

operations = dict()
operations['+'] = operator.add
operations['-'] = operator.sub
operations['/'] = operator.truediv
operations['*'] = operator.mul

def solve(target, numbers):
    """List ways to make target from numbers."""
    numbers = [Fraction(x) for x in numbers]
    return solve_inner(target, numbers)

def solve_inner(target, numbers):
    if len(numbers) == 1:
        if numbers[0] == target:
            yield str(target)
        return

    # combine a pair of numbers with an operation, then recurse
    for a,b in itertools.permutations(numbers, 2):
        for symbol, operation in operations.items():
            try:
                product = operation(a,b)
            except ZeroDivisionError:
                continue

            subnumbers = list(numbers)
            subnumbers.remove(a)
            subnumbers.remove(b)
            subnumbers.append(product)

            for solution in solve_inner(target, subnumbers):
                # expand product (but only once)
                yield solution.replace(str(product), "({0}{1}{2})".format(a, symbol, b), 1)

if __name__ == "__main__":
    numbers = [1, 5, 6, 7]
    target = 21
    solutions = solve(target, numbers)
    for solution in solutions:
        print("{0}={1}".format(target, solution))

Solutions to the three puzzles:

>>> solve(21,[1,5,6,7])
6/(1-(5/7))
>>> solve(11,[1,5,6,7])
(6*(7-5))-1
((7-5)*6)-1
>>> solve(16,[1,5,6,7])
[]

(The third puzzle is impossible)

like image 42
Colonel Panic Avatar answered Nov 11 '22 06:11

Colonel Panic