我正试图在无限列表中使用Sieve of Eratosthenes算法生成素数。我听说foldr会懒惰地查看列表,但每次我尝试使用以下算法时都会出现堆栈溢出异常:
getPrimes :: [Int]
getPrimes = foldr getNextPrime [2] [3,5..]
where
getNextPrime n primes
| not $ isAnyDivisibleBy primes n = primes ++ [n]
| otherwise = primes
isAnyDivisibleBy primes n = any (\x -> isDivisibleBy n x) primes
isDivisibleBy x y = x `mod` y == 0
示例:
takeWhile (\x -> x < 10) getPrimes
*** Exception: stack overflow
某个地方正在评估列表,但我无法弄清楚在哪里。
答案 0 :(得分:3)
我认为foldr
让你感到困惑,所以让我们用明确的递归来写出来:
getPrimes :: [Int]
getPrimes = getPrimesUsing [3,5..]
getPrimesUsing :: [Int]->[Int]
getPrimesUsing [] = [2]
getPrimesUsing (n:primes)
| not $ isAnyDivisibleBy primes n = primes ++ [n]
| otherwise = primes
where
primes = getPrimesUsing primes
isAnyDivisibleBy primes n = any (\x -> isDivisibleBy n x) primes
isDivisibleBy x y = x `mod` y == 0
你现在能看到麻烦吗?
一个不相关的观点:你似乎试图在这里实现的算法是不是 Eratosthenes的筛选,而是一种效率低得多的算法,称为试验分割。
答案 1 :(得分:2)
foldr getNextPrime [2] [3, 5 .. ]
扩展为:
(getNextPrime 3 (getNextPrime 5 (getNextPrime 7 ...
由于getNextPrime
总是需要检查它的第二个参数,我们只得到一个非终止的getNextPrime
个调用链,并且从不使用初始的[2]
列表。
答案 2 :(得分:2)
foldr
定义为
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
所以当你插入参数时,你会得到
foldr getNextPrime [2] [3,5..]
= getNextPrime 3 (foldr getNextPrime [2] [5,7..])
= getNextPrime 3 (getNextPrime 5 (foldr getNextPrime [2] [7,9..])
etc...
为了这个懒惰地产生值(处理无限列表时你想要的),getNextPrime
需要懒惰地产生值。查看getNextPrime
,primes ++ [n]
的定义,意味着您将值附加到primes
列表的末尾,但primes
的{{1}}是{ {1}}。但是getNextPrime 3
getNextPrime 5 (foldr getNextPrime [2] [7,9..])
的{{1}}是primes
等等。您实际上无法为getNextPrime 5
生成正常的表单值,它始终是一个永远不会计算的链返回。
另一种看待这种情况的方法是看这是用运算符替换getNextPrime 7 (foldr getNextPrime [2] [9,11..])
,让它称之为primes
getNextPrime
(这就是为什么它被称为右折叠,parens嵌套在右边)
这适用于在.:
中使用foldr (.:) [2] [3,5..9]
= 3 .: (5 .: (7 .: (9 .: [2])))
:
:
因为foldr
只是构建一个新的数据结构,所以可以在不计算结构其余部分的情况下检查该数据结构的第一个元素。但是foldr (:) [2] [3,5..9]
= 3 : (5 : (7 : (9 : [2])
不太好,它首先需要计算:
,然后是.:
,然后是x1 = 9 .: [2]
,最后是x2 = 7 .: x1
。对于x3 = 5 .: x2
而言,您永远无法计算3 .: x3
的最后一次调用,但是haskell会继续尝试计算它并且会使堆栈崩溃。