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:
An obvious approach would be the folllwing:
S
of four numbers: S = ( 1, 5, 6, 7 )
a
and b
from S
, remove them from S
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)c
into S
, thus obtaining S'
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With