ぱそきいろのIT日記

ぱそきいろがITに関する記事を書いていきます。

モンティホール問題をPythonで解く

こんばんは、ぱそきいろです。
割と有名なモンティホール問題をPythonで解いていきます。

モンティホール問題

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

モンティ・ホール問題 - Wikipedia
初めて聞いたとき、直感的に変えた方がいいと思ったのですが説明に困ったのを覚えています。
解説はWikipediaを読んでいただくとして、計算した結果、変更しない場合の確率は1/3、変更した場合の確率は2/3になります。
これを実際に試して確認します。

方針

3パターン用意して、

  • 変更するかしないかをランダム(1/2)で決める
  • 必ず変更しない
  • 必ず変更する

を試してみます。
ちなみに、一つ目の変更するかしないかをランダムで決める場合は、正解する確率は1/2になります。(1/3*1/2+2/3*1/2)

コード

まずは、ドアのクラスとプレイヤーのクラスを宣言します。
簡単に説明をコメントしてあります。

#ドアのクラス。3つの変数
#0が外れ、1があたり
class Doors:
    def __init__(self):
        self.doors = [0, 0, 0]
        rand=random.randint(0,2)
        self.doors[rand]=1

#回答者の親クラス
class Player:
    def __init__(self):
        self.door=Doors()
        self.firstbox=0
        self.secondbox=0
        self.lastbox=0
    #一回目の選択
    def firstChoice(self):
        rand=random.randint(0,2)
        self.firstbox=self.door.doors[rand]
        self.door.doors.pop(rand)

    #一回目の選択の後、はずれのドアを開く
    def openBox(self):
        if self.door.doors[0]==0:
            self.lastbox=self.door.doors[1]
        else:
            self.lastbox=self.door.doors[0]

#変更するかしないかランダムで決めるプレイヤー
class RandPlayer(Player):
    #2回目の選択
    #正解したら1、外したら0を返す
    def secondChoice(self):
        rand=random.randint(0,1)
        if rand==0:
            return self.firstbox
        else:
            return self.lastbox

#必ず変更するプレイヤー
class ChangePlayer(Player):
    def secondChoice(self):
        return self.lastbox

#必ず変更しないプレイヤー
class NoChengePlayer(Player):
    def secondChoice(self):
        return self.firstbox

次に実際にゲームをしてみます。
とりあえず1万回試してみることにします。
最終的な勝率と、勝率の推移を表示します。

if __name__ == '__main__':
    randwin=0
    nochangewin=0
    changewin=0
    randlist=[]
    nochangelist=[]
    changelist=[]
    n=100000

    #交換するかランダム
    for i in range(n):
        randplayer=RandPlayer()
        randplayer.firstChoice()
        randplayer.openBox()
        randwin+=randplayer.secondChoice()
        randlist.append(randwin/(i+1))

    #必ず交換しない
    for i in range(n):
        nochangeplayer=NoChengePlayer()
        nochangeplayer.firstChoice()
        nochangeplayer.openBox()
        nochangewin+=nochangeplayer.secondChoice()
        nochangelist.append(nochangewin/(i+1))

    #必ず交換する
    for i in range(n):
        changeplayer=ChangePlayer()
        changeplayer.firstChoice()
        changeplayer.openBox()
        changewin+=changeplayer.secondChoice()
        changelist.append(changewin/(i+1))

    print("randwin",randwin/n)
    print("nochangewin",nochangewin/n)
    print("changewin",changewin/n)

    fig=plt.figure()
    plt.plot(randlist)
    fig.savefig("random.png")

    fig=plt.figure()
    plt.plot(nochangelist)
    fig.savefig("nochange.png")

    fig=plt.figure()
    plt.plot(changelist)
    fig.savefig("change.png")
結果

最終的な確率です。

randwin 0.50007
nochangewin 0.33494
changewin 0.66545
  • 変更するかランダム:0.50007
  • 必ず変更しない:0.33494
  • 必ず変更する:0.66545

で、計算通り、1/2、1/3、2/3になってますね。

念のため確率の推移です。
変更するかランダム
f:id:takabsk55:20210709012254p:plain
必ず変更しない
f:id:takabsk55:20210709012329p:plain
必ず変更する
f:id:takabsk55:20210709012339p:plain

割とすぐに計算通りに収束していますね。

まとめ

他にやっている方もいるのですが、モンティホール問題をPythonで解きました。
結果確率通りになることが分かりました。