在Haskell的Prime Sieve

时间:2012-08-01 23:21:15

标签: haskell primes sieve-of-eratosthenes

我对Haskell很新,我只想找到前200万个素数的总和。我正试图用筛子生成素数(我想是Eratosthenes的筛子?),但它真的很慢,我不知道为什么。这是我的代码。

sieve (x:xs) = x:(sieve $ filter (\a -> a `mod` x /= 0) xs)
ans = sum $ takeWhile (<2000000) (sieve [2..])

提前致谢。

4 个答案:

答案 0 :(得分:5)

这是sieve of Eratosthenes

P = { 3 5 ,...} \ {{ p 2 p 2 + 2p ,...} | P }

中的 p

(没有2)。 :)或者在“功能”即基于列表的Haskell中,

primes = 2 : g (fix g)  where
   g xs = 3 : (gaps 5 $ unionAll [[p*p, p*p+2*p..] | p <- xs])

unionAll ((x:xs):t) = x : union xs (unionAll $ pairs t)  where
  pairs ((x:xs):ys:t) = (x : union xs ys) : pairs t 
fix g = xs where xs = g xs
union (x:xs) (y:ys) = case compare x y of LT -> x : union  xs (y:ys)
                                          EQ -> x : union  xs    ys 
                                          GT -> y : union (x:xs) ys
gaps k s@(x:xs) | k<x  = k:gaps (k+2) s    
                | True =   gaps (k+2) xs 

answer by augustss中的试验分部代码相比,它在生成200k素数时速度快1.9倍,在400k时速度快2.1倍,empirical time complexityO(n^1.12..1.15)相比stream processing paradigm 1}},在上述范围内。它产生1百万个素数的速度快2.6倍。

为什么特纳筛子这么慢

因为它为每个素数太早打开了多次过滤流,因此最终会出现太多。我们不需要按素数进行过滤,直到在输入中看到它的正方形。

Fixing it下看到,O(n^1.4)可以看作是在它自身工作时创建一个流传感器管道:

sieve (x:xs) = x:sieve [y|y<-xs, rem y p/=0]

其中[2..] ==> sieve --> 2 [3..] ==> nomult 2 ==> sieve --> 3 [4..] ==> nomult 2 ==> nomult 3 ==> sieve [5..] ==> nomult 2 ==> nomult 3 ==> sieve --> 5 [6..] ==> nomult 2 ==> nomult 3 ==> nomult 5 ==> sieve [7..] ==> nomult 2 ==> nomult 3 ==> nomult 5 ==> sieve --> 7 [8..] ==> nomult 2 ==> nomult 3 ==> nomult 5 ==> nomult 7 ==> sieve 。但是,8不需要检查3的可除性,因为它小于nomult p = filter (\y->rem y p/=0),更不用说5或7了。

这是该代码中唯一最严重的问题,尽管在每篇人都提到的那篇文章的开头它被认为是无关紧要的。 {{3}}通过推迟过滤器的创建实现了显着的加速。

答案 1 :(得分:3)

你所做的不是Eratosthenes的筛子;它的试验部门(注意mod运算符)。这是我的版本的Eratosthenes筛选:

import Control.Monad (forM_, when)
import Control.Monad.ST
import Data.Array.ST
import Data.Array.Unboxed

sieve :: Int -> UArray Int Bool
sieve n = runSTUArray $ do
    let m = (n-1) `div` 2
        r = floor . sqrt $ fromIntegral n
    bits <- newArray (0, m-1) True
    forM_ [0 .. r `div` 2 - 1] $ \i -> do
        isPrime <- readArray bits i
        when isPrime $ do
            forM_ [2*i*i+6*i+3, 2*i*i+8*i+6 .. (m-1)] $ \j -> do
                writeArray bits j False
    return bits

primes :: Int -> [Int]
primes n = 2 : [2*i+3 | (i, True) <- assocs $ sieve n]

您可以在http://ideone.com/mu1RN运行它。

答案 2 :(得分:3)

就个人而言,我喜欢这种产生素数的方式

primes :: [Integer]
primes = 2 : filter (isPrime primes) [3,5..]
  where isPrime (p:ps) n = p*p > n || n `rem` p /= 0 && isPrime ps n

与此处提出的其他一些方法相比,它也相当快。它仍然是试验部门,但它只用质数进行测试。 (但是,此代码的终止证明有点棘手。)

答案 3 :(得分:1)

你使用的算法根本不是一个筛子,因此就缓慢而言,你应该期待使用试验分割。

Primes大致出现平方根函数的频率......即在1和 n 之间存在ballpark n / log(n)素数。因此,对于前200万个素数,您将需要高达约3200万。但是,您正在构建一个200万元素的数据结构,这些素数将会通过。所以你可以开始明白为什么这么慢。实际上它是O(n ^ 2)。您可以将其减少到O(n *(log n)* log(log n))

这是一个关于各种治疗方法的页面,引导您了解如何减少一点。 http://en.literateprograms.org/Sieve_of_Eratosthenes_(Haskell)