ズンドコキヨシをLINQでゴリ押してみる

# ズンドコキヨシをLINQでゴリ押してみる

## 概要という名の感想

- 正直LINQで先駆者がいるので、別の面白いアイデアがなかなか浮かばなかった
- でも`Random().NextBytes()`というアイデアを得て、1時間くらいで作成
- ソースは簡略化を目指して、indexの配列(List)を持たせることで解決
- やっぱりもうちょっとスマートに出来るんじゃないかと勘ぐってます

## ズンドコキヨシとは

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">Javaの講義、試験が「自作関数を作り記述しなさい」って問題だったから<br>「ズン」「ドコ」のいずれかをランダムで出力し続けて「ズン」「ズン」「ズン」「ズン」「ドコ」の配列が出たら「キ・ヨ・シ！」って出力した後終了って関数作ったら満点で単位貰ってた</p>&mdash; てくも (@kumiromilk) <a href="https://twitter.com/kumiromilk/status/707437861881180160?ref_src=twsrc%5Etfw">2016年3月9日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

プログラミングのスキルを簡易的に見る、「FizzBuzz」問題と同じような問題として、その発想のセンスと共に話題となったアルゴリズム問題です。

[ズンドコキヨシまとめ - Qiita](https://qiita.com/shunsugai@github/items/971a15461de29563bf90)

既にまとめとなっているように、数々の言語（中には言語なのか判別できないものも）でチャレンジされており、良問として親しまれているようです。  
実は、既にLINQを用いた回答例は出ているのですが、`yield`などまだ慣れない内容もあったので、自分なりにLINQだけを用いたアイデアでチャレンジしてみました。

前回のLINQ: [バーコードのチェックディジットをLINQでゴリ押してみる - Qiita](https://qiita.com/urahito_solution/items/459e21480feb5dc8a8e1)

### ポイント

- 「ズン」「ドコ」「キ・ヨ・シ！！」の部分文字列の配列を先に用意する
- index(int型)の配列を答えにもち、乱数を保持する配列も0,1の2値をとるようにする
- 前回も利用した、比較順としてのリストを`Enumerable.Range`で生成する(for文の省略)

### 新しい内容

- `results.All(x => x)`はbool型のリストがすべてtrueであれば、結果もtrueである
- `string.Concat()`は`IEnumerate<string>`も入力として結合できる
- `Random().NextBytes(bytes[])`のため、byte配列を用意しておく必要がある

## ソースコード全文

```C#:Zundoko.cs
private static void Zundoko()
{
    var output = new StringBuilder();
    var rnd = new Random();
                
    string[] seedArr = new string[] { "ズン", "ドコ", "キ・ヨ・シ！！" };    // 出力の配列
    var answerArr = new int[] { 0, 0, 0, 0, 1 }.ToList();   // 正解の配列(ToListはindexのため利用)
    var idxs = Enumerable.Range(0, answerArr.Count);        // 整列のためのindex
    var resBytes = new byte[answerArr.Count];               // 乱数を格納するbyte配列

    while (true)
    {
        rnd.NextBytes(resBytes);    // byte列を一括で取得
        var rdmList = resBytes.Select(x => x % 2).ToList();                     // Mod 2で0,1のindexに変換
        var results = idxs.Select(idx => answerArr[idx].Equals(rdmList[idx]));  // 並び順の比較

        // results(IEnumerate<bool>)の結果がすべてtrueなら
        if (results.All(x => x))
        {
            // キ・ヨ・シ！！のindexを追加
            answerArr.Add(2);
            var resStr = string.Concat(answerArr.Select(x => seedArr[x]));  // 出力の文字列を生成
            output.AppendLine(resStr);
            break;
        }
        else
        {
            var resStr = string.Concat(rdmList.Select(x => seedArr[x]));    // 出力の文字列を生成
            output.AppendLine(resStr);
        }
    }

    Debug.Print(output.ToString());     // 結果の出力
}
```

### 結果

```
ドコドコズンズンドコ
ズンドコズンドコズン
ドコドコドコズンドコ
ドコズンズンドコズン
ドコズンズンドコドコ
ズンドコドコドコズン
ズンズンドコドコドコ
ズンズンズンドコドコ
ズンドコズンドコズン
ズンズンドコズンズン
ズンドコドコドコドコ
ドコズンズンドコズン
ズンドコズンズンズン
ドコドコドコズンドコ
ドコズンドコズンズン
ドコズンドコドコズン
ズンドコドコズンズン
ドコドコドコズンズン
ズンドコドコズンドコ
ズンドコドコドコズン
ズンドコドコズンドコ
ズンドコドコドコドコ
ドコズンドコズンズン
ズンドコズンドコズン
ズンズンズンドコドコ
ドコズンドコズンドコ
ズンズンズンズンズン
ドコズンズンズンズン
ズンズンズンズンドコキ・ヨ・シ！！
```

## その他の方法

新しい内容については、近日中にまとめます。

### キュー(Queue)を利用する

```C#:Zundoko2.cs
private static string Zundoko2()
{
     string[] seedArr = new string[] { "ズン", "ドコ", "キ・ヨ・シ！！" };
     var answerArr = new int[] { 0, 0, 0, 0, 1 }.ToList();
     var resQueue = new Queue<int>();
     var rnd = new Random();
     int rndTimes = 0;
     string resStr = string.Empty;
     sw.Reset();
     sw.Start();

     while (!resQueue.Contains(2))
     {
        rndTimes++;
        resQueue.Enqueue(rnd.Next(0, 2));

        if (resQueue.Count < 5)
        {
            continue;
        }

        // 最後の5個を取り出す
        var lastFive = resQueue.Skip(resQueue.Count - 5);

        // 答えと並びが一緒であること
        if (lastFive.SequenceEqual(answerArr))
        {
            resQueue.Enqueue(2);
            resStr = string.Concat(lastFive.Select(x => seedArr[x]));
        }
    }

    sw.Stop();
    return string.Format("{0}, {1}, {2}", rndTimes, sw.ElapsedMilliseconds, resStr);
}
```

### スタック(Stack)を利用して一般化

```C#:Zundoko4.cs
private static string Zundoko4(string inputStr, string lastStr = "")
{
     // ここが重要
     // inputStrを分解し、重複を削除してから結合
     var seedArr = string.Concat(inputStr.Select(x => x).Distinct());  
     // inputStrをseedArrのindexに置き換え  
     var answerArr = inputStr.Select(x => seedArr.IndexOf(x));    

     var resStack = new Stack<int>();
     int lastIdx = answerArr.Max() + 1;
     var rnd = new Random();
     int rndTimes = 0;
     string resStr = string.Empty;
     sw.Reset();
     sw.Start();

     Debug.Print("seedArr: {0}", seedArr);
     while (!resStack.Contains(lastIdx) && sw.ElapsedMilliseconds < 1000)
     {
		rndTimes++;
        // 0～lastIdx未満の整数を追加
		resStack.Push(rnd.Next(0, lastIdx));
        // 比較用に反転
		var revStack = resStack.Reverse();

        // 答えと並びが一緒であること
		if (!revStack.SequenceEqual(answerArr.Take(resStack.Count)))
		{
		    resStack.Pop();
		}
		else if (resStack.Count == answerArr.Count())
		{
		    resStack.Push(lastIdx);
		    resStr = string.Concat(inputStr, lastStr);
		}
     }

     sw.Stop();
     return string.Format("{0}, {1}, {2}", rndTimes, sw.ElapsedMilliseconds, resStr);
 }
```

### おまけ：Stackの場合の結果の変化

```
1: コ
2: ド
3: ド
4: ン
5: ズ
6: ズド
7: ズズ
8: ズン
9: ズンン
10: ズンド
11: ズンン
12: ズンン
13: ズンコ
14: ズンン
15: ズンド
16: ズンド
17: ズンド
18: ズンズ
19: ズンズド
20: ズンズド
21: ズンズズ
22: ズンズド
23: ズンズン
24: ズンズンズ
25: ズンズンズン
26: ズンズンズンズ
27: ズンズンズンズズ
28: ズンズンズンズコ
29: ズンズンズンズド
30: ズンズンズンズズ
31: ズンズンズンズズ
32: ズンズンズンズン
33: ズンズンズンズンン
34: ズンズンズンズンコ
35: ズンズンズンズンド
36: ズンズンズンズンドド
37: ズンズンズンズンドン
38: ズンズンズンズンドド
39: ズンズンズンズンドコ
```