Reputation: 953
This function takes a string of digits and a target integer and prints all possible ways to insert operators to reach the desired target. The output must in all cases contain all the digits in order; none may be omitted.
#!/usr/bin/env python3
def find_expressions(digits, target):
if not (isinstance(digits, str) or isinstance(digits, list)) or not isinstance(target, int):
raise TypeError
if len(digits) != 5:
raise TypeError('digits must be of length 5')
solutions = []
operators = ('', ' + ', ' - ', ' * ', ' / ')
for i in operators:
for j in operators:
for k in operators:
for l in operators:
s = digits[0] + i + digits[1] + j + digits[2] + k + digits[3] + l + digits[4]
try:
if eval(s) == target:
solutions.append(s + ' == {}'.format(target))
except (ZeroDivisionError, SyntaxError):
pass
print('\n'.join(solutions))
It isn't pretty, but it works. The problem is that it only takes strings of length 5. How can I make it take strings of arbitrary length? (E.g., calling find_expressions('12345678', 2)
should be valid.) I'm thinking I need to replace the for
loops with recursion, but I'm at a loss as to how to accomplish this.
Calling find_expressions('75228', 5)
prints:
7 + 5 + 2 / 2 - 8 == 5
7 * 5 - 22 - 8 == 5
7 * 5 - 2 - 28 == 5
I'm not satisfied with the whole brute-force approach I'm taking by looping over every possibility. If there's a better algorithm overall, I'd like to hear about it. However, this question is about making this function take arbitrary-length input while making the smallest possible changes.
Upvotes: 1
Views: 136
Reputation: 287815
Use itertools.product
:
import itertools
def find_expressions(digits, target):
if not (isinstance(digits, str) or isinstance(digits, list)) or not isinstance(target, int):
raise TypeError
solutions = []
operators = ('', ' + ', ' - ', ' * ', ' / ')
for ops in itertools.product(operators, repeat=len(digits)-1):
s = digits[0] + ''.join(op + digit for op, digit in zip(ops, digits[1:]))
try:
if eval(s) == target:
solutions.append(s + ' == {}'.format(target))
except (ZeroDivisionError, SyntaxError):
pass
print('\n'.join(solutions))
Upvotes: 5
Reputation: 22457
The trick is to get all possible permutations of the sequence +-*/
– including duplicates, so the first sequence needs to be ++++
; its length should be len(digits)-1
, because for n digits, you need n - 1
operators in between.
The total number of permutations is operators ** positions
; that is, every operator can appear in every single position. I don't see any handy shortcut for not actually testing all possibilities.
The following is loosely based on Get all 4-character combinations of a given alphabet; without the interjection of the digits and with the # print ..
lines enabled, you will see it shows the entire list.
If your input may or may not be strings, use str(digits[x])
so the string concatenation does not barf.
The following code
def find_expressions(digits, target):
if not (isinstance(digits, str) or isinstance(digits, list)) or not isinstance(target, int):
raise TypeError
solutions = []
operators = ('', ' + ', ' - ', ' * ', ' / ')
used = [0] * (len(digits)-1)
for i in range(len(operators)**(len(digits)-1)):
attempt = str(digits[0])
for j in range(1,len(digits)):
attempt += operators[used[j-1]] + str(digits[j])
# print (operators[used[j-1]], end="")
# print ()
for j in range(len(digits)-1):
used[j] += 1
if used[j] >= len(operators):
used[j] = 0
else:
break
try:
if eval(attempt) == target:
print (attempt, '==', eval(attempt))
except (ZeroDivisionError, SyntaxError):
pass
find_expressions ([7,5,2,2,8], 5)
shows
7 * 5 - 2 - 28 == 5
7 * 5 - 22 - 8 == 5
7 + 5 + 2 / 2 - 8 == 5.0
(where the last one prints as float because the /
makes it so).
Upvotes: 1