Max*_*nke 6 primes haskell sieve-of-eratosthenes number-theory
我正在解决Haskell中的一些经典问题以开发我的功能技能,我在实现这个"Programming Praxis"网站上建议的优化时遇到了问题:
我有三个解决这个问题的方法,第三个解决方案与第二个解决方案相比太慢.有人可以对我的代码提出一些改进吗?
我的实现是:
-- primeira implementação
primes n
| n < 2 = []
| n == 2 = [2]
| n `mod` 2 == 0 = primes'
| otherwise = if (find (\x -> n `mod` x == 0) primes') == Nothing then
n:primes'
else
primes'
where primes' = primes (n - 1)
-- segunda implementação
primes' :: Integer -> [Integer]
primes' n = sieve $ 2 : [3,5..n]
where sieve :: [Integer] -> [Integer]
sieve [] = []
sieve l@(x:xs)
| x*x >= n = l
| otherwise = x : sieve list'
where list' = filter (\y -> y `mod` x /= 0) xs
-- terceira implementação
primes'' :: Integer -> [Integer]
primes'' n = 2 : sieve 3 [3,5..n]
where sieve :: Integer -> [Integer] -> [Integer]
sieve _ [] = []
sieve m l@(x:xs)
| m*m >= n = l
| x < m*m = x : sieve m xs
| otherwise = sieve (m + 2) list'
where list'= filter (\y -> y `mod` m /= 0) l
Run Code Online (Sandbox Code Playgroud)
首先,它mod是缓慢的,所以rem在无关紧要的情况下使用(当你没有处理否定时,基本上).其次,使用Criterion显示(对自己)什么是更快,哪些更改实际上是优化.我知道我没有给你一个完整的答案,但这对你(以及其他潜在的回答者)来说是个好地方,所以这里有一些代码:
import List
import Criterion.Main
main = do
str <- getLine
let run f = length . f
input = read str :: Integer
defaultMain [ bench "primes" (nf (run primes) input)
, bench "primes'" (nf (run primes') input)
, bench "primes''" (nf (run primes'') input)
, bench "primesTMD" (nf (run primesTMD) input)
, bench "primes'TMD" (nf (run primes'TMD) input)
, bench "primes''TMD" (nf (run primes''TMD) input)
]
putStrLn . show . length . primes'' $ (read str :: Integer)
-- primeira implementação
primes n
| n < 2 = []
| n == 2 = [2]
| n `mod` 2 == 0 = primes'
| otherwise = if (find (\x -> n `mod` x == 0) primes') == Nothing then
n:primes'
else
primes'
where primes' = primes (n - 1)
primesTMD n
| n < 2 = []
| n == 2 = [2]
| n `mod` 2 == 0 = primes'
| otherwise = if (find (\x -> n `rem` x == 0) primes') == Nothing then
n:primes'
else
primes'
where primes' = primesTMD (n - 1)
-- segunda implementação
primes' :: Integer -> [Integer]
primes' n = sieve $ 2 : [3,5..n]
where sieve :: [Integer] -> [Integer]
sieve [] = []
sieve l@(x:xs)
| x*x >= n = l
| otherwise = x : sieve list'
where list' = filter (\y -> y `mod` x /= 0) xs
primes'TMD :: Integer -> [Integer]
primes'TMD n = sieve $ 2 : [3,5..n]
where sieve :: [Integer] -> [Integer]
sieve [] = []
sieve l@(x:xs)
| x*x >= n = l
| otherwise = x : sieve list'
where list' = filter (\y -> y `rem` x /= 0) xs
-- terceira implementação
primes'' :: Integer -> [Integer]
primes'' n = 2 : sieve 3 [3,5..n]
where sieve :: Integer -> [Integer] -> [Integer]
sieve _ [] = []
sieve m l@(x:xs)
| m*m >= n = l
| x < m*m = x : sieve m xs
| otherwise = sieve (m + 2) list'
where list'= filter (\y -> y `mod` m /= 0) l
primes''TMD :: Integer -> [Integer]
primes''TMD n = 2 : sieve 3 [3,5..n]
where sieve :: Integer -> [Integer] -> [Integer]
sieve _ [] = []
sieve m l@(x:xs)
| m*m >= n = l
| x < m*m = x : sieve m xs
| otherwise = sieve (m + 2) list'
where list'= filter (\y -> y `rem` m /= 0) l
Run Code Online (Sandbox Code Playgroud)
请注意使用以下方法改进变体的运行时rem:
$ ghc --make -O2 sieve.hs
$./sieve
5000
...
benchmarking primes
mean: 23.88546 ms, lb 23.84035 ms, ub 23.95000 ms
benchmarking primes'
mean: 775.9981 us, lb 775.4639 us, ub 776.7081 us
benchmarking primes''
mean: 837.7901 us, lb 836.7824 us, ub 839.0260 us
benchmarking primesTMD
mean: 16.15421 ms, lb 16.11955 ms, ub 16.19202 ms
benchmarking primes'TMD
mean: 568.9857 us, lb 568.5819 us, ub 569.4641 us
benchmarking primes''TMD
mean: 642.5665 us, lb 642.0495 us, ub 643.4105 us
Run Code Online (Sandbox Code Playgroud)
虽然我看到你正在为自己的教育做这件事,但值得注意的是Haskell.org上Primes的相关链接以及关于hackage的快速Primes包.
小智 6
在我看来,你的第三个版本的问题是如何选择下一个要筛选的元素.你不分青红皂白地增加2.问题是你然后筛选不必要的数字.例如,在这个版本中,你最终会将9作为m传递,并且你将进行额外的递归来过滤9,即使它甚至不在列表中,因此你应该从未选择它第一个位置(因为它将在3的第一个过滤器中删除)
即使第二个版本没有开始过滤它所筛选的数字的平方,但它从不选择不必要的筛选值.
换句话说,我认为你最终会对3到n之间的每个奇数进行筛选.相反,你应该对之前通过尚未删除的每个奇数进行筛选.
我认为要正确实现在当前筛选值的平方上开始筛的优化,你必须保留列表的前面,同时筛选背面,其中后面包含元素> =筛选值的平方.我认为这会强制你使用连接,我不太确定优化是否足以抵消使用++引起的开销.
| 归档时间: |
|
| 查看次数: |
2338 次 |
| 最近记录: |