The Monty Hall Problem illustrates a surprising fact of probability. The problem is loosely based on the old game show Let’s Make a Deal and its host, Monty Hall. In the Monty Hall Problem, you can pick one of three doors. Behind one door is a prize: a new car. Each of the other two doors opens onto a worthless goat. Say you pick Door #1. Before the door you choose is opened, the host opens another door (either #2 or #3), which leads to a goat. You can choose to either open the door you originally picked or switch to the other unopened door.
It may seem like it doesn’t matter if you switch or not, but your odds do improve if you switch doors! This program demonstrates the Monty Hall problem by letting you do repeated experiments.
To understand why your odds improve, consider a version of the Monty Hall Problem with one thousand doors instead of three. You pick one door, and then the host opens 998 doors, which all reveal goats. The only two doors that are unopened are the one you selected and one other door. If you correctly picked the car door to begin with (a 1 in a 1,000 chance), then the host left a random goat door closed. If you picked a goat door (a 999 in a 1,000 chance), the host specifically chose the car door to leave closed. The choice of which doors to open isn’t random; the host knows to leave the car door closed. It’s almost certain that you didn’t pick the car door to begin with, so you should switch to the other door.
Another way to think of it is that you have 1,000 boxes and one box contains a prize. You guess which box the prize is in and the host puts it in your hands. Do you think the prize is in your box or one of the 999 other boxes? You don’t need the host to open 998 of the 999 boxes that don’t contain a prize; the amount of choice is the same as with the 1,000 doors. The odds that you guessed correctly in the beginning are 1 in 1,000, while the odds that you did not (and that the prize is in one of the other boxes) is a near certain 999 in 1,000.
More information about the Monty Hall Problem can be found at https://en.wikipedia.org/wiki/Monty_Hall_problem.
When you run montyhall.py, the output will look like this:
The Monty Hall Problem, by Al Sweigart [email protected]
--snip--
+------+ +------+ +------+
| | | | | |
| 1 | | 2 | | 3 |
| | | | | |
| | | | | |
| | | | | |
+------+ +------+ +------+
Pick a door 1, 2, or 3 (or "quit" to stop):
> 1
+------+ +------+ +------+
| | | | | (( |
| 1 | | 2 | | oo |
| | | | | /_/|_|
| | | | | | |
| | | | |GOAT|||
+------+ +------+ +------+
Door 3 contains a goat!
Do you want to swap doors? Y/N
> y
+------+ +------+ +------+
| (( | | CAR! | | (( |
| oo | | __| | oo |
| /_/|_| | _/ | | /_/|_|
| | | | /_ __| | | |
|GOAT||| | O | |GOAT|||
+------+ +------+ +------+
Door 2 has the car!
You won!
Swapping: 1 wins, 0 losses, success rate 100.0%
Not swapping: 0 wins, 0 losses, success rate 0.0%
Press Enter to repeat the experiment...
--snip--
The multiline strings for the ASCII-art doors are stored in several constant variables, such as ALL_CLOSED
, FIRST_GOAT
, and FIRST_CAR_OTHERS_GOAT
. The code that uses these constants, like print(FIRST_GOAT)
on line 125, stays the same even if we update the graphics. By placing the multiline strings together toward the top of the source code file, we’ll have an easier time comparing them to make sure the graphics are consistent.
1. """The Monty Hall Problem, by Al Sweigart [email protected]
2. A simulation of the Monty Hall game show problem.
3. More info at https://en.wikipedia.org/wiki/Monty_Hall_problem
4. This code is available at https://nostarch.com/big-book-small-python-programming
5. Tags: large, game, math, simulation"""
6.
7. import random, sys
8.
9. ALL_CLOSED = """
10. +------+ +------+ +------+
11. | | | | | |
12. | 1 | | 2 | | 3 |
13. | | | | | |
14. | | | | | |
15. | | | | | |
16. +------+ +------+ +------+"""
17.
18. FIRST_GOAT = """
19. +------+ +------+ +------+
20. | (( | | | | |
21. | oo | | 2 | | 3 |
22. | /_/|_| | | | |
23. | | | | | | |
24. |GOAT||| | | | |
25. +------+ +------+ +------+"""
26.
27. SECOND_GOAT = """
28. +------+ +------+ +------+
29. | | | (( | | |
30. | 1 | | oo | | 3 |
31. | | | /_/|_| | |
32. | | | | | | |
33. | | |GOAT||| | |
34. +------+ +------+ +------+"""
35.
36. THIRD_GOAT = """
37. +------+ +------+ +------+
38. | | | | | (( |
39. | 1 | | 2 | | oo |
40. | | | | | /_/|_|
41. | | | | | | |
42. | | | | |GOAT|||
43. +------+ +------+ +------+"""
44.
45. FIRST_CAR_OTHERS_GOAT = """
46. +------+ +------+ +------+
47. | CAR! | | (( | | (( |
48. | __| | oo | | oo |
49. | _/ | | /_/|_| | /_/|_|
50. | /_ __| | | | | | |
51. | O | |GOAT||| |GOAT|||
52. +------+ +------+ +------+"""
53.
54. SECOND_CAR_OTHERS_GOAT = """
55. +------+ +------+ +------+
56. | (( | | CAR! | | (( |
57. | oo | | __| | oo |
58. | /_/|_| | _/ | | /_/|_|
59. | | | | /_ __| | | |
60. |GOAT||| | O | |GOAT|||
61. +------+ +------+ +------+"""
62.
63. THIRD_CAR_OTHERS_GOAT = """
64. +------+ +------+ +------+
65. | (( | | (( | | CAR! |
66. | oo | | oo | | __|
67. | /_/|_| | /_/|_| | _/ |
68. | | | | | | | /_ __|
69. |GOAT||| |GOAT||| | O |
70. +------+ +------+ +------+"""
71.
72. print('''The Monty Hall Problem, by Al Sweigart [email protected]
73.
74. In the Monty Hall game show, you can pick one of three doors. One door
75. has a new car for a prize. The other two doors have worthless goats:
76. {}
77. Say you pick Door #1.
78. Before the door you choose is opened, another door with a goat is opened:
79. {}
80. You can choose to either open the door you originally picked or swap
81. to the other unopened door.
82.
83. It may seem like it doesn't matter if you swap or not, but your odds
84. do improve if you swap doors! This program demonstrates the Monty Hall
85. problem by letting you do repeated experiments.
86.
87. You can read an explanation of why swapping is better at
88. https://en.wikipedia.org/wiki/Monty_Hall_problem
89. '''.format(ALL_CLOSED, THIRD_GOAT))
90.
91. input('Press Enter to start...')
92.
93.
94. swapWins = 0
95. swapLosses = 0
96. stayWins = 0
97. stayLosses = 0
98. while True: # Main program loop.
99. # The computer picks which door has the car:
100. doorThatHasCar = random.randint(1, 3)
101.
102. # Ask the player to pick a door:
103. print(ALL_CLOSED)
104. while True: # Keep asking the player until they enter a valid door.
105. print('Pick a door 1, 2, or 3 (or "quit" to stop):')
106. response = input('> ').upper()
107. if response == 'QUIT':
108. # End the game.
109. print('Thanks for playing!')
110. sys.exit()
111.
112. if response == '1' or response == '2' or response == '3':
113. break
114. doorPick = int(response)
115.
116. # Figure out which goat door to show the player:
117. while True:
118. # Select a door that is a goat and not picked by the player:
119. showGoatDoor = random.randint(1, 3)
120. if showGoatDoor != doorPick and showGoatDoor != doorThatHasCar:
121. break
122.
123. # Show this goat door to the player:
124. if showGoatDoor == 1:
125. print(FIRST_GOAT)
126. elif showGoatDoor == 2:
127. print(SECOND_GOAT)
128. elif showGoatDoor == 3:
129. print(THIRD_GOAT)
130.
131. print('Door {} contains a goat!'.format(showGoatDoor))
132.
133. # Ask the player if they want to swap:
134. while True: # Keep asking until the player enters Y or N.
135. print('Do you want to swap doors? Y/N')
136. swap = input('> ').upper()
137. if swap == 'Y' or swap == 'N':
138. break
139.
140. # Swap the player's door if they wanted to swap:
141. if swap == 'Y':
142. if doorPick == 1 and showGoatDoor == 2:
143. doorPick = 3
144. elif doorPick == 1 and showGoatDoor == 3:
145. doorPick = 2
146. elif doorPick == 2 and showGoatDoor == 1:
147. doorPick = 3
148. elif doorPick == 2 and showGoatDoor == 3:
149. doorPick = 1
150. elif doorPick == 3 and showGoatDoor == 1:
151. doorPick = 2
152. elif doorPick == 3 and showGoatDoor == 2:
153. doorPick = 1
154.
155. # Open all the doors:
156. if doorThatHasCar == 1:
157. print(FIRST_CAR_OTHERS_GOAT)
158. elif doorThatHasCar == 2:
159. print(SECOND_CAR_OTHERS_GOAT)
160. elif doorThatHasCar == 3:
161. print(THIRD_CAR_OTHERS_GOAT)
162.
163. print('Door {} has the car!'.format(doorThatHasCar))
164.
165. # Record wins and losses for swapping and not swapping:
166. if doorPick == doorThatHasCar:
167. print('You won!')
168. if swap == 'Y':
169. swapWins += 1
170. elif swap == 'N':
171. stayWins += 1
172. else:
173. print('Sorry, you lost.')
174. if swap == 'Y':
175. swapLosses += 1
176. elif swap == 'N':
177. stayLosses += 1
178.
179. # Calculate success rate of swapping and not swapping:
180. totalSwaps = swapWins + swapLosses
181. if totalSwaps != 0: # Prevent zero-divide error.
182. swapSuccess = round(swapWins / totalSwaps * 100, 1)
183. else:
184. swapSuccess = 0.0
185.
186. totalStays = stayWins + stayLosses
187. if (stayWins + stayLosses) != 0: # Prevent zero-divide.
188. staySuccess = round(stayWins / totalStays * 100, 1)
189. else:
190. staySuccess = 0.0
191.
192. print()
193. print('Swapping: ', end='')
194. print('{} wins, {} losses, '.format(swapWins, swapLosses), end='')
195. print('success rate {}%'.format(swapSuccess))
196. print('Not swapping: ', end='')
197. print('{} wins, {} losses, '.format(stayWins, stayLosses), end='')
198. print('success rate {}%'.format(staySuccess))
199. print()
200. input('Press Enter repeat the experiment...')
Try to find the answers to the following questions. Experiment with some modifications to the code and rerun the program to see what effect the changes have.
doorThatHasCar = random.randint(1, 3)
on line 100 to doorThatHasCar = 1
?print([FIRST_GOAT, SECOND_GOAT, THIRD_GOAT][showGoatDoor - 1])
?