[Javascript]おばあとおじいの1週間の献立を無理やり再帰にする準備をする


背景

「どうせ学んでいるのならアウトプットせい!」とお叱り愛の叱咤激励をもらいました。
ごもっともなので毎日投稿を習慣化してみる。

自己紹介(=情報の信頼度)

2020年4月から学習をはじめた駆け出しエンジニア
HTML、CSS、Javascript、React、Vue、Laravelを学習
AWSもamplifyとかS3、EC2あたりをちょこっと。ふくおか。

今日のおはなし

〜おじいおばあの家〜

おばあ「今週のごはんは何にしようか。」

おじい「精のつくものとらねばねえ。」

おじい「しかし、ついつい昨日のごはんも忘れてしまうとよ。困ったもんちゃ!」

おばあ「では、私が「1週間の献立」をつくってみようかね」

おばあ「最近、ぷろぐらみんぐ教室で学んだの。」

お題

[朝、昼、夜]の献立表をつくる。

献立表=[
[🍛、🍛、🍛][🍛、🍛、🍣][🍛、🍛、🍜]
[🍛、🍣、🍛][🍛、🍣、🍣][🍛、🍣、🍜]
[🍛、🍜、🍛][🍛、🍜、🍣][🍛、🍜、🍜]
[🍣、🍛、🍛][🍣、🍛、🍣][🍣、🍛、🍜]
[🍣、🍣、🍛][🍣、🍣、🍣][🍣、🍣、🍜]
[🍣、🍜、🍛][🍣、🍜、🍣][🍣、🍜、🍜]
[🍜、🍛、🍛][🍜、🍛、🍣][🍜、🍛、🍜]
[🍜、🍣、🍛][🍜、🍣、🍣][🍜、🍣、🍜]
[🍜、🍜、🍛][🍜、🍜、🍣][🍜、🍜、🍜]
]

※おじいさんとおばあさんは、カレー、寿司、ラーメンしかたべません。

再帰を学ぼう!

再帰とは

関数が自分自身を呼ぶこと。
たとえば、繰り返し処理(forループ)を、

こう書き換えられる。

この場合、
n==3であれば、x*pow(2,3) //-> 2*2
n==2であれば、x*pow(2,3) //-> 2*2*2
n==1になるとき、x*pow(2,3)//-> 8

となっている。

@fujitanozomu様にご指摘いただきました!ありがとうございます!

0乗が正しく求まらないので書き換えられていないのでは

仰られる通りです。ご指摘いただきありがとうございます。
下記に訂正いたします。

function pow(x, n) {
  if (n == 0) {
    return 1;
  } else {
    return x * pow(x, n - 1);
  }
}

再帰を考えるときのコツ

再帰を終了する条件:ベースケース
再帰を継続する条件:リカーシブケース
と呼ぶらしい。

再帰関数自体にベースケース、リカーシブケースに関係する値を引数としていれておくこと。

実際に再帰で考えてみよう!

おばあ「まず、全体の流れを理解しよう。」

おばあ「すべての献立パターンがつくれて、関数を実行すると「献立表」が返ってくる。」

おばあ「食べ物は、食糧庫からとってこよう。」

おばあ「朝、昼、夜、の3食決まったら再帰は終了だね。間食はしないからね」

おばあ「じゃあ、まだ朝、昼までしか決まっていないときは、再帰を継続する必要があるね。」


おばあ「あ、再帰したときに、減らす引数がない!」

おばあ「これでよしっと。」

おばあ「繰り返し処理になっている部分は、、と。」

おばあ「各食事に対してpantryからすべての食事を取り出す。」

おばあ「まず、[🍛、🍛、🍛]をつくるには、」

おばあ「1回目の食事を[🍛]いれて、2回目の食事にする。」

おばあ「2回目の食事を[🍛]いれて、3回目の食事にする。」

おばあ「3回目の食事を[🍛]いれて、3食分の献立をresultにいれる。」

おばあ「このあたりが繰り返しの処理っぽいな。」

おばあ「次に、[[🍛、🍛、🍣]をつくる。]

おばあ「途中まで[🍛、🍛]をつくってるから、その上に対して🍣を入れたいけど、どうしようか」

おばあ「作成途中の献立[🍛、🍛]を受けとれるようにしよう。」

おばあ「作成途中の献立を受け取れるように引数を変更して、」

おばあ「それらに対してpantryから次の食事を加えよう。」

おばあ「これで、自分自身を呼ぶことで、配列を作るrecuse関数ができあがった」

おばあ「あ。recuse関数の発火をしてない。えい。」

const menu = (numberOfMeals=3) => {
  // Your code here
  const pantry = ["🍛", "🍣", "🍜"];
  let result = [];
    function recurse(meal=[],numberOfMeals) {
    if (numberOfMeals > 0) {
      //再帰の処理
      for (let i = 0; i < pantry.length; i++) {
       recurse(meal.concat(pantry[i]),numberOfMeals - 1,) 
      }
    } else {
      //3食分の献立ができたので終了する
      result.push(meal)
    }
    return result;
  }
  return recurse([],numberOfMeals)
};

その後

おばあ「meal()[1]」

おじい「もぐもぐ」

おばあ「meal()[4]」

おじい「もぐもぐ」

補足

pushではなく、concatで記述している理由は、配列のままにしたいからです。

pushだと要素数を返す
=>数字になる。
=>昼のごはんが選べなくなる
=cannnot read proptyとなるため避けています。

recuse関数外にresultを定義しているのはクロージャーを使いたいためです。

後日記事にします。

解釈間違い等、何かあればぜひコメントください。

誰か再帰の使いみちを教えてください・・・。