问题
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?
回答1:
An obvious approach would be the folllwing:
- You start with a vector
S
of four numbers:S = ( 1, 5, 6, 7 )
- Pick any two numbers
a
andb
fromS
, remove them fromS
- Apply an arbitrary arithmetic operation to
a
andb
, thus obtaining a new numberc
(take care to avoid division by zero and verify exact division, if the problem requires it) - Include
c
intoS
, thus obtainingS'
- Continue from step 1, now using
S'
in place ofS
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.
回答2:
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)
来源:https://stackoverflow.com/questions/29353375/how-to-brute-force-arithmetic-puzzle