【问题标题】:Infinite loop in haskellhaskell中的无限循环
【发布时间】:2013-10-05 11:47:37
【问题描述】:

所以我正在编写一个程序来在 haskell 中生成素数列表。我创建了如下所示的两个函数:

{-
Given a list of prime numbers, this function will
add the next prime number to the list. So, given the 
list [2, 3], it will return [2, 3, 5]
-}
nextPrime xs =  xs ++ [lastVal + nextCounts]
    where
        lastVal       =  (head . reverse) $ xs
        isNextPrime y =  0 `elem` ( map ( y `mod`) xs )
        nextVals      =  (map isNextPrime [lastVal, lastVal+1 ..] )
        nextCounts    =  length $ takeWhile (\x -> x) nextVals


allPrimes xs = allPrimes np
    where 
        np = nextPrime xs

现在函数“nextPrime”正在做它应该做的事情。但是,当我调用 allPrimes 时,如下所示:

take 5 $ allPrimes [2,3]

程序进入无限循环。我认为 Haskell 的“懒惰”功能应该处理所有这些问题?我错过了什么??

【问题讨论】:

  • 问题:allPrimes 什么时候产生第一个值?答:从来没有。它只是用更大的列表调用它自己,这一步永远不会结束。一个简单的解决方案是在再次递归之前产生一些结果。
  • 我建议不要编写一个函数来获取一个列表,然后返回一个带有下一个素数的新列表,为什么不直接创建一个生成无限素数列表的函数呢? Haskell 使用惰性求值,因此您只在请求时计算列表中的一个元素。

标签: haskell infinite-loop lazy-evaluation infinite


【解决方案1】:

如果您开始在纸上评估表达式,您就会明白为什么懒惰在这里没有帮助。从你的表达开始:

take 5 $ allPrimes [2,3]

首先,尝试计算 allPrimes 表达式:

allPrimes [2, 3]

变成了

allPrimes np
    where 
        np = nextPrime [2, 3]

where子句中的东西放入表达式中,就变成了

allPrimes (nextPrime [2, 3])

现在,计算nextPrime [2, 3](您可以在ghci 中执行此操作,因为该函数有效)并得到[2, 3, 5],您可以在前面的表达式中替换它,它变成了

allPrimes [2, 3, 5]

重复上面的,就变成了

allPrimes [2, 3, 5, 7]

这就是你的问题! allPrimes 从未评估为任何值,它评估为 allPrimes 应用于越来越长的列表。要查看惰性在哪里起作用,请尝试在纸上评估 zip 之类的函数,来自 Prelude

zip :: [a] -> [b] -> [(a,b)]
zip (a:as) (b:bs) = (a,b) : zip as bs

zip [1, 2, 3] ['a', 'b', 'c']

a 变为 1as 变为 [2, 3]b 变为 'a'bs 变为 ['b', 'c'],所以你得到

(1, 'a') : zip [2, 3] ['b', 'c']

这里的区别是有一个列表有一个,那么列表的其余部分就是一个表达式。在您的 allPrimes 函数中,您只是不断获得更多表达式

要了解更多信息,请查看弱头范式,但是如果您是 Haskell 新手,我建议您先熟悉语法和“在 Haskell 中思考”的基础知识,然后再开始查看WHNF 之类的东西。

【讨论】:

  • [(1, 'a'), zip ... 实际上是错误类型的,因为 zip 返回一个元组列表。
  • 是的。我会删除它。
  • @Drew:没有什么会强制调用nextPrime [2, 3],所以不是allPrimes (nextPrime (nextPrime ... [2, 3])...)吗?
  • 我不这么认为,但我对图形缩减的了解还不够,无法肯定地说。
【解决方案2】:

我已经阅读了 Drew 的回答,以很好地解释出了什么问题,但为了快速演示如何使这项工作发挥作用,

nextPrime xs =  xs ++ [lastVal + nextCounts]
  where
    lastVal       =  (head . reverse) $ xs
    isNextPrime y =  0 `elem` ( map ( y `mod`) xs )
    -- ^ Style note, this name is super misleading, since it returns
    -- false when the number is prime :)
    nextVals      =  (map isNextPrime [lastVal, lastVal+1 ..] )
    nextCounts    =  length $ takeWhile (\x -> x) nextVals


allPrimes xs = last np : allPrimes np
  where np = nextPrime xs

现在我们正在构建列表,haskell 是惰性的,因此它可以在评估allPrimes np 之前获取np 的最后一个元素。换句话说,head (a : infiniteLoop)a,而不是无限循环。

但是,这真的效率低下。列表在 Haskell 中是单独链接的,所以 lastO(n),而不是像 Python 这样的 O(1)。并且++ 也很昂贵,O(n) 第一个列表的长度。

相反

 nextPrime xs = lastVal + nextCounts
   where lastVal     = head xs
         isNextPrime = 0 `elem` map (y `rem`) xs
         nextVals    = map isNextPrime [lastVal ..]
         nextCount   = length $ takeWhile id nextVals

 allPrimes xs = p : allPrimes (p:xs)
    where p = nextPrime xs

因此,我们将列表颠倒过来以避免那些代价高昂的遍历。我们也可以简化nextPrime

import Data.Maybe
nextPrime xs = fromJust nextPrime
  where isPrime y =  not $ 0 `elem` map (rem y) xs
        nextPrime = find isPrime [head xs ..]

我们只是在列表中搜索第一个素数元素并将其添加到我们的列表中。 fromJust 通常是坏的,如果没有下一个素数我们会得到一个错误。但由于我们在数学上知道总会有下一个素数,所以这是安全的。

最后,代码是这样的

 import Data.Maybe
 import Data.List
 nextPrime xs = fromJust nextPrime
   where isPrime y = 0 `notElem` map (rem y) xs
         nextPrime = find isPrime [head xs ..]
 allPrimes xs = p : allPrimes (p:xs)
   where p = nextPrime xs

要评估它,请致电allPrimes [2]


一个更简洁的方法是有一个函数isPrime,它返回一个数字是否是素数。然后就拥有了

allPrimes = filter isPrime [1..]

但我会把它留给好奇的读者。

【讨论】:

  • 虽然您的建议更简洁,但它可能会更慢。如果isPrime a 函数在a 之前有一个所有素数的列表,则可以更有效地实现它。
  • @Drew 这是真的,这就是为什么说清洁不快:)
  • 很公平。就我个人而言,我不愿推荐更清洁但更慢的解决方案,这对我来说似乎是错误的权衡。
  • @Drew 我提到它是因为filter 是一个非常常见的习语,通常值得在任何有关列表处理的帖子中提出。但更高效的实现是在它上面 10 行,所以我认为这里还可以
【解决方案3】:

正如 Drew 指出的那样,您的函数 allPrimes 不会从懒惰中受益,因为我们永远无法访问它的计算结果。这是因为我们要查看的列表是 allPrimes 的参数,而不是返回值。

因此我们需要公开 allPrimes 正在构建的列表,并且仍然保留一个函数调用,该函数调用将无限构建此列表的以下值。

好吧,既然 allPrimes 是一次又一次地重新应用自身,我们只需要一个公开中间值的函数。我们有一个!

 iterate f a == [a, f (f a),...]

因此,使用 iterate 和 nextPrime,我们可以构建以下(相当奇怪的)函数:

-- nextPrime renamed as nextPrimeList
infiniteListofListofPrimes =  iterate nextPrimeList [2,3]
primeN n =   (infiniteListofListofPrimes !! n) !! n
takeN n  =  take n (infiniteListofListofPrimes !! n)

我们正在生成我们的素数,但它看起来并不好。我们宁愿拥有[primes],而不是多余的[[some primes]]

下一步是在 WHNF 上构建列表:

elem1:elem2:aux
where aux = newvalue:aux

aux 将计算新值并将所有内容留待下一个。

为此,我们需要nextPrime 坚持生成一个新的素数:

nextPrime xs = lastVal + nextCounts

并找到可以永远构建listOfPrimesaux

我想出了这个:

  infiniteListofPrimes = 2:3:aux 2
       where aux n  = nextPrime (take n infiniteListofPrimes):(aux (n+1))

【讨论】:

    猜你喜欢
    • 2016-01-27
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-05
    • 2012-04-06
    • 1970-01-01
    相关资源
    最近更新 更多