Project Euler 98

Problem 98
CAREという単語の文字をそれぞれ1,2,9,6に置き換えることにより、平方数1296 = 362を成す。注目すべきことは、同じ数字の置き換えによって、アナグラムのRACEもまた平方数9216 = 962を成すことである。CARE(とRACE)を平方アナグラム語と呼び、さらに0で始まるのは許さない、違う文字が同じ数字に置き換えられることも許さない。
(中略)平方アナグラム語のペアを全て見つけよ(回文語は自分自身のアナグラムになっている、とは考えない)。
このようなペアの要素を成す最大の平方数はなにか。
http://projecteuler.net/index.php?section=problems&id=98

まず、各単語をコード順にソートして辞書に入れます。例えば、

dic["ACER"] = [ "CARE", "RACE" ]

要素が2つ以上ある場合に、各々のペアの単語の両方が平方になる置き換えがあるか調べます。例えば、

[ "CARE", "RACE", "ACER" ]

と3つの要素があれば、3C2 = 3つのペアを調べます。
そして、桁数が合うすべての平方数を一方の単語にあてはめて、同じ置き換えでもう一つの単語が平方数になるかを調べます。

from itertools import ifilter, combinations
from math import sqrt

def digits(n):
    if n == 0:
        return [ ]
    else:
        a = digits(n / 10)
        a.append(n % 10)
        return a

def numerize(a):
    return reduce(lambda x, y: x * 10 + y, a)

def is_square(n):
    m = int(sqrt(n))
    return m * m == n

def not_duplicated(a):
    b = [ False ] * 10
    for e in a:
        if b[e]:
            return False
        else:
            b[e] = True
    return True

def make_dic(a):
    def sort(s):
        return "".join(sorted(s[k] for k in range(len(s))))
    
    def add(a, s):
        if a in m:
            m[a].append(s)
        else:
            m[a] = [s]
    
    m = { }
    for s in a:
        add(sort(s), s)
    return m

def index(a, n):
    t = ()
    for k in range(len(a)):
        t = t + (k,)
    return t

def is_match(s, a):
    c = [ ' ' for n in range(10) ]
    for k in range(len(s)):
        if c[a[k]] == ' ':
            c[a[k]] = s[k]
        elif c[a[k]] != s[k]:
            return False
    
    for k in range(1, len(s)):
        for l in range(k):
            if s[k] == s[l] and a[k] != a[l]:
                return False
    
    return True

def match_square(pair):
    s1, s2 = pair
    convert = [ s2.index(c) for c in s1 ]
    l = len(s1)
    begin = int(sqrt(10**(l-1)))
    end = int(sqrt(10**l-1))
    for a in ifilter(lambda a: is_match(s1, a),
                (digits(k * k) for k in xrange(end, begin, -1))):
        n = numerize(map(lambda k: a[convert[k]], range(l)))
        if n >= 10 ** (l-1) and is_square(n):
            return max(n, numerize(a))
    return 0

file = open("words.txt")
a = map(lambda s: s[1:-1], file.read().split(','))
file.close()
b = filter(lambda s: len(s) > 1, make_dic(a).itervalues())
print reduce(max, (match_square(d) for c in b for d in combinations(c, 2)))