あみだくじの確率の偏りをPythonで検証した話をわかりやすく解説

## はじめに

### やったこと

あみだくじにはどうやら偏りがあるらしい。

[Wikipedia](https://ja.wikipedia.org/wiki/%E3%81%82%E3%81%BF%E3%81%A0%E3%81%8F%E3%81%98#%E7%A2%BA%E7%8E%87)にも書いてあるし、[数学的に正しい「あみだくじ必勝法」とは](https://president.jp/articles/-/26920)にも書いてある。

僕は理系ながら、数学が致命的にできないためプログラマらしくパソコンを使ってゴリ押し検証してみました。

### 実行条件とコード

この記事のコードはGoogleColaboratoryを用い、Python 3.7.10で実行しています。

[私のGitHubに公開](https://github.com/taikis/amidakuji)しており、そこからGoogleColaboratoryのソースコードを見ることができます。

https://github.com/taikis/amidakuji

### あみだくじの定義

この記事でいうあみだくじとは、ごくごく一般的な真っ直ぐな横棒と縦棒で構成されるものを言います。

こんなものとか

![戻る線が追加されたあみだくじの画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1623593/d158cd6d-bbf4-dc04-3428-e086a687c91d.png)

こんなものはあみだくじと言いません（過激派）

![トンネルが追加されたあみだくじの画像](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1623593/ade53fd0-2d08-1217-8758-d055eb557cfc.png)

## プログラム中でのあみだくじはどう作るか

### 定義

縦棒をV本引き、横棒をH本引くとします。

縦棒を*V個分の配列*として考えれば、初期の値を通し番号として格納できます。

横棒は*H回隣同士で置換すること*と考えることができます。

### 例

![あみだくじの画像　初期値が0, 1, 2, 3](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1623593/68b0c50b-7546-48d4-2ef5-2dbb730ccc12.png)

縦棒が4本なので、配列の値の個数は4つですね。

初期値として、配列`potision = [0,1,2,3]`が入っています。

横棒は4本なので、4回置換しましよう。
最初の横棒は、`potision[0]`と`potision[1]`を置換します。

この時点で、`potision`は`[1,0,2,3]`になります。

これをあと3回繰り返せば、`[0,1,2,3]`→`[1,0,2,3]`→`[1,2,0,3]`→`[1,0,2,3]`→`[1,0,3,2]`と結果が出ました。

## 偏りがあることの検証

まず、変数を準備します。今回は、35人クラスの席替えを想定し、35人分の縦棒と、1人1回横棒を引くと想定します。

```python
#縦棒の本数
v_num = 35
#横棒の本数
h_num = 35
#試行回数
test_num = 10000
potision = []
result = np.zeros((v_num,v_num))
```

次にあみだくじを作成→記録を繰り返し行います。

関数は後述しますが、`repoti(potision)`は配列を通し番号で初期化、`swap(potision)`はランダムに配列中の場所を選び、、隣と入れ替えを行なう関数です。

横棒の本数である、`h_num`回入れ替えを行なっています。

最終的にできた結果を二次元`result`配列に整理しています。`j`番目の縦棒に、どの数字が入っているかをインクリメントしています。


```python
for i in tqdm.tqdm(range(test_num)):
    potision = repoti(potision)
    for j in range(h_num):
        swap(potision)
    for j in range(v_num):
        result[j][potision[j]] += 1
```

さっき飛ばした関数の中身

`repoti()`関数は`0`から`v_num`まで初期化をしています。

`swap()`関数は`0`から`v_num-2`までの値をランダムに取ります。

ランダムに取った値の次の値と置換するため、-2にしないとオーバーフローしますね。


```python
def repoti(potision):
    potision = list(range(v_num ))
    return potision

def swap(potision):
    s = random.randint(0,v_num-2)
    temp = potision[s]
    potision[s] = potision [s+1]
    potision [s+1] = temp
    return potision
```

この結果を表で見たかったため、エクセルで開けるCSV形式で保存します。Pandasを用いました。

GoogleColabratoryを使ったので`files.download()`関数を用いてダウンロードが必要です。

```python
sheet = pd.DataFrame(result)
sheet.to_csv("result.csv")

files.download('result.csv')
```

## 結果

何パターンか試行したため、もし結果だけ見たい方はGitHubからCSVファイルダウンロードしてください。

今回は35×35の結果を紹介します。

まず表をお見せしましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1623593/edef5094-7a82-59fe-0442-9d58d0b29cd2.png)

明らかに偏りがありますね。

特に、一番左である0番目の縦棒に、元々の数値である0が入った回数は10,000回中5,134回となりました。

真ん中ら辺を見てみましょう。下は17番目の縦棒の結果をグラフにしたものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1623593/c07fe911-8905-4532-a32c-9d77f288e702.png)

明らかに17が多い、結果として正規分布のようなグラフになりました。

さらに特筆すべき点として、7ほど離れると個数が0になります。

偏りがあるということが、これでわかったと思います。

## 結論

あみだくじをやるときは、狙うあたりの真上を選択しましょう。

作る側は、結果部分を隠さないと狙われます。

## あとがき

この記事は、私の最初のQiita記事になります。

知ってる人は知ってることで、今更記事にすることではないかもしれませんが、私の衝撃がこれを上回り思わず記事にしました。

過不足や改善点などありましたら教えていただけると、**とってもとっても**嬉しいです。

## 参考文献

### Qiita

[pythonであみだくじもどきを作ってみた。
](https://qiita.com/ryo_110/items/bb426fded5e9471bd58e)

[公平なあみだくじって何回横線を引けばいいの？](https://qiita.com/Nakubaru/items/ab63a6c8621ac5129b54)

### その他

[あみだくじ - Wikipedia](https://ja.wikipedia.org)

[数学的に正しい「あみだくじ必勝法」とは](https://president.jp/articles/-/26920)

[私のGitHub](https://github.com/taikis/amidakuji)