用python觀察三門問題的頻率(Monty Hall Problem)

2020-09-27 09:00:24

三門問題(Monty Hall Problem)是概率統計中的一個著名的問題,原問題大概如此:

Monty Hall是美國電視節目的主持人,在這個節目中,有一個給嘉賓開獎的環節,開獎的環節是這樣的:現在一共有三扇門,其中有一扇門背後是一輛汽車,另外兩扇門背後是山羊。現在嘉賓可以選擇一扇門,此時主持人會開啟另外兩扇門中一扇背後是山羊的門,然後會問嘉賓是否要更換之前的選擇。嘉賓選擇更換或不更換後,主持人會開啟嘉賓最終選擇的那一扇門,假如開啟之後是一輛汽車的話,那麼就會將汽車送給嘉賓。

在這個問題中,看似換與不換的概率都是不變的,都是1/3。但是實際上更換選擇後能夠得到汽車的概率是2/3,不更換選擇能夠得到汽車的概率是1/3。這個問題的解釋方法有很多種,比較嚴謹的方法是利用貝葉斯公式,概率論中的解釋此處不再加以贅述。

雖然從概率論的角度能夠嚴謹地理解三門問題,但是仍然不夠直觀。如果想要直觀地感受這個概率,就需要親自做試驗來感受試驗中兩種情況分別的頻率。不過,鑑於每次和別人玩都需要提供一輛汽車,實在太過費錢,無法將試驗進行下去。所以我決定找一個可以自己和自己玩的好朋友(沒錯,就是python),讓他自己和自己玩足夠多次數後,將頻率告訴我們。

這是程式碼執行後的結果:

請輸入要做三門問題的次數:1000000
若換,做1000000次實驗,開啟正確的門的頻率是0.666369

若不換,做1000000次實驗,開啟正確的門的頻率是0.332320

接下來開始上程式碼:
這個程式要依靠python中的random模組,其中主要是random.choice()這個函數。

下面是python自己和自己玩三門遊戲的函數:

import random as r
def do_monty_hall (determine):
    '''determine變數只能選擇'change'或者'maintain'
       選擇‘change’表示要換門,選擇'maintain'表示不換門.
       返回值是此次遊戲結束後,是否選對了背後是車子的門,
       如果選對了,返回值就是1,選錯了返回值就是0.'''
    
    name_dict = {'door1':0,'door2':0,'door3':0}
    #建立包含三門名字和是否有車的字典,其中0表示門後面是山羊,1表示門後面有車子
    #此時還未確定哪扇門後面是車子
    name_list = ['door1','door2','door3']
    #建立包含三門名字的列表
    ans = r.choice(name_list)
    #利用隨機函數,隨機選定一個門後面有車子
    name_dict[ans] = 1
    
    choice = r.choice(name_list)
    #choice表示嘉賓選擇的門
    name_list.remove(choice)
    open_door = r.choice(name_list)
    #open_door表示主持人開啟的那扇有山羊的門
    if name_dict[open_door] == 0 :
        name_list.remove(open_door)
        change_door = name_list[0]
    else :
        change_door = open_door
   #用隨機選擇加判斷語句決定主持人開啟哪一扇門來提示嘉賓     
    
    if determine == 'change' :
        if name_dict[change_door] == 1 :
            return 1
        else :
            return 0
    elif determine == 'maintain' :
        if name_dict[choice] == 1 :
            return 1
        else :
            return 0
 #此部分determine的選擇和返回值可以參看前面的函數檔案

該函數執行的結果的1和0分別代表了選對有車子的門和沒有選對,這樣就可以統計選對的次數。

主函數如下:

if __name__ == '__main__' :
    n = int(input('請輸入要做三門問題的次數:'))
    right_time1 = 0
    right_time2 = 0
    k = 0
    while k <= n:
        right_time1 += do_monty_hall('change')
        right_time2 += do_monty_hall('maintain')
        k+=1
    print('若換,做%d次實驗,開啟正確的門的頻率是%f\n'%(n,(right_time1/n)))
    print('若不換,做%d次實驗,開啟正確的門的頻率是%f\n'%(n,(right_time2/n)))

這部分就是執行使用者想要的做三門遊戲的次數,然後計算出換與不換分別的頻率,並輸出。可以通過一個迴圈呼叫上面定義的函數實現。

完整的程式碼是這樣的:

import random as r
def do_monty_hall (determine):
    '''determine變數只能選擇'change'或者'maintain'
       選擇‘change’表示要換門,選擇'maintain'表示不換門.
       返回值是此次遊戲結束後,是否選對了背後是車子的門,
       如果選對了,返回值就是1,選錯了返回值就是0.'''
    
    name_dict = {'door1':0,'door2':0,'door3':0}
    #建立包含三門名字和是否有車的字典,其中0表示門後面是山羊,1表示門後面有車子
    #此時還未確定哪扇門後面是車子
    name_list = ['door1','door2','door3']
    #建立包含三門名字的列表
    ans = r.choice(name_list)
    #利用隨機函數,隨機選定一個門後面有車子
    name_dict[ans] = 1
    
    choice = r.choice(name_list)
    #choice表示嘉賓選擇的門
    name_list.remove(choice)
    open_door = r.choice(name_list)
    #open_door表示主持人開啟的那扇有山羊的門
    if name_dict[open_door] == 0 :
        name_list.remove(open_door)
        change_door = name_list[0]
    else :
        change_door = open_door
   #用隨機選擇加判斷語句決定主持人開啟哪一扇門來提示嘉賓     
    
    if determine == 'change' :
        if name_dict[change_door] == 1 :
            return 1
        else :
            return 0
    elif determine == 'maintain' :
        if name_dict[choice] == 1 :
            return 1
        else :
            return 0
 #此部分determine的選擇和返回值可以參看前面的函數檔案



if __name__ == '__main__' :
    n = int(input('請輸入要做三門問題的次數:'))
    right_time1 = 0
    right_time2 = 0
    k = 0
    while k <= n:
        right_time1 += do_monty_hall('change')
        right_time2 += do_monty_hall('maintain')
        k+=1
    print('若換,做%d次實驗,開啟正確的門的頻率是%f\n'%(n,(right_time1/n)))
    print('若不換,做%d次實驗,開啟正確的門的頻率是%f\n'%(n,(right_time2/n)))

通過輸入一個較大的遊戲次數,我們就可以直觀地體會到三門問題中換與不換的選擇分別對應多少的頻率。