如何将循环的索引推入递归数组?

Posted

技术标签:

【中文标题】如何将循环的索引推入递归数组?【英文标题】:How to push the indexes of a loop into an array with recursion? 【发布时间】:2021-11-05 08:26:15 【问题描述】:

由于阶乘算法,我最近正在学习递归。不过,我的问题是:

    如何模拟指定长度的“for循环” 如何通过递归将其索引推送到数组中?

例如,循环 5 次,然后将其“索引”推入数组渲染 [1,2,3,4,5]

我创建了 2 个函数 [ 循环 | recursive ] 每个都试图实现相同的输出。 “for循环”版本如下:

function loop(index, length) 
  const result = [];
  for (let i = index; i <= length; i++) result.push(i);
  return result;


console.log(loop(1, 5)); // [ 1, 2, 3, 4, 5 ]

但是,“递归”版本没有打印期望的结果。

function recursive(index, length) 
  const result = [];
  result.push(index);
  return (index < length) ? recursive(++index, length) : result;


console.log(recursive(1, 5)); // [ 5 ]

谁能告诉我为什么它没有输出 [ 1, 2, 3, 4, 5 ]

【问题讨论】:

【参考方案1】:

Jeremy Tille 的 answer 很棒。它解释了哪里出了问题,并提供了一个有用且易于理解的替代方案。代码是干净的,因为它是尾递归的,如果它变得普遍,它可以利用尾调用优化。但是,其中有一个错误:大概recursive (10, 3) 应该返回一个空数组。在这里它将返回[10]

虽然我们可以很容易地解决这个问题,但让我们看看一个替代方案,它在逻辑上更简单并且避免了默认参数。 (earlier answer 解释了默认参数的一些潜在问题。请理解它们仍然是一种有用且强大的技术,但它们确实有缺点。)

所以这里有一个替代版本,都是现代语法:

const range = (from, to) =>
  from > to ? [] : [from, ... range (from + 1, to)]

还有更旧的风格:

function range (from, to) 
  if (from > to) 
    return []
  
  return [from] .concat (range (from + 1, to))

在任一版本中,我们只需创建一个新数组即可递归,从 from 值开始,然后包含递归调用中的所有值。当from 大于to 并且我们返回一个空数组时,我们遇到了基本情况。

这有一个很大的可能最终不利因素。它不是尾递归的。任何递归调用立即返回而无需进一步处理的函数称为尾递归。 JS 引擎可以对尾递归函数进行一些非常有用的优化,以避免递归堆栈溢出。

我们可以通过添加默认参数或添加将参数传递给内部递归辅助函数的包装函数来使这个尾递归。要么很容易做到。但是,we can see 在现代 JS 中几乎没有什么区别,因为在给出这个答案的时候,几乎没有引擎支持正确的尾调用,尽管我相信这样做仍然在规范中。所以现在,我们不会打扰。

这里是这个版本:

const range = (from, to) =>
  from > to ? [] : [from, ... range (from + 1, to)]

console .log (range (1, 5)) //=> [1, 2, 3, 4, 5]
console .log (range (4, 4)) //=> [4]
console .log (range (5, 1)) //=> []
.as-console-wrapper max-height: 100% !important; top: 0

【讨论】:

你的代码很有说服力,但你确定它工作正常--->你的代码中的 range (5, 1) 实际上返回 [ ] 而不是 [5,4,3,2,1 ] 它按预期工作,并且与原始迭代样本匹配。我不知道我是如何以及为什么将评论放在那里的。该测试用例是为了与 Jeremy Tille 的回答区分开来,后者返回 [5] 的奇怪结果。我现在已经修复了评论。 谢谢 Scott :) 你的代码非常雄辩和现代。更重要的是,它居然一石二鸟。【参考方案2】:

因为每次调用函数时,result 都会重新初始化为 []。您没有将之前的result 传递给自己,因此它会被重置。最后,只发回最后一个索引 ([5]) 的最后一次迭代。

你可以做的是使用第三个参数,可选,你可以初始化为[]

function recursive(index,length, result=[]) 
  result.push(index);
  return (index < length) ? recursive(++index,length, result) : result;


console.log(recursive(1,5)); // [1,2,3,4,5]

【讨论】:

是的,这样更干净,感谢您的评论。 @Jeremy Thille --> 感谢您的及时和令人兴奋的回复 :) 您不仅提供了解决方案,而且您的详细解释使学习过程如此令人鼓舞和值得。顺便说一句,您将“结果”作为第三个参数的实现简直太棒了?? 啊,哦,好吧……呵呵。谢谢:) 注意,虽然recursive(5, 1) 应该返回[],但这返回[5]。一个简单的解决方法是用if (index &lt;= length) 保护push【参考方案3】:

一种解决方法是在递归函数中添加一个累加器,以便在每个步骤中引入您在第一次调用开始时开始的数组。

function recursive(index, length) 
  const result = [];
  result.push(index);
  return recursiveAcc(++index, length, result);


function recursiveAcc(index, length, result) 
  result.push(index);
  return (index < length) ? recursiveAcc(++index, length, result) : result;


console.log(recursive(1, 5)); // [ 1, 2, 3, 4, 5 ]

【讨论】:

如果您为result 参数传递一个默认的空数组,则只需一个函数即可获得相同的效果。 (fwiw,两个函数方法在 Erlang 等语言中很流行,您可以根据参数的数量来选择要调用的函数)

以上是关于如何将循环的索引推入递归数组?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PHP 中循环遍历多维数组并按名称递归删除键?

如何知道数组中偶数索引值之和与奇数索引值之和之间的差异(递归代码)

如何在递归分治解决方案中自下而上传递原始数组的索引?

递归简单学习

这个递归数组置换函数如何在幕后工作?

如何在反应原生渲染中以无限循环返回数组项视图?