岩石剪刀的递归解法

recursive solution to rock paper scissors

本文关键字:递归 石剪刀      更新时间:2023-09-26

我们今天在课堂上讨论了这个问题,我很难想象递归是如何准确工作的。你应该返回一个数组,所有可能的组合为n个石头剪刀,一个玩家。在n=3的情况下,它将返回长度为27的数组。

我在递归调用中得到了roundsLeft-1参数,但每次调用函数时会发生什么?非常感谢您的高级别解释。我认为正在发生的是:

子程序递归函数会忽略第一个元素,然后将接下来的两个元素连接起来。我没有看到它是如何得到所有的解决方案的,而不仅仅是那些以摇滚作为第一个元素,最后两个元素连接在一起的解决方案。:-/

var rockPaperScissors = function(numRounds) {
  var outcomes = [];
  var plays = ["rock", "paper", "scissors"];
  // can add rounds later, get 3 working. 
  // for a three round game, it would be 3^3 = 27.
  // for any number of rounds it would be 3^numrounds. 
function findOutCome(roundsLeft, result){
  // when you cover all the rounds
  // push to the outcomes
  if (roundsLeft === 0) {
    outcomes.push(result);
    return;
  }
  plays.forEach(function(play){
    //result.push(play);
    //concat returns the entire array
    findOutCome(roundsLeft-1, result.concat(play))
  })
}
findOutCome(numRounds, []); // give it a starting point
return outcomes;
}

console.log(rockPaperScissors(3)); // returns an array of length 27
var rockPaperScissors = function(numRounds) {
  var outcomes = [];
  var plays = ["rock", "paper", "scissors"];
  // can add rounds later, get 3 working. 
  // for a three round game, it would be 3^3 = 27.
  // for any number of rounds it would be 3^numrounds. 
function findOutCome(roundsLeft, result){
  // when you cover all the rounds
  // push to the outcomes
  if (roundsLeft === 0) {
    outcomes.push(result);
    return;
  }
  plays.forEach(function(play){
    //result.push(play);
    //concat returns the entire array
    findOutCome(roundsLeft-1, result.concat(play))
  })
}
findOutCome(numRounds, []); // give it a starting point
return outcomes;
}

console.log(rockPaperScissors(3)); // returns an array of length 27

上面发生的事情是,在执行之前,我们首先定义一个大函数,其中嵌套了一个函数

然后我们调用console.log(rockPaperScissors(3));,它调用我们的大函数并分配numRounds=3。在我们的功能体内部,我们发现:

3和CCD_ 4。这些将保留为我们的递归函数定义,以读取plays并写入outcomes

然后定义我们将用于递归的嵌套函数。

最后,我们的嵌套函数被调用:findOutCome(numRounds, []);

这是第一次调用我们的嵌套函数,并分配roundsLeft=numRoundsresult=[].

我们的第一个递归调用如下:

if (roundsLeft === 0){...}此语句为false,因为roundsLeft设置为3,所以我们继续…

由于播放被设置为CCD_ 12,所以CCD_。

第一个循环function(play){...}是用play="rock"调用的,在回调函数体中我们调用:

findOutCome(roundsLeft-1, result.concat(play));

它称之为findOutCome(2,result.concat("rock"))

这里使用的concat不会修改结果数组,而是在副本上工作,并将[]"rock"连接,从而创建["rock"]

如果我们想实际修改结果数组,我们可以在这里使用result.push(...)。但每个递归实例都有自己的本地版本的结果,所以这不会起作用,因为更改不会影响任何事情。

我们的第一个递归实例仍然是打开的,当我们开始递归调用时,我们仍然在第一个forEach循环中。

我们调用了findOutCome的第二个递归实例。在我们的第二个实例CCD_ 22和CCD_。

if (roundsLeft === 0) {...}为false,因此我们进入forEach循环。。。

我们输入第一个forEach循环和play="rock"。然后我们呼叫findOutCome(1, ["rock","rock"])

因此,我们进入第三级递归,并设置roundsLeft=1result=["rock","rock"]

if (roundsLeft === 0) {...}仍然为假,所以我们继续…

因此,我们进入了我们的forEach循环的第三层,它循环通过我们的播放阵列。。。第一个循环使用var outcomes = [];0,因此我们的循环以结束

findOutCome(0,["rock","rock","rock"])

然后,我们进入第四个递归级别并设置roundsLeft=0result=["rock","rock","rock"]

if (roundsLeft === 0) {outcomes.push(result);return;}这句话是真的,所以我们处理它的逻辑。

我们当前设置为[]outcomes数组附加了["rock","rock","rock"],从而创建:

outcomes=[["rock","rock","rock"]];

然后,我们的if语句遇到return,它结束了我们的第4个递归级别,并返回到我们的第3个递归级别。

在我们的第三个递归级别中,我们仍然在forEach循环中,所以我们继续循环中的第二个元素。

请记住,在我们的第三个递归级别中,我们的var plays = ["rock", "paper", "scissors"];0函数是用roundsLeft=1result=["rock","rock"]调用的,并且没有被修改。变量永远不会被修改,而是每个递归实例都使用这些变量的自己的本地副本。因此,在我们的forEach循环中,因为它是循环通过的第二个元素,play="paper"

然后我们遇到findOutCome(roundsLeft-1, result.concat(play)),其评估为:

findOutCome(0, ["rock","rock","paper"])

因此,我们进入第四个递归级别,并且if (roundsLeft === 0) {outcomes.push(result);return;}为真,防止了超过3个递归级别的深度,因此我们处理其逻辑。

outcomes.push(result)["rock","rock","paper"]附加到我们的数组中。

因此,我们的结果数组现在为:outcomes=[["rock","rock","rock"],["rock","rock","paper"]];

然后,我们遇到return语句,关闭我们的第4个递归深度级别,并恢复我们的第3个递归级别的forEach循环。

当我们的forEach循环在第三级递归outcomes=[["rock","rock","rock"],["rock","rock","paper"],["rock","rock","scissors"]]; 中完成时

然后我们的forEach循环结束,从而返回到我们的第二级递归的forEah循环,其中roundsLeft=2result=["rock"]

我们继续进行forEach的第二个循环,以获得第二个递归深度级别。play="paper"。然后我们遇到:

findOutCome(roundsLeft-1, result.concat(play))

从而创建具有CCD_ 56和CCD_ 57的新的第三深度级别。

第三级通过另一个forEach并设置result=["rock","paper","rock"]roundsLeft=0将其发送到第四级深度。

我们的结果被添加到结果中。因此,我们现在有:outcomes=[["rock","rock","rock"],["rock","rock","paper"],["rock","rock","scissors"],["rock","paper","rock"]];

Etc等……最终,我们的outcomes数组的大小增加到27个元素,并且用roundsLeft=3result=[]调用的第一级递归完成了它的forEach循环。最后,我们遇到return outcomes;,从而将我们的答案返回给console.log(...),CCD_65将我们的回答输出到控制台。控制台现在显示了一个包含27个元素的数组,每个元素包含一个3个元素大小的数组。

如果您查看函数findOutCome:

function findOutCome(roundsLeft, result){
    // when you cover all the rounds
    // push to the outcomes
    if (roundsLeft === 0) {
        outcomes.push(result);
        return;
    }
    plays.forEach(function(play){
        //result.push(play);
        //concat returns the entire array
        findOutCome(roundsLeft-1, result.concat(play));
    });
}

你可能会注意到:

if (roundsLeft === 0) {
    outcomes.push(result);
    return;
}

意味着当CCD_ 67到达CCD_。

现在让我们看看plays.forEach:

plays.forEach(function(play){
    //result.push(play);
    //concat returns the entire array
    findOutCome(roundsLeft-1, result.concat(play));
});

其中,对于plays中的每个选项(rockpaperscissors(,当前计算的结果数组从该选项开始,然后将结果相加:

findOutCome(roundsLeft-1, result.concat(play))

这意味着我们在回顾CCD_ 74时少了一轮。

所以说3最初是传入的:

1) `3 === 0` is `false` so we move on.
2) `plays.forEach` says take each option in `plays` (added to `result` which starts off empty) and pass it in to `findOutCome` with one round less. We pass in `Rock` as `result` and `2` as roundsLeft`:
    A) `2 === 0` is `false` so we move on.
    B) `plays.forEach` says do same as #2. We pass in [`Rock`, `Rock`] as `result` with `1` as `roundsLeft`.
        I) `1 === 0` is `false` so we move on.
        II) `plays.forEach says do same as #B. We pass in [`Rock`, `Rock`, `Rock`] as `result` with `0` as `roundsLeft`.
            Z) `0 === 0` is `true` so this time we add the `result` array we just got to the `outcomes` array.
    Now wait a second, we left off going through each `plays` in #B.  We need to repeat #I, #II, and #Z with the next option (after `rock`) in `plays`. Let's use `paper`. The result should be another array of ['rock', 'rock', `paper`] being pushed to `outcomes`. 

使用最后一个选项scissors(带有步骤#I、#II和#III(应将[rockrockscissors]数组添加到outcomes

等等,我们也在做整个过程的中间,roundsLeft就是2。我们只是为rock做的。让我们对第二个选项paper重复#A和#B。然后在1处重复roundsLeft,其中plays.forEach在[rockpaper处添加所有可能性#I、#II和#Z。这将向outcomes添加另外三个阵列:

[`rock`, `paper`, `rock`]
[`rock`, `paper`, `paper`]
[`rock`, `paper`, `scissors`]

然后我们可以运行第三个选项,其中roundsLeft2,并向outcomes添加另外三个阵列:

[`rock`, `scissors`, `rock`]
[`rock`, `scissors`, `paper`]
[`rock`, `scissors`, `scissors`]

现在,等待我们处理每个plays并将9可能的数组添加到outcomes。我们从[rock开始。让我们在[paper处添加另一个9,在[scissors.处添加另个9

这为我们提供了我们正在寻找的27可能的组合。

随着roundsLeft开始变得越来越大,我们运行这一切的层就越大,这使我们能够处理所有这些增长的可能性。

要理解递归,必须了解函数范围。为了让事情变得足够简单以便理解,假设定义功能块(主体(的开始和结束的花括号表示其范围,并且该范围可以用一个框来说明。只需想象一个盒子。现在,每当一个函数被声明时,它都以box的形式出现
函数可以使用外部代码或内部代码(递归(调用。因此,如果您在函数本身(或另一个函数(中调用一个函数,该函数将作为位于父框中的子框出现。您可以将参数传递给函数,但参数只知道它传递给它的值。因此,应用于参数的任何更改都不应影响与参数同名的外部变量。话虽如此,让我们回到你的问题,试着用我们的盒子类比来解释结果。

对于以下内容,我们将使用不同颜色的框(红、绿、黄、白(。请记住,这些框说明了各种作用域。"rock"是"r","paper"是"p","剪刀"是"s"。

在第一次调用"findOutcome"时,会创建一个RED框,其中包含参数roundleft=3。结果=[]。

//RED BOX : roundleft = 3, result =[]
//----for each rock, paper, scissor
//start with rock (r)  -- note : 3 , r means "roundleft, rock"
3 , r  => internal call to findOutcome() creates a GREEN BOX, 
          so that GREEN BOX is inside the RED one,
          with decremented roundleft value 2.
          Keep in mind that parameter only knows about its value
 //GREEN BOX : roundleft = 2, result =[r]
 //-----for each r p s,
 //starts with rock, 
 2 , r  => internal call to findOutcome() creates a YELLOW BOX,
           inside the GREEN BOX which is inside the RED BOX
           with decremented roundleft value 1.
  //YELLOW BOX : roundleft 1, result =[r, r]
  //----- for each r p s, 
  //starts with rock, roundleft =1, result =[r, r]
  1, r  => internal call to findOutcome() creates a WHITE BOX
           with decremented roundleft value 0.
           at this point RED > GREEN > YELLOW > WHITE
    //WHITE BOX: round = 0, result =[r, r, r]
    roundsLeft = 0 => push result to outcomes. 
                      return means exit WHITE BOX. 
                      we are now (back) inside YELLOW BOX 
                      where roundl= 1, res=[r, r] 
                      is there another element to go through? YES : "paper"
  //follow with paper, roundleft =1, result =[r, r]
  1,  paper(p) // internal call creates a WHITE BOX
    //WHITE BOX: round 0, result =[r, r, p]
    0 => push result to outcomes.
         exit white box, back to YELLOW BOX
         is there another element to go through? YES : "scissor"
  //follow with scissor. roundleft =1, result =[r, r]
  1, scissor // internal call creates a WHITE BOX
    //WHITE BOX: roundl 0, result =[r, r, s]
    0 => push result to outcomes.
         exit WHITE BOX, back to YELLOW BOX
         is there another element to go through? NO =>exit YELLOW BOX
         back inside GREEN BOX where roundl 2, result =[r]
 /*------ at this point outcomes length = 3 ---------- */

 //GREEN BOX (is inside RED BOX)
 //is there another element to go through? YES : "paper"
 //follow with paper, roundleft =2, result =[r]
 2, p  // internal call creates a YELLOW BOX
  //----- for each r p s, 
  //starts with rock, roundleft =1, result =[r, p]
  1, r // internal call creates a WHITE BOX
    //WHITE BOX: roundl 0, result =[r, p, r]
    0 => push result to outcomes. 
         exit WHITE BOX  and back inside YELLOW BOX 
         where roundl= 1, res=[r, p] 
         is there another element to go through? YES : "paper"
  //follow with paper, roundleft =1, result =[r, p]
  1, p   // internal call creates a WHITE BOX inside the YELLOW
    //WHITE BOX: roundl 0, result =[r, p, p]
    0 => push result to outcomes.
         exit WHITE BOX, back to YELLOW BOX
         is there another element to go through? YES : "scissor"

  //follow with scissor. roundleft =1, result =[r, p]
  1, s  // internal call creates a WHITE BOX
    //WHITE BOX: roundl 0, result =[r, p, s]
    0 => push result to outcomes.
         exit WHITE BOX, back to YELLOW BOX
         is there another element to go through? NO =>exit YELLOW BOX
         back inside GREEN BOX where roundl 2, result =[r]
 /*------ at this point outcomes length = 6 --------*/

 //GREEN BOX (inside the RED BOX)
 //is there another element to go through? YES : "scissor"
 //follow with scissor, roundleft =2, result =[r]
 2, s
  //----- for each r p s, 
  //starts with rock, roundleft =1, result =[r, s]
  1, r
    //WHITE BOX: roundl 0, result =[r, s, r]
    0 => push result to outcomes. 
         exit WHITE BOX  and back inside YELLOW BOX
         where roundl= 1, res=[r, s] 
         is there another element to go through? YES : "paper"
  //follow with paper, roundleft =1, result =[r, s]
  1, p 
    //WHITE BOX: roundl 0, result =[r, s, p]
    0 => push result to outcomes.
         exit WHITE BOX, back to YELLOW BOX
         is there another element to go through? YES : "scissor"

  //follow with scissor. roundleft =1, result =[r, s]
  1 s // decrement to 0
    //WHITE BOX: roundl 0, result =[r, p, s]
    0 => push result to outcomes.
         exit WHITE BOX, back to YELLOW BOX
         is there another element to go through? NO =>exit YELLOW BOX
         back inside GREEN BOX where roundl 2, result =[r]
 /*------ at this point outcomes length = 9 --------------*/

 //GREEN BOX : is there another element to go through after scissor ? 
 //    NO =>exit GREEN BOX
 //back inside RED BOX where roundl 3, result =[]
// RED BOX
//is there another element to go through after rock? YES : "paper"
//start with paper
3, p  // internal call creates a GREEN BOX
 //GREEN BOX : roundl 2, result =[p] 
 //-----for each r p s,
 //starts with rock, 
 2, r   // internal call creates a YELLOW BOX
  //YELLOW BOX : roundl 1, result = [p, r]
  //----- for each r p s, 
  //starts with rock, roundleft =1, result =[p, r]
  1, r   // internal call creates a WHITE BOX
    //WHITE BOX: roundl 0, result = [p, r, r]
    0 => push result to outcomes. 
         exit WHITE BOX and back inside YELLOW BOX
         where roundl= 1, result = [p, r] 
         is there another element to go through? YES : "paper"
  //follow with paper, roundleft =1, result =[p, r]
  // ... and so on...

以此类推,直到我们再次回到红盒子,结果长度将从9(岩石(到18(纸上(再到27(剪刀(
因此,基本上,在递归中,每次创建新的作用域时,都会在内部调用一个函数。执行相同的代码块,每次都使用只知道其值的参数,因此不会影响范围外的参数(框(。当点击框的底部时,如果没有函数的内部调用,它将返回到父框(向上一级(
我的母语是法语,所以希望我的英语不会那么生疏,这个长方体类比可以帮助你理解递归是如何工作的。