【问题标题】:For loop effecting recursion variablesFor 循环影响递归变量
【发布时间】:2026-01-24 07:20:03
【问题描述】:

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

我目前正在学习 JavaScript,并且正在做我已经知道的 Python 工作来测试 JavaScript 中的一些基本原理。 然而 for 循环似乎总是跳过一些数字。我相信正在发生的是,当函数调用自身并再次运行 for 循环时,它会影响更高一级函数中的变量 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))

【问题讨论】:

  • i 是一个全局变量,因为你没有用var 声明它。
  • 在声明函数时,尝试将它们声明为简单函数,而不是变量。 function simplex(s,t) { ... } 在这种情况下。在某些情况下,声明为变量或const 更可取,但这只会使其过于复杂。
  • @tadman 这是个人风格,很多优秀的程序员似乎都喜欢这个任务。
  • @Barmar 这很烦人,而且通常毫无意义。新程序员似乎从这些烦人且毫无意义的例子中吸取了教训,不必要地延续了它。这似乎是一个 Cargo Cult 编程神器。
  • 无论如何,解决方法是使用for (var i = 1; ...)

标签: javascript for-loop recursion scope


【解决方案1】:

如果您没有为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 循环中具有相关性。

【讨论】:

    【解决方案2】:

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

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

    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}`);
    

    【讨论】:

      【解决方案3】:

      其他人指出了未声明变量的相当简单的问题。

      但即使在修复之后,仍然存在三个逻辑错误。您没有在循环内使用循环变量i。虽然有时这没问题,但它不在这里。您需要它作为simplex 的参数。其次,你的循环边界停止了。您想要上一列的 t 条目,但您通过测试 i &lt; t 停止使用 t - 1st 条目。最后,您以1 开始您的积累(n)。总和应该以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)。做到这一点并不难,而且有很多好处。但是将其转换为其他格式很容易,只增加了非常小的实现复杂性。


      更新

      我忘了提到我的第一种方法,即把单纯形数看作是直接进入帕斯卡三角的索引。帕斯卡三角形包含二项式系数,可以使用简单的递归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}

      【讨论】: