ひさしぶりにPythonシェイプアップ

http://blog.bestinclass.dk/index.php/2009/10/python-vs-clojure-evolving/

Clojureで書いたコードに比べてPythonは行数も多くて無様だとか言われた。

from itertools import ifilter
import operator

def mul(nums):
    return reduce(operator.mul, nums)

def icross(*sequences):
    if sequences:
        for x in sequences[0]:
            for y in icross(*sequences[1:]):
                yield (x,)+y
    else: yield ()

def digits_from_num(num, base=10):
    def recursive(num, base):
        if num < base:
            return [num]
        return [num%base] + recursive(num/base, base)
    return list(reversed(recursive(num, base)))

def is_palindrome(num, base=10):
    digitslst = digits_from_num(num, base)
    return (digitslst == list(reversed(digitslst)))

def euler4(lstlst):
    canditates = (mul(ns) for ns in icross(*lstlst))
    return max(ifilter(is_palindrome, canditates))

print euler4(2*[range(111, 1000)])

まずなぜいきなり直積を定義しているのか。

def icross(*sequences):
    if sequences:
        for x in sequences[0]:
            for y in icross(*sequences[1:]):
                yield (x,)+y
    else: yield ()

clojureの方では

            (for [x (range 100 1000)
                  y (range 100 1000)]
              (* x y))))

って書いているのに。pythonでだって「for x in range(100, 1000) for y in range(100, 1000)」って書けばいいじゃん。全削除。

次に数字を文字列表記でひっくり返す関数。

def digits_from_num(num, base=10):
    def recursive(num, base):
        if num < base:
            return [num]
        return [num%base] + recursive(num/base, base)
    return list(reversed(recursive(num, base)))

いやいや。なにしてんの、しかも再帰呼び出しとか。Clojureの側で(= (seq s) (reverse s))ってやってんだからPythonでもlist(a) == list(reversed(a))でいいじゃん。全削除。

次mul。リストを受け取ってその中身を全部掛け算する関数。

def mul(nums):
    return reduce(operator.mul, nums)

これ自体はいい。でもClojureの側って(* x y)じゃん。引数の個数が2個に固定なんだったらこんなの定義しなくていいじゃん。全削除。

ここまでで中間報告

from itertools import ifilter

def is_palindrome(num):
    digitslst = str(num)
    return (list(digitslst) == list(reversed(digitslst)))

def euler4(xs, ys):
    canditates = (x * y for x in xs for y in ys)
    return max(ifilter(is_palindrome, canditates))

print euler4(range(111, 1000), range(111, 1000))

半分以下に縮みましたが。

でもってClojureの側は名前を付けて関数を定義したりしないでぜんぶ1つにまとめてるよね。フェアじゃないのでPythonでもそうしよう。なるべくこれに見かけを似せて:

(reduce max
    (filter #(let [s (str %)]
               (= (seq s) (reverse s)))
            (for [x (range 100 1000)
                  y (range 100 1000)]
              (* x y))))

こうなった

print max(v for v in (x * y 
                      for x in range(100, 1000) 
                      for y in range(100, 1000))
          if list(str(v)) == list(reversed(str(v))))

あれ?Pythonの方が短いよ?


と日本語で書いてもしょうがないので英語でも書いた
http://blog.hackers-cafe.net/2009/10/re-python-vs-clojure.html


行数だけ縮めて、速度の面ではめんどくさくなったのでチェックしなかったのだけどまともとさんによるとこの改良によってPythonの方が早くなっていたみたい。追記:というのは勘違いでした(コメント欄参照)

ということは、こういうことか。p [*100..1000].product([*100..1000]).map{|x,y| x*y}.select{|s|s=s.to_s; s==s.reverse}.max

速度比較をすると、オリジナルPython版(2.6)で12.2秒、西尾版で4.9秒、Ruby版(1.8.7)で2.8秒、1.9 trunkで0.9秒であった。Pythonは3.1にするともうちょっとだけ速いみたい。

もと記事では「(オリジナル)PythonClojureより300%遅い」って書いてたけど、それってつまり1.9はClojureの4倍速いってことだよね。要するに、つまらない比較ってこと

http://twitter.com/yukihiro_matz/status/4901341641
http://twitter.com/yukihiro_matz/status/4901791131
http://twitter.com/yukihiro_matz/status/4901849726