from itertools import permutations, chain
import re
def solve(problem):
problem = re.sub('^([^=]+)=+(.*)$', r'(\1)-(\2)', problem)
expr = compile(problem, '<string>', 'eval')
variables = expr.co_names
letters = [ord(x) for x in set(''.join(variables))]
default_mapping = dict(zip(letters, [ord('5')] * len(letters)))
varlens = [len(x) for x in variables]
max_depth = max(varlens)
tmp = [[x - i for x in varlens] for i in range(max_depth)]
error_limits = [sum(int(10**(e - 1)) for e in t) for t in tmp]
def advance(n, mapping, remaining):
def distance(candidate):
newmap = dict(chain(mapping.items(), candidate.items()))
values = [int(v.translate(newmap).translate(default_mapping))
for v in variables]
r = remaining
if candidate:
r = [d for d in remaining if d not in candidate.values()]
env = dict(zip(variables, values))
return abs(eval(expr, {}, env)), tuple(env.items()), newmap, r
if n == max_depth:
r = distance({})
if not r[0]: yield r[1]
else:
nth = set(''.join(v[n:n+1] for v in variables))
letters = [ord(x) for x in nth if ord(x) not in mapping]
if not letters:
yield from advance(n + 1, mapping, remaining)
else:
xs = [ord(x) for x in '123456789'] if n == 0 else remaining
ps = permutations(xs, len(letters))
for r in sorted(distance(dict(zip(letters, c))) for c in ps):
if r[0] < error_limits[n]:
yield from advance(n + 1, r[2], r[3])
yield from advance(0, {}, [ord(x) for x in '0123456789'])
print(next(solve('send + more = money')))
print(next(solve('apple + grape = cherry')))