【问题标题】:Counting up and down in recursive manner in JS在JS中以递归方式向上和向下计数
【发布时间】:2021-05-02 04:41:20
【问题描述】:

我目前正在研究函数式编程技术。有关于这个问题的主题[尤其是关于 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 上运行此命令时,它会给出 rangeError “已达到最大堆栈大小”。

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

for循环版本:

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

【问题讨论】:

  • !=== 在 JS 中不是一个东西。 x = y 也不应该处于某种状态。改用x == y 甚至更好的x === y
  • @yunzen 下次会更加小心。我可能是一个月前开始的。知道这些,但过于专注于问题,谢谢你

标签: javascript recursion functional-programming counter


【解决方案1】:

您不需要向下循环或减少值,因为当您到达基本情况(停止递归的东西)时,您将跳回调用函数,该函数保存前一个 @ 987654321@:

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(如上图所示,子 counter 调用),然后打印它们当前的 value。当您到达基本情况时,您打印当前值并返回,这会导致您将控制权传递回调用函数。我的意思是当你调用一个函数时,该函数将运行。当函数完成时,代码会从函数最初调用的地方返回:

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 函数,当子函数完成执行(返回)时我们跳转(将控制权传回给)。一旦控制权被传回。到调用函数(即:上图中的父函数)然后您可以再次记录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

【讨论】:

  • @afyonkes 我已经更新了我的答案以澄清,我添加了另一个(隐藏的)代码 sn-p 来尝试解释事情并向它们添加了 cmets。我也详细阐述了答案
  • @afyonkes 这是一个函数调用堆栈:你在递归调用函数时结束它,然后当你到达基本情况时,它会自动展开。
  • @afyonkes 没错,当我们返回图表时,我们正在“收集”这些值。不过,if 语句不会“调用”计数器功能。如果您在图中看到,对counter() 的唯一没有子对象的调用是counter(10, 10),这是因为执行了 if 语句,因此不再调用 counter() 函数。一旦它完成执行,调用它的父函数将继续它的执行并记录它的值(即:9),这会继续图表(就像你提到的那样)
  • 不错的答案,漂亮的 ascii 艺术。我建议value &gt; limit 是基本情况,并使函数对所有valuelimit 都具有总和。请参阅我的帖子以获得解释。
  • @Thankyou - 干杯,我看到了你的回答,我认为这很棒。生成器功能也很酷。我同意返回结果而不是仅仅将内容记录到控制台会更好,我主要坚持使用日志记录方法,因为它似乎是 op 试图做的。
【解决方案2】:

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

  1. 如果开始a大于停止b,我们就达到了基本情况。返回空结果,[]
  2. (归纳)a 小于或等于b。如果a 等于b,则返回单例结果,即山的“峰”[a]
  3. (感应)a 小于 b。重复子问题a + 1, b 并使用a 附加/前置结果

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

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))
// []

如果您希望数字一个接一个地出现而不是返回一个数组,您可以使用 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

【讨论】:

  • 不错!我看到了这一点,决定这里的答案很好,我没有什么可以添加到讨论中的。不过这个答案其实加分不少。
【解决方案3】:

这样的?

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/>

【讨论】:

  • 我猜这不是您在代码中评论的递归函数,但谢谢。我还没有开始咖喱,但它在排队。
  • 这不是递归的,这是真的。恕我直言,无论如何,通过递归解决您的原始任务没有多大意义。大多数引入递归的课程的问题在于它们没有给你提供有意义的任务
  • 问题只是一个例子。递归是一种非常微妙的技术,我非常喜欢。它类似于独立于软件开发的核心逻辑概念。
  • @yunzen 这是基于意见的。递归是一种与处理迭代任务的命令式循环一样通用的工具。声称一个任务不适合递归意味着它也不适合命令式循环。
  • @scriptum 我并不是要暗示递归是一个坏概念。我的意思是,在编程课程中教你递归的大多数任务没有意义。它们不是任何人首先会使用递归的现实生活示例,而是带有循环的普通旧迭代。
猜你喜欢
  • 2016-09-14
  • 1970-01-01
  • 2015-01-22
  • 1970-01-01
  • 2020-01-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多