对于项目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,对我来说,看起来很相似,但性能的差异是古怪的。 我仍然想继续使用我自己的实现,并想知道我的函数是否可以轻松更改以使用更少的内存。
答案 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