【问题标题】:Prime sieve results in (me going to) stack overflowPrime sieve 导致(我要去)堆栈溢出
【发布时间】:2015-02-02 01:32:15
【问题描述】:
对于 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,在我看来,它看起来完全一样,但性能上的差异却很奇怪。
我仍然想继续使用我自己的实现,并想知道我的函数是否可以轻松更改以使用更少的内存。
【问题讨论】:
标签:
performance
haskell
time-complexity
primes
sieve
【解决方案1】:
你的代码里面有一个指数级的爆炸:
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 在这里通过四个sieves 到达顶部后,每个将分成两个创建四个新的sieve 7s,所以会有八个 sieves 在链中!!更糟糕的是,当 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