問題を解いている間、私は数の約数を計算しなければなりませんでした。特定の数値に対してすべての約数 > 1 を生成する 2 つの実装があります。
1 つ目は単純な再帰を使用する方法です。
divisors :: Int64 -> [Int64]
divisors k = divisors' 2 k
where
divisors' n k | n*n > k = [k]
| n*n == k = [n, k]
| k `mod` n == 0 = (n:(k `div` n):result)
| otherwise = result
where result = divisors' (n+1) k
2 つ目は Prelude のリスト処理関数を使用します。
divisors2 :: Int64 -> [Int64]
divisors2 k = k : (concatMap (\x -> [x, k `div` x]) $!
filter (\x -> k `mod` x == 0) $!
takeWhile (\x -> x*x <= k) [2..])
最初の実装の方が高速であることがわかりました (返されたリスト全体を出力したので、怠惰のために結果のどの部分も評価されずに残りません)。2 つの実装では異なる順序の除数が生成されますが、それは私にとっては問題ではありません。(実際、k が完全平方の場合、2 番目の実装では平方根が 2 回出力されますが、これも問題ではありません)。
一般に、このような再帰的な実装は Haskell の方が高速ですか? また、これらのコードのいずれかを高速化するための指針をいただければ幸いです。ありがとう!
編集:
これら2つの実装のパフォーマンスを比較するために使用しているコードは次のとおりです: https://gist.github.com/3414372
ここに私のタイミング測定があります:
厳密な評価 ($!) で divisor2 を使用する
$ ghc --make -O2 div.hs
[1 of 1] Compiling Main ( div.hs, div.o )
Linking div ...
$ time ./div > /tmp/out1
real 0m7.651s
user 0m7.604s
sys 0m0.012s
遅延評価 ($) で divisors2 を使用する:
$ ghc --make -O2 div.hs
[1 of 1] Compiling Main ( div.hs, div.o )
Linking div ...
$ time ./div > /tmp/out1
real 0m7.461s
user 0m7.444s
sys 0m0.012s
関数除数の使用
$ ghc --make -O2 div.hs
[1 of 1] Compiling Main ( div.hs, div.o )
Linking div ...
$ time ./div > /tmp/out1
real 0m7.058s
user 0m7.036s
sys 0m0.020s