再帰関数とは
自分自身を呼び出す関数のこと。ループと似ており同じコードを何度も実行するので、無限再帰を防ぐための条件が必要になる。
再帰のコードを書くときのポイント
- 実際の値を当てはめて、コードの流れを考える
- どのような関数をどこで呼び出すか考える
- ベースケースとリカーシブケースを考える
- 「リカーシブケース」・・・何を対象に再帰処理を継続していくか(ループの繰り返し処理にあたる部分)
- 「ベースケース」・・・どういう条件を満たしたら再帰処理を止めるのか(ループ終了)
- 木構造で考えると分かりやすい
実際にコードを分解してみる
- じゃんけんの組み合わせ(下のサイトからコード参照)
const rockPaperScissors = (rounds = 3) => {
let out = [];
const weapons = ["rock", "paper", "scissors"];
const play = (rounds, result = []) => {
// exit case
if (rounds < 1) {
out.push(result);
return;
}
// recursive case
for (let i = 0; i < weapons.length; i++) {
const weapon = weapons[i];
play(rounds - 1, result.concat(weapon));
}
};
play(rounds);
return out;
};
- 分解して考えると・・・
rockPaperScissors(); //関数呼び出しすると・・・
// 大前提 = すべてのループは3回ずつ回ること!!!
play(3, []); //rounds = 3
// loop 0 (i = 0 --> "rock") 親のループが始まる
weapon = "rock";
play(2, result = ["rock"]);
// loop 0-0 (i = 0 --> "rock") 親のループの中で、子のループが始まる
weapon = "rock";
play(1, result = ["rock", "rock"]);
// loop 0-0-0 (i = 0 --> "rock") 子のループの中で、孫のループが始まる
weapon = "rock";
play(0, result = ["rock", "rock", "rock"]);
//exit case === True
out = [["rock", "rock", "rock"]];
return
//つまり「処理の終了」、孫のループの値が更新される。
play(0, result = ["rock", "rock", "rock"]);
// loop 0-0-1 (i = 1 --> "paper")
weapon = "paper";
play(0, result = ["rock", "rock", "paper"]);
//exit case === True
out = [["rock", "rock", "rock"], ["rock", "rock", "paper"]];
return
// loop 0-0-2 (i = 2 --> "scissors")
weapon = "scissors";
play(0, result = ["rock", "rock", "scissors"]);
//exit case === True
out = [["rock", "rock", "rock"], ["rock", "rock", "paper"], ["rock", "rock", "scissors"]];
return
// 孫のループがすべて回り切ったので、いったん子のループに戻る(子ループの値更新)
// loop 0-1 (i = 1 --> "paper")
weapon = "paper";
play(1, result = ["rock", "paper"]);
// また孫のループが始まる
// loop 0-1-0 (i = 0 --> "rock")
weapon = "rock";
play(0, result = ["rock", "paper", "rock"]);
//exit case === True
out = [["rock", "rock", "rock"], ["rock", "rock", "paper"], ["rock", "rock", "scissors"], ["rock", "paper", "rock"]];
return
// loop 0-1-1
// loop 0-1-2
// loop 0-2-0
// loop 0-2-1
// loop 0-2-2
// loop 1-0-0
以下、省略・・・
動画
オススメしていただいた動画。解説が分かりやすかったです。
ひとこと
はじめて再帰のコードを一から書いたとき、再帰処理の流れより先に実行すべき関数ばかり考えて、手が止まってしまいました。その時に頂いたアドバイスが「再帰のコードを書くときのポイント」です。
何パターンかコードを見たり書いたりしているうちに処理の流れが理解できて、リファクタリングもできるようになりました。引き続き理解を深めていこうと思います!