岩石剪刀的递归解法
recursive solution to rock paper scissors
我们今天在课堂上讨论了这个问题,我很难想象递归是如何准确工作的。你应该返回一个数组,所有可能的组合为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=numRounds
和result=[].
我们的第一个递归调用如下:
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=1
和result=["rock","rock"]
。
if (roundsLeft === 0) {...}
仍然为假,所以我们继续…
因此,我们进入了我们的forEach循环的第三层,它循环通过我们的播放阵列。。。第一个循环使用var outcomes = [];
0,因此我们的循环以结束
findOutCome(0,["rock","rock","rock"])
然后,我们进入第四个递归级别并设置roundsLeft=0
和result=["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=1
和result=["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=2
和result=["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=3
和result=[]
调用的第一级递归完成了它的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
中的每个选项(rock
、paper
、scissors
(,当前计算的结果数组从该选项开始,然后将结果相加:
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(应将[rock
、rock
、scissors
]数组添加到outcomes
。
等等,我们也在做整个过程的中间,roundsLeft
就是2
。我们只是为rock
做的。让我们对第二个选项paper
重复#A和#B。然后在1
处重复roundsLeft
,其中plays.forEach
在[rock
,paper
处添加所有可能性#I、#II和#Z。这将向outcomes
添加另外三个阵列:
[`rock`, `paper`, `rock`]
[`rock`, `paper`, `paper`]
[`rock`, `paper`, `scissors`]
然后我们可以运行第三个选项,其中roundsLeft
是2
,并向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(剪刀(
因此,基本上,在递归中,每次创建新的作用域时,都会在内部调用一个函数。执行相同的代码块,每次都使用只知道其值的参数,因此不会影响范围外的参数(框(。当点击框的底部时,如果没有函数的内部调用,它将返回到父框(向上一级(
我的母语是法语,所以希望我的英语不会那么生疏,这个长方体类比可以帮助你理解递归是如何工作的。
- 数组在递归方法中设置为null
- Kendo:我该如何在树视图中创建一个递归的hieiarchy
- 递归使用 eval() 是检查程序执行的好方法吗?
- 使用递归、Ramda.js和无点样式重构字符串的getPermutations()
- 递归深度比较
- Eloquent JavaScript递归示例如何终止为返回1,但仍然输出指数值
- 递归函数中断
- 如何递归地获取嵌套对象中所有子对象的列表
- JavaScript 素数搜索无限递归
- 在递归生成器函数中,yield后面的*(星号/星号)语法意味着什么
- 递归|两个函数名
- 有没有一种方法可以在Javascript中进行可变递归currying
- 如何对不同的表递归使用以下代码
- 将jQuery对象传递到setTimeout递归函数中
- 有更好的方法吗?(递归解析HTML unicode实体)
- 为什么递归生成器函数没有't在ES2015工作
- 使用递归实现加性持久性
- 递归显示n元树
- 岩石剪刀的递归解法
- 石头剪刀布递归