【问题标题】:Infinite fibonacci sequence无限斐波那契数列
【发布时间】:2014-05-19 19:34:53
【问题描述】:

我正在尝试使用序列在 F# 中模仿 Haskell 著名的无限斐波那契列表。为什么以下序列没有按预期进行评估?评价如何?

let rec fibs = lazy (Seq.append 
                        (Seq.ofList [0;1]) 
                        ((Seq.map2 (+) (fibs.Force()) 
                                       (Seq.skip 1 (fibs.Force())))))

【问题讨论】:

  • 应该注意的是,Haskell 惰性列表与 F# 序列不同(惰性列表在求值时保存在内存中,而在迭代它们时重新计算序列)。这使得 Haskell 模式变慢(如果你直接重写它)或丑陋(如果你添加缓存以提高性能)。
  • @TomasPetricek 澄清一下:你的意思是 Haskell 模式的 F# 实现是缓慢或丑陋的,不是吗? (另外,在@kvb 的解决方案中添加Seq.cache 对我来说似乎还不错?)

标签: f# functional-programming fibonacci lazy-sequences


【解决方案1】:

另一种方式:

let rec fib = seq { yield 0; yield! Seq.scan (+) 1 fib }

【讨论】:

    【解决方案2】:

    除了@kvb 的回答:如果你只是想偷懒而不一定是zip 技巧,你可以这样做:

    let fibs = Seq.unfold (fun (m,n) -> Some (m, (n,n+m))) (0,1)  
    

    这使得Seq.take n fibs 运行时间为 O(n)。

    【讨论】:

      【解决方案3】:

      要添加到kvb 的答案,您还可以使用Seq.unfold 生成(惰性)序列:

      let fibs = Seq.unfold (fun (a, b) -> Some(a+b, (b, a+b))) (0, 1)
      

      val fibs : seq

      【讨论】:

        【解决方案4】:

        问题是你的代码仍然不够懒惰:Seq.append 的参数在结果可以访问之前被评估,但是评估第二个参数 (Seq.map2 ...) 需要评估它自己的参数,这会强制正在定义的相同惰性值。这可以是 通过使用 Seq.delay 函数解决。你也可以放弃lazy wrapper,lists 已经是seqs,所以你不需要Seq.ofList

        let rec fibs = 
            Seq.append [0;1]
                (Seq.delay (fun () -> Seq.map2 (+) fibs (Seq.skip 1 fibs)))
        

        但是,我个人建议使用序列表达式,我发现它更易于阅读(并且更容易正确书写):

        let rec fibs = seq {
            yield 0
            yield 1
            yield! Seq.map2 (+) fibs (fibs |> Seq.skip 1)
        }
        

        【讨论】:

        • 我认为您需要let rec fibs = Seq.cache <| seq { ... 才能正确模拟 Haskell 原版:Haskell 是按需调用的,因此永远不会重新计算递归定义序列的先前计算值。否则,我认为您不会在 O(n) 时间内获得前 n 个数字。
        • 说起来,上述方案的复杂度是多少?
        • 这是一个很好的答案。我很难理解第一个示例和第二个示例是如何相同的。我认为源代码中的这条评论有助于理解:github.com/fsharp/fsharp/blob/…
        猜你喜欢
        • 2015-06-05
        • 1970-01-01
        • 2013-08-03
        • 2014-05-19
        • 2015-08-30
        • 2013-03-14
        • 2013-02-24
        相关资源
        最近更新 更多