Kotlin でズンドコキヨシをやってみる

```kotlin
mutableListOf<String>().run {
    do {
        add(arrayOf("ズン", "ドコ")[Math.random().times(2).toInt()].apply { println(this) })
        if (size > 5) removeAt(0)
        if (joinToString("") == "ズンズンズンズンドコ") break
    } while (true)
    println("キ・ヨ・シ")
}

```

apply などのスコープ関数をうまく使うとメソッドを繋げて書くことができますが、だいぶみにくいと思います。

~~`generateSequence` を使うことも考えたが、最初に要素数を確定しないといけないみたいで、使えそうにはなかった。~~

## `generateSequence` を使った例

```kotlin
{
    val words = arrayOf("ズン", "ドコ")
    var i = 0
    loop@ for (word in generateSequence { words[Math.random().times(2).toInt()] }) {
        print(word)
        when (word) {
            "ズン" -> if (i < 4) i++
            else -> if (i == 4) break@loop else i = 0
        }
    }
    println("キ・ヨ・シ")
}()
```

## 平均何回 `ズン` もしくは `ドコ` が表示されるのか

状態を次のように決める。

| 状態番号 | 状態 | 
|--:|:--|
| 0 | 初期状態。 |
| 1 | "ズン" が1回連続で出た状態。 |
| 2 | "ズン" が2回連続で出た状態。 |
| 3 | "ズン" が3回連続で出た状態。 |
| 4 | "ズン" が4回以上連続で出た状態。 |
| 5 | 終了状態。 状態番号4のあとで "ドコ" が出た状態。|

乱数による 0, 1 の値がそれぞれ 1/2 の確率で出ると仮定すると、推移確率行列は下の `m` となる。 
ここから平均訪問回数を求める。計算の詳しいことは *（10） 吸収マルコフ過程* (Kimura, n.d.) に記載されている。

```python
>>> import numpy as np

>>> m
matrix([[0.5, 0.5, 0. , 0. , 0. , 0. ],
        [0.5, 0. , 0.5, 0. , 0. , 0. ],
        [0.5, 0. , 0. , 0.5, 0. , 0. ],
        [0.5, 0. , 0. , 0. , 0.5, 0. ],
        [0. , 0. , 0. , 0. , 0.5, 0.5],
        [0. , 0. , 0. , 0. , 0. , 1. ]])
```

ここから一時的状態の推移行列を取り出して `mp` とする。

```python
>>> mp = m[:5, :5]
```

`mp` の 0, 1, 2, ... 乗 の総和が訪問回数を表す行列となる。
厳密には、0乗 は加算せず、 終了状態に至るときの「ドコ」を加算する必要があるが、 1引いて1足すので0乗を加算して考えても差し支えない。
これは単位行列から `mp` を引いた行列の逆行列に等しい。

```python
>>> i = np.identity(5)
>>> n = i - mp
>>> n
matrix([[ 0.5, -0.5,  0. ,  0. ,  0. ],
        [-0.5,  1. , -0.5,  0. ,  0. ],
        [-0.5,  0. ,  1. , -0.5,  0. ],
        [-0.5,  0. ,  0. ,  1. , -0.5],
        [ 0. ,  0. ,  0. ,  0. ,  0.5]])
>>> ni = np.linalg.inv(n)
>>> ni
matrix([[16.,  8.,  4.,  2.,  2.],
        [14.,  8.,  4.,  2.,  2.],
        [12.,  6.,  4.,  2.,  2.],
        [ 8.,  4.,  2.,  2.,  2.],
        [ 0.,  0.,  0.,  0.,  2.]])
```

ここから、終了状態に至るまでに、「ドコ」が平均16回表示されることがわかる。
これは状態番号0への訪問回数が1行1列の値で表されることから計算できる。
開始時点の訪問では「ドコ」を表示せず、終了状態に至る時に「ドコ」を表示するため、16-1+1という計算になる。

この行列 `ni` に 列ベクトル `[1, 1, 1, 1, 1]` をかけたときの1番目の要素が、 状態番号0からスタートして状態番号5に至るまでのステップ数に等しい。

```python
>>> np.dot(ni, [1, 1, 1, 1, 1])
matrix([[32., 30., 26., 18.,  2.]])
```

これより 「ズン」 と 「ドコ」 は平均で合計32回表示されることがわかる。
「ドコ」は平均16回表示されるので、「ズン」も平均で16回表示されることになる。


### 実験

`random` が 1/2 の確率で 0, 1 をそれぞれ出力しているか検証する。
実際に `ズン` と `ドコ` が表示される回数の平均を計算する。

```kotlin
fun main() {
    val a = fun () : Pair<Int, Int> {
        val words = arrayOf("ズン", "ドコ")
        var i = 0
        var j = 0
        var k = 0
        loop@ for (word in generateSequence { words[Math.random().times(2).toInt()] }) {
            // 検証のため出力はコメントアウト
            //print(word)
            j++
            when (word) {
                "ズン" -> {
                    if (i < 4) i++
                    k++
                }
                else -> if (i == 4) break@loop else i = 0
            }
        }
        // 検証のため出力はコメントアウト
        //println("キ・ヨ・シ")
        return Pair(j, k)
    }

    // random が1を出す確率検証    
    var i = 0
    repeat(20000) {
        i += Math.random().times(2).toInt() 
    }
    println(i / 20000.0)

    // 平均 ズン・ドコ 出力回数   
    var r = 0L
    var s = 0L
    repeat(20000) {
        a().let {
            r += it.first
            s += it.second
        }
    }
    println("ズン ${s / 20000.0}")
    println("ドコ ${(r - s) / 20000.0}")
    println("ズン+ドコ ${r / 20000.0}")
}
```

#### Output

*Kotlin Playground* (Jetbrains, n.d.) で実行した。

```kotlin
0.5029
ズン 16.01475
ドコ 16.00645
ズン+ドコ 32.0212
```

random 関数 は ほぼ 1/2 の確率で 0, 1 を出力していた。
そして、平均で`ズン`もしくは`ドコ`が出力された回数は いずれもほぼ 16回 に等しい結果となった。

## References

* JetBrains. (n.d.). *Kotlin Playground*. https://play.kotlinlang.org/
* Kimura. (n.d.).（10） 吸収マルコフ過程. *工船舶海洋システム工学コース システム設計工学*. 九州大学 工学部地球環境学科. http://sysplan.nams.kyushu-u.ac.jp/gen/edu/SystemsDesignEngineering/2017/kougi10/kougi10.pdf