Project Euler 32(1)

Problem 32
積7254は、39 x 186 = 7254と書くと1〜9をちょうど1回ずつ使うという意味で特別である。同様の積の和を求めよ。
http://projecteuler.net/index.php?section=problems&id=32

普通に解くと、1〜9の順列を出して、例えば、(1, 8, 6, 3, 9, 7, 2, 5, 4)と出れば、

1863 x 9 = 7254
186 x 39 = 7254

が成り立っているかをチェックします。


# code1
from itertools import permutations

def numerize(a):
return reduce(lambda n, d: n * N + d, a, 0)

def gen_pandigits():
for digits in permutations(range(1, N)):
c = numerize(digits[M:])
for k in range(M - M / 2, M): # position of x
a = numerize(digits[:k])
b = numerize(digits[k:M])
if a * b == c:
yield c

N = 10 # base
M = N / 2 # number of left-hand digits
s = set(gen_pandigits()) # avoid duplication
print sum(s)

これでも十分ですが、より速くするにはどうしたらよいでしょうか。
すぐに思いつくのは、例えば左辺の両方の項の頭の数字が1と2だったら、右辺の頭は3〜5となる、ことを利用する方法です。そして、残りの数で順列を出して、掛け算が成り立っているかを調べます。
これで確かに速くなるのですが、小手先の方法のような気がします。



# code2
from itertools import permutations, product

def numerize(a):
return reduce(lambda n, d: n * N + d, a, 0)

def gen_head():
for d1, d2 in filter(lambda t: t[0] != t[1],
product(range(1, N), repeat = 2)):
d3_min = d1 * d2
d3_max = (d1 + 1) * (d2 + 1)
if N & 1:
d3_min /= N
d3_max = (d3_max + N - 1) / N
d3_min = max(d3_min, 1)
d3_max = min(d3_max, N)
for d3 in range(d3_min, d3_max):
if d3 != d1 and d3 != d2:
yield d1, d2, d3

def remove(a, d1, d2, d3):
a.remove(d1)
a.remove(d2)
a.remove(d3)
return a

def gen_pandigits():
for d1, d2, d3 in gen_head():
for digits in permutations(remove(range(1, N), d1, d2, d3)):
c = numerize( (d3,) + digits[M-2:])
for k in range(M - M / 2, M):
a = numerize( (d1,) + digits[:k-1])
b = numerize( (d2,) + digits[k-1:M-2])
if a * b == c:
yield c

N = 13
M = N / 2
s = set(gen_pandigits())
print sum(s)