Prime筛导致(我会)堆栈溢出

时间:2014-12-04 08:37:01

标签: performance haskell time-complexity primes sieve

对于项目euler我正在寻找一种方法来实现Eratosthenes的筛子,因为我预计它比我原来的实施更快(并且需要它如此)。我想出了这个功能:

sieve :: Int -> [Int] -> [Int]
sieve p (x:xs) | rem x p == 0 = sieve p xs
               | otherwise = x : (sieve x $ sieve p xs)
sieve _ [] = []

哪个有效,但是非常快速地击中风扇,导致堆栈溢出。我前往这里寻求一些建议并立即敲了一个spoiler,对我来说,看起来很相似,但性能的差异是古怪的。 我仍然想继续使用我自己的实现,并想知道我的函数是否可以轻松更改以使用更少的内存。

1 个答案:

答案 0 :(得分:4)

你的代码内部呈指数级爆炸:

sieve p (x:xs) | rem x p == 0 = sieve p xs
               | otherwise = x : (sieve x $ sieve p xs)
--                                          ^^^^^^^       here!
--                                ^^^^^^^                 and here!

您希望内部sieve调用只是继续按p进行过滤,但由于您使用相同的sieve函数,因此它还会启动新素数的新过滤器,因为它遇到他们 - 但自从"鞋帮"以来,这完全是多余的。等级调用也会为同一个素数启动新的过滤器!

sieve 2 [3..]
 = 3 : sieve 3 (sieve 2 [4..])
 = 3 : sieve 3 (5 : sieve 5 (sieve 2 [6..]))
 = 3 : 5 : sieve 5 (sieve 3 (sieve 5 (sieve 2 [6..])))
....         -- ^^^               ^^^                      -- !!!

7 在这里通过四个sieve传到顶部后,每个将分成两个,创建四个新sieve 7 s,因此会有 <链中的em> 8 sieve!更糟糕的是,当 9 - 复合材料! - 通过筛子,它将分割 2 7 5 ,并且只会被拒绝3 。因此它实际上更糟而不是指数的素数 n ,并且接近于生成的最后一个素数的指数(这是 ~ n log(n) 的)。

将其更改为

sieve p (x:xs) | rem x p == 0 = sieve p xs
               | otherwise = x : (sieve x $ filter ((>0).(`rem` p)) xs)

你得到的代码相当于你引用的代码。

如果您希望手动编写所有代码,可以引入一个新参数,一个控制是否启动新过滤器的布尔标志:

primes = 2 : sieve True 2 [3..]
sieve b p (x:xs) | rem x p == 0 = sieve b p xs
                 | b = x : (sieve b x $ sieve False p xs)
                 | otherwise = x : sieve b p xs