在Haskell中表达递归 - 素数序列

时间:2014-09-13 02:36:41

标签: haskell recursion primes lazy-evaluation

我需要表达素数序列。 (在Euler项目中与前三名挣扎)。

我碰巧遇到了这个递归定义:

is_not_dividable_by :: (Integral a) => a -> a -> Bool
is_not_dividable_by x y = x `rem` y /= 0

accumulate_and :: (Integral a) => [a] -> (a -> Bool) -> Bool
accumulate_and (x:xs) (f) = (accumulate_and xs (f)) && f(x)
accumulate_and [] f = True

integers = [2,3..]

prime_sequence = [n | n <- integers, is_prime n]
                where is_prime n = accumulate_and 
                                        (takeWhile (<n) (prime_sequence)) 
                                        ( n `is_not_dividable_by`)

result = take 20 prime_sequence
str_result = show result
main = putStrLn str_result

虽然它编译得很好但是在执行时会陷入循环,只返回<<loop>>

我的问题是我认为我可以在Haskell中自由表达递归定义。 但显然这个定义根本不符合语言。

然而,当我在心理上尝试解决prime_sequence时,我认为我成功并且增长了序列,但当然是先于命令式编程。

我的递归定义中有什么错误,这使得这段代码在Haskell中不起作用?

2 个答案:

答案 0 :(得分:3)

这是一个无限循环的原因是因为这一行:

prime_sequence =
  [n | n <- integers, is_prime n]
  where is_prime n = accumulate_and (takeWhile (< n) prime_sequence)
                                    (n `is_not_dividable_by`)

为了计算is_prime n,需要将所有素数小于n。但是,为了让takeWhile知道何时停止,还需要检查尚未计算的n

(以手工波浪的方式,这意味着您的prime_sequence 太懒所以它最终会biting its own tail并变成无限循环。)

以下是如何在不进入无限循环的情况下生成素数的无限列表:

-- | An infinite list of prime numbers in ascending order.
prime_sequence :: [Integer]
prime_sequence = find [] integers
  where find :: [Integer] -> [Integer] -> [Integer]
        find primes [] = []
        find primes (n : remaining)
          | is_prime   = n : find (n : primes) remaining
          | otherwise  = find primes remaining
          where is_prime = accumulate_and primes (n `is_not_dividable_by`)

此处的重要功能是find,其中包含primes的现有列表和remaining整数列表,并生成 next {{1} } prime是prime,然后通过remaining捕获它来延迟剩余的计算,直到稍后。

答案 1 :(得分:3)

罪魁祸首就是这个定义:

prime_sequence = [n | n <- [2,3..], is_prime n]  where 
  is_prime n = accumulate_and 
                 (takeWhile (< n) (prime_sequence)) 
                 ( n `is_not_dividable_by`)

尝试找到prime_sequence的头元素(由main打印的20个中的第一个)导致takeWhile需要检查prime_sequence&#39 ; s元素。这导致takeWhile调用需要检查prime_sequence的头元素。它一次又一次地发生。

那是黑洞,马上就到了。 takeWhile 甚至无法开始沿着它的输入行走,因为那里还没有。

这可以通过启动序列轻松修复:

prime_sequence = 2 : [n | n <- [3,4..], is_prime n]  where 
  is_prime n = accumulate_and 
                 (takeWhile (< n) (prime_sequence)) 
                 ( n `is_not_dividable_by`)

现在它开始工作,并遇到Rufflewind's answer中描述的第二个问题:takeWhile无法继续沿其输入行走。最简单的解决方法是停在n/2。但是停在sqrt会好得多:

prime_sequence = 2 : [n | n <- [3,4..], is_prime n]  where 
  is_prime n = accumulate_and 
                 (takeWhile ((<= n).(^ 2)) (prime_sequence)) 
                 ( n `is_not_dividable_by`)

现在应该可以了。