三門問題模擬對比試驗
前言
“三門問題”是一個很有意思的概率論問題,涉及貝葉斯公式,是人工智能領域的一個經典問題。
為了直觀地研究這個問題,也為了測試一下Python的性能,阿色用VB6和Python3分別編制模擬試驗程序,並將源代碼和EXE文件公布。
VB6和Python3采用了相同的算法,並使用同一台電腦測試。
希望對大家有所幫助。
1 什么是“三門問題”?
三門問題(Monty Hall Problem)亦稱為蒙提霍爾問題、蒙特霍問題或蒙提霍爾悖論,大致出自美國的電視游戲節目Let's Make a Deal。問題名字來自該節目的主持人蒙提·霍爾(Monty Hall)。
參賽者會看見三扇關閉了的門,其中一扇的后面有一輛汽車,選中后面有車的那扇門可贏得該汽車,另外兩扇門后面則各藏有一只山羊。
當參賽者選定了一扇門,但未去開啟它的時候,節目主持人開啟剩下兩扇門的其中一扇,露出其中一只山羊。
主持人其后會問參賽者要不要換另一扇仍然關上的門。
問題是:換另一扇門會否增加參賽者贏得汽車的機率。
如果嚴格按照上述的條件,即主持人清楚地知道,自己打開的那扇門后是羊,那么答案是會。
不換門的話,贏得汽車的幾率是1/3。換門的話,贏得汽車的幾率是2/3。
雖然該問題的答案在邏輯上並不自相矛盾,但十分違反直覺。這問題曾引起一陣熱烈的討論。
(摘自百度百科)
2 VB6模擬試驗
2.1 試驗結果
下列各圖是利用Visual Basic 6 實現,始終采取“不換”或“換”策略、是“否動態顯示”為條件的模擬情況。
動態顯示耗時較多,為了客觀對比,特別區分了“純計算”和“動態顯示”的情況。
純計算試驗1000000次,動態顯示試驗1000次。
試驗總體情況:
(1)得車概率
采取“不換”策略,得車概率是1/3(約33%);
采取“換”策略,得車概率是2/3(約67%)。
(2)模擬計算用時
純計算1000000次,“不換”和“換”策略基本相同:4秒;
動態顯示計算1000次,“不換”和“換”策略基本相同:61~62秒。
2.2 VB6源代碼
源代碼如下(因排版等問題,請以附后的下載文件包為准):
(1)三門問題試驗.vbw
Form1 = 0, 0, 1308, 803, , 26, 26, 1005, 569, C
(2)三門問題試驗.vbp
Type=Exe
Form=三門問題試驗.frm
Reference=*\G{00020430-0000-0000-C000-000000000046}#2.0#0#C:\Windows\SysWOW64\stdole2.tlb#OLE Automation
IconForm="Form1"
Startup="Form1"
HelpFile=""
Title="三門問題試驗"
ExeName32="三門問題試驗.exe"
Command32=""
Name="三門問題試驗"
HelpContextID="0"
CompatibleMode="0"
MajorVer=1
MinorVer=0
RevisionVer=0
AutoIncrementVer=0
ServerSupportFiles=0
VersionCompanyName="大系統觀開放論壇 BSV"
VersionLegalCopyright="開源免費"
VersionProductName="三門問題模擬試驗"
CompilationType=0
OptimizationType=0
FavorPentiumPro(tm)=0
CodeViewDebugInfo=0
NoAliasing=0
BoundsCheck=0
OverflowCheck=0
FlPointCheck=0
FDIVCheck=0
UnroundedFP=0
StartMode=0
Unattended=0
Retained=0
ThreadPerObject=0
MaxNumberOfThreads=1
(3)三門問題試驗.frm
VERSION 5.00
Begin VB.Form Form1
AutoRedraw = -1 'True
BackColor = &H00FFFFFF&
BorderStyle = 1 'Fixed Single
Caption = "三門問題模擬試驗對比 大系統觀-阿色/2020.11"
ClientHeight = 9285
ClientLeft = 45
ClientTop = 390
ClientWidth = 17055
Icon = "三門問題試驗.frx":0000
LinkTopic = "Form1"
MaxButton = 0 'False
MinButton = 0 'False
ScaleHeight = 9285
ScaleWidth = 17055
StartUpPosition = 3 '窗口缺省
Begin VB.TextBox TXT_Computing
Alignment = 2 'Center
BackColor = &H00C0FFC0&
BorderStyle = 0 'None
BeginProperty Font
Name = "宋體"
Size = 48
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 975
Left = 1920
TabIndex = 28
Text = "正在計算..."
Top = 3960
Visible = 0 'False
Width = 5895
End
Begin VB.CheckBox CHK_DynamicShow
BackColor = &H00FFFFFF&
Caption = "顯示動態-影響速度,最多1000次"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 255
Left = 5160
TabIndex = 25
Top = 7680
Width = 3975
End
Begin VB.CommandButton BT_SourceCode
Caption = "查看源碼/下載"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 975
Left = 6960
TabIndex = 3
Top = 8040
Width = 2175
End
Begin VB.TextBox TXT_Stat
Height = 1000
Left = 4800
MultiLine = -1 'True
TabIndex = 17
TabStop = 0 'False
Text = "三門問題試驗.frx":424A
Top = 2520
Width = 4335
End
Begin VB.TextBox TXT_TestTimes
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 285
Left = 1440
TabIndex = 4
Text = "1000000"
Top = 7680
Width = 1095
End
Begin VB.TextBox TXT_SingleData
Height = 5055
Left = 360
MultiLine = -1 'True
ScrollBars = 3 'Both
TabIndex = 7
TabStop = 0 'False
Text = "三門問題試驗.frx":42D0
Top = 2520
Width = 4335
End
Begin VB.CommandButton BT_Quit
Caption = "不玩了"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 975
Left = 4800
TabIndex = 2
Top = 8040
Width = 2175
End
Begin VB.CommandButton BT_Change
Caption = "每次都換"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 975
Left = 2520
TabIndex = 1
Top = 8040
Width = 2175
End
Begin VB.CommandButton BT_NoChange
Caption = "每次都不換"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 975
Left = 360
TabIndex = 0
Top = 8040
Width = 2175
End
Begin VB.Label Label13
Alignment = 2 'Center
Appearance = 0 'Flat
AutoSize = -1 'True
BackColor = &H80000005&
Caption = "完成次數"
BeginProperty Font
Name = "宋體"
Size = 15.75
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
ForeColor = &H80000008&
Height = 315
Left = 6360
TabIndex = 27
Top = 3840
Width = 1335
End
Begin VB.Label LB_TimesFinished
Alignment = 2 'Center
BackColor = &H00C0C0FF&
Caption = "1000000"
BeginProperty Font
Name = "宋體"
Size = 36
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 705
Left = 5160
TabIndex = 26
Top = 4200
Width = 3615
End
Begin VB.Label Label6
Alignment = 2 'Center
BackStyle = 0 'Transparent
Caption = "大系統觀指導人工智能"
BeginProperty Font
Name = "宋體"
Size = 15
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
ForeColor = &H00008000&
Height = 495
Left = 10320
TabIndex = 24
Top = 8640
Width = 4815
End
Begin VB.Image Image3
Height = 2055
Left = 14900
Picture = "三門問題試驗.frx":42F7
Stretch = -1 'True
Top = 6840
Width = 1840
End
Begin VB.Line Line2
X1 = 3000
X2 = 6600
Y1 = 840
Y2 = 840
End
Begin VB.Line Line1
BorderColor = &H00C0C0C0&
X1 = 9480
X2 = 9480
Y1 = 240
Y2 = 9000
End
Begin VB.Label LB_Strategy
Alignment = 2 'Center
BackColor = &H00C0C0FF&
Caption = "不換"
BeginProperty Font
Name = "宋體"
Size = 18
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 7560
TabIndex = 23
Top = 1320
Width = 1575
End
Begin VB.Label Label16
BackStyle = 0 'Transparent
Caption = "你的策略:"
BeginProperty Font
Name = "宋體"
Size = 18
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 5760
TabIndex = 22
Top = 1320
Width = 2055
End
Begin VB.Label LB_Lang
Alignment = 2 'Center
BackColor = &H00C0C0FF&
Caption = "VB6"
BeginProperty Font
Name = "宋體"
Size = 18
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 2160
TabIndex = 21
Top = 1320
Width = 1455
End
Begin VB.Label Label14
BackStyle = 0 'Transparent
Caption = "編程語言:"
BeginProperty Font
Name = "宋體"
Size = 18
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 360
TabIndex = 20
Top = 1320
Width = 2055
End
Begin VB.Label LB_TimeLen
Alignment = 2 'Center
BackColor = &H00C0C0FF&
Caption = "333470"
BeginProperty Font
Name = "宋體"
Size = 36
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 705
Left = 5160
TabIndex = 19
Top = 6840
Width = 3615
End
Begin VB.Label Label11
Alignment = 2 'Center
Appearance = 0 'Flat
AutoSize = -1 'True
BackColor = &H80000005&
Caption = "計算用時(秒)"
BeginProperty Font
Name = "宋體"
Size = 15.75
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
ForeColor = &H80000008&
Height = 315
Left = 6015
TabIndex = 18
Top = 6480
Width = 2025
End
Begin VB.Label Label10
BackStyle = 0 'Transparent
Caption = "(單位:次)"
Height = 255
Left = 8280
TabIndex = 16
Top = 2040
Width = 975
End
Begin VB.Label Label9
BackStyle = 0 'Transparent
Caption = "1號門 2號門 3號門 合計"
BeginProperty Font
Name = "宋體"
Size = 9
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 255
Left = 6240
TabIndex = 15
Top = 2280
Width = 3255
End
Begin VB.Label Label8
Alignment = 2 'Center
BackStyle = 0 'Transparent
Caption = "單次試驗數據"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 960
TabIndex = 14
Top = 1920
Width = 2895
End
Begin VB.Label Label7
Alignment = 2 'Center
BackColor = &H00E0E0E0&
BackStyle = 0 'Transparent
Caption = "綜合統計結果"
BeginProperty Font
Name = "宋體"
Size = 24
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 615
Left = 2760
TabIndex = 13
Top = 360
Width = 4095
End
Begin VB.Label LB_Prob
Alignment = 2 'Center
BackColor = &H00C0C0FF&
Caption = "33.33%"
BeginProperty Font
Name = "宋體"
Size = 36
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 705
Left = 5160
TabIndex = 12
Top = 5520
Width = 3615
End
Begin VB.Label Label5
Alignment = 2 'Center
Appearance = 0 'Flat
AutoSize = -1 'True
BackColor = &H80000005&
Caption = "得車概率"
BeginProperty Font
Name = "宋體"
Size = 15.75
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
ForeColor = &H80000008&
Height = 315
Left = 6360
TabIndex = 11
Top = 5160
Width = 1335
End
Begin VB.Label LB_SummaryChangge
Alignment = 2 'Center
BackStyle = 0 'Transparent
Caption = "數據統計"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 375
Left = 5640
TabIndex = 10
Top = 1920
Width = 2655
End
Begin VB.Label Label4
BackStyle = 0 'Transparent
Caption = "序號 車在門號 選門號 排除門號 換否 結果"
BeginProperty Font
Name = "宋體"
Size = 9
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 255
Left = 360
TabIndex = 9
Top = 2280
Width = 4455
End
Begin VB.Label Label3
BackStyle = 0 'Transparent
Caption = "試驗次數 *最多1000000次"
BeginProperty Font
Name = "宋體"
Size = 12
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 495
Left = 360
TabIndex = 8
Top = 7680
Width = 4695
End
Begin VB.Label Label2
Alignment = 2 'Center
BackStyle = 0 'Transparent
Caption = "三 門 問 題"
BeginProperty Font
Name = "宋體"
Size = 24
Charset = 134
Weight = 700
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 495
Left = 10560
TabIndex = 6
Top = 360
Width = 5295
End
Begin VB.Image Image2
BorderStyle = 1 'Fixed Single
Height = 2220
Left = 9840
Picture = "三門問題試驗.frx":1567F
Stretch = -1 'True
Top = 1080
Width = 6840
End
Begin VB.Label Label1
BackColor = &H00FFFFFF&
Caption = $"三門問題試驗.frx":2E53E
BeginProperty Font
Name = "宋體"
Size = 10.5
Charset = 134
Weight = 400
Underline = 0 'False
Italic = 0 'False
Strikethrough = 0 'False
EndProperty
Height = 3255
Left = 9840
TabIndex = 5
Top = 3480
Width = 6855
End
Begin VB.Image Image1
Height = 1770
Left = 9720
Picture = "三門問題試驗.frx":2E78B
Stretch = -1 'True
Top = 6960
Width = 5130
End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'************************************************************** 三門問題模擬驗證 ***************************************************************
'
' 三門問題是人工智能公司面試時經常提問的問題。它是關於貝葉斯公式的概率論問題。這個仿真模擬驗證有助於理解。
'
' 【三門問題描述】
' 這是個猜獎游戲,游戲規則和過程:
' 1、在台上放3扇門,關着。
' 2、主持人隨機地分別在門后面放兩只羊和一台車。
' 3、你選一扇門,門后的獎品歸你。把你的選擇告訴主持人。
' 4、主持人在剩下的兩扇門中打開一扇后面是羊的門。
' 5、主持人對你說:我已經幫你排除了一扇有羊的門。現在剩下兩扇門了。你現在可以選擇是否換掉你原來的選擇。
' 6、不論你換不換,你所選擇的門后的獎品都歸你。
'
' 【問題】為了得到車,你換不換?——試試再說!
'
'*************************************************************************************************************************************************
'********************************************************************* 定義與聲明 ****************************************************************
Const PROG_LANG = "VB6" '編程語言,對比VB和Python
Const MAX_TEST_TIMES = 1000000 '最大試驗次數,如果顯示動態,則為1000次,因為動態顯示非常耗時
Const MAX_TEST_TIMES_DYNAMIC = 1000
Dim Door(3) As Integer '定義3扇門。 Door(i) = 0:羊,1:車,2:被主持人打開(羊)
Dim TotalTestTimes As Long '試驗總次數
Dim CarNo, SheepNo, YouSelectNo, HostOpenNo As Integer '車所在門號,羊所在門號,你選擇的門號,猜對(得車)次數
Dim TimesCarDoor(3), TimesYouSelectDoor(3), TimesHostOpenDoor(3) As Long '車在各個門的總次數,你選擇各個門的總次數,主持人打開各個門的總次數
Dim TimesGetCar, TimesGetSheep As Long '得到車的總次數,得到羊的總次數
Dim Start, Finish As Single '計時
Dim OneRecord As String '單次試驗數據記錄
Private Declare Function ShellExecute Lib "shell32.dll" Alias "ShellExecuteA" (ByVal hwnd As Long, _
ByVal lpoperation As String, ByVal lpfile As String, ByVal lpparameters As String, _
ByVal lpdirectory As String, ByVal nshowcmd As Long) As Long
'******************************************************************* 定義與聲明結束 **************************************************************
'****************************************************************** 程序與函數 *******************************************************************
Private Sub BT_NoChange_Click() '每次都不換
Test_Exe ("不換")
End Sub
Private Sub BT_Change_Click() '每次都換
Test_Exe ("換")
End Sub
Private Sub Test_Exe(ChangeOrNot As String)
Dim i As Long
Dim tmpstr As String
Dim i_ShowStep As Long '當試驗次數很大時,間隔記錄每一次實驗數據,否則文本框裝不下,系統有限制,1000條左右記錄較好
Init_Data '初始化數據
LB_Strategy.Caption = ChangeOrNot
If CHK_DynamicShow.Value = 0 Then TXT_Computing.Visible = True
tmpstr = ""
i_ShowStep = TotalTestTimes / 1000
If i_ShowStep < 1 Then i_ShowStep = 1
'=============================== 開始多次模擬試驗 ===============================
For i = 1 To TotalTestTimes
'****************************** 開始一次試驗 *****************************
'初始化三扇門
Set_3_Doors (i)
'你的第一次選擇,你隨機選一扇門
YouSelectNo = YouFirstSelect()
'主持人打開一扇有羊的門
HostOpenNo = HostOpenADoor
'你換不換
If ChangeOrNot = "不換" Then
YouChangeOrNot (False)
Else
YouChangeOrNot (True)
End If
'揭曉
Show_Result
'是否動態顯示數據
If CHK_DynamicShow.Value = 1 Then
Show_SingleData (i)
Else
If i Mod i_ShowStep = 0 Then tmpstr = tmpstr & OneRecord & vbCrLf '不動態顯示時,間隔保存單次試驗記錄,不間隔的話數據太大,文本框裝不下
End If
'****************************** 一次試驗結束 *****************************
'交出控制權
DoEvents
Next i
'=============================== 多次模擬試驗結束 ===============================
'不動態顯示時,最后顯示單次試驗記錄
If tmpstr <> "" Then
TXT_SingleData.Text = tmpstr
End If
'顯示多次試驗的數據統計
Show_Stat (i - 1)
TXT_Computing.Visible = False
End Sub
Private Sub BT_Quit_Click() '結束本程序
End
End Sub
Private Sub BT_SourceCode_Click()
t = ShellExecute(Me.hwnd, "open", "https://www.cnblogs.com/BigSystemsView/p/13901753.html", "", "", 0)
End Sub
Private Sub Form_Load()
Init_Data
End Sub
Private Sub Init_Data() '初始化數據
Randomize
TXT_SingleData.Text = ""
TXT_Stat.Text = ""
LB_Lang.Caption = PROG_LANG
LB_Strategy.Caption = ""
LB_TimesFinished.Caption = ""
LB_Prob.Caption = ""
LB_TimeLen.Caption = ""
For i = 1 To 3
TimesCarDoor(i) = 0
TimesYouSelectDoor(i) = 0
TimesHostOpenDoor(i) = 0
Next i
TimesGetCar = 0
TimesGetSheep = 0
TotalTestTimes = CLng(TXT_TestTimes.Text)
If CHK_DynamicShow.Value = 0 Then '根據是否顯示動態,設定最大實驗次數
If TotalTestTimes > MAX_TEST_TIMES Then
TotalTestTimes = MAX_TEST_TIMES
TXT_TestTimes.Text = TotalTestTimes
End If
Else
If TotalTestTimes > MAX_TEST_TIMES_DYNAMIC Then
TotalTestTimes = MAX_TEST_TIMES_DYNAMIC
TXT_TestTimes.Text = TotalTestTimes
End If
End If
Start = Timer
End Sub
Private Sub Set_3_Doors(i As Long) '每一次游戲開始時,設置三扇門,放置車和羊
For j = 1 To 3 '放置三扇門,均放置羊
Door(j) = 0 '0代表羊
Next j
CarNo = Int(Rnd() * 3) + 1 '主持人隨機地把一扇門后面的羊換成車
Door(CarNo) = 1 '1代表車
TimesCarDoor(CarNo) = TimesCarDoor(CarNo) + 1 '統計每個門放置車的總次數
OneRecord = CStr(i) & vbTab & CarNo & vbTab '記錄每一次的情況
End Sub
Function YouFirstSelect() As Integer '你第一次選擇,返回選擇的門號
Dim t_YouSelectNo As Integer
t_YouSelectNo = Int(Rnd() * 3) + 1 '你隨機選一扇門
TimesYouSelectDoor(t_YouSelectNo) = TimesYouSelectDoor(t_YouSelectNo) + 1 '統計每個門被選擇的總次數
OneRecord = OneRecord & t_YouSelectNo & vbTab '記錄每一次選擇
YouFirstSelect = t_YouSelectNo
End Function
Private Function HostOpenADoor() As Integer '主持人打開一扇有羊的門
Dim t_HostOpenNo As Integer
For j = 1 To 3
If j <> YouSelectNo And Door(j) = 0 Then '主持人打開你沒選且有羊的任何一個門,可以取小號的門
t_HostOpenNo = j
TimesHostOpenDoor(j) = TimesHostOpenDoor(j) + 1
Exit For
End If
Next j
OneRecord = OneRecord & t_HostOpenNo & vbTab '記錄主持人每次打開的門號
HostOpenADoor = t_HostOpenNo
End Function
Private Sub YouChangeOrNot(YesOrNot As Boolean) '你是否換掉原來的選擇
If YesOrNot = True Then
OneRecord = OneRecord & "換" & vbTab
Select Case YouSelectNo
Case 1
If HostOpenNo = 2 Then
YouSelectNo = 3
Else
YouSelectNo = 2
End If
Case 2
If HostOpenNo = 1 Then
YouSelectNo = 3
Else
YouSelectNo = 1
End If
Case 3
If HostOpenNo = 1 Then
YouSelectNo = 2
Else
YouSelectNo = 1
End If
End Select
Else
OneRecord = OneRecord & "不換" & vbTab
End If
End Sub
Private Sub Show_Result() '揭曉本次結果
If Door(YouSelectNo) = 1 Then
OneRecord = OneRecord & "車*"
TimesGetCar = TimesGetCar + 1
Else
OneRecord = OneRecord & "羊"
TimesGetSheep = TimesGetSheep + 1
End If
End Sub
Private Sub Show_SingleData(i As Long) '顯示單次試驗數據記錄
TXT_SingleData.Text = TXT_SingleData.Text & OneRecord & vbCrLf
TXT_SingleData.SelStart = Len(TXT_SingleData.Text)
If i Mod 10 = 0 Then '每10次刷新一下顯示
Show_Stat (i)
End If
End Sub
Private Sub Show_Stat(i As Long) '顯示數據統計
TXT_Stat.Text = "汽車所在" & vbTab & TimesCarDoor(1) & vbTab & TimesCarDoor(2) & vbTab & TimesCarDoor(3) & vbTab _
& (TimesCarDoor(1) + TimesCarDoor(2) + TimesCarDoor(3)) & vbCrLf
TXT_Stat.Text = TXT_Stat.Text & "第一次選" & vbTab & TimesYouSelectDoor(1) & vbTab & TimesYouSelectDoor(2) & vbTab _
& TimesYouSelectDoor(3) & vbTab & (TimesYouSelectDoor(1) + TimesYouSelectDoor(2) + TimesYouSelectDoor(3)) & vbCrLf
TXT_Stat.Text = TXT_Stat.Text & "主持人打開" & vbTab & TimesHostOpenDoor(1) & vbTab & TimesHostOpenDoor(2) & vbTab _
& TimesHostOpenDoor(3) & vbTab & (TimesHostOpenDoor(1) + TimesHostOpenDoor(2) + TimesHostOpenDoor(3)) & vbCrLf
TXT_Stat.Text = TXT_Stat.Text & "得車次數" & vbTab & vbTab & vbTab & vbTab & TimesGetCar & vbCrLf
TXT_Stat.Text = TXT_Stat.Text & "得羊次數" & vbTab & vbTab & vbTab & vbTab & TimesGetSheep & vbCrLf
LB_TimesFinished.Caption = i
LB_Prob.Caption = Format(TimesGetCar * 100 / i, "##0.00") & "%"
Finish = Timer
LB_TimeLen.Caption = CStr(Int(Finish - Start))
End Sub
'**************************************************************** 程序與函數結束 *****************************************************************
3 Python模擬試驗
3.1 試驗結果
下列各圖是利用Python3 實現,PyCharm環境編碼調試,PyInstaller編譯,始終采取“不換”或“換”策略、是“否動態顯示”為條件的模擬情況。
動態顯示耗時較多,為了客觀對比,特別區分了“純計算”和“動態顯示”的情況。
純計算試驗1000000次,動態顯示試驗1000次。
試驗總體情況:
(1)得車概率
采取“不換”策略,得車概率是1/3(約33%);
采取“換”策略,得車概率是2/3(約67%)。
(2)模擬計算用時
純計算1000000次,“不換”和“換”策略基本相同:5秒;
動態顯示計算1000次,“不換”和“換”策略基本相同:37秒。
3.2 Python3源代碼
源代碼如下(因排版等問題,請以附后的下載文件包為准):
(1)三門問題試驗20201110.py
PROG_LANG = "Python3" # 編程語言,對比VB和Python
MAX_TEST_TIMES = 1000000 # 最大試驗次數,如果顯示動態,則為1000次,因為動態顯示非常耗時
MAX_TEST_TIMES_DYNAMIC = 1000
import tkinter as tk
from tkinter import *
import os
import time
import random
import webbrowser
class ThreeDoorsTest:
def __init__(self):
# ******************************************** 繪制GUI ******************************************
window = tk.Tk()
window.title("三門問題模擬試驗對比 大系統觀-阿色/2020.11")
window.iconbitmap("三門問題試驗\\BSV-LOGO.ico")
window.resizable(0, 0)
FRM_Main = tk.Frame(window)
FRM_Main.grid(row=1, rowspan=2, column=1, padx=20, sticky=S + W + E + N)
label1 = Label(FRM_Main, text='綜合統計結果', font=("宋體", 24, "bold", "underline"), height=3)
label1.grid(row=1, column=1, columnspan=10)
label2 = Label(FRM_Main, text="編程語言:", font=("宋體", 18, "bold"), width=8)
label2.grid(row=2, column=1, columnspan=1, sticky=W)
label3 = Label(FRM_Main, text=PROG_LANG, width=13, font=("宋體", 18, "bold"), bg="#FFC0C0")
label3.grid(row=2, column=2, columnspan=1, sticky=W)
label4 = Label(FRM_Main, text=" 你的策略:", font=("宋體", 18, "bold"))
label4.grid(row=2, column=6, columnspan=2)
self.LB_Strategy = Label(FRM_Main, text="", width=8, font=("宋體", 18, "bold"), bg="#FFC0C0")
self.LB_Strategy.grid(row=2, column=8)
label5 = Label(FRM_Main, text="\n單次試驗數據", font=("宋體", 12, "bold"), height=2)
label5.grid(row=3, column=1, columnspan=4)
label6 = Label(FRM_Main, text="\n數據統計", font=("宋體", 12, "bold"))
label6.grid(row=3, column=7, columnspan=2)
label7 = Label(FRM_Main, text="\n\n(單位:次)", font=("宋體", 10))
label7.grid(row=3, column=8, columnspan=1)
label8 = Label(FRM_Main, text="序號 車在門號 選門號 排除門號 換否 結果", font=("宋體", 10, "bold"))
label8.grid(row=4, column=1, columnspan=4)
label9 = Label(FRM_Main, text=" 1號門 2號門 3號門 合計", font=("宋體", 10, "bold"))
label9.grid(row=4, column=5, columnspan=4)
# *************************************** 顯示單次試驗數據 **********************************************
SCRB_SD = Scrollbar(FRM_Main) # 創建Scrollbar組件
self.TXT_SingleData = tk.Text(FRM_Main, width=44, height=29, padx=5, yscrollcommand=SCRB_SD.set)
SCRB_SD['command'] = self.TXT_SingleData.yview
SCRB_SD['width'] = 16
self.TXT_SingleData.grid(row=5, rowspan=7, column=1, columnspan=4)
SCRB_SD.grid(row=5, rowspan=7, column=5, sticky=S + W + N)
SCRB_SD.config(command=self.TXT_SingleData.yview)
# ************************************* 顯示統計數據 ***************************************
self.TXT_Stat = Text(FRM_Main, width=40, height=5, padx=5)
self.TXT_Stat.grid(row=5, column=7, columnspan=2)
label9_1 = Label(FRM_Main, text="\n完成次數", font=("宋體", 16, "bold"))
label9_1.grid(row=6, column=7, columnspan=4)
self.LB_TimesFinished = Label(FRM_Main, text="", width=11, font=("宋體", 36, "bold"), bg="#FFC0C0")
self.LB_TimesFinished.grid(row=7, column=7, columnspan=4)
label10 = Label(FRM_Main, text="\n得車概率", font=("宋體", 16, "bold"))
label10.grid(row=8, column=7, columnspan=4)
self.LB_Prob = Label(FRM_Main, text="", width=11, font=("宋體", 36, "bold"), bg="#FFC0C0")
self.LB_Prob.grid(row=9, column=7, columnspan=4)
label12 = Label(FRM_Main, text="\n 計算用時(秒)", font=("宋體", 16, "bold"))
label12.grid(row=10, column=7, columnspan=4)
self.LB_TimeLen = Label(FRM_Main, text="", width=11, font=("宋體", 36, "bold"), bg="#FFC0C0")
self.LB_TimeLen.grid(row=11, column=7, columnspan=4)
# *************************************** 顯示正在計算 ***************************************
self.FRM_Computing = tk.Frame(window)
self.LB_Computing = Label(self.FRM_Computing, text="正在計算...", font=("宋體", 48, "bold"), bg="#C0FFC0", height=1,
width=12)
# ************************************* 輸入試驗次數、按鍵 ***********************************
FRM_TestTimes = Frame(window)
FRM_TestTimes.grid(row=3, column=1, padx=20, pady=8, sticky=W + E)
label14 = Label(FRM_TestTimes, text="試驗次數:", font=("宋體", 12))
label14.grid(row=1, column=1)
self.TotalTestTimes = IntVar(value=1000000)
self.ETY_TestTimes = Entry(FRM_TestTimes, width=10, textvariable=self.TotalTestTimes)
self.ETY_TestTimes.grid(row=1, column=2)
label15 = Label(FRM_TestTimes, text=" *最多1000000次", font=("宋體", 12), width=23)
label15.grid(row=1, column=3, columnspan=1)
self.CHK_DynamicShowValue = IntVar(value=0)
self.CHK_DynamicShow = Checkbutton(FRM_TestTimes, text="顯示動態-影響速度,最多1000次", font=("宋體", 12),
variable=self.CHK_DynamicShowValue)
self.CHK_DynamicShow.grid(row=1, column=4)
FRM_Buttons = Frame(window)
FRM_Buttons.grid(row=4, column=1, padx=20, pady=0)
BT_NoChange = Button(FRM_Buttons, text="每次都不換", width=15, height=3, font=("宋體", 12),
command=self.processNoChange)
BT_NoChange.grid(row=2, column=1)
BT_Change = Button(FRM_Buttons, text="每次都換", width=15, height=3, font=("宋體", 12),
command=self.processChange)
BT_Change.grid(row=2, column=2)
BT_Quit = Button(FRM_Buttons, text="不玩了", width=15, height=3, font=("宋體", 12),
command=self.processQuit)
BT_Quit.grid(row=2, column=3)
BT_SourceCode = Button(FRM_Buttons, text="查看源碼/下載", width=15, height=3, font=("宋體", 12),
command=self.processSourceCode)
BT_SourceCode.grid(row=2, column=4)
label16 = Label(FRM_Buttons, text="\n", font=("宋體", 6))
label16.grid(row=3, column=1)
# *********************************** 三門問題介紹 **********************************************
FRM_Intro = Frame(window, bg="white")
FRM_Intro.grid(row=1, rowspan=14, column=2, padx=6, pady=6, sticky=W + N)
label17 = Label(FRM_Intro, text='三 門 問 題', font=("宋體", 28, "bold"), height=2, bg="white")
label17.grid(row=1, column=1, columnspan=2)
PIC_Intro = PhotoImage(file="三門問題試驗\\三門問題.gif")
label18 = Label(FRM_Intro, image=PIC_Intro, bg="white")
label18.grid(row=2, column=1, columnspan=2)
message = Message(FRM_Intro, text="\n 三門問題是人工智能公司面試時經常提問的問題。它是關於貝葉斯公式的概率論問題。這個仿真模擬驗證有助於理解。\n\n"
" 【三門問題描述】\n"
" 這是個猜獎游戲,游戲規則和過程:\n"
" 1、在台上放3扇門,關着。\n"
" 2、主持人隨機地分別在門后面放兩只羊和一台車。\n"
" 3、你選一扇門,門后的獎品歸你。把你的選擇告訴主持人。\n"
" 4、主持人在剩下的兩扇門中打開一扇后面是羊的門。\n"
" 5、主持人對你說:我已經幫你排除了一扇有羊的門。現在剩下兩扇門了。你現在可以選擇是否換掉你原來的選擇。\n"
" 6、不論你換不換,你所選擇的門后的獎品都歸你。\n\n"
" 【問題】為了得到車,你換不換?——試試再說!\n\n", font=("宋體", 11), width=500, bg="white")
message.grid(row=3, column=1, columnspan=2)
PIC_BSV = PhotoImage(file="三門問題試驗\\大系統觀logo.gif")
label19 = Label(FRM_Intro, image=PIC_BSV, bg="white")
label19.grid(row=4, column=1, sticky=W)
PIC_BSVQRCode = PhotoImage(file="三門問題試驗\\微信公眾號二維碼200.gif")
label20 = Label(FRM_Intro, image=PIC_BSVQRCode, bg="white")
label20.grid(row=4, column=2, sticky=W)
label21 = Label(FRM_Intro, text='大系統觀指導人工智能\n\n', font=("宋體", 14, "bold"), fg="green", bg="white")
label21.grid(row=5, column=1, columnspan=2)
# 窗口主循環
window.mainloop()
# ******************************************** 處理按鍵事件 ******************************************
def processNoChange(self): # 每次都不換
Test_Exe(self, "不換")
def processChange(self):
Test_Exe(self, "換")
def processQuit(self):
os._exit(0)
def processSourceCode(self):
url = "https://www.cnblogs.com/BigSystemsView/p/13901753.html"
webbrowser.open(url)
# ******************************* ThreeDoorsTest類定義結束 ************************************
def InitWinData(win_T, ChangeOrNot): # 初始化數據
# 清空數據
win_T.LB_Strategy["text"] = ""
win_T.TXT_SingleData.delete('1.0', 'end') # 清空單次試驗數據
win_T.TXT_Stat.delete('1.0', 'end') # 清空單次試驗數據
win_T.LB_TimesFinished["text"] = ""
win_T.LB_Prob["text"] = ""
win_T.LB_TimeLen["text"] = ""
TotalTestTimes = win_T.TotalTestTimes.get()
if win_T.CHK_DynamicShowValue.get() == 0: # 根據是否顯示動態,設定最大實驗次數
if TotalTestTimes > MAX_TEST_TIMES:
TotalTestTimes = MAX_TEST_TIMES
win_T.TotalTestTimes.set(MAX_TEST_TIMES)
win_T.ETY_TestTimes.update()
else:
if TotalTestTimes > MAX_TEST_TIMES_DYNAMIC:
TotalTestTimes = MAX_TEST_TIMES_DYNAMIC
win_T.TotalTestTimes.set(MAX_TEST_TIMES_DYNAMIC)
win_T.ETY_TestTimes.update()
win_T.LB_Strategy["text"] = ChangeOrNot
if win_T.CHK_DynamicShowValue.get() == 0: # 如果不顯示動態,則顯示"正在計算..."提示
win_T.FRM_Computing.grid(row=1, rowspan=5, column=1)
win_T.LB_Computing.grid(row=1, column=1, columnspan=10)
win_T.LB_Computing.update()
def Test_Exe(win_T, ChangeOrNot):
# 定義並初始化變量
Door = [0, 0, 0] # 定義3扇門。 Door[i] = 0:羊,1:車,2:被主持人打開(羊)
TotalTestTimes = 0 # 試驗總次數
CarNo = SheepNo = YouSelectNo = HostOpenNo = 0 # 車所在門號,羊所在門號,你選擇的門號,猜對(得車)次數
TimesCarDoor = [0, 0, 0] # 車在各個門的總次數,不可以用連= ,那意味着不同名稱指向同一個列表
TimesYouSelectDoor = [0, 0, 0] # 你選擇各個門的總次數
TimesHostOpenDoor = [0, 0, 0] # 主持人打開各個門的總次數
TimesGetCar = TimesGetSheep = 0 # 得到車的總次數,得到羊的總次數
Start = Finish = 0 # 計時
OneRecord = "" # 單次試驗數據記錄
tmpstr = ""
InitWinData(win_T, ChangeOrNot) # 窗口數據初始化
TotalTestTimes = int(win_T.TotalTestTimes.get())
i_ShowStep = int(TotalTestTimes / 1000)
if i_ShowStep < 1:
i_ShowStep = 1
Time_Start = time.time()
# =============================== 開始多次模擬試驗 ===============================
for i in range(1, TotalTestTimes + 1):
# ****************************** 開始一次試驗 *****************************
# 初始化三扇門
Door = [0, 0, 0] # 放置三扇門,均放置羊,0代表羊
CarNo = random.randint(0, 2) # 主持人隨機地把一扇門后面的羊換成車
Door[CarNo] = 1 # 1代表車
TimesCarDoor[CarNo] += 1 # 統計每個門放置車的總次數
OneRecord = str(i) + "\t" + str(CarNo) + "\t" # 記錄每一次的情況
# 你的第一次選擇,你隨機選一扇門
YouSelectNo = random.randint(0, 2) # 你隨機選一扇門
TimesYouSelectDoor[YouSelectNo] += 1 # 統計每個門被選擇的總次數
OneRecord += str(YouSelectNo) + "\t" # 記錄每一次選擇
# 主持人打開一扇有羊的門
for j in range(0, 3):
if j != YouSelectNo and Door[j] == 0: # 主持人打開你沒選且有羊的任何一個門,可以取小號的門
HostOpenNo = j
TimesHostOpenDoor[j] += 1
break
OneRecord += str(HostOpenNo) + "\t" # 記錄主持人每次打開的門號
# 你換不換
OneRecord += ChangeOrNot + "\t"
if ChangeOrNot == "換":
if YouSelectNo == 0:
if HostOpenNo == 1:
YouSelectNo = 2
else:
YouSelectNo = 1
elif YouSelectNo == 1:
if HostOpenNo == 0:
YouSelectNo = 2
else:
YouSelectNo = 0
elif YouSelectNo == 2:
if HostOpenNo == 0:
YouSelectNo = 1
else:
YouSelectNo = 0
# 揭曉
if Door[YouSelectNo] == 1:
OneRecord += "車*"
TimesGetCar += 1
else:
OneRecord += "羊"
TimesGetSheep += 1
# 是否動態顯示數據
if win_T.CHK_DynamicShowValue.get() == 1:
# 顯示單次試驗數據記錄
win_T.TXT_SingleData.insert(END, OneRecord + "\n")
win_T.TXT_SingleData.yview_moveto(1)
win_T.TXT_SingleData.update() # 動態更新
# 顯示多次試驗的數據統計,沒10次刷新一下顯示
if i % 10 == 0:
win_T.TXT_Stat.delete('1.0', 'end')
win_T.TXT_Stat.insert(END,
"汽車所在" + "\t" + str(TimesCarDoor[0]) + "\t" + str(TimesCarDoor[1]) + "\t" + str(
TimesCarDoor[2])
+ "\t" + str(TimesCarDoor[0] + TimesCarDoor[1] + TimesCarDoor[2]) + "\n")
win_T.TXT_Stat.insert(END, "第一次選" + "\t" + str(TimesYouSelectDoor[0]) + "\t" + str(
TimesYouSelectDoor[1]) + "\t" + str(TimesYouSelectDoor[2])
+ "\t" + str(
TimesYouSelectDoor[0] + TimesYouSelectDoor[1] + TimesYouSelectDoor[2]) + "\n")
win_T.TXT_Stat.insert(END,
"主持人開" + "\t" + str(TimesHostOpenDoor[0]) + "\t" + str(
TimesHostOpenDoor[1]) + "\t" + str(
TimesHostOpenDoor[2])
+ "\t" + str(
TimesHostOpenDoor[0] + TimesHostOpenDoor[1] + TimesHostOpenDoor[2]) + "\n")
win_T.TXT_Stat.insert(END, "得車次數" + "\t" + "\t" + "\t" + "\t" + str(TimesGetCar) + "\n")
win_T.TXT_Stat.insert(END, "得羊次數" + "\t" + "\t" + "\t" + "\t" + str(TimesGetSheep))
win_T.LB_TimesFinished["text"] = i
win_T.LB_Prob["text"] = (str(TimesGetCar / i * 100))[0:5] + "%"
Time_End = time.time() # 動態顯示用時
win_T.LB_TimeLen["text"] = int(Time_End - Time_Start + 0.5)
else:
if i % i_ShowStep == 0:
tmpstr = tmpstr + OneRecord + "\n" # 不動態顯示時,間隔保存單次試驗記錄,不間隔的話數據太大,文本框裝不下
# print(i)
# ****************************** 一次試驗結束 *****************************
# =============================== 多次模擬試驗結束 ===============================
# 不動態顯示時,最后顯示單次試驗記錄和最終統計數據
if tmpstr != "":
win_T.TXT_Stat.delete('1.0', 'end')
win_T.TXT_SingleData.insert(END, tmpstr)
win_T.TXT_Stat.delete('1.0', 'end')
win_T.TXT_Stat.insert(END, "汽車所在" + "\t" + str(TimesCarDoor[0]) + "\t" + str(TimesCarDoor[1]) + "\t" + str(
TimesCarDoor[2])
+ "\t" + str(TimesCarDoor[0] + TimesCarDoor[1] + TimesCarDoor[2]) + "\n")
win_T.TXT_Stat.insert(END, "第一次選" + "\t" + str(TimesYouSelectDoor[0]) + "\t" + str(
TimesYouSelectDoor[1]) + "\t" + str(TimesYouSelectDoor[2])
+ "\t" + str(
TimesYouSelectDoor[0] + TimesYouSelectDoor[1] + TimesYouSelectDoor[2]) + "\n")
win_T.TXT_Stat.insert(END,
"主持人開" + "\t" + str(TimesHostOpenDoor[0]) + "\t" + str(TimesHostOpenDoor[1]) + "\t" + str(
TimesHostOpenDoor[2])
+ "\t" + str(TimesHostOpenDoor[0] + TimesHostOpenDoor[1] + TimesHostOpenDoor[2]) + "\n")
win_T.TXT_Stat.insert(END, "得車次數" + "\t" + "\t" + "\t" + "\t" + str(TimesGetCar) + "\n")
win_T.TXT_Stat.insert(END, "得羊次數" + "\t" + "\t" + "\t" + "\t" + str(TimesGetSheep))
win_T.LB_TimesFinished["text"] = i
win_T.LB_Prob["text"] = (str(TimesGetCar / i * 100))[0:5] + "%"
Time_End = time.time() # 動態顯示用時
win_T.LB_TimeLen["text"] = int(Time_End - Time_Start + 0.5)
# 隱藏"正在計算..."提示
win_T.LB_Computing.grid_forget()
win_T.FRM_Computing.grid_forget()
# **************************************** 執行主程序 ****************************************
ThreeDoorsTest()
4 VB6和Python3對比及試驗結論
(1)純計算1000000次用時
二者基本相當:VB6用時4秒;Python3用時5秒。
(2)動態顯示計算1000次用時
二者相差較大:VB6用時62秒;Python3用時37秒。
Python3用時更少,出乎預料。本以為Python是解釋型語言,效率會低一些,而實際情況卻相反。原因可能是Python3語言比較新,優化得更好。
(3)程序文件大小
源代碼:VB6較長,大約是Python3源代碼的2倍。
EXE文件:編譯后VB6的可執行文件不到0.5MB,而Python3編譯后的可執行文件約為9MB。原因是Python的EXE文件需要打包解釋器。
(4)三門問題的模擬試驗結論
“不換”的得車概率是1/3,“換”的得車概率是2/3,所以應該選擇“換”的策略。
5 文件下載
VB6和Python3源代碼和EXE文件均在此下載。
代碼以本文附帶壓縮包為准。
點此下載【三門問題模擬試驗程序-VB6和Python3對比】
阿色剛剛嘗試用Python編程,專業素養尚淺,不好的習慣還未克服,不足之處敬請批評指正。謝謝!