Monty_Hall is a gameshow host that proves that it is statistically better to switch when offered the chance after getting additional knowledge in a fair game. The caveat is "statistically better", which means any single trial can have unsupporting results. But "statistically better" is exactly where the fist-fights at bars and family reunions start, so no matter the single trial outcomes.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 | #!/usr/bin/env python
# Author: Mark Connolly
# Dedicated to my favorite sister, Caroline
# Contact: mark_connolly at acm dot org
class Monty_Hall(object):
"""Monty, teach me probabilities.
Monty_Hall is a gameshow host that proves that it is statistically better to switch
when offered the chance after getting additional knowledge in a fair game. The caveat
is "statistically better", which means any single trial can have unwanted results.
But "statistically better" is exactly where the fist-fights at bars and family reunions
start, so no matter the single trial outcomes.
Monty has a game set, which has doors. Monty has a scoreboard which keeps track of
wins and losses. You tell Monty waht to do, Monty does it.
You create an instance of Monty_Hall and send him messages. For example:
(when you start python, you first want to
>>> import gameshow
then you have access to what you need)
>>> monty = gameshow.Monty_Hall() # get yourself a gameshow Monty_Hall and attach its reference to a variable
>>> monty.choose_door(2) # 1. tell your Monty_Hall to choose a door (number 2 in this case)
>>> monty.switch() # 2. tell your Monty_Hall you'd like to switch
>>> monty.start_game() # 3. tell your Monty_Hall to start a new game, Monty_Hall will keep score
You can repeat the three steps to you heart's content. Stay with the same Monty_Hall
for he knows the score. Once you think you have suffered a sufficient number of games:
>>> monty.tell_me_the_score() # and your Monty_Hall will
You can also tell your Monty_Hall
>>> monty.start_new_series()
And your Monty_Hall will clear the scoreboard and start gathering statistics anew.
If Monty's music is bringing you down, you can set the wait to zero seconds:
>>> monty.music_duration = 0 # or any number of seconds. There is no actual music!
If you are lazy, you can have the gameshow automaton play for you:
>>> gameshow.automaton() # plays 100 games (by default) and has its Monty_Hall print out the statistics
You can have the gameshow automaton play any number of games for you with the form
>>> gameshow.automaton(iterations=1000)
Notes: The prize is randomly distributed to Monty's three game set doors for each game. The
statistical results will vary around the expected values 1/3 and 2/3. Variance is to be expected,
but large numbers of trials should be very close to the expected values. Mendel cooked his books.
"""
def __init__(self, music_duration = 4):
"""
Do all the initialization stuff when a new
instance of Monty_Hall is created. Granted,
this aint much.
"""
self.music_duration = music_duration
self.start_new_series()
def start_game(self):
"""
Starts a new game without resetting the scoreboard
"""
self.game_set = Game_Set()
print("\nYou can now choose a door.\n")
def start_new_series(self):
"""
A series is a set of games for which win/loss statistics are created.
Starting a new series clears the scoreboard and starts a new game.
"""
self.scoreboard = Scoreboard()
print("Scoreboard has been cleared")
self.start_game()
def choose_door(self, door_number):
"""
Monty gives you the door you ask for, then he opens a door you did not pick.
"""
import time
try:
self.game_set.select_door(door_number)
print("I will now open a door you did not select.")
print("(music plays)(no music actually plays)")
time.sleep(self.music_duration)
self.game_set.open_door()
except ValueError, explanation:
pass
print explanation
def switch(self):
"""
Monty switches your door with the one you did not pick. Monty then opens
your new selection to reveal the prize or the goat.
"""
try:
self.game_set.switch_door()
result = self.game_set.open_selected_door()
print("Door contains %s" % result)
if (result == "prize"):
print("You win!")
self.scoreboard.won_switched()
else:
print("Baaaaa!")
self.scoreboard.lost_switched()
except ValueError, explanation:
print explanation
pass
def stay(self):
"""
Monty understands you would like to stay. Monty shrugs and opens
your door to reveal the prize or the goat.
"""
result = self.game_set.open_selected_door()
print("Door contains %s" % result)
if (result == "prize"):
print("You win!")
self.scoreboard.won_stayed()
else:
print("Baaaaa!")
self.scoreboard.lost_stayed()
def tell_me_the_score(self):
"""
Monty has his scoreboard print itself out with the wins, losses, and percentages.
"""
self.scoreboard.tell_me_the_score()
class Scoreboard(object):
def __init__(self):
self.stats = {"Won switched" : 0.0, # this is a hash structure
"Lost switched": 0.0, # it is composed of keys and values
"Won stayed": 0.0, # values are set as float numbers (has a decimal
# component) as opposed to integers.
"Lost stayed": 0.0} # The reason is that the value will be used in
# calculating percentages. Integers would render
# integers and the decimal would be lost.
def won_switched(self):
self.stats["Won switched"] += 1
def lost_switched(self):
self.stats["Lost switched"] += 1
def won_stayed(self):
self.stats["Won stayed"] += 1
def lost_stayed(self):
self.stats["Lost stayed"] += 1
def tell_me_the_score(self):
play_made = "Stayed"
won = self.stats["Won stayed"]
lost = self.stats["Lost stayed"]
self.print_score(play_made, won, lost)
play_made = "Switched"
won = self.stats["Won switched"]
lost = self.stats["Lost switched"]
self.print_score(play_made, won, lost)
def print_score(self, play, w, l):
print("\nStats for " + play)
if (w + l > 0):
print(
"wins: %i losses: %i, percent win: %5.2f%%" # Text with formatting placeholders
%(w, l, (w / (w + l) * 100.0)) # The collection of ordered values to
# substitute and format
) # The %% prints a single % literal at
# the end of the formatted string
else:
print("No statistics for %s" % play)
class Game_Set(object):
"""
A collection of doors. Each door can hold something desireable
or something not so desireable. However, only one door in a game
can hold something desireable.
"""
def __init__(self):
import random
doors = {
0: Door(1),
1: Door(2),
2: Door(3)}
# random.randrange(startInt,endInt) generates a random integer in the range
# of startInt (which is included in the possibilities) to endInt (which is
# not included in the possibilities.
prize_door = random.randrange(0,3)
doors[prize_door].contents = "prize"
doors[((prize_door + 1) % 3)].contents = "goat"
doors[((prize_door + 2) % 3)].contents = "goat"
self.keep_track = {"doors": doors,
"prize": doors[prize_door],
"selected": None,
"opened": None}
def select_door(self, door_number):
"""
Select a door by number.
"""
# Has a door already been selected or switched this game?
if (self.keep_track["selected"]):
# raise an error to the caller and do no more
raise ValueError, "You have already selected a door."
# is an appropriate door being selected?
if door_number in (1,2,3):
# appropriate door number, transform to door key by subtracting 1
door_number = door_number - 1
else:
# raise an error to the caller and do no more
raise ValueError, "You entered %s, which is not a recognized door." % door_number
# that takes care of the possible errors
# now, moved the selected door out of the collection
self.keep_track["selected"] = self.keep_track["doors"][door_number]
del self.keep_track["doors"][door_number]
print("\nYou have selected door number %s.\nYou have a 1 in 3 chance of holding the prize."
% self.keep_track["selected"].label)
print("The house has a 2 in 3 chance of holding the prize.\n")
def open_door(self):
"""
Open one of the doors that has not been selected. One of the doors may have
the prize. Don't open that one.
"""
keys = self.keep_track["doors"].keys()
for key in keys:
if self.keep_track["doors"][key] == self.keep_track["prize"]:
pass
else:
self.keep_track["opened"] = self.keep_track["doors"][key]
del self.keep_track["doors"][key]
print("\nDoor %s is open and contains a %s.\n"
% (self.keep_track["opened"].label, self.keep_track["opened"].contents)
)
print("Your odds of holding the prize behind door number %s have not changed."
% self.keep_track["selected"].label)
print("The house odds have not changed just because one of the house doors has been opened.")
print("You now know which of the two doors held by the house does not contain the prize.")
print("Switching your selection is the same as switching to both doors held by the house.")
print("This is because you have taken the open door out of the selection options.")
print("Switching to the open door would just be silly, unless you want the goat.")
print("Switching to the house's closed door exchanges your odds (1 in 3) for the house odds (2 in 3)\n")
break
def switch_door(self):
"""
Exchange the selected door to the unopened door
"""
keys = self.keep_track["doors"].keys()
key = keys[0] # only one door left in the collection
# one removed when selected. one removed when opened
# swap the doors
hold_this_a_second = self.keep_track["doors"][key]
self.keep_track["doors"][key] = self.keep_track["selected"]
self.keep_track["selected"] = hold_this_a_second
print("\nYou now hold door %s.\n" % self.keep_track["selected"].label)
def open_selected_door(self):
"""
Opens the selected door to see if the prize is THE prize or a goat
"""
return self.keep_track["selected"].contents
class Door(object):
def __init__(self, label):
self.contents = None
self.label = label
def automaton(iterations=100, select_door="random"):
"""
Plays the game with its own Monty_Hall. Turns off the music so
the games proceeds apace.
default iterations is 100
door selection is random unless specified by the named variable select_door
"""
import random
"create a function that either returns a random door or a specified door"
if (select_door == "random"):
select_a_door = lambda : random.randrange(1,4)
else:
select_a_door = lambda : select_door
monty = Monty_Hall(music_duration=0)
for i in range(1,iterations):
select_door = select_a_door()
monty.choose_door(select_door)
monty.switch()
monty.start_game()
monty.choose_door(select_door)
monty.stay()
monty.start_game()
"finish up with the scores"
monty.tell_me_the_score()
"""
print a little help at import or reload
"""
help(Monty_Hall)
|
This is from a prompting at the Linked In group for The R Project for Statistical Computing. No integration with R at this point, but maybe someone will find this entertaining.
Nice work, here's a link to a great indepth discussion of the monty hall problem at http://en.wikipedia.org/wiki/Monty_Hall_problem
Here's a less verbose Monty hall problem simulator that concentrates on finding the effects of using two strategies: Always switching vs always sticking with first choices: http://rosettacode.org/wiki/Monty_Hall_problem#Python