今回も他人様の動画をネタにしています。いわゆる「ギャンブラーの誤謬」ですね。
ガチャ確率1%であれば100回やれば当たる?
ガチャ確率1%であれば100回やれば当たるのではないか? これは数学的に考えると当たるとは限りません。数学的に証明することは可能ですが、ここはプログラミングで検証してみましょう。前提として一度引いたくじは箱に戻します。そうしないと100個あるくじを100回引いたら絶対に当たるのは自明なので・・・。
まずくじのクラスを作成します。
1 2 3 4 5 6 7 8 |
public class Lottery { public Lottery() { } public bool IsWinningLottery = false; } |
そしてくじを100個つくり、そのなかのひとつだけを「当たり」にします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { Random r = new Random((int)DateTime.Now.Ticks); List<Lottery> Create100Lotteries() { List<Lottery> lotteries = new List<Lottery>(); // くじを100個つくる for(int i =0; i<100; i++) lotteries.Add(new Lottery()); // 100個のうちどれかひとつを当たりにする lotteries[r.Next(100)].IsWinningLottery = true; return lotteries; } } |
そして100回くじをひきます。当たったらその回数を記録して戻り値として返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { int Draw100lots() { int winCount = 0; int allCount = 100; for(int i = 0; i < allCount; i++) { List<Lottery> lotteries = Create100Lotteries(); // 100個のうちどれかを引く int selectedIndex = r.Next(100); Lottery selectedLottery = lotteries[selectedIndex]; if(selectedLottery.IsWinningLottery) winCount++; } return winCount; } } |
そしてこの操作を10000回繰り返して、的中率を調べます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { int winCount = 0; int allCount = 10000; for(int i=0; i< allCount; i++) { winCount += Draw100lots(); } // 的中率をパーセンテージで表示 double rate = (double)winCount / allCount * 100; Text = rate.ToString(); } } |
この場合は100%前後の値が表示されます。
では最低1回は的中する確率は?となるとどうでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { private void button2_Click(object sender, EventArgs e) { int winCount = 0; int allCount = 10000; for(int i=0; i< allCount; i++) { if(Draw100lots() != 0) winCount++; } // 的中率をパーセンテージで表示 double rate = (double)winCount / allCount * 100; Text = rate.ToString(); } } |
実験してみると63%台になります。100回くじを引けば複数回的中することもあるし、1回も的中しないこともあります。これを平均すると1/100なのです。「100回引けば必ず1回は的中する」とはなりません。約37%の確率で1回も的中しない場合もあるのです。
モンティ・ホール問題
さて、
この動画のコメントに
「直感と実際の確率が合わないケースとして特に有名なのがモンティ・ホール問題ですよね」
というのがありました。
プレーヤーの前に閉じた3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会のモンティが残りのドアのうちヤギがいるドアを開けてヤギを見せる。
ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。
ここでプレーヤーはドアを変更すべきだろうか?
これを見たとき、変更しても変更しなくても同じではないのか?
そのように思いました。ところが実際にはそうではありません。変更すべきです。変更することで的中率が2倍にアップします。
これも実際にプログラミングをして実験してみましょう。
まずくじを3つ作りましょう。そしてどれかひとつを当たりにしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { List<Lottery> Create3Lotteries() { List<Lottery> lotteries = new List<Lottery>(); // くじを3つつくる lotteries.Add(new Lottery()); lotteries.Add(new Lottery()); lotteries.Add(new Lottery()); // 3つのうちどれかひとつを当たりにする lotteries[r.Next(3)].IsWinningLottery = true; return lotteries; } } |
このなかからランダムにくじをひきます。そして引かれなかったくじのなかには1つ、または2つのハズレくじがあります。1つしかない場合はそのくじを、2つある場合はそのなかからランダムにひとつ選んで取り除きます。これで引かれなかったくじは1つだけになります。
Drawlotメソッドの引数 doesChange はtrueなら交換する、falseなら交換しないを意味しています。最終的に選んだくじが当たりかどうかしらべて当たりならtrue、そうでなならfalseを返します。
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 40 41 42 43 44 45 46 |
public partial class Form1 : Form { bool Drawlot(bool doesChange) { // くじを3つつくる List<Lottery> lotteries = Create3Lotteries(); // 3つのうちどれかを引く int selectedIndex = r.Next(3); Lottery selectedLottery = lotteries[selectedIndex]; // 引かれたくじは引かれていないくじの中から除去される lotteries.Remove(selectedLottery); // 引かれていないくじのなかからハズレくじを探す List<Lottery> edgeLotteries = lotteries.Where(x => !x.IsWinningLottery).ToList(); // 引かれていないくじのなかからハズレくじをひとつだけ取り除く // ハズレくじがひとつだけのときはそれを取り除く // ハズレくじが2つのときは乱数でどちらかを決めてそれを取り除く if(edgeLotteries.Count == 1) lotteries.Remove(edgeLotteries[0]); else if(edgeLotteries.Count == 2) { int openedIndex = r.Next(2); lotteries.Remove(edgeLotteries[openedIndex]); } // 蛇足かもしれないけどエラーチェック // 引かれていないくじはひとつだけである // そうでないならなにかがおかしい if(lotteries.Count != 1) { MessageBox.Show("なにかがおかしいです"); return false; } // 残されたくじと交換できるのだが、交換をするか? if(doesChange) selectedLottery = lotteries[0]; // 最終的に選んだくじは当たりかどうか? return selectedLottery.IsWinningLottery; } } |
あとはDrawlot(doesChange)メソッドを何回か繰り返して的中率を調べます。10,000回やれば充分だと考えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { double Drawlots(bool doesChange) { int winCount = 0; int allCount = 10000; for(int i = 0; i < allCount; i++) { if(Drawlot(doesChange)) winCount++; } // 的中率をパーセンテージで返す return (double)winCount / allCount * 100; } } |
あとはDrawlotsメソッドの引数をtrueとfalseにして違いを調べるだけです。
実験してみると引数falseなら33%前後、引数trueなら66%前後になり、両者には2倍の違いがあることがわかります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public partial class Form1 : Form { // 一度選んだものは変更しない private void button1_Click(object sender, EventArgs e) { double d = Drawlots(false); Text = d.ToString(); } // 選んだものを変更する private void button2_Click(object sender, EventArgs e) { double d = Drawlots(true); Text = d.ToString(); } } |
なぜこんなことになるのでしょうか?
それは最初に当たりを選ぶと交換した場合、ハズレになります。最初にハズレを選ぶと交換することで最終的に当たりになります。ということは最初に当たりならハズレ、ハズレなら当たりです。
では最初に当たりとハズレを引く確率はどうなっているのでしょうか? ハズレを引く確率は2/3、当たりを引いてしまう確率は1/3です。だから交換することで最終的に当たりになる確率は2/3に上昇するのです。