はじめに
モンティ・ホール問題とは――――Wikipediaによれば
プレーヤーの前に閉じた3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会のモンティが残りのドアのうちヤギがいるドアを開けてヤギを見せる。
ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。
ここでプレーヤーはドアを変更すべきだろうか?
という問題だそうです。(※但し、モンティは外れの場所を知っていて、必ずヤギのいる場所を開ける。)
一見ドアを変えても変えなくても確率は変わらないように見えますが、実は変えたほうがプレイヤーに有利なのだそうです。今回はこの実験を大量に試行して結果を比較したいと思います。
言語はいつも安心のJava (自分が好きなだけ) を使用します。
要件
- ドアの数をNUMBER_DOORSとして、モンティ・ホールを試行する。
- モンティはドアを{NUMBER_DOORS - 2}回開ける。(つまり、最後の2択になるまで繰り返す。)
- ドアを変えるかどうかはCHANGE_DOORで表される。(trueならドアを変える。falseならドアを変えない。)
- TRY_TIMESだけ試行し、新車を引き当てた回数とTRY_TIMESとで、新車を引き当てた割合を求める。
- Java(Oracle JDK 13)を使用。
コード
Monty.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
public class Monty {
public static final int NUMBER_DOORS = 4;
public static final int TRY_TIMES = 10000;
public static final boolean CHANGE_DOOR = true;
//指定されたドア以外のドアをランダムに得る
public static int getOtherDoor(Collection<Integer> doorIndices) {
List<Integer> notSelectedDoors = new ArrayList<>();
for (int i = 0; i < NUMBER_DOORS; i++) {
if(!doorIndices.contains(i)){
notSelectedDoors.add(i);
}
}
Collections.shuffle(notSelectedDoors);
return notSelectedDoors.get(0);
}
//モンティ・ホール試行
public static boolean montyHoll(){
//モンティがドアを開ける回数
final int OPEN_DORE_TIMES = NUMBER_DOORS - 2;
Random r = new Random();
int collectDoorIndex = r.nextInt(NUMBER_DOORS);
int firstSelectDoorIndex = r.nextInt(NUMBER_DOORS);
Set<Integer> notWillOpenDoorIndices = new HashSet<>();
Set<Integer> openDoorIndices = new HashSet<>();
notWillOpenDoorIndices.add(collectDoorIndex);
notWillOpenDoorIndices.add(firstSelectDoorIndex);
for (int i = 0; i < OPEN_DORE_TIMES; i++) {
int otherDoor = getOtherDoor(notWillOpenDoorIndices);
openDoorIndices.add(otherDoor);
notWillOpenDoorIndices.add(otherDoor);
}
Set<Integer> notWillSelectDoorIndices = new HashSet<>(openDoorIndices);
notWillSelectDoorIndices.add(firstSelectDoorIndex);
int secondSelectDoorIndex = CHANGE_DOOR?getOtherDoor(notWillSelectDoorIndices):firstSelectDoorIndex;
boolean getCar = (collectDoorIndex == secondSelectDoorIndex);
/*System.out.printf("c:%d,fs:%d,ss:%d,g:%s\n",
collectDoorIndex, firstSelectDoorIndex, secondSelectDoorIndex, Boolean.toString(getCar));*/
return getCar;
}
public static void main(String[] args) {
int getCarTimes = 0;
for (int i = 0; i < TRY_TIMES; i++) {
if(montyHoll()){
getCarTimes++;
}
}
System.out.println((double)getCarTimes / (double)TRY_TIMES);
}
}
#結果
- それぞれ三回実験しました。
- 但し、ドアを変えた場合と変えなかった場合は、別々に行っています。(つまり横方向は対応しない。)
表1:ドアが三個
No. | ドアを変えた場合 | ドアを変えなかった場合 |
---|---|---|
1 | 0.6687 | 0.3401 |
2 | 0.6683 | 0.3409 |
3 | 0.6631 | 0.3351 |
表2:ドアが四個
No. | ドアを変えた場合 | ドアを変えなかった場合 |
---|---|---|
1 | 0.7561 | 0.2531 |
2 | 0.7486 | 0.2463 |
3 | 0.7534 | 0.2514 |
表3:ドアが五個
No. | ドアを変えた場合 | ドアを変えなかった場合 |
---|---|---|
1 | 0.8006 | 0.2086 |
2 | 0.8038 | 0.2059 |
3 | 0.8011 | 0.1947 |
まとめ
- どれだけドアがあっても、ドアを変える方が確率が高くなると思われる。
- ドアが増えれば増えるほど、変えたほうがより有利になると思われる。
#おわりに
この問題は、つい最近まで全く腑に落ちないものでした。
これで、取り敢えず飲み込めた感じです。
やっぱり確かめることは大事ね。