【问题标题】:Generating Fibonacci numbers in Haskell?在 Haskell 中生成斐波那契数字?
【发布时间】:2010-11-09 11:56:49
【问题描述】:

在 Haskell 中,如何根据第 n 个斐波纳契数等于第 (n-2) 个斐波纳契数加上第 (n-1) 个斐波纳契数的属性生成斐波纳契数?

我看过这个:

fibs :: [Integer]
fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

我真的不明白这一点,也不明白它是如何产生一个无限列表而不是一个包含 3 个元素的列表。

我如何编写通过计算实际定义而不是通过对列表函数做一些非常奇怪的事情来工作的 haskell 代码?

【问题讨论】:

  • 如果你避免使用“奇怪的”列表函数,你就会错过 Haskell 的所有乐趣。但是对于它的价值,这里有一个很好的解释递归是如何在上面的代码中工作的:scienceblogs.com/goodmath/2006/11/…
  • @rtperson 链接的文章现在位于scienceblogs.com/goodmath/2006/11/28/…
  • 斐波那契系列有一个替代的 Haskell 定义,我认为它更容易分析:| fibSerie a b = a : (fibSerie b (a+b)) 然后:fibs = fibSerie 1 1
  • ω = 2 + min ω (ω - 1)zipWith 在这里生成一个(无限)整数列表,而不仅仅是一个整数,所以它不是2 + 1 整体元素,而是2 + ω。这是ω

标签: haskell fibonacci


【解决方案1】:

这是一个计算第 n 个斐波那契数的不同且更简单的函数:

fib :: Integer -> Integer
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

您所指的实现基于一些关于斐波那契值如何相互关联的观察(以及 Haskell 如何根据自身定义数据结构,从而有效地创建无限数据结构)

你问题中的函数是这样工作的:

假设您已经有一个无限的斐波那契数列:

   [ 1, 1, 2, 3, 5,  8, 13, .... ]

此列表的tail

   [ 1, 2, 3, 5, 8, 13, 21, .... ]

zipWith 使用给定的运算符逐个元素地组合两个列表:

   [ 1, 1, 2, 3,  5,  8, 13, .... ]
+  [ 1, 2, 3, 5,  8, 13, 21, .... ]
=  [ 2, 3, 5, 8, 13, 21, 34, .... ]

因此,斐波那契数的无限列表可以通过将元素 11 添加到使用 + 将斐波那契数的无限列表的尾部与斐波那契数的无限列表的尾部压缩的结果之前来计算运算符。

现在,要获取第 n 个斐波那契数,只需获取斐波那契数无限列表的第 n 个元素:

fib n = fibs !! n

Haskell 的美妙之处在于它在需要之前不会计算斐波那契数列中的任何元素。

我让你的头爆炸了吗? :)

【讨论】:

  • 我喜欢 - 通过将您要计算的列表的相应值相加来计算列表。我的大脑通常不会那样工作 - 这就像试图观察自己的耳朵。
  • fib 0 = 1 应该是 fib 0 = 0。我只注意到这一点,因为我刚刚犯了同样的错误。哈哈。
  • @Christopher 有时会省略序列的前 0。
  • @Abarax 不,实际上尾递归会使这个技巧变得不可能。它是惰性和受保护的递归,递归调用在构造函数字段的每一步中,fibo : recursive_call,所以要达到它,我们必须解构上一次调用的结果。因此递归深度永远不会大于 1。
  • @Zelphir 您正在使用0 : 1 : zipWith (+) fibs (tail fibs) 生成无限列表。您从 [0, 1...] 开始,然后将 zipWith (+) fibs (tail fibs) 附加到它上面。 fibs 的第一个元素是0,tail fibs 的第一个元素是10 so the next element is 0 + 1 = 1` 给你[0, 1, 1...] 现在你得到zipWith ... 的第二个元素1 + 1 = 2给你@ 987654342@等等。
【解决方案2】:

按照定义,斐波那契数列的每一项都是前两项之和。把这个定义放到懒惰的haskell中会给你这个!

fibo a b = a:fibo b (a+b)

现在只需从 fibo 中提取 n 个从 0,1 开始的项目

take 10 (fibo 0 1)

【讨论】:

  • a, b = (0,1) : (b, a+b) 或在 Haskell 中,map fst $ ((\(a,b)->(b,a+b)) iterate` (0,1))`。 :)
  • fibs = map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)wiki.haskell.org/The_Fibonacci_sequence#With_iterate
  • fibs = 0 : 1 : zipWith (+) fibs (tail fibs)相比,计算复杂度是多少?
  • 这是一个美丽的功能,美丽是数学和编程的一切。简单而有说服力是非凡的。它富有诗意,紧凑而富有意义。
【解决方案3】:

扩展 dtb 的答案:

“简单”的解决方案有一个重要区别:

fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)

还有你指定的那个:

fibs = 1 : 1 : zipWith (+) fibs (tail fibs)

简单的解决方案需要O(1.618NN) 时间来计算第 N 个元素,而您指定的需要 O(N2)。那是因为您指定的那个考虑到计算fib nfib (n-1)(计算它是必需的)共享fib (n-2) 的依赖关系,并且可以为两者计算一次以节省时间。 O(N2) 是用于 O(N) 位数的 N 次加法。

【讨论】:

  • @newacct: 如果你只想要“fibs !! n”,你需要计算所有的“拿 n 个 fibs”,n 个项目,每个计算 O(n),因为两个数字相加O(n) 位数是 O(n)。
  • @newacct:您假设“fib k”(其中 k 是一个常数)的每个不同的动态出现都被合并到一个单一的 thunk 中。在这种情况下,GHC 可能足够聪明,可以做到这一点,但我认为不能保证。
  • 好吧,我误读了这个问题。我看到你已经说了我想说的
  • 为什么不简单地说黄金比例 (Phi) 而不是不精确的1.618
  • @Zelphir:这需要读者也熟悉黄金比例。精确性对这个论点并不重要
【解决方案4】:

斐波那契数列here 有多种不同的 Haskell 算法。 “幼稚”的实现看起来就像您所追求的那样。

【讨论】:

    【解决方案5】:
    fibs :: [Integer]
    fibs = 1 : 1 : zipWith (+) fibs (tail fibs)
    

    首先,使用fibstail fibs,我们可以得到第三个元素:

    fibs                        : [1, 1, ?
    tail fibs                   : [1, ?
    zipWith (+) fibs (tail fibs): [2, ?
    

    现在,我们知道第三个是 2,我们可以得到第四个:

    fibs                        : [1, 1, 2, ?
    tail fibs                   : [1, 2, ?
    zipWith (+) fibs (tail fibs): [2, 3, ?
    

    现在是 5 号:

    fibs                        : [1, 1, 2, 3, ?
    tail fibs                   : [1, 2, 3, ?
    zipWith (+) fibs (tail fibs): [2, 3, 5, ?
    

    等等..

    【讨论】:

      【解决方案6】:

      fibonaci(n)的定义是:

      fibonacci (n) = fibonacci (n-1) + fibonacci (n-2)

      Haskell 中的幼稚实现

      fibonacci :: Integer -> Integer
      fibonacci 0 = 1
      fibonacci 1 = 1
      fibonacci x = fibonacci (x-1) + fibonacci (x-2)
      

      所有的公式都可以追溯到这个定义,有些跑得很快,有些跑得很慢。上面的实现有 O(n) = 2^n

      本着你问题的精神,让我删除列表的使用,并给你一些在 O(n) 中运行的东西,即我们不要将所有从 0 到 n 的斐波那契数都保存在一个列表中。

      如果我们有一个三元组(一个包含三个成员的元组),看起来像:

      (n, fibonacci[n-1], fibonacci[n])

      记住最初的定义,我们可以从上一个三元组计算下一个三元组

      (n+1, fibonacci[n], fibonacci[n-1] + fibonacci[n]) = (n+1, fibonacci[n], fibonacci[n+1])

      最后一个三元组的下一个三元组: (n+2, fibonacci[n+1], fibonacci[n] + fibonacci[n+1]) = (n+1, fibonacci[n+1], fibonacci[n+2])

      等等...

      n = 0 => (0,0,1) 
      n = 1 => (1,1,1) - calculated from the previous triple
      n = 2 => (2,1,2) - calculated from the previous triple
      n = 3 => (3,2,3) - calculated from the previous triple
      n = 4 => (4,3,5) - calculated from the previous triple
      n = 5 => (5,5,8) - calculated from the previous triple
      

      让我们在 Haskell 中实现它并使用自我解释的变量名称:

      nextTripleIfCurrentNIsLessThanN :: (Int, Integer, Integer) -> Int -> (Int, Integer, Integer)
      nextTripleIfCurrentNIsLessThanN (currentN, x, y) n = if currentN < n
      then nextTripleIfCurrentNIsLessThanN (currentN + 1, y, x + y) n
      else (currentN, x, y)
      
      thirdElementOfTriple :: (x,y,z) -> z
      thirdElementOfTriple (x,y,z) = z
      
      fibonacci :: Int -> Integer
      fibonacci n = thirdElementOfTriple (nextTripleIfCurrentNIsLessThanN (0,0,1) n)
      

      这将在 O(n) 中工作 [它是温和的二次方,大量出现。原因是添加大数字比添加小数字更昂贵。但这是关于计算模型的单独讨论。]

      fibonacci 0
      1
      fibonacci 1
      1
      fibonacci 2
      2
      fibonacci 3
      3
      fibonacci 4
      5
      fibonacci 5
      8
      fibonacci 5000
      6276302800488957086035253108349684055478528702736457439025824448927937256811663264475883711527806250329984690249846819800648580083040107584710332687596562185073640422286799239932615797105974710857095487342820351307477141875012176874307156016229965832589137779724973854362777629878229505500260477136108363709090010421536915488632339240756987974122598603591920306874926755600361865354330444681915154695741851960071089944015319300128574107662757054790648152751366475529121877212785489665101733755898580317984402963873738187000120737824193162011399200547424034440836239726275765901190914513013217132050988064832024783370583789324109052449717186857327239783000020791777804503930439875068662687670678802914269784817022567088069496231111407908953313902398529655056082228598715882365779469902465675715699187225655878240668599547496218159297881601061923195562143932693324644219266564617042934227893371179832389642895285401263875342640468017378925921483580111278055044254198382265567395946431803304304326865077742925818757370691726168228648841319231470626
      

      【讨论】:

        【解决方案7】:

        使用迭代

        fibonaci = map fst (iterate f (0,1)) where f (x,y) = (y,x+y)
        

        使用

        take 10 fibonaci
        
        [0,1,1,2,3,5,8,13,21,34,55,89,144,233,377]
        

        【讨论】:

          【解决方案8】:

          通过unfoldr 可以轻松实现生成无限斐波那契数列的一种懒惰方式,如下所示;

          fibs :: [Integer]
          fibs = unfoldr (\(f,s) -> Just (f,(s,f+s))) (0,1)
          

          【讨论】:

            【解决方案9】:

            大声笑,我喜欢 Haskell 模式匹配,但它在标准斐波那契函数中变得毫无用处。标准列表是从右侧构建的。要使用模式匹配和缺点,必须从左侧构造列表。好吧,至少有一个安慰是,这真的很快。 〜O(n),应该是。需要一个辅助函数来反转无限列表(您只能在 Haskell 中做的事情,joy),并且该函数输出运行的每个后续列表,因此“last”也用于辅助函数管道中。

            f (x:y:xs) = (x+y):(x:(y:xs))
            

            帮手

            fib n = reverse . last . take n $ iterate f [1,0]
            

            这是一个列表版本,我认为,它解释了列表是如何构造的,这就是目的。我想做一个元组版本。

            2018 年 3 月 15 日编辑

            首先,Will Ness 启发了我,即每次迭代生成整个列表是不必要的,只需要使用的最后两个值,结果列表的值是每个列表的第一个值或对生成。太有趣了。在威尔告诉我列表的值是列表的第一个值之后,我运行它并看到值 0,1,1,2,3,5,8,13 作为每个列表的每个头部,我说 WTF,会在我的电脑上更改我的代码吗?价值观在那里,但如何!?过了一会儿,我意识到他们一直在那里,但我只是没有看到他们。啊。 Will的函数和辅助函数的版本是:

            f = (\(x:y:xs) -> (x+y):x:xs) -- notice, no y: put back only x+y & x
            

            和他的辅助函数重写

            fib n = map head . take n $iterate f [0,1]
            

            我也认为它们现在可以合并:

            fib n = take n . map head $ iterate (\(x:y:xs) -> (x+y):x:xs) [0,1]
            

            顺便说一句,该函数也可以与元组一起使用

            fib n = take n . map fst $ iterate (\(a,b) -> (b,a+b)) (0,1)
            

            另一种形式,列表推导形式,也可以写成所有人:

            fib n = take n [ fst t | t <- iterate (\(a,b) -> (b,a+b)) (0,1)]
            

            这些都是迭代且健壮的。最快的是 fib 5000 的列表为 12.23 秒的 map。fib 5000 的元组理解在 13.58 秒时第二快。

            【讨论】:

            • haskell 列表可以从顶部(左)构造,但同样容易,使用受保护的递归(即由于惰性;例如this answer)。 last . take n 只是 (!! (n-1))。使用您的fibfib n 并不能帮助我们尽可能多地找到fib (n+1)。只需定义 fibs = map head $ iterate f [1,0] 然后 fib n = fibs !! n。现在我们发现它在每一步都创建了一个完整的列表,但只使用了它的两个头元素,所以我们将其更改为fibs = map fst $ iterate g (1,0)f 相应地更改为g。瞧。
            • 需要真实的视觉才能看到生成的每个列表的开头都是所需的数字。我缺乏那种远见。非常感谢,这一课远远超出了这个问题和你对它的深刻洞察。也就是说,我认为 map fst $ iterate g (1,0) 是令人愉快的幽默。 tuple 版本确实是要替换 f 也是在 "fibs = map head $ iterate f [1,0]" 中使用 [0,1] 作为参数导致 0 作为 "take n $ map head 的输出列表的头部$ iterate f [0,1]' 我没有元组版本的工作概念,但是,是的,语言中的懒惰比冰淇淋好。几乎。
            • 试试mapM_ print $ take 15 $ iterate f [1,0]。现在将f 更改为f (x:y:xs) = (x+y):(x:xs) 并再次尝试mapM_ ... 行并比较输出。
            • 想被懒惰吹走,试试ps n = q where q = scanl (\\) [2..n] [[p,p+p..n] | p &lt;- map head q],然后试试map head $ ps 100map head $ ps 555。您可能需要先import Data.List 才能获得(\\)。要查看那里发生了什么,请尝试mapM_ print $ ps 100
            • @Will Ness 是个巫师 他用“f (x:y:xs) = (x+y):(x:xs)” 改进了我的抱歉代码,它更简洁。他对辅助函数的修改是“map head $ take 24 $ iterate f [0,1]”,这也非常干净,Haskell 的懒惰防止了任何性能损失,因为表达清晰。我是 Haskell 新手,所以珍惜这个网站和 Will Ness 的精彩人物 B/c,我刚刚使用了一个 monad,很快将开始探索我也从未做过的 '\\' 运算符和 scanl,Will Ness,我是什么真正寻找的是 f 。 F 。 f ... f (x) 使用 Y 组合器应该很甜
            【解决方案10】:

            输入代码,你的定义是

            fib :: Int -> Integer
            fib 0 = 1
            fib 1 = 1
            fib n = fib (n-1) + fib (n-2)
              -- i.e.
              -- fib (n+2) = fib (n+1) + fib n
            

            Int -&gt; a ~= [a] 因为

            from f = map f [0..]     -- from :: (Int -> a) -> [a]
            to = (!!)                -- to :: [a] -> (Int -> a)
            

            这样

            fibs :: [Integer]
            fibs = from fib 
            
            fibs !! 0 = 1
            fibs !! 1 = 1
            fibs !! (n+2)    = fibs !! (n+1)     +  fibs !! n
            -- or,
            drop 2 fibs !! n = drop 1 fibs !! n  +  fibs !! n
                             = zipWith (+) (tail fibs) fibs !! n
            -- i.e.
            take 2 fibs = [1,1]
            drop 2 fibs = zipWith (+) (tail fibs) fibs
            -- hence, 
            fibs = take 2 fibs ++ drop 2 fibs
                 = 1 : 1 : zipWith (+) (tail fibs) fibs
            

            或者,如a, b = (0,1) : (b, a+b)

            fibs :: [Integer]
            fibs = a
              where
              (a,b) = unzip $ (0,1) : zip b (zipWith (+) a b)
            

            【讨论】:

              【解决方案11】:

              我在做CIS194的作业6,发现你可以这样写。 计算前 n 个元素只需要 O(n) 次加法运算。

              fibs2 :: [Integer]
              fibs2 = [0, 1] ++ [fibs2 !! (n-1) + fibs2 !! (n-2) | n <- [2..]]
              

              【讨论】:

                猜你喜欢
                • 2011-12-18
                • 2015-04-25
                • 2017-12-06
                • 2011-02-17
                • 2015-01-06
                • 2017-11-13
                • 2013-02-24
                • 1970-01-01
                相关资源
                最近更新 更多