繁体   English   中英

了解 Haskell 的斐波那契

[英]Understanding Haskell's fibonacci

fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]

这会生成斐波那契数列。

我理解:ziptail警卫的行为,但我不明白<- 它在这里做什么?

由于投票,我将我的评论变成了答案。

你看到的不是守卫,而是列表理解 首先,将其视为一种表达数学集合符号的方式,例如 A = { x | x 元素 N } 表示以下内容: 集合 A 是所有自然数的集合。 在列表理解中,这将是[x | x <- [1..] ] [x | x <- [1..] ]

您还可以对数字使用约束: [x | x <- [1..], x `mod` 2 == 0 ] [x | x <- [1..], x `mod` 2 == 0 ]和许多其他东西。

有很多好的haskell turorials 涵盖了列表理解,甚至还有一个关于haskell 资源的StackOverflow 问题。

唯一棘手的是zip fibs (tail fibs) zip只是从它的每个参数中生成一个成对列表。 因此,如果您有两个这样的列表:

[ 1, 2, 3, 4 ]
[ "a", "b", "c", "d" ]

压缩它们将使:

[ (1,"a"), (2,"b"), (3,"c"), (4,"d") ]

左箭头(分配到解构模式)只是提取成对的元素,以便将它们添加在一起。 被压缩的两个列表是fibs(tail fibs) —— 换句话说,Fibonacci 序列和偏移 1 个元素的 Fibonacci 序列。 Haskell 是惰性求值的,因此无论需要多少元素,它都可以计算列表。 这也适用于 zip。

让我们展开它。

zip从两个列表的内容中创建对。 所以第一对zip fibs (tail fibs)给我们的是(0, 1) ,加起来为 1。所以现在列表是[0,1,1] 我们现在知道列表中的三个元素,因此列表推导式可以继续,从列表中抓取下一项和从尾部抓取下一项,得到(1,1) — 相加,得到 2。然后我们得到下一个对,即(1,2) ,使序列中的下一个数字为 3。这可以无限地继续,因为理解将始终提供足够的项目。

对于它的价值,我发现以下版本更容易理解:

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

函数式编程的一个优点是您可以像数学问题一样手动计算表达式:

fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
     = 0 : 1 : [ a + b | (a, b) <- zip [0, 1, ??] (tail [0, 1, ??])]

这里的?? 是尚未评估的部分。 我们将在继续时填写它。

     = 0 : 1 : [ a + b | (a, b) <- zip [0, 1, ??] [1, ??])]
     = 0 : 1 : [ a + b | (a, b) <- (0, 1) : zip [1, ??] [??]]

请注意,我省略了zip的评估,因为这里没有给出它的定义,而且细节与当前问题并没有真正的密切关系。 这是我将用来显示每对数字由zip创建并由列表理解使用的符号。

     = 0 : 1 : 0+1 : [ a + b | (a, b) <- zip [1, ??] [??]]
     = 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, ??] [??]]

现在我们知道?? 1

     = 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, 1, ??] [1, ??]]
     = 0 : 1 : 1 : [ a + b | (a, b) <- (1, 1) : zip [1, ??] [??]]
     = 0 : 1 : 1 : 1+1 : [ a + b | (a, b) <- zip [1, ??] [??]]
     = 0 : 1 : 1 : 2 : [ a + b | (a, b) <- zip [1, ??] [??]]

下一个元素是 2:

     = 0 : 1 : 1 : 2 : [ a + b | (a, b) <- zip [1, 2, ??] [2, ??]]

冲洗并重复。

括号中的列表理解:

[ a + b | (a, b) <- zip fibs (tail fibs)]

返回一个包含输出 (a + b) 的列表,其中变量 a 和 b 来自

zip fibs (tail fibs)

它定义了这个流程图, (*)

             .---->>---->>----.
            /                  \ 
     <---- 0 <---- 1 ----<<--- (+) 
                    \          / 
                     *--->>---* 

它在生成时从自身提取新输入,但总是在生成点后面一到两个位置,将两个“反向指针”保持在序列中,相隔一个位置。

这反映在定义中,

--         .------->>------>>---.
--        /                      \
fibs  =  0 : 1 : [ a + b | a <- fibs 
{-            \  -}      | b <- tail fibs]
--             \                 /
--              *-->>------>>---*

使用并行列表推导( :set -XParallelListComp等)。

由于它只使用它的最后两个元素,所以它相当于

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

(*)该图的执行过程如下:

                 .---->>---->>----.
                /                  \     0
       ? <---- 0 <---- 1 ----<<--- (+) 
                        \          /     1
                         *--->>---* 

                 .---->>---->>----.
                /                  \     1
     0,? <---- 1 <---- 1 ----<<--- (+) 
                        \          /     1
                         *--->>---* 

                 .---->>---->>----.
                /                  \     1
   0,1,? <---- 1 <---- 2 ----<<--- (+) 
                        \          /     2
                         *--->>---* 

                  .--->>---->>----.
                 /                 \     2
   0,1,1,? <--- 2 <--- 3 ----<<--- (+) 
                        \          /     3
                         *--->>---* 

                    .--->>--->>---.
                   /               \     3
   0,1,1,2,? <--- 3 <-- 5 ---<<--- (+) 
                        \          /     5
                         *--->>---* 
 ......

我还是不明白。 我喜欢这个答案: https//stackoverflow.com/a/42183415/246387 (来自code-apprentice )。

但我不明白这条线是怎么回事:

= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, ??] [??]]

它转向这个:

= 0 : 1 : 1 : [ a + b | (a, b) <- zip [1, 1, ??] [1, ??]]

除此之外,我还有其他困扰我的事情:

我怎么能在列表理解中使用fib ,如果我根本没有fib (所以看起来,但确定我错了),因为fib尚未计算。 它“等待”(在等号的左侧)在右侧(等号)计算。

这里的关键概念是惰性求值,这意味着如果值就在那里,则无需进一步计算就直接使用它,说我已经得到了值并且工作已经完成,我现在不需要临时计算未来值。 如果该值不可用,则只需计算它,当然它是懒惰的,因此它不会打扰计算下一个需要的值。

我写了另一个实现来说明这一点并使用?? 是一个值占位符,需要时需要计算。

fibs = 1:1:[ y!!1 + y!!0 | x<-[2..], y <- [[last (take x fibs), last (take (x-1) fibs)]] ]

我使用 x 来表示 fibs 中可用值的数量(不需要再次计算),y 是一个 [[last fib values]] 嵌套列表。 它的内部列表包含 fibs 中可用值的最后两个值。

所以这里是计算过程:

  1. x == 2,所以 y 是[last (take 2 fibs), last (take 1 fibs)] 惰性求值让我们只获取可用的值,而不必担心将来的值。 它告诉我取 fibs 的第一个 2 和 1 个值,这些值就在那里 1,1 和 1。y 现在是[last [1,1], last [1]][1,1]并得到它需要计算y!!1 + y!!0最终值。 这很明显,最终值是 2。所以现在 fibs 是[1, 1, 2, ??]
  2. x == 3,与步骤 1 相同。 y 是[last [take 3 fibs], last [take 2 fibs]][last [1,1,2], last [1,1]] ,现在价值是可用的,所以我们可以接受它并继续。 最后,我们得到了第四个值 3。
  3. 就是这样,只需重复上述步骤,即使它是无限的,您也可以获得所有值

是不是看起来很熟悉? 就像使用递归函数来计算 fibs 一样。 我们现在让编译器自己来做这些事情(指的是)。 我们在这里使用惰性求值。

zip 实现只是此处 [last fibs values] 的另一种表示。 您只需要稍作修改即可了解 fibs 实现的 zip 版本。

  1. haskell wiki:惰性求值

  2. for <-符号:列表理解

我更喜欢更一般的

fib = 1 : [ x + y | (x, y) <- zip fib (0 : fib) ]

这最接近地模拟了人们如何在生成函数方面理解斐波那契数列。 如果f(n) = 0 for n < 0f(0) = 1 ,并且f(n) = a*f(n-1) + b*f(n-2) for n > 0 ,那么我们'我希望能够写一行

f(n) = 1_0 + a*f(n-1) + b*f(n-2)

让读者知道我们的意思。 不幸的是,这需要一些未说明的约定才能有意义。

使用生成函数g(t) = f(0) + f(1)t + f(2)t^2 + ...我们可以写出方程

g(t) = 1 + a t g(t) + b t^2 g(t)

这很容易以封闭形式求解g(t)

g(t) = 1 / (1 - a t - b t^2)

哈斯克尔代码

g = 1 : [ a*x + b*y | (x, y) <- zip g (0 : g) ]

实现相同的等式。

解析器如何知道(a,b)的内容呢?

编辑:感谢ViralShah,我将使这一点少一些。 “< - ”告诉解析器将右侧“zip fibs(tail fibs)”的对列表分配给左侧“(a,b)”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM