[英]Understanding Haskell's fibonacci
fibs :: [Int]
fibs = 0 : 1 : [ a + b | (a, b) <- zip fibs (tail fibs)]
这会生成斐波那契数列。
我理解:
、 zip
和tail
警卫的行为,但我不明白<-
。 它在这里做什么?
由于投票,我将我的评论变成了答案。
你看到的不是守卫,而是列表理解。 首先,将其视为一种表达数学集合符号的方式,例如 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 中可用值的最后两个值。
所以这里是计算过程:
[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, ??]
。[last [take 3 fibs], last [take 2 fibs]]
即[last [1,1,2], last [1,1]]
,现在价值是可用的,所以我们可以接受它并继续。 最后,我们得到了第四个值 3。是不是看起来很熟悉? 就像使用递归函数来计算 fibs 一样。 我们现在让编译器自己来做这些事情(指的是)。 我们在这里使用惰性求值。
zip 实现只是此处 [last fibs values] 的另一种表示。 您只需要稍作修改即可了解 fibs 实现的 zip 版本。
我更喜欢更一般的
fib = 1 : [ x + y | (x, y) <- zip fib (0 : fib) ]
这最接近地模拟了人们如何在生成函数方面理解斐波那契数列。 如果f(n) = 0
for n < 0
, f(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.