JS递归递归计数

Counting up and down in recursive manner in JS

我目前正在研究函数式编程技术。有关于这个问题的话题[特别是有一个关于java]但没有关于JS。

我想创建一个递归函数,它可以首先,从我指定的数字开始计数,直到我决定的极限然后达到上限后开始倒计时。我已经可以用 for 循环来做到这一点,但它感觉像是硬编码 因为我在循环中提供数字。

基本上是这样的:

counter(0,10);
// 0, 1, 2, 3 ... 10, 9, 8... 0

这是我的想法:

counter = (number, limit) => {
limit !=== 0

if ( number = limit ) {
  counter(number -1, limit -1)
  console.log(number)
} else if ( number < limit ) {
  counter(number + 1, limit + 1)
  }
}

这背后的想法是,如果数字小于限制计数,如果它们相等,则将每个参数减 1 以继续满足第一个 if 条件

当我在 v8 上 运行 这个命令时,它给出了一个范围错误“达到最大堆栈大小”。

此外,这不应该是一个无限循环。

For 循环版本:

for (let i = 0; i < 11; i++ ) { console.log(i) }
for (let i = 9; i < 11 && i > -1; i--) { console.log(i) }

您不需要向下循环或递减该值,因为当您达到基本情况(停止递归的情况)时,您将跳回调用函数,该函数包含上一个 value:

counter(0, 10) // logs: 0
    |           ^
    |           | (returns back to)
    |---> counter(1, 10) // logs: 1
              |             ^
              |             | (returns back to)
              |---> counter(2, 10) // logs: 2 <---
                        |                         | (returns back to)
                        |                         |
                        ........   ---> counter(10, 10) // logs: 10 - base case

每次调用 counter() 都会再次调用计数器(如上图所示,子 counter 调用),然后打印它们当前的 value。当您达到基本情况时,您打印当前值和 return,这会使您将控制权交还给调用函数。我的意思是当你调用一个函数时,该函数将 运行。当函数结束时,代码从最初调用函数的地方返回:

function bar() {
  console.log("bar");
}

console.log("foo"):
bar(); // call the function makes the code execution jump up into `bar` function. When that completes, our code execution jumps back to the next line, which logs "baz"
console.log("baz");

在我们的 counter() 示例中,调用子 counter() 函数的地方是它的父 counter 函数,我们在子函数完成时跳转(将控制权传回)执行(returns)。一旦控制被传回。到调用函数(即:上图中的父函数),然后您可以再次记录 value ,因为它包含 value:

的先前值

function counter(value, limit) {
  if(value === limit) {
    console.log(value);
  } else {
    console.log(value); // on the way down / going deeper (increment)
    counter(value+1, limit);
    console.log(value); // on the way up / coming up from the depths (decrement)
  }
}

counter(0,10);
// 0, 1, 2, 3 ... 10, 9, 8... 0

是这样的吗?

console.clear();
{
  "use strict"
  
  // Curry function
  const nextStepInit = start => max => {
    let increment = 1;
    let counter = start;
    
    return () => {
      counter += increment;
      if (counter >= max) {
        increment = -increment;
        return max;
      }
      if (counter <= start) {
        return start;
      }
      return counter;
    }
  }
  
  // this is a function, because nextStep(start)(max) returns a function
  const nextStep = nextStepInit(0)(10)
  
  const output = result => document.getElementById('output').value = result
  
  const onClick = () => output(nextStep())
  
  output(0)
  
  document.getElementById('next-step').addEventListener('click', onClick)
}
<button id="next-step">Next Step</button><br>
<input id="output" disabled/>

递归是一种函数式继承,因此将它与函数式风格结合使用会产生最佳效果。这意味着要避免诸如突变、变量重新分配和其他副作用之类的事情。

  1. 如果开始 a 大于停止 b,我们就达到了基本情况。 Return 空结果,[]
  2. (归纳)a 小于或等于 b。如果a等于b,return单例结果,山之“峰”,[a]
  3. (归纳)a 小于 b。重复子问题 a + 1, b 和 append/prepend 结果 a

这编码为纯函数表达式,下面的注释与上面的编号解释相匹配 -

const uppendown = (a, b) =>
  a > b
    ? []                                // #1
: a == b
    ? [ a ]                             // #2
: [ a, ...uppendown(a + 1, b), a ]      // #3
    
const ex1 =
  uppendown(0, 10)
  
const ex2 =
  uppendown(3,7)

const ex3 =
  uppendown(9,7)
  
console.log(JSON.stringify(ex1))
// [0,1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1,0]

console.log(JSON.stringify(ex2))
// [3,4,5,6,7,6,5,4,3]

console.log(JSON.stringify(ex3))
// []

uppenDown(3,7)
= [ 3, ...uppendDown(3 + 1, 7), 3 ]                   // #3
= [ 3, 4, ...uppendDown(4 + 1, 7), 4, 3 ]             // #3
= [ 3, 4, 5, ...uppendDown(5 + 1, 7), 5, 4, 3 ]       // #3
= [ 3, 4, 5, 6, ...uppendDown(6 + 1, 7), 6, 5, 4, 3 ] // #2
= [ 3, 4, 5, 6, 7, 6, 5, 4, 3 ]
uppendown(9,7)                        // #1
= []

如果出于某种任意原因您不喜欢链接函数式风格的三元表达式表达式,您可以将它们换成命令式风格if 语句-

function uppendown (a, b)
{ if (a > b)
    return []                                // #1
  else if (a == b)
    return [ a ]                             // #2
  else
    return [ a, ...uppendown(a + 1, b), a ]  // #3
}
    
const ex1 =
  uppendown(0, 10)
  
const ex2 =
  uppendown(3,7)

const ex3 =
  uppendown(9,7)
  
console.log(JSON.stringify(ex1))
// [0,1,2,3,4,5,6,7,8,9,10,9,8,7,6,5,4,3,2,1,0]

console.log(JSON.stringify(ex2))
// [3,4,5,6,7,6,5,4,3]

console.log(JSON.stringify(ex3))
// []


如果您希望数字一个接一个地输出而不是 return 一个数组,您可以使用 JavaScript 的生成器。请注意每个程序变体之间惊人的相似性 -

function* uppendown (a, b)
{ if (a > b)
    return                                  // #1
  else if (a == b)
    yield a                                 // #2
  else
    ( yield a                               // #3
    , yield *uppendown(a + 1, b)            //
    , yield a                               //
    )
}
    
for (const x of uppendown(3, 7))
  console.log(x)
  
// 3
// 4
// 5
// 6
// 7
// 6
// 5
// 4
// 3