Journal InTime


2011-12-10 (Sat) [長年日記]

_ 素数の計算

Haskellで素数を求めるコードは、以下のように簡潔に書ける。

sieve (x:xs) = x : sieve [y | y <- xs, y `mod` x /= 0]
primes = sieve [2..]

main = print $ primes !! 1000

これが遅いのは有名だけど、実はSICPにも同じような例がある。

(define (integers-starting-from n)
  (cons-stream n (integers-starting-from (+ n 1))))

(define (divisible? x y) (= (remainder x y) 0))

(define (sieve stream)
  (cons-stream
   (stream-car stream)
   (sieve (stream-filter
           (lambda (x)
             (not (divisible? x (stream-car stream))))
           (stream-cdr stream)))))

(define primes (sieve (integers-starting-from 2)))

(display (stream-ref primes 1000))

MIT Schemeだとこのまま動いたが、Gaucheだと(use util.stream)した上でcons-streamはstream-consにする必要があるようだ。

比べてみるとHaskellの方が断然速い。

$ time ./prime
7927
./prime  0.11s user 0.00s system 95% cpu 0.122 total
$ time mit-scheme --batch-mode < prime.scm
7927mit-scheme --batch-mode < prime.scm  2.39s user 0.18s system 98% cpu 2.602 total
$ time gosh prime2.scm
7927gosh prime2.scm  14.84s user 0.06s system 99% cpu 14.979 total

別にSchemeが遅いと言いたいわけではなくて、このアルゴリズムは効率悪いけど、Haskell自体はやっぱり速いんだな、と。

しかし、Project EulerとかやるとやっぱりHaskellでも遅いのでprimesパッケージを使うのであった(ずるい)。 primesパッケージのprimesとかprimeFactorsはすごく速いけど、コード読んでもさっぱりわからないのでだれか解説してください。