プリキュアカレーのシール全30種コンプするには何箱必要か？

# はじめに

人気大食い系 YouTuber の<b>しのけん</b>氏が最近開設したサブチャンネルの動画で，丸美屋のプリキュアカレーのシール全 30 種のコンプリートに挑戦する動画を上げています。メインチャンネルのほうでは，いつも凛々しいスーツ姿で大食いチャレンジする<b>しのけん</b>氏ですが，サブチャンネルのほうでは自宅（自室？）ということもあってカジュアルな姿でチャレンジする方針のようです。

[プリキュアカレーのシールコンプするまで限界食いするも一生ダブり続けて絶望する漢 - しのけんの別腹 <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490655/ee21aa0e-add2-222e-dadc-d8ffad3be7a7.png">](https://www.youtube.com/watch?v=BZxgchEPXis)

一食あたりの価格は安価ですが，全種類コンプリートしようとすると途方もない努力を必要とするコレクターズアイテム・・・古くは永谷園のお茶漬けの「東海道五拾三次カード」が思い出されますが，家族全員で（お年寄りでも）食せるお茶漬けと異なり，小児用のカレーとなると消費するのが大変です。

世の中の親御さんたちへ，もしも自分の子供たちが全種コンプリートに挑戦しようとしたら，それは果たして確率的に実現可能なのか？事前に＊よおく＊考える必要がありますよ，という記事です。

# 問題の定義

プリキュアカレー（小売用）には一箱につきシールが一枚入っているとします。シールは全 30 種類あり，ランダムかつ等確率であるとします。シール全 30 種類をコンプリートするためには，何箱必要になるでしょうか？

# シミュレーションプログラム

手計算で解こうかと思いましたが，それは後回しにします。まずはシミュレーションで概要を把握しましょう。

プログラムは C# で作ります。採用の理由はまず乱数の品質です。C# の乱数ジェネレータ `Random()` の品質についてはいろいろ議論されているようですが，C 言語の標準ライブラリの `rand()` よりは格段にマシと判断しました。そして何よりも手軽さです。Windows PC であれば内蔵の C# コンパイラがあるので別途開発ツールをインストールしなくて済むからです。また実行スピードもネイティブコンパイラ並みに速いのも利点です。

ソースコードを以下に示します。シールは全 30 種ですから，1 種類 を 1bit として 32bit 整数型変数 `flag` に収まります。何箱食したかのカウントはヒストグラムの配列変数 `hist[]` でカウントします。C# の配列は 0-origin ですので，`n + 1` 箱食した場合は `hist[n]` でカウントします。配列 `hist[]` の要素数は 1000 なので 1000 箱以上になるとエラーになりますが，まあ確率的に起こらないだろうという判断です。

これを $10^9$ 回，すなわち 10 億回試行してみます。

```C#:PRECURE.CS
using System;
using System.IO;
class PRECURE {
	static	long	MAX_LOOP = 1000*1000*1000;
	static	int		Main() {
		var	hist = new int[1000];
// 		var	seal = new long[30];
		var	rand = new Random(0);
		for(long i = 0; i < MAX_LOOP; i++) {
			for(int n = 0, flag = 0; n < hist.Length; n++) {
				int	x = rand.Next(30);
//				seal[x]++;
				if((flag |= (1 << x)) == 0x3FFFFFFF) {
					hist[n]++;
					goto BREAK;
				}
			}
			Console.Error.WriteLine("TABLE OVERFLOW ERROR!!");
			return -1;
BREAK:
			;
		}
//		for(int i = 0; i < seal.Length; i++)
//			Console.Out.WriteLine("{0}\t{1}", i, seal[i]);
		for(int i = 0; i < hist.Length; i++)
			Console.Out.WriteLine("{0}\t{1}", i + 1, (double)hist[i] / MAX_LOOP);
		return 0;
	}
}
```

# ビルド方法

下記のバッチファイル `BUILD.CMD` と上記の C# プログラム `PRECURE.CS` を同じフォルダに保存します。その後，エクスプローラから下記のバッチファイル `BULID.CMD` をダブルクリックします。

<details><summary><b>ビルド用のバッチファイル BUILD.CMD はコチラ</b></summary>

※バッチファイルの文字コードは Shift-JIS で保存して下さい。

```:BUILD.CMD
@echo off
setlocal
rem ----------------------------------------------------------------------------
rem ターゲットプログラム
rem ----------------------------------------------------------------------------
set TARGET=precure
rem ----------------------------------------------------------------------------
rem csc.exe にパスが通っているかどうかの確認
rem ----------------------------------------------------------------------------
set DOTNETPATH=
for %%I in ( csc.exe ) do set DOTNETPATH=%%~$PATH:I
if defined DOTNETPATH goto LETS_COMPILE
rem ----------------------------------------------------------------------------
rem .NET Framework のインストールパスを探す
rem ----------------------------------------------------------------------------
for /F "usebackq tokens=1,2,*" %%I in (
  `reg query "HKLM\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" /v InstallPath`
) do (
  if "%%I"=="InstallPath" set "DOTNETPATH=%%K"
)
if defined DOTNETPATH goto APPEND_PATH
echo .NET Framework のインストールパスが見つかりません！
pause
exit /b
:APPEND_PATH
echo パスに %DOTNETPATH% を追加します。
set "PATH=%PATH%;%DOTNETPATH%"
:LETS_COMPILE
rem ----------------------------------------------------------------------------
rem コンパイル
rem ----------------------------------------------------------------------------
echo %TARGET%.cs をコンパイルします。
if exist %TARGET%.exe del %TARGET%.exe
csc -nologo -o -w:4 %TARGET%.cs
if exist %TARGET%.exe goto SUCCESS
echo %TARGET% のコンパイルに失敗しました！
pause
exit /b
:SUCCESS
echo %TARGET% のコンパイルに成功しました。
pause
exit /b
```

</details>

ビルドに成功すると下記のメッセージが表示されます。実行ファイル `PRECURE.EXE` が作られているはずです。

```:ビルドに成功したとき
パスに C:\Windows\Microsoft.NET\Framework64\v4.0.30319\ を追加します。
precure.cs をコンパイルします。
precure のコンパイルに成功しました。
続行するには何かキーを押してください . . .
```

# 実行方法

コマンドプロンプトを開き，シミュレーションプログラムが作られたフォルダに移動して `PRECURE.EXE` を実行します。データは標準出力に出力されるので，適当なファイルにリダイレクトして下さい。

```:実行方法
precure > DATA
```

# 計算結果

計算結果をグラフにまとめました。ちなみに ThinkPad X280 で約 25 分間かかりました。最新の PC なら半分の時間で済むでしょう。

```plantuml
@startuml
<style>
root {
  margin 0
  padding 0
}
title {
  fontSize 17
  fontColor black
  fontStyle normal
}
</style>
title
<img:https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3490655/4333c9df-f844-4b49-4ee2-9bc44660f327.png>

図１　シール全 30 種をコンプリートするまでに食する箱数とその確率
end title
@enduml
```

確率のピーク（最頻値）は 101 箱，中央値は 113 箱，平均値は約 120 箱となりました。ちなみに最小値は 33 箱，最大値は 719 箱でした。つまり，<b>しのけん</b>氏が 10 億人いたとして，たった 33 箱でコンプリート出来る人が一人いた一方，719 箱までかかる人も一人いたということです。

<dl>
<dt>最頻値の意味</dt>
<dd>これは見てすぐ分かるように 101 箱でコンプリートできた<b>しのけん</b>氏が一番多かったという意味です。</dd>
<dt>中央値の意味</dt>
<dd>少ない箱数でコンプリートできた順に並べて，ちょうど半分の 5 億番目の<b>しのけん</b>氏が 113 箱だったということです。</dd>
<dt>平均値の意味</dt>
<dd>このシミュレーションを行うのにプリキュアカレーが計 1200 億箱必要でした。これを 10 億人で割ると一人当たり 120 箱になります。また，これはこのチャレンジにおける期待値でもあります。</dd>
</dl>

# まとめ

全 30 種をコンプリートするための期待値は 120 箱になりました。

普通の人が個人で挑戦するとしたら一か月以上にわたり毎日三食カレー三昧になりそうですが，学校の一学年全員の協力を得られたら何とかなりそうです。

なお，今回のシミュレーションを手計算，あるいは解析的に解こうとすると非常に難しいので，次回に回します。

https://qiita.com/TETSURO1999/items/0314c6aa01881689675d?1

# 参考文献

- [【C#, VB.NET】乱数の生成 - Qiita](https://qiita.com/SHiNKi_wst/items/5bd376308f1fb36096f3)

- [乱数の仕組み - Qiita](https://qiita.com/mk668a/items/d53515817c41e22e77f0)

- [メルセンヌ・ツイスタ（Mersenne Twister）についてちょっとだけ真面目に解説する記事 Part1: 線形合同法とLFSR - Qiita](https://qiita.com/seekworser/items/54e2b4596e72e8b9f9ea)

- [10年前の自分に伝えたい、たった一つの擬似乱数生成器 - Qiita](https://qiita.com/umireon/items/8c1f20c03a4d7b6b7587)

- [.NET System.Random の実装と欠陥について ～ 重箱の隅をつつきたおす ～ 屋根裏工房改 - HatenaBlog](https://andantesoft.hatenablog.com/entry/2021/01/09/203050)

- [良い乱数・悪い乱数 - 和田維作のホームページ](http://isaku-wada.my.coocan.jp/rand/rand.html)

- [プリキュアシリーズ - 丸美屋](https://www.marumiya.co.jp/product/series/character/precure/)

- [商品情報 - 味ひとすじ永谷園](https://www.nagatanien.co.jp/product/)