【问题标题】:++, last & init faster than :, head & tail?++, last & init 比 :, head & tail 快吗?
【发布时间】:2023-03-10 14:19:01
【问题描述】:

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

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 使用 ++lastinit 而不是 :headtail 用于 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 寻找最佳的素数生成器,我只是对这两个函数的速度差异感到困惑。

【问题讨论】:

  • primes1 自下而上扫描素数,primes2 自上而下扫描。后者是过滤出合数的一种非常糟糕的慢速方法。
  • @n.m.谢谢,这很有道理。
  • 能被 2 或 3 整除的数字远远多于能被 997 整除的数字。
  • @DanielFischer 我认为 n.m.已经说过了。
  • 我只是对此进行了扩展,说明了为什么这是一种缓慢的方法。

标签: haskell primes


【解决方案1】:

正如“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

ghci10000 为参数的速度:

*Main> primesTo3 10000
...
(0.41 secs, 241128512 bytes)

并用ghc -O2编译:

$ time ./primes3
...
./primes  0.05s user 0.00s system 24% cpu 0.209 total

【讨论】:

  • 试试all (\p -&gt; rem x p /= 0) $ takeWhile (\p-&gt; p*p &lt;= x) $ reverse ps
  • ghci(0.13 secs, 177274752 bytes),编译:0.01s user 0.00s system 82% cpu 0.018 total
  • 尝试两个尺寸点,例如 n=10000 和 n=20000。然后检查the empirical order of growthn^a 在该范围内的算法,a = logBase (n2/n1) (t2/t1)(对于 10k 与 20k,logBase 2 (t2/t1))。 :) 也试试你原来的算法。 :)
【解决方案2】:

我知道您说您“不是在为 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]) 

这实际上表达了最优试划分筛子。

【讨论】:

    猜你喜欢
    • 2016-10-30
    • 2021-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-25
    相关资源
    最近更新 更多