我们今天在课堂上遇到了这个问题,但我无法直观地看出递归是如何工作的。你应该为一个玩家返回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
答案 0 :(得分:3)
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
。在我们的函数体内部,我们发现:
var outcomes = [];
和var plays = ["rock", "paper", "scissors"];
。
这些将继续为我们的递归函数定义,以便阅读plays
并写入outcomes
。
然后定义我们将用于递归的嵌套函数。
最后我们的嵌套函数被调用:findOutCome(numRounds, []);
这样做是第一次调用我们的嵌套函数并指定roundsLeft=numRounds
和result=[].
我们的第一个递归调用如下所示:
if (roundsLeft === 0){...}
这句话是假的,因为roundsLeft设置为3所以我们继续......
plays.forEach(function(play){...}
此播放循环3次,因为播放设置为["rock", "paper", "scissors"]
。
使用function(play){...}
调用第一个循环play="rock"
,并在回调函数体中调用:
findOutCome(roundsLeft-1, result.concat(play));
这样做是为了调用findOutCome(2,result.concat("rock"))
此处使用concat不会修改结果数组,而是在副本上工作,并使[]
与"rock"
进行联接,从而创建["rock"]
。
如果我们想要实际修改结果数组,我们在这里使用result.push(...)
。
但是每个递归实例都有自己的本地版本的结果,因此不会起作用,因为这些更改不会影响任何内容。
我们的第一个递归实例仍处于打开状态,当我们开始递归调用时,我们仍然在第一个forEach循环中。
我们调用了findOutCome
的第二个递归实例。在我们的第二个实例中roundsLeft=2
和result=["rock"]
。
if (roundsLeft === 0) {...}
是假的,所以我们转到forEach循环...
我们输入第一个forEach循环和play="rock"
。然后我们致电findOutCome(1, ["rock","rock"])
我们因此进入第三级递归并设置roundsLeft=1
和result=["rock","rock"]
。
if (roundsLeft === 0) {...}
仍然是假的,所以我们继续......
我们因此进入我们的forEach循环的第3级循环遍历我们的play数组......第一个循环使用play="rock"
因此我们的循环结束于:
findOutCome(0,["rock","rock","rock"])
然后我们输入第4个递归级别并设置roundsLeft=0
和result=["rock","rock","rock"]
。
if (roundsLeft === 0) {outcomes.push(result);return;}
这句话是真的,所以我们处理它的逻辑。
当前设为outcomes
的{{1}}数组会附加[]
,从而创建:
["rock","rock","rock"]
然后我们的if语句遇到outcomes=[["rock","rock","rock"]];
,它结束了我们的第四个递归级别并返回到我们的第三个递归级别。
在我们的第3个递归级别中,我们仍然在forEach循环中,所以我们继续循环中的第2个元素。
请记住,在我们的第3个递归级别中,我们使用return
和findOutCome
调用了roundsLeft=1
函数,并且未对其进行修改。永远不会修改变量,而是每个递归实例都使用自己的这些变量的本地副本。因此,在我们的forEach循环中,因为它是循环的第二个元素,result=["rock","rock"]
。
然后我们遇到play="paper"
,其评估结果为:
findOutCome(roundsLeft-1, result.concat(play))
因此我们输入第4个递归级别并且findOutCome(0, ["rock","rock","paper"])
为真,防止超过3个递归级别的深度,因此我们处理其逻辑。
if (roundsLeft === 0) {outcomes.push(result);return;}
将outcomes.push(result)
添加到我们的数组中。
因此,我们的结果数组现在为:["rock","rock","paper"]
然后我们遇到outcomes=[["rock","rock","rock"],["rock","rock","paper"]];
语句并关闭我们的第四个递归级别的深度并恢复我们的第三个递归级别forEach循环。
当我们的forEach循环在我们的第三级递归中完成时,return
然后我们的forEach循环结束,从而返回到outcomes=[["rock","rock","rock"],["rock","rock","paper"],["rock","rock","scissors"]];
和roundsLeft=2
的第二级递归的forEach循环。
我们继续使用forEach的第二个循环来获得第二个递归级别的深度。 result=["rock"]
。然后我们遇到:
play="paper"
因此,使用findOutCome(roundsLeft-1, result.concat(play))
和roundsLeft=1
创建新的第3级深度。
第3级经过另一个forEach并设置result=["rock","paper"]
,result=["rock","paper","rock"]
将其发送到第4级深度。
我们的结果被添加到结果中。因此,我们现在有:
roundsLeft=0
Etc等...最终我们的outcomes=[["rock","rock","rock"],["rock","rock","paper"],["rock","rock","scissors"],["rock","paper","rock"]];
数组的大小增加到27个元素,我们用outcomes
和roundsLeft=3
调用的第一级递归完成了forEach循环。最后我们遇到result=[]
,然后将我们的答案返回到return outcomes;
,它将我们的答案输出到控制台。控制台现在显示一个包含27个元素的数组,每个元素包含一个3个元素的数组。
答案 1 :(得分:0)
要理解递归,您必须了解函数范围。为了使事情变得简单易于理解,让我们说定义函数块(正文)的开头和结尾的花括号表示其范围,并且该范围可以用框来说明。只是想象一个盒子。 现在,每次声明一个函数时,它都是盒子 可以使用外部代码或内部代码(递归)调用函数。因此,如果您在其自身(或其他函数)内部调用该函数,则该函数将作为位于父框内的子框。您可以将参数传递给函数,但参数只知道它传递给它的值。因此,应用于参数的任何更改都不应影响与参数同名的外部变量。话虽如此,让我们回到你的问题,并尝试使用我们的盒子类比来解释结果。
对于以下内容,我们将使用不同颜色的盒子(红色,绿色,黄色,白色)。请记住,方框说明了各种范围。 “摇滚”也是“r”,“纸”是“p”,“剪刀”是“s”。
在第一次调用'findOutcome'时,会创建一个RED框,其中包含参数roundleft = 3. result = []。
show()
依此类推,直到我们再次回到RED BOX,结果长度将从9(摇滚)变为18(在纸上)到27(在剪刀上)。
所以基本上,在递归中,每次创建新范围时,都会在内部调用函数。执行相同的代码块,每次都使用仅知道其值的参数,因此不会影响范围之外(框)。当击中盒子底部时,如果没有内部调用该函数,它将返回到父盒子(向上一级)。
我的母语是法语,所以希望我的英语不那么生疏,这个盒子类比可以帮助你理解递归是如何工作的。