++,last&初始速度比:,head&尾巴?

时间:2013-05-15 16:42:51

标签: haskell primes

考虑到这两种编写函数的方法,可以找到所有素数到特定数字:

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

为什么primesTo1primesTo2快很多,即使使用了不同的函数; 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的最佳素数生成器,我只是对这两个函数的速度差异感到困惑。

2 个答案:

答案 0 :(得分:2)

正如“n.m.”所指出的,其原因是primes2首先尝试除以找到的最大素数,而primes1则以最低值开始。

因此,在all中使用它们之前首先反转当前素数列表实际上比primes1primes2更快:

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]) 

实际上表达最佳试验分区筛。