【问题标题】:Haskell: faster summation of primesHaskell:更快的素数求和
【发布时间】:2010-11-11 12:22:48
【问题描述】:

免责声明:我正在研究欧拉问题 9。

我将一些相当大的数字相加,从 1 到 2 000 000 的所有素数。

对这些素数求和需要很长时间。我正在使用内置函数“sum”的haskell。

如:

sum listOfPrimes

还有其他更快的选择吗?

--我的主要生成器是我代码中的慢速链接。

【问题讨论】:

    标签: haskell primes


    【解决方案1】:

    听起来您的问题不是对数字求和,而是生成它们。您对 listOfPrimes 的实现是什么?

    本文可能感兴趣:http://lambda-the-ultimate.org/node/3127

    【讨论】:

    • 我同意!那个对我帮助很大!
    【解决方案2】:

    我希望您使用的是 ghc -O2 而不是 ghci,对吗?你的问题将在生成,而不是总和。

    一种更快的方法是使用基于流融合的序列,这样可以更好地优化。使用常规列表:

    import Data.List
    import qualified Data.Map as M
    
    primes :: [Integer]
    primes = mkPrimes 2 M.empty
      where
        mkPrimes n m = case (M.null m, M.findMin m) of
            (False, (n', skips)) | n == n' ->
                mkPrimes (succ n) (addSkips n (M.deleteMin m) skips)
            _ -> n : mkPrimes (succ n) (addSkip n m n)
        addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m
        addSkips = foldl' . addSkip
    
    -- fuse:
    main = print (sum (takeWhile (<= 2000000) primes))
    

    我们得到,

    $ ghc -O2 --make A.hs
    $ time ./A           
    142913828922
    ./A  9.99s user 0.17s system 99% cpu 10.166 total
    

    切换到流,所以 sum 。 takeWhile 熔断器:

    import qualified Data.List.Stream as S
    main = print (S.sum (S.takeWhile (<= 2000000) primes))
    

    节省一些时间,

    $ time ./A           
    142913828922
    ./A  9.60s user 0.13s system 99% cpu 9.795 total
    

    但是您的问题将是素数生成,因为我们可以看到我们是否完全放弃求和,将 sum 替换为 last:

    $ time ./A           
    1999993
    ./A  9.65s user 0.12s system 99% cpu 9.768 total
    

    所以找到一个更好的素数生成器。 :-)

    最后,还有一个用于快速素数生成器的 Hackage 库:

    http://hackage.haskell.org/packages/archive/primes/0.1.1/doc/html/Data-Numbers-Primes.html

    使用它,我们的时间变成:

    $ cabal install primes
    $ cabal install stream-fusion
    
    $ cat A.hs
    import qualified Data.List.Stream as S
    import Data.Numbers.Primes
    
    main = print . S.sum . S.takeWhile (<= 2000000) $ primes
    
    $ ghc -O2 -fvia-C -optc-O3 A.hs --make
    
    $ time ./A
    142913828922
    ./A  0.62s user 0.07s system 99% cpu 0.694 total
    

    【讨论】:

    【解决方案3】:

    我写了一个“埃拉托色尼筛法”here

    import Data.List
    import qualified Data.Map as M
    primes :: [Integer]
    primes = mkPrimes 2 M.empty
      where
        mkPrimes n m = case (M.null m, M.findMin m) of
            (False, (n', skips)) | n == n' ->
                mkPrimes (succ n) (addSkips n (M.deleteMin m) skips)
            _ -> n : mkPrimes (succ n) (addSkip n m n)
        addSkip n m s = M.alter (Just . maybe [s] (s:)) (n+s) m
        addSkips = foldl' . addSkip
    

    使用它,在我的桌面上print . sum $ takeWhile (&lt;= 20000000) 大约需要 25 秒。有待改善?当然,J 运行时间不到 1 秒

    +/p:i.p:^:_1]20000000 12272577818052

    但它有一个非常优化的素数生成器。

    【讨论】:

    • 非常好,清晰,好!三项标准改进 - 仅赔率、推迟将素数添加到地图中以及双素数馈送 - 使其在 200 万(测试)时运行速度提高 10.6 倍,在 2000 万时运行速度提高约 13.4 倍(预计)。所以运行时间会变成~1.9s。我已将更改后的代码添加到the haskellwiki page。 :)
    • @WillNess 有史以来最好的回复 :-)
    • 为什么,非常感谢。 :) ...忘了提到它现在在接近恒定的空间中运行,1..2 MB。但这是意料之中的。
    【解决方案4】:

    您的函数的缓慢部分肯定是生成素数,而不是 sum 函数。生成素数的好方法是:

    isprime :: (Integral i) => i -> Bool
    isprime n = isprime_ n primes
      where isprime_ n (p:ps)
              | p*p > n        = True
              | n `mod` p == 0 = False
              | otherwise      = isprime_ n ps
    
    primes :: (Integral i) => [i]
    primes = 2 : filter isprime [3,5..]
    

    我认为它的可读性很强,尽管它完全可以工作可能有点令人惊讶,因为它使用了primes 列表的递归和惰性。它也相当快,尽管可以以牺牲可读性为代价进行进一步的优化。

    【讨论】:

    • 嘿,我以前没有在 Haskell 中看到过这种特殊的算法。很酷,它比我的更简单,感觉更像 Haskell。
    • 这实际上相当于低效的算法。它仍然针对每个素数测试每个数字。
    • @newacc:它使用“低效”算法,但它比“高效”版本更快,至少在这种情况下。 “高效”算法需要对地图进行大量修改,并不断迭代越来越大的地图。这并不一定比与几个素数比较好。
    • 没错。它感觉就像传统的primes = sieve [2..] where sieve (p:xs) = p:[x | x &lt;- xs, mod x p /= 0] 一样简单和干净——事实上,它是等价的——但运行效率更高。
    • 这是一个(几乎)最优试除法 (OTD) 筛,从根本上比“传统”“低效”算法(即 Turner ps=sv[2..] where sv(p:xs)=p:sv[x|x&lt;-xs,rem x p&gt;0] 的筛)更有效,因为它会在其平方根处停止对 n 的测试 - 特纳对数字本身以下的所有素数的测试。 OTD 的理论时间复杂度为O(k^1.5/(log k)^0.5)(经验上通常被视为~ k^1.45),而对于特纳的-O(k^2),在k 中产生素数。因此,在产生一百万个素数时,OTD 将比特纳的筛子快~ 2000x ... 4000x(或更多)。
    猜你喜欢
    • 1970-01-01
    • 2014-01-05
    • 2015-02-17
    • 1970-01-01
    • 2017-01-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多