【问题标题】:Why does this 'eloquent Javascript' recursive solution work?为什么这个“雄辩的 Javascript”递归解决方案有效?
【发布时间】:2015-08-04 08:24:00
【问题描述】:

我正在使用 Eloquent Javascript,该练习要求找到一个递归解决方案,以在此表单的嵌套列表中查找第 n 个元素;

var list = {
   value: 1, 
   rest: {
     value: 2, 
     rest: {
       value: 3,
       rest: null
     }
   }
};

在为这个问题挠头太久之后,我正式检查了答案。

function nth(list, n) {
  if (!list)
    return undefined;
  else if (n == 0)
    return list.value;
  else
    return nth(list.rest, n - 1);

我可以看到这确实产生了正确的结果,但我不明白为什么。

我对逻辑的理解如下

如果没有提供列表或列表为假,则返回未定义, 否则,如果 n 为 0,则返回列表的第一个元素 - 由 list.value 给出。 否则再次调用该函数,但将 n 减 1。

显然我一定遗漏了一些东西,但我不明白这个逻辑如何返回第 n 个元素。似乎它会一直递归直到 n==0。此时它将简单地返回列表的第一个元素。

【问题讨论】:

  • 您是否真的尝试过使用一个小的示例列表来运行代码?它真的像你想象的那样运行到n == 0 吗?
  • 注意,递归调用传递的是list.rest,而不是整个list

标签: javascript recursion


【解决方案1】:

这是一个典型的函数式链表迭代。注意递归调用继续list.rest,而不是listlist.rest 是从下一个元素开始的列表。在程序样式中,相当于获取列表的其余部分是增加一个索引指针。

这种风格的美妙之处在于您在代码中描述了列表的属性:列表 L 的第 n 个元素也是列表中从 L 的头部开始的第 (n-1) 个元素。更接近声明形式有时可以更容易验证您所写内容的正确性。

这种风格的缺点出现在大型列表的情况下,这样的函数将遇到 Javascript 引擎很可能无法处理的深度递归。如果可能,专用于函数式语言的编译器/解释器将消除这种递归,将其转换为循环,这种转换称为“尾调用优化”。 TCO 将开发人员从绑定到堆栈时的约束中解放出来,允许函数式语言使用递归作为主要的迭代方式,因此在这个世界中,您可以到处看到和编写这样的构造。

【讨论】:

  • 换句话说,当n 到达0 时,lists 的第一个元素就是你想要的——你总是返回原始列表的给定子集的第一个元素.
【解决方案2】:

这句话也让我好好思考。我终于想通了,所以我觉得我应该补充上一个答案。

nth(list.rest, n - 1) 中的“n-1”语句本质上是一个计时器,用于确定程序何时应该终止。请记住,当它变为 0 时,它会根据此语句返回值。

 else if (n == 0)
    return list.value;

例如,一个列表被传递到你的函数中 “{值:1,休息:{值:2,休息:{值:3,休息:空}}}” 你传入 2 作为参数。 它调用函数并遍历 if 语句,然后进入递归语句。它调用 List.Rest,然后将传入参数的值减 1。 它重复上面的循环并到达递归函数,现在传递的内容是 List.Rest.Rest 你的参数再次减少 1 并变为 0 。 当它通过 if 和 else if 语句时,因为参数现在是 0,所以下面的行被触发

else if (n == 0)
    return list.value;

然后它返回列表。 value.... 请记住,您的列表现在是 list.Rest.Rest 立即创造价值 3.

【讨论】:

    猜你喜欢
    • 2015-11-05
    • 1970-01-01
    • 2017-10-03
    • 1970-01-01
    • 2017-01-30
    • 2019-11-25
    • 2021-06-23
    • 1970-01-01
    • 2021-05-20
    相关资源
    最近更新 更多