鑑賞を鑑賞を鑑賞を鑑賞を鑑賞を鑑賞しないを鑑賞しないを鑑賞から学ぶ再帰処理

# 天竺鼠 川原 究極シリーズ
お笑い芸人 天竺鼠 川原さんのYoutubeの動画を見て、なんとなく再帰処理の練習になるのでは？と思ったので実際にやってみました。

Youtubeを見てもらうとわかるのですが、最初の動画を起点に色々な芸人さんが鑑賞してもらうのをみなさんに見ていただく動画です。（↓の画像のイメージです。）

![鑑賞を鑑賞を鑑賞.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/123946/d983e76b-1a76-4066-1f77-6f5ec52bf890.png)

要件は以下になります。
1. 動画の最初は「オープニング、オープニング...」で始まる
1. 必ず鑑賞した順番を最初述べ、最後に「皆さんに見てもらいます」と言い、動画をスタートする
1. 動画を見る人と見ない人がいる
1. 一番最初の動画である、せいやさんと川原さんの動画が流れる
1. 必ず鑑賞した順番を最後に述べ、最後に「皆さんに見てもらいました」と言い、「おわり」で動画が終わる

# 鑑賞を鑑賞を鑑賞
簡単にするため、まずは必ず動画を鑑賞することとします。

```typescript
function opening() {
  console.log("オープニング、オープニング...オープニング");
}
function ending() {
  console.log("お わ り");
}

function originalStream() {
  opening();
  console.log("せっせっせいや");
  ending();
}

function getDescription(names: string[]): string {
  return ["川原さんのYoutubeを"].concat(names.map(name => `${name}さんが見てるのを`)).join("");
}

function recursive_watch(names: string[], isFirst: boolean) {
  // 探索要素が無くなった場合の処理。
  if (names.length === 0) {
    // 最後まで行ったら元々のYoutubeが流れる。
    originalStream();
    return;
  }

  if (isFirst) {
    // 動画の最初の人だけオープニングが流れる
    opening();
  }
  const preDescription = `${getDescription(names)}皆さんに見てもらいます。`;
  console.log(preDescription);

  // 自身を除いた配列にする。実行が終わった要素を除いていくことで処理が終了に向かう
  const previousNames = names.slice(0, -1);
  // PCを開いて動画を見る。
  recursive_watch(previousNames, false);
  const postDescription = `${getDescription(names)}皆さんに見てもらいました。ありがとうございました。`;
  console.log(postDescription);
  ending();
}

function main() {
  const comedianNames = ["川原", "秋山", "山名", "川西"];
  const isFirst = true;
  recursive_watch(comedianNames, isFirst);
}

main();

```
実行結果
```shell
オープニング、オープニング...オープニング
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを皆さんに見てもらいます。
オープニング、オープニング...オープニング
せっせっせいや
お わ り
川原さんのYoutubeを川原さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り

```

# 鑑賞を鑑賞を鑑賞を鑑賞しないを鑑賞しないを鑑賞
今度は鑑賞しない人を混ぜてみます。
鑑賞する人だけであれば名前の配列だけを渡して文字列にするだけで十分でした。が、今度はする人としない人を区別する必要があります。

そこで鑑賞する/しないの状態を持たせてみます。
```typescript
interface Comedian {
  name: string;
  shouldWatch: boolean; // 鑑賞をするべきか否か
}

function getDescription(comedians: Comedian[]): string {
  return ["川原さんのYoutubeを"]
    // フラグに応じてメッセージを組み立てる
    .concat(comedians.map(comedian => `${comedian.name}さんが${comedian.shouldWatch ? "見てる" : "見てない"}のを`))
    .join("");
}

// 変数名のみ修正
function recursive_watch(comedians: Comedian[], isFirst: boolean) {
  // 探索要素が無くなった場合の処理。
  if (comedians.length === 0) {
    // 最後まで行ったら元々のYoutubeが流れる。
    originalStream();
    return;
  }

  if (isFirst) {
    // 動画の最初の人だけオープニングが流れる
    opening();
  }
  const preDescription = `${getDescription(comedians)}皆さんに見てもらいます。`;
  console.log(preDescription);

  // 自身を除いた配列にする。実行が終わった要素を除いていくことで処理が終了に向かう
  const previousComedians = comedians.slice(0, -1);
  // PCを開いて動画を見る。
  recursive_watch(previousComedians, false);
  const postDescription = `${getDescription(comedians)}皆さんに見てもらいました。ありがとうございました。`;
  console.log(postDescription);
  ending();
}
```

実行すると以下になります。

```shell
オープニング、オープニング...オープニング
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを秋山さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを皆さんに見てもらいます。
オープニング、オープニング...オープニング
せっせっせいや
お わ り
川原さんのYoutubeを川原さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを秋山さんが見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
```

# 要件は満たしたが、、、
元の動画を見てもらうとわかるのですが、各芸人さんは動画を見ながらよくコメントをする人もいれば、ぽつりぽつりコメントを入れる方もいらっしゃいます。
もし、さらに動画に近づけようとした際に、このままのコードでわかりやすいコードになるでしょうか？
純粋な再帰処理と芸人さんの振る舞いは分けて考えた方がよさそうです。interface ではなくて class にしてみます。


```typescript
class Comedian {
  constructor(private name: string, private shouldWatch: boolean, public isFirst: boolean = false) {}

  private _getOriginalStreamDescription(): string {
    return "川原さんのYoutube";
  }

  private _getSelfDescription(): string {
    return `僕が${this.shouldWatch ? "見てる" : "見てない"}`;
  }

  private _getOtherDescription(comedian: Comedian): string {
    return `${comedian.name}さんが${comedian.shouldWatch ? "見てる" : "見てない"}`;
  }

  private _getDescription(comedians: Comedian[]): string {
    return comedians.map(c => {
      // 本当はequalsメソッド的なのがあった方が良い
      if (c === this) {
        return `${this._getSelfDescription()}のを`;
      } else {
        return `${this._getOtherDescription(c)}のを`;
      }
    }).join("");
  }

  talkOpening(comedians: Comedian[]) {
    const description = this._getDescription(comedians);
    const opening = `${this._getOriginalStreamDescription()}を${description}皆さんに見てもらいます。`;
    console.log(opening);
  }

  talkEnding(comedians: Comedian[]) {
    const description = this._getDescription(comedians);
    const ending = `${this._getOriginalStreamDescription()}を${description}皆さんに見てもらいました。ありがとうございました。`;
    console.log(ending);
  }
}

function recursive_watch(comedians: Comedian[]) {
  if (comedians.length === 0) {
    // 最後まで行ったら元々のYoutubeが流れる。
    originalStream();
    return;
  }

  const currentComedian = comedians[comedians.length - 1];
  if (currentComedian.isFirst) {
    // 動画の最初の人だけオープニングが流れる
    opening();
  }
  currentComedian.talkOpening(comedians);
  // PCを開いて動画を見る。
  recursive_watch(comedians.slice(0, -1));
  currentComedian.talkEnding(comedians);
  ending();
}

```
クラスの実装自体は長くなってしまったんですが、文字列の整形やconsole.logがなくなり再帰の構造が見やすくなってたら嬉しいです。

出力は以下です。
```shell
オープニング、オープニング...オープニング
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを僕が見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを僕が見てないのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを僕が見てないのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを僕が見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを僕が見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを僕が見てるのを皆さんに見てもらいます。
川原さんのYoutubeを川原さんが見てるのを僕が見てるのを皆さんに見てもらいます。
川原さんのYoutubeを僕が見てるのを皆さんに見てもらいます。
オープニング、オープニング...オープニング
せっせっせいや
お わ り
川原さんのYoutubeを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを僕が見てないのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを僕が見てないのを皆さんに見てもらいました。ありがとうございました。
お わ り
川原さんのYoutubeを川原さんが見てるのを秋山さんが見てるのを山名さんが見てるのを川西さんが見てるのを石井さんが見てるのを津田さんが見てないのを川原さんが見てないのを僕が見てるのを皆さんに見てもらいました。ありがとうございました。
お わ り
````

# お わ り

本記事を読むを読むを読まないを読む記事待ってます。

今回のソースコード

https://github.com/shikazuki/recursive-watch-watch-watch