最初は答えを書くつもりはなかった。しかし、別のユーザーが、最初のカップルの素数を単純に乗算することは、繰り返し適用するよりも計算コストが高いという奇妙な主張をした後、私に言われましたlcm
。そのため、2つのアルゴリズムといくつかのベンチマークを以下に示します。
私のアルゴリズム:
素数の無限リストを提供する素数生成アルゴリズム。
isPrime :: Int -> Bool
isPrime 1 = False
isPrime n = all ((/= 0) . mod n) (takeWhile ((<= n) . (^ 2)) primes)
toPrime :: Int -> Int
toPrime n
| isPrime n = n
| otherwise = toPrime (n + 1)
primes :: [Int]
primes = 2 : map (toPrime . (+ 1)) primes
次に、その素数リストを使用して、一部の結果を計算しますN
。
solvePrime :: Integer -> Integer
solvePrime n = foldl' (*) 1 $ takeWhile (<= n) (fromIntegral <$> primes)
今、確かに一方で、私は最初から素数生成を実現し(そして、そのパフォーマンスの低下にスーパー簡潔なリストの内包アルゴリズムを使用していない)主な理由は、かなり簡潔で、他のLCMベースのアルゴリズム、lcm
単にから輸入されましたPrelude
。
solveLcm :: Integer -> Integer
solveLcm n = foldl' (flip lcm) 1 [2 .. n]
-- Much slower without `flip` on `lcm`
ベンチマークでは、それぞれに使用したコードは単純でした:(-prof -fprof-auto -O2
その後+RTS -p
)
main :: IO ()
main = print $ solvePrime n
-- OR
main = print $ solveLcm n
:n = 100,000
solvePrime
total time = 0.04 secs
total alloc = 108,327,328 bytes
対solveLcm
:
total time = 0.12 secs
total alloc = 117,842,152 bytes
:n = 1,000,000
solvePrime
total time = 1.21 secs
total alloc = 8,846,768,456 bytes
対solveLcm
:
total time = 9.10 secs
total alloc = 8,963,508,416 bytes
:n = 3,000,000
solvePrime
total time = 8.99 secs
total alloc = 74,790,070,088 bytes
対solveLcm
:
total time = 86.42 secs
total alloc = 75,145,302,416 bytes
結果はそれを物語っていると思います。
プロファイラーは、プライム生成が実行時間のn
増加に応じてますます少ない割合を占めることを示します。それがボトルネックではないので、今のところは無視できます。
これはlcm
、1つの引数が1からn
になり、他の引数が幾何学的に1からになる呼び出しを実際に比較していることを意味しans
ます。*
同じ状況で呼び出し、すべての非素数をスキップできるという追加の利点(より高価な性質のため、無料で漸近的に*
)。
そして、よく知られている*
よりも高速であるlcm
として、lcm
の繰り返しのアプリケーションを必要とmod
し、mod
漸近的に遅く(あるO(n^2)
対~O(n^1.5)
)。
したがって、上記の結果と簡単なアルゴリズム分析により、どのアルゴリズムが高速であるかが非常に明確になります。