【问题标题】:How do I get pairs of elements from infinite lists in Haskell?如何从 Haskell 的无限列表中获取元素对?
【发布时间】:2014-02-04 23:40:22
【问题描述】:

一般问题

我有一个无限列表,我想选择一对(a,b),其中ab 都来自列表,并且这对满足某些属性。使用列表推导似乎不起作用,因为列表是无限的。

具体实例

我试图找到一对加起来等于给定数字的素数(请参阅this code golf problem)。

我已经定义了primes,它是一个无限的素数列表,但是当我天真地尝试如下选择一对素数时,该过程永远不会终止。

goldbach n = head [(a,b) | a<-primes, b<-primes, a+b==n]

我意识到这是因为生成的素数列表是[(2,2), (2,3), (2,5)...]。基本上,a 正在成为primes 的第一个元素,然后,一旦b 用尽,它将移动到第二个元素。因为primes是无限的,所以永远不会耗尽!

有没有一种简单的方法来使用列表推导来解决这个问题?如果做不到这一点,有没有简单的解决方案?

【问题讨论】:

  • 请注意,NovaDenizen 的解决方案比我的要高效得多。

标签: haskell functional-programming lazy-evaluation


【解决方案1】:

最好的方法是使用breadth-first list monad。由于列表推导可以被视为只是 monad 语法糖(很像 do),因此您确实可以使其看起来完全像现在的样子:

{-# LANGUAGE MonadComprehensions #-}
import Control.Monad.Omega

goldbach n = head $ runOmega [(a,b) | a<-ps, b<-ps, a+b==n]
  where ps = each primes

【讨论】:

  • 这似乎正是我想要的(而且非常干净!)。我承认我是一个 Haskell 菜鸟,而且我还没有开始理解 monad,所以我暂时不会找到这个。谢谢!
  • @crazedgremlin 我确实倾向于认为我不再是 Haskell 的新手,但我从来没有考虑过一个名为“runOmega”的函数在这里会有所帮助。有时,非常高级别的抽象意味着函数名称变得如此通用,以至于您只需要知道它们就可以知道发生了什么——名称本身并不能告诉您任何事情。
【解决方案2】:
goldbach n = head [(a,b) | let ps = takeWhile (<n) primes, a<-ps, b<-ps, a+b==n]

不过这样会快很多。

goldbach2 n = aux ps (reverse ps) where
    ps = takeWhile (<n) primes
    aux [] _ = []
    aux _ [] = []
    aux (a:as) (b:bs)  
      | a > b = []
      | otherwise = case compare (a+b) n of
        EQ -> (a,b):aux as bs
        LT -> aux as (b:bs)
        GT -> aux (a:as) bs

【讨论】:

  • +1 表示goldbach2,但我认为缺少head 调用 - 我想应该提到该函数假定列表primes 按升序排序(其中当然,这不是不合理的假设 - 但仍然)。
  • 我选择你的解决方案是最好的,因为它不需要单子。谢谢!
  • @crazedgremlin:你的意思是,它不需要外部库类型(确实是一个有效的参数)。您会感到惊讶,实际上在这个解决方案中也有 两个 monads...
【解决方案3】:

很遗憾您没有接受@leftaroundabout 的解决方案。它还有很多。例如,另一个解决方案有一个启发式“所有数字必须小于 n”(如果你没有这个启发式的问题,你会怎么做?),以及一些其他的步骤,这使得它更难证明它实际上是是解决哥德巴赫问题的方法 - 例如你能证明枚举素数的方式将把所有有用的素数对放在一起吗? (确实如此,但你能证明一下吗?这是该解决方案的弱点

所以在这里我将展示如何构建@leftaroundabout 提出的解决方案,而不用说“monad”这个词。

您最初构建的列表理解的问题在于它搜索“深度优先”的解决方案 - 从第一个列表中获取一个元素,然后尝试使用该元素的所有组合。但是,如果有无限数量的组合,您将永远无法使用第一个列表中的第二个元素枚举任何对。这就是我们需要讨论“广度优先”搜索解决方案的地方。

假设gen 是解决方案的生成器:

gen x = map (\y -> (x,y)) -- construct pairs for a given element x

假设gens 是解决方案生成器的生成器:

gens xs ys = map (\x -> gen x ys) xs

我们现在需要做的就是通过一次从每个生成器中获取一个解决方案来枚举解决方案。请注意,如果生成器的数量是无限的,我们不能一个接一个地获取它们——那样我们将永远无法从第一个生成器中获取第二个解决方案,因此我们需要以某种方式“步进”它们:

om xs = f [] [] xs where -- this is your omega enumerating solutions
                         -- from generator of generators
  f [] [] [] = [] -- this is where the pot stops cooking
  f [] gs [] = f gs [] []
  f [] gs (g:gens) = f (g:gs) [] gens -- ok, took one solution from all generators -
                          -- add one generator to the list of generators we enumerate
                          -- next time - this is how we "step" the generators
  f ([]:gs) gs' gens = f gs gs' gens  -- generator eliminator: last solution was taken
                          -- from one generator
  f ((x:xs):gs) gs' gens = x : f gs (xs:gs') gens -- take one solution from the first
                          -- generator, and move the rest of the generator to the list
                          -- for the next iteration

就是这样。

> find (\(x,y) -> x+y==190) $ om $ gens primes primes
Just (179,11)

现在,为了效率。全部在gensgen 中。给gens加一个条件到takeWhile ((&lt;=n) . (x+)),即使哥德巴赫猜想错了也能行(但你根本不用加)。将条件添加到gentakeWhile (&lt;=x),您将只枚举第一个质数大于另一个质数的对。

【讨论】:

    【解决方案4】:

    或一个 lambda,从无限列表中给出无限的对列表

    (\x->let p=(\(a:b:oo)->(a,b):p oo) in p x)

    关于 ghci let p=(\x->let p=(\(a:b:oo)->(a,b):p oo) in p x) take 10 (p [1..])

    【讨论】:

      猜你喜欢
      • 2011-01-02
      • 1970-01-01
      • 2015-02-28
      • 1970-01-01
      • 2021-06-26
      • 2021-12-23
      • 2014-12-16
      • 1970-01-01
      • 2023-03-07
      相关资源
      最近更新 更多