from itertools import permutations
import re
def solve(problem):
problem = re.sub('^([^=]+)=+(.*)$', r'(\1)-(\2)', problem)
variables = compile(problem, '<string>', 'eval').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 reconstruct(expr):
return re.sub(r'^\((.*?)\)-\((.*?)\)$', r'\1==\2', expr)
def advance(n, mapping, remaining):
def distance(candidate):
newmap = mapping + candidate
expr = problem.translate(dict(newmap)).translate(default_mapping)
r = remaining
if candidate:
r = [d for d in remaining if all(d != c[1] for c in candidate)]
return abs(eval(expr)), expr, newmap, r
if n == max_depth:
result = distance(())
if not result[0]:
yield reconstruct(result[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 dict(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))
rs = list(sorted(distance(tuple(zip(letters, c))) for c in ps))
for r in rs:
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')))