LoginSignup
4

More than 5 years have passed since last update.

モンティホール問題をシミュレーションする

Last updated at Posted at 2015-10-17

モンティホール問題

こんな問題(Wikipedia)です。

「プレーヤーの前に閉まった3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会者(モンティ)が残りのドアのうちヤギがいるドアを開けてヤギを見せる。
ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。プレーヤーはドアを変更すべきだろうか?」

直感的にはドアを変更する場合も変更しない場合も変わらないように見えます。

シミュレーションしてみる

10000回ドアを開けたと仮定してみると以下のような結果になりました。
originが最初に選んだドアの場合でchangeがドアを変更した場合の確率です。

1回目
origin:3301(33%)
change:6699(66%)
2回目
origin:3302(33%)
change:6698(66%)
3回目
origin:3309(33%)
change:6691(66%)
4回目
origin:3302(33%)
change:6698(66%)

ドアを変えたほうが大体2倍の確率で景品がもらえそうです。
ソースは以下のような感じです。
C++11の機能を無駄に使っていますが気にしないでもいいです。

montihall.cpp
#include <iostream>
#include <random>
#include <sstream>
#include <memory>

using namespace std;

struct Door{
    bool state[3]; //goat(false) or prize(true)
    int player;
    int monti;
public:
    Door(){
        for(auto s : state){s = false;}
        player = 0;
        monti = 0;
    }
};

class MontiHall{

    const int DOOR_NUM = 10000;
    vector<Door> doors;
    auto randomDoor() -> int;

public:

    int originPrize = 0;
    int changePrize = 0;

    MontiHall();
    MontiHall(int);
    auto init() -> void;
    auto selectedByPlayer() -> void;
    auto selectedByMonti() -> void;
    auto calcPrizeNum() -> void;
    auto originPercentage() -> int;
    auto changePercentage() -> int;
};

auto MontiHall::randomDoor() -> int{
    random_device rd;
    mt19937 mt(rd());
    uniform_int_distribution<int> dist(0,2);
    return dist(mt);
}

MontiHall::MontiHall(){
    doors.resize(DOOR_NUM);
}

MontiHall::MontiHall(int door_num){
    doors.resize(door_num);
}

auto MontiHall::init() -> void{
    for(auto& door : doors){
        int p = randomDoor();
        door.state[p] = true;
    }
}

auto MontiHall::selectedByPlayer() -> void{
    for(auto& door : doors){
        int s = randomDoor();
        door.player = s;
    }
}

auto MontiHall::selectedByMonti() -> void{
    for(auto& door : doors){
        int m;
        do{
            m = randomDoor();
        }while(
            door.player == m || 
            door.state[m] == true
        );
        door.monti = m;
    }
}

auto MontiHall::calcPrizeNum() -> void{
    for(auto door : doors){
        if(door.state[door.player]){
            originPrize++;
        }else if(!door.state[door.monti]){
            changePrize++;
        }
    }
}

auto MontiHall::originPercentage() -> int{
    return (originPrize/(double)(originPrize+changePrize)) * 100;
}

auto MontiHall::changePercentage() -> int{
    return (changePrize/(double)(originPrize+changePrize)) * 100;
}

auto main(int argc, char* argv[]) -> int{
    unique_ptr<MontiHall> montihall;
    if(argc>=2){
        montihall.reset(new MontiHall(atoi(argv[1])));
    }else{
        montihall.reset(new MontiHall());
    }
    montihall->init();
    montihall->selectedByPlayer();
    montihall->selectedByMonti();
    montihall->calcPrizeNum();
    cout << "origin:" << montihall->originPrize 
    << "(" << montihall->originPercentage() << "%)" << endl;
    cout << "change:" << montihall->changePrize 
    << "(" << montihall->changePercentage() << "%)" << endl;

    return 0;
}

なんでドアを変更したほうがいいか

最初はどのドアも1/3の確率で景品をもらえます。
モンティがその2つのドアのうち外れのドアを開いた時点でその外れのドアが景品である確率は0になります。この時、プレイヤーが選択していない2つのドアが景品である確率がモンティもプレイヤーも選択していないドアに集中して2/3になるので、プレイヤーはドアを変更したほうが2倍景品をもらえる確率が高くなります。

ちょっとわかりづらいのでタイミング別に確率を表に書くと以下のようになると思います。

タイミング Aの確率 Bの確率 Cの確率
初期状態 1/3 1/3 1/3
プレイヤーがAを選択 1/3 1/3 1/3
モンティがCを開く(中身はヤギ) 1/3 2/3 0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4