考虑到这两种编写函数的方法,可以找到所有素数到特定数字:
primes1 = iterate
(\ps -> ps ++ [([x |
x <- [last ps + 1..],
all (\p -> x `mod` p /= 0) ps] !! 0)])
[2]
primesTo1 :: Integer -> [Integer]
primesTo1 n = init $ head $ dropWhile (\p -> last p <= n) primes1
primes2 = iterate
(\ps -> ([x |
x <- [head ps + 1..],
all (\p -> x `mod` p /= 0) ps] !! 0)
: ps)
[2]
primesTo2 :: Integer -> [Integer]
primesTo2 n = tail $ head $ dropWhile (\p -> head p <= n) primes2
为什么primesTo1
比primesTo2
快很多,即使使用了不同的函数; primesTo1
使用++
,last
&amp; init
代替:
,head
&amp; tail
中使用了primesTo2
。
ghci
:set +s
的输出:
*Main> primesTo1 10000
...
(0.51 secs, 124779776 bytes)
*Main> primesTo2 10000
...
(3.30 secs, 570648032 bytes)
使用ghc -O2
编译:
$ time ./primes1
...
./primes1 0.06s user 0.00s system 68% cpu 0.089 total
$ time ./primes2
...
./primes2 0.28s user 0.00s system 98% cpu 0.283 total
<小时/> 注意:我不是在寻找Haskell的最佳素数生成器,我只是对这两个函数的速度差异感到困惑。
答案 0 :(得分:2)
正如“n.m.”所指出的,其原因是primes2
首先尝试除以找到的最大素数,而primes1
则以最低值开始。
因此,在all
中使用它们之前首先反转当前素数列表实际上比primes1
和primes2
更快:
primes3 = iterate
(\ps -> ([x |
x <- [head ps + 1..],
all (\p -> x `mod` p /= 0) $ reverse ps] !! 0)
: ps)
[2]
primesTo3 :: Integer -> [Integer]
primesTo3 n = tail $ head $ dropWhile (\p -> head p <= n) primes3
以ghci
作为参数的 10000
速度:
*Main> primesTo3 10000
...
(0.41 secs, 241128512 bytes)
并使用ghc -O2
编译:
$ time ./primes3
...
./primes 0.05s user 0.00s system 24% cpu 0.209 total
答案 1 :(得分:1)
我知道你说你“没有为Haskell寻找最佳素数发生器”,但你仍然对“两个函数的速度差异”感兴趣。所以这里对你的纠正函数primes3
进行了更多的句法操作,它按照(:)
按相反顺序添加素数,并反转它们以便每次进行测试,
primes3 :: [[Integer]]
primes3 = iterate
(\ps -> ([x |
x <- [head ps + 1..],
all (\p -> x `mod` p /= 0) $
takeWhile (\p-> p*p <= x) $ reverse ps] !! 0)
: ps) -- ^^^^^^^^^^
[2]
可以修改此代码(虽然这不会改变效率):
primes3b :: [[Integer]]
primes3b = iterate
(\ps -> ([x |
x <- [head ps + 1..],
all (\p -> x `mod` p /= 0) $
takeWhile (\p-> p*p <= x) $ map head $ primes3b ] !! 0)
: ps) -- ^^^^^^^^^^^^^^^^^^^
[2]
不是吗?这相当于(注意类型改变)
primes4 :: [Integer]
primes4 = map head $ iterate
(\ps -> ([x |
x <- [head ps + 1..],
all (\p -> x `mod` p /= 0) $
takeWhile (\p-> p*p <= x) primes4 ] !! 0)
: ps) -- ^^^^^^^
[2]
与
相同primes5 :: [Integer]
primes5 = iterate
(\p -> head [x | x <- [p + 1..],
all (\p -> x `mod` p /= 0) $
takeWhile (\p-> p*p <= x) primes5 ]
) -- nothing!
2
或者,更快一点,
primes6 :: [Integer]
primes6 = 2 : iterate
(\p -> head [x | x <- [p + 2, p + 4..],
all (\p -> x `mod` p /= 0) $ tail $
takeWhile (\p-> p*p <= x) primes6 ] )
3 -- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
最后,(并且速度有了相当大的提升,甚至empirical orders of growth结果) - 为什么在每次迭代时只添加一个数字,花了很多工作来获取要测试的质数?我们可以在连续的素数平方之间按部分工作。获取的素数列表对于这些段具有相同长度,并且该段的长度从段到段增加1:
primes7 :: [Integer]
primes7 = concatMap snd $ iterate
(\((n,p:ps@(q:_)),_) -> ((n+1,ps),
[x | let lst = take n $ tail primes7,
x <- [p*p + 2, p*p + 4..q*q-2],
all ((/= 0).rem x) lst]))
((1,tail primes7),[2,3,5,7])
实际上表达最佳试验分区筛。