【问题标题】:Generating Fibonacci series in F#在 F# 中生成斐波那契数列
【发布时间】:2011-02-20 04:55:02
【问题描述】:

我刚开始使用 VS2010 学习 F#,下面是我第一次尝试生成斐波那契数列。我要做的是建立一个所有小于 400 的数字的列表。

let fabList = 
    let l =  [1;2;]
    let mutable a = 1
    let mutable b = 2
    while l.Tail < 400 do
        let c = a + b
        l.Add(c)
        let a = b
        let b = c

我的第一个问题是,在最后一条语句中,我在最后一行收到错误消息“在表达式中的此点或之前的结构构造不完整”。我不明白我在这里做错了什么。

虽然这似乎是一种以相当有效的方式(来自 c++/C# 程序员)构建列表的明显方式,但根据我对 f# 的了解,这似乎不是正确的方式做节目。我的这种感觉对吗?

【问题讨论】:

  • 是的,你做错了。您正在使用一种函数式编程语言,例如程序语言。首先尝试不使用while 或任何类似的循环结构。

标签: f# fibonacci tail-recursion


【解决方案1】:

其他帖子告诉您如何使用递归函数编写 while 循环。这是在 F# 中使用 Seq 库的另一种方式:

// generate an infinite Fibonacci sequence
let fibSeq = Seq.unfold (fun (a,b) -> Some( a+b, (b, a+b) ) ) (0,1)
// take the first few numbers in the sequence and convert the sequence to a list
let fibList = fibSeq |> Seq.takeWhile (fun x -> x<=400 ) |> Seq.toList

如需解释,请参考solution 2 中的F# for Project Euler Problems,其中解决了前50 个欧拉问题。我想你会对这些解决方案感兴趣。

【讨论】:

  • 感谢链接 - 用于 F# 解决 Project Euler 问题。我正在解决其中一些问题以帮助学习 F#。
  • 我认为第一行实际上应该是: let fibSeq = Seq.unfold (fun (a,b) -> Some( a+b, (a+b, a) ) ) (0, 1)
  • 有没有什么方法可以在不重复 a+b 的情况下在功能上做到这一点?
  • F# for Project Euler Problems 网站已过期,这是一个存档:web.archive.org/web/20120702222856/http://…
【解决方案2】:

首先,您使用let 就好像它是一个改变变量的语句,但事实并非如此。在 F# 中,let 用于声明一个新值(它可能隐藏任何以前的同名值)。如果你想使用突变编写代码,那么你需要使用类似的东西:

let c = a + b  // declare new local value
l.Add(c)  
a <- b   // mutate value marked as 'mutable'
b <- c   // .. mutate the second value

您的代码的第二个问题是您试图通过向其中添加元素来改变 F# 列表 - F# 列表是不可变的,因此一旦创建它们,您就无法修改它们(特别是,没有 Add成员!)。如果你想用变异来写这个,你可以写:

let fabList = 
  // Create a mutable list, so that we can add elements 
  // (this corresponds to standard .NET 'List<T>' type)
  let l = new ResizeArray<_>([1;2])
  let mutable a = 1
  let mutable b = 2
  while l.[l.Count - 1] < 400 do
    let c = a + b
    l.Add(c) // Add element to the mutable list
    a <- b
    b <- c
  l |> List.ofSeq // Convert any collection type to standard F# list

但是,正如其他人已经指出的那样,以这种方式编写代码并不是惯用的 F# 解决方案。在 F# 中,您将使用不可变列表和递归而不是循环(例如 while)。比如这样:

// Recursive function that implements the looping
// (it takes previous two elements, a and b)
let rec fibsRec a b =
  if a + b < 400 then
    // The current element
    let current = a + b
    // Calculate all remaining elements recursively 
    // using 'b' as 'a' and 'current' as 'b' (in the next iteration)
    let rest = fibsRec b current  
    // Return the remaining elements with 'current' appended to the 
    // front of the resulting list (this constructs new list, 
    // so there is no mutation here!)
    current :: rest
  else 
    [] // generated all elements - return empty list once we're done

// generate list with 1, 2 and all other larger fibonaccis
let fibs = 1::2::(fibsRec 1 2)

【讨论】:

  • 感谢您的详细回答。不变性作为一种正常的编程模式是我仍在尝试使用的东西,您的解释为我澄清了概念。更不用说函数式编程了。
  • @photo_tom:这就是我们所有人第一次有必要背景的感觉:-)。不变性是基本概念之一,其余的功能思想都由此而来。
  • 非常好的答案。值得一提的一点是,作为创建递归函数时的一般方法,对函数本身的调用应该始终是该特定分支中的最后一个操作,以便可以执行尾调用优化。在这种以 400 作为限制的特殊情况下,stackoverflow 通常不是问题
【解决方案3】:
let rec fibSeq p0 p1 = seq {
    yield p0
    yield! fibSeq p1 (p0+p1)
}

【讨论】:

  • 投了反对票,因为没有任何解释的裸代码不太可能帮助自称“刚开始学习 F#”的 OP。
  • np。我同意这个理由。
  • 不错的无限序列,可以懒惰地评估。只是不要尝试将无限序列转换为数组或列表,除非你有很多 RAM ;)。
  • 值得注意的是,将 fibSeq 传输到 Seq.Cache 以获得更好的性能
【解决方案4】:

这是一个使用序列表达式的无限尾递归解决方案。它非常有效,只需几秒钟即可生成第 100,000 个术语。 "yield" 运算符就像 C# 的 "yield return" 和 "yield!"运算符可以读作“yield all”,而在 C# 中,您必须执行“foreach item ... yield return item”。

https://stackoverflow.com/questions/2296664/code-chess-fibonacci-sequence/2892670#2892670

let fibseq =    
    let rec fibseq n1 n2 = 
        seq { let n0 = n1 + n2 
              yield n0
              yield! fibseq n0 n1 }
    seq { yield 1I ; yield 1I ; yield! (fibseq 1I 1I) }

let fibTake n = fibseq |> Seq.take n //the first n Fibonacci numbers
let fib n = fibseq |> Seq.nth (n-1) //the nth Fibonacci number

这种方法类似于 C# 中的以下方法(使用 while(true) 循环而不是递归):

Finding Fibonacci sequence in C#. [Project Euler Exercise]

【讨论】:

  • C# 对 yield 的使用是我必须要做的事情。一种全新的收益率思考方式。我只是在学习 F#,所以 f# 的答案很酷,但试图理解它却让我头疼。谢谢!!!
  • 确实!我在 2008 年拿起了 Expert F# 书,通读并尽可能多地吸收,但当时还没有真正准备好。但是,它让我在日常工作中尝试了 C# 中的 yield/IEnumerable 和委托(即使是 pre-linq C# 2.0 也具有这些功能),而且我现在发现,两年后,我的时间要容易得多认真的 F#。
【解决方案5】:

是的,可变变量和 while 循环通常是您的代码不是很实用的好兆头。斐波那契数列也不是以 1,2 开头 - 它以 0,1 或 1,1 开头,具体取决于您询问的对象。

我会这样做:

let rec fabListHelper (a:int,b:int,n:int) =
  if a+b < n then
    a+b :: fabListHelper (b, a+b, n)
  else
    [];;

let fabList (n:int) = 0 :: 1 :: fabListHelper (0,1, n);;

(*> fabList 400;;
val it : int list = [0; 1; 1; 2; 3; 5; 8; 13; 21; 34; 55; 89; 144; 233; 377]*)

【讨论】:

  • fabListHelper 不会进行尾调用优化吧?我的意思是缺点“a+b::fabListHelp(b,a+b,n)”会阻止尾调用优化,不是吗?
【解决方案6】:

一个使用聚合(折叠):

let fib n = 
  [1..n] |> List.fold (fun ac _ -> (ac |> List.take 2 |> List.sum) :: ac) [1;1] |> List.rev

【讨论】:

    【解决方案7】:

    一个数组:

    let fibonacci n = [|1..n|] |> Array.fold (fun (a,b) _ -> b, a + b) (0,1) |> fst
    

    【讨论】:

      【解决方案8】:

      这个函数“fib”将返回一个不大于 500 的斐波那契数列

      let rec fib a b =
          let current = a + b
          match current with
          | _ when current >= 500 -> []
          | _ -> current :: fib b current 
      
      let testFib = fib 1 2;;
      

      【讨论】:

        【解决方案9】:

        这是 .Net 大师 Scott Hanselman 撰写的一篇关于在 F# 中生成斐波那契数列的好文章

        let rec fib n = if n < 2 then 1 else fib (n-2) + fib(n-1)
        

        http://www.hanselman.com/blog/TheWeeklySourceCode13FibonacciEdition.aspx

        它还与其他语言进行比较作为参考

        【讨论】:

        • a) 这是一种非常低效的计算斐波那契数的方法,并且 b) 使用它来创建列表效率更低,因为您必须为列表中的每个项目再次计算整个事物。
        【解决方案10】:

        另一种 codata 方式:

        let rec fib = seq {
          yield! seq {0..1}
          yield! Seq.map (fun(a,b)->a+b) <| Seq.zip fib (Seq.skip 1 fib)
        }
        let a = fib |> Seq.take 10 |> Seq.toList
        

        【讨论】:

          【解决方案11】:

          Scott Hanselman 的出色解决方案没有报告斐波那契数列以 0 开头。

          所以这里是他的解决方案的一个小改动,也报告了 0。 我使用了一个从 0 到 10 的小列表来显示序列的前 11 项。

          let nums=[0..10]
          let rec fib n = if n < 1 then 0 else if n < 2 then 1 else fib (n-2) + fib(n-1)
          let finres = List.map fib nums
          printfn "%A" finres
          

          对于 f#,我是新手且无能,但仍不能完全理解它的需求。但发现这是一个有趣的测试。

          只是为了好玩:如果找到计算第 n 个斐波那契数的 Binet 公式。 不幸的是,需要一些浮点函数才能最终得到整数结果: [比内的斐波那契公式][1]

          http://i.stack.imgur.com/nMkxf.png

          let fib2 n = (1.0 / sqrt(5.0)) * ( (((1.0 + sqrt(5.0)) /2.0)**n)  -  (((1.0 -  sqrt(5.0)) /2.0)**n) )
          let fib2res = fib2 10.0
          System.Console.WriteLine(fib2res)
          let strLine = System.Console.ReadLine()
          

          对 f# 的快速而肮脏的翻译将如上所示。我相信其他人可以在风格和效率方面有所改进。该示例计算第 10 个数字。结果将是 55。

          【讨论】:

          • 您的答案似乎与@DavidReihans 的重复。此外,正如 cmets 对其他问题所指出的那样,这种方法既不有效也不尾递归。
          猜你喜欢
          • 2011-12-18
          • 2015-04-25
          • 2017-11-13
          • 1970-01-01
          • 2012-04-26
          • 2013-02-24
          • 1970-01-01
          • 1970-01-01
          • 2012-04-09
          相关资源
          最近更新 更多