我最初并不是打算写答案。但是有人告诉我,另一个用户提出了一个奇怪的主张,即简单地乘以前两个素数比重复应用会更加计算昂贵lcm
。因此,这是两种算法以及一些基准测试:
我的算法:
素数生成算法,给我无限数量的素数。
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
vs 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
vs 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
vs solveLcm
:
total time = 86.42 secs
total alloc = 75,145,302,416 bytes
我认为结果不言而喻。
探查器表明,质数随着n
增加而占运行时间的百分比越来越小。因此,这不是瓶颈,因此我们现在可以忽略它。
这意味着我们实际上正在比较调用时lcm
,其中一个参数从1到n
,另一个参数从1到几何ans
。*
在相同的情况下拨打电话,以及跳过每个非素数号码的额外好处(由于的价格更高,因此渐近地免费*
)。
并且众所周知,*
它比快lcm
,因为lcm
需要重复应用mod
,并且mod
渐近变慢(O(n^2)
vs ~O(n^1.5)
)。
因此,以上结果和简要的算法分析应使显而易见的是哪种算法更快。