对于循环影响递归变量

时间:2020-10-02 00:12:09

标签: javascript for-loop recursion scope

我正在尝试使用递归来创建一个函数,该函数可以从帕斯卡三角形内的任何序列中获取任何项。基本上将自然数用作第一组的加法序列,然后将每个先前的集用作加法序列,始终从1开始。 Simplex Numbers

我目前正在学习JavaScript,并且正在做我已经知道的在Python中工作的方法,以测试JavaScript的一些基本原理。 但是,for循环似乎总是跳过一些数字。我所相信的是,当函数调用自身并再次运行for循环时,它将影响函数I上一级的变量I,从而使其跳过序列中的某个数字。我不知道如何避免这种情况,因为JavaScript允许变量在同一范围内的函数中使用,这很有意义,但是我不知道如何避免这种情况。

 var simplex = function(s,t){
  if (s == 1){
    return t
  } else{
    var n=1;
    for (i = 1; i<t; i++){
      n += simplex(s-1,t+1);
    }
    return n
  }

}

console.log(simplex(3,3))

3 个答案:

答案 0 :(得分:0)

如果您未声明i的作用域,则最终将在递归调用中持续存在。您应始终根据需要在较小的范围内声明它们。

这是一个清理的版本:

function simplex(s,t){
  if (s == 1){
    return t
  }
  
  let n = 1;
  
  for (let i = 1; i < t; i++){
    n += simplex(s-1, t+1);
  }
  
  return n;
}

console.log(simplex(3,3));

由于现在是2020年,并且广泛支持letconst,因此建议您优先使用那些考虑范围问题的旧var样式。 let的范围更窄,更符合期望。

作为一种习惯,您应该努力编写以下形式的循环:

for (let i = 0; ...)

明确显示let的位置。这清楚表明迭代变量仅与该for循环相关。

答案 1 :(得分:0)

在javascript中,变量具有函数作用域。

正如@Barmar在评论中所说,问题出在这一行:

for (i = 1; i<t; i++)

如果您键入

for(var i = 1; i<t; i++)

然后您在此函数内声明一个新变量i,问题就解决了。

当您在javascript中键入以下内容时:

i = 1;

如果尚未声明i,则将隐式创建一个变量i,并将其分配给全局window对象。

您可以通过以下简单代码进行演示:

i = 7;
console.log(`window.i = ${window.i}`);

答案 2 :(得分:0)

其他人指出了一个非常简单的问题,即未声明变量。

但是即使在修复之后,仍然存在三个逻辑错误。您没有在循环内使用循环变量i。虽然有时候可以这样做,但事实并非如此。您需要使用它作为simplex的参数。其次,您的循环界限会短暂终止。您需要上一列的t条目,但是通过测试t - 1可以使第i < t个条目停止。最后,您用n开始积累(1)。总和应从0开始累积。解决这些问题后,我们有一个工作功能如下:

const simplex = function(s, t) {
  if (s == 1) {
    return t
  } else {
    var n = 0;
    for (let i = 1; i <= t; i++){
      n += simplex(s - 1, i)
    }
    return n
  }
}

添加一些日志记录语句以显示流程,我们可以得到:

const simplex = function (s, t, d = 0) {
  console.log('|   '.repeat(d) + `simplex (${s}, ${t})`)
  if (s == 1) {
    console.log('|   '.repeat(d) + `\`+-> ${t}`)
    return t
  } else {
    var n = 0;
    for (let i = 1; i <= t; i++){
      n += simplex (s - 1, i, d + 1)
    }
    console.log('|   '.repeat(d) + `\`+-> ${n}`)
    return n
  }

}

simplex (3, 3) //=> 10
.as-console-wrapper {max-height: 100% !important; top: 0}

simplex (3, 3)
|   simplex (2, 1)
|   |   simplex (1, 1)
|   |   `+-> 1
|   `+-> 1
|   simplex (2, 2)
|   |   simplex (1, 1)
|   |   `+-> 1
|   |   simplex (1, 2)
|   |   `+-> 2
|   `+-> 3
|   simplex (2, 3)
|   |   simplex (1, 1)
|   |   `+-> 1
|   |   simplex (1, 2)
|   |   `+-> 2
|   |   simplex (1, 3)
|   |   `+-> 3
|   `+-> 6
`+-> 10

但是我相信可以通过多种方式简化此功能。有一些简单的事情,例如将else块移到根,并使用箭头函数代替函数表达式。对我而言,更重要的是使用sum之类的辅助函数来总计多个值,而不是在该函数中包括该逻辑。通过还包含countTo(仅返回第一个n个计数数字),我们可以使用map,使代码更具声明性。所以我可能更喜欢这样的版本:

const countTo = (n) => n < 1 ? [] : [...countTo (n - 1), n]
const sum = (xs) => xs .reduce ((a, b) => a + b, 0)

const simplex = (s) => (t) =>
  s == 1
    ? t
    : sum (countTo (t) .map (simplex (s - 1)))

console .log (simplex (3) (3))

此版本还对API进行了一次更改。该函数将采用单纯形数字并返回带有项并返回值的 function ,而不是采用单纯形数字和项并返回值的函数。这意味着像simplex (s) (t)而不是simplex (s, t)那样调用它。这样做并不难,并且有很多好处。但是将其转换为另一种格式将很容易,只给实现增加了非常小的复杂性。


更新

我忘了提到我的第一种方法,那就是将单纯形数视为直接进入Pascal三角形的索引。帕斯卡(Pascal)的三角形包含二项式系数,可以通过简单的递归choose (n, k) = choose (n, k - 1) + choose (n - 1, k - 1)以及一些简单的基本情况来计算。

使用simplex (s, t)就是choose (s + t - 1, t)。可能看起来像这样:

const choose = (n, k) =>
  k == 0
    ? 1
  : n == 0
    ? 0
  : choose (n - 1, k) + choose (n - 1, k - 1)

// Ex: choose (7, 3) //=> 35

console .log ('Pascal Triangle:\n');
console .log (
  Array .from (
    {length: 9}, 
    (_, n) => Array .from ({length: n + 1}, (_, r) => choose (n, r))
  ).map (
    r => r .map (n => `${n}`.padStart(2, ' ')) .join (' ')
  ) .join ('\n') + '\n ...'
)

const simplex = (s, t) => 
  choose (s + t - 1, t)
  
console.log ('simplex (3, 3): ', simplex (3, 3))
.as-console-wrapper {max-height: 100% !important; top: 0}

相关问题