【问题标题】:take n with zipWith and tail用 zipWith 和 tail 取 n
【发布时间】:2017-09-15 03:51:36
【问题描述】:

在 Haskell GHC 中编写此代码:

lazee = 2:5:(zipWith (+) lazee (tail lazee))

然后

take 6 lazee

给你这个:

[2,5,7,12,19,31]

我只能看到它如何给出25,不知道为什么在[7,12,19,31] 部分。

【问题讨论】:

  • 2 + 5 = 7; 5 + 7 = 12; 7 + 12 = 19; 12 + 19 = 31
  • @Josep 谢谢,你有时间把这个评论作为答案,这样我就可以接受/投票了吗?
  • 谢谢,但不用担心:@bipll 已经回答了这个问题。我认为你应该接受他们的回答。都很好。 :-)
  • @Josep 好的,我会这样做的,感谢您在 cmets 中的提示 :)

标签: haskell


【解决方案1】:

正如@Josep 在评论中指出的那样,这是用初始条件 2 和 5 定义斐波那契数列 x_n = x_n-1 + x_n-2 的典型 Haskell 方法。

【讨论】:

  • 我现在明白这个概念了,不过你有什么办法可以详细说明吗?我可以看到它是如何得到 [2, 5, 7] 的,因为 tail lazee 是 5 并且将 2 添加到 5 = 7,但是为什么它会变成 5 + 7 = 12?
  • 假设列表lazee 是列表5, 7, a, b, c, d, e ...,那么tail lazee7, a, b, c, d, e ...。现在zipwith (+) 将计算总和a = 5+7, b = 7+a, c = a+b, d = b+c ...。在每个步骤中,都有计算下一个值所需的值。
【解决方案2】:

这是在 Haskell 中编写斐波那契的一种非常惯用的方式。

基本上,在 Haskell 中,人们试图尽可能地利用惰性(事实数据仅在需要时计算)。

例如,如果你写 a = 1 : a,你会得到一个无限的 1 流 (1:1:1:1:...)。但是,如果您执行take 100 a,您会得到正好 100 个 1,并且程序不会挂起。这是因为 Haskell 不关心列表的其余部分,除非您要求。

那么它与您的问题有什么关系呢?好吧,让我们手动运行这个定义好吗?

lazee = 2:5:(zipWith (+) lazee (tail lazee))

首先,您将 2 和 5 放入 lazee。所以现在我们有

lazee = 2 : 5 : ...
tail lazee = 5 : ...

(我使用... 来显示尚未计算的值) 然后你做了一些非常棘手的事情:你根据它本身来定义 lazee 。我发现这实际上比单纯的递归更漂亮,因为你将递归抽象出来,只留下非常清晰的模式。 ZipWith 是一个将两个列表与一个函数一起压缩的函数 所以

zipWith (+) lazee (tail lazee) = zipWith (+) (2 : 5 : ...) (5 : ...) = 7 ...

那么现在

lazee = 2, 5, 7, ...
tail lazee = 5, 7, ...
zipWith (+) lazee (tail lazee) = zipWith (+) (2, 5, 7, ...) (5, 7, ...) = 7 : zipWith (+) (5, 7, ...) (7, ...) = 7 : 12 : ...

以此类推,Haskell 继续创建 lazee,将它当前在 lazee 上读取的术语添加到下一个。


这是您可能经常在一个班轮中看到的模式:以自身表示的常量值。一个常见的例子是非常低效的素数列表

primes = sieve [2..]
  where sieve (p:xs) = p : (filter ((== 1) . (gcd p)) $ sieve xs)

它有什么作用?那么filter (== 1) . (gcd p) 只取与 p 互质的值(即那些 gcd 大于 p 的 1)

所以首先我们有

sieve [2..] = 2 : (filter ((== 1) . (gcd 2) $ sieve [3..])

这意味着我们将 2 作为素数,并且 sieve [3..] 中的每个数字都不是偶数。 sieve [3..] 是 3 后跟每个不与 3 互质的数字。

如果我们在一个小例子上运行它:

sieve [4..] = 4 : ...
sieve [3..] = 3 : 4 : ...
sieve [2..] = 2 : 3 : ...

注意4 是如何被删除的。


我希望这会有所帮助。如果你想看一些有趣的单线,考虑看看https://wiki.haskell.org/Blow_your_mind 我个人建议您避免直接递归(即,您只需使用更改的参数调用自己的递归),以支持您发布的模式和列表操作。 (例如写fact n = product [1..n]来定义阶乘)

在 Haskell 旅途愉快;)

【讨论】:

  • 你的意思是无限的 1 流吗?
  • 哦,是的,谢谢,我本来打算先做a = 'a' : a,然后才意识到这会很混乱。忘记改了,谢谢关注^^
  • 谢谢,这是一个非常详细的答案。有时间我会再看一遍的。
猜你喜欢
  • 2016-02-05
  • 1970-01-01
  • 1970-01-01
  • 2010-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多