# Monty hall python simulation

I have the following code that simulates the monty hall problem (see google for more details). I used sets to do it but is there a more intuitive or efficient way to do it?

import random as r
from sets import Set
def montysim(N):
K = 0
for i in range(N):
s = Set([1,2,3])
doorstoswitch = Set([1,2,3])
cardoor = r.randint(1,3)
chosendoor = r.randint(1,3)
doorstoswitch.remove(chosendoor)
if chosendoor != cardoor:
s.remove(chosendoor)
s.remove(cardoor)

montydoor = r.sample(s, 1)[0]
doorstoswitch.remove(montydoor)
newdoor = r.sample(doorstoswitch, 1)[0]
if newdoor == cardoor:
K+=1
return float(K) / float(N)

print montysim(10000)

• /OT Can't help that sounds like "Monty Python's flying circus" ;-) No offence. – πάντα ῥεῖ Apr 12 '17 at 17:34

There are a couple of changes you can make:

1. You can use random.choice(...) on a list, rather than random.sample(...)[0] on a set.
2. You shouldn't use sets:

Deprecated since version 2.6: The built-in set/frozenset types replace this module.

This means you can change Set to just set, or instead use the syntactic sugar: {1, 2, 3}.

3. You can make doorstoswitch at the end. You can do this by inverting the check. So rather than cardoor == r.choice(doorstoswitch) you can use cardoor not in {chosendoor, montydoor}.

4. You can then simplify the above to just cardoor != chosendoor, as montydoor can't be the car.

5. You can remove the sets, as there's no need for them anymore.

And so your code can be:

import random

def montysim(n):
k = 0
for _ in range(n):
k += random.randrange(3) != random.randrange(3)
return float(k) / n

• I think this is a bit extreme from a readability perspective as it doesnt simulate the switching of doors etc. I would want the code to be self annotating to the point where somebody who reads it can understand what the monty hall problem is. – user3079275 Apr 13 '17 at 0:35
• @user3079275 I agree with you, but performance optimizations normally come at the cost of readability. Feel free to follow as many of my steps to achieve code you're happy with. I think to step 3 would be best for that. :) Also it could further be changed to something like: montysim = lambda n: float(sum(randrange(3) != 0 for _ in range(n))) / n, if you want to see what the extreme is. – Peilonrayz Apr 13 '17 at 0:47
• @user3079275 I agree with you; this code wouldn't convince anybody that it was simulating the Monty Hall problem. The goal of code isn't always optimum performance! – ShreevatsaR Apr 13 '17 at 3:06
• For instance, for even faster performance one could just change the function to return numpy.random.binomial(n, 2.0/3) / float(n) :-) It would give the same results, and much faster, but claiming that it is a simulation of the Monty Hall would be absurd (the same with the current code in this answer). – ShreevatsaR Apr 13 '17 at 3:33

Two things stick out to me:

First, Python has an official style-guide, PEP8. It recommends consistently using 4 spaces as indentation (you use a mix of two and four). It also recommends using lower_case_with_underscores for names.

Second, the sets module has been deprecated. You can just use the built-in set, instead of sets.Set.

import random

def monty_sim(n):
k = 0
for i in range(n):
s = set([1, 2, 3])
doors_to_switch = set([1, 2, 3])
car_door = random.randint(1, 3)
chosen_door = random.randint(1, 3)
doors_to_switch.remove(chosen_door)
if chosen_door != car_door:
s.remove(chosen_door)
s.remove(car_door)

monty_door = random.sample(s, 1)[0]
doors_to_switch.remove(monty_door)
newdoor = random.sample(doors_to_switch, 1)[0]
if newdoor == car_door:
k += 1
return float(k) / float(n)

if __name__ == "__main__":
print monty_sim(10000)


In [2]: %timeit montysim(10000)
10 loops, best of 3: 109 ms per loop

In [4]: %timeit monty_sim(10000)
10 loops, best of 3: 54 ms per loop

• Try this for a bigger speed boost: import numpy and then def montySim(n): return numpy.random.binomial(n, 2.0/3) / float(n) :-) (See the comments on the other answer.) – ShreevatsaR Apr 13 '17 at 7:25

I think this is a bit extreme from a readability perspective as it doesnt simulate the switching of doors etc. I would want the code to be self annotating to the point where somebody who reads it can understand what the monty hall problem is.

I remember being bemused by this puzzle many years ago. And in the spirit of your quote I offer this rather hacked-together implementation. It runs through the entire game-show scenario in text before repeating it silently 10,000 times.

It's not efficient. It's not polished. It's not even particularly good, but I think it does the explaining bit. And if you squint you may not even notice the latent divide-by-zero error.

# Simulate the Monty Hall Problem

import random

class Stats:
def __init__(self,title):
self.title = title
self.games = 0
self.wins = 0
def printit(self):
print(self.title+": "+str(self.wins)+"/"+str(self.games)+" = " + str(self.wins*100//self.games)+"% " )

class Door:
""" One for each door. """
def __init__(self):
self.status = "closed"
self.contents = None
self.chosen = False

class MontyHallProblem:
""" Simulate one play of the Monty Hall problem """
stats = None
chosen_door = None

def __init__(self,silent=False):
""" Initialise the doors. """
# this happens before the game starts, the player does not know
# what is behind the doors. We put a car behind one door, and
# fill the other two with goats.
self.three_doors = [Door(), Door(), Door()]
self.three_doors[random.randint(0,2)].contents = "FERRARI"
for door in self.three_doors:
if not door.contents:
door.contents = "goat"
if not silent:
print("\nMonty says 'After that brief message from our sponsors WELCOME BACK to 'Let's Make a Deal'")
print("As you can see, there are three doors in front of you ...")

def look(self):
""" Inspect the doors from the player's point of view. """
for i, door in enumerate(self.three_doors):
print("\nDoor "+str(i+1)+" is "+door.status)
if door.status == "open":
print("... there is a "+door.contents+" behind it!!")
if door.chosen:
print("... a green light shows that this is the door you have chosen")

def choose_door(self,silent=False):
""" Player chooses a door. """
if silent:
i = random.randint(0,2)
self.chosen_door = self.three_doors[i]
self.chosen_door.chosen = True
return
else:
i = input("\nMonty says 'Choose a door - 1, 2 or 3': ")
while i not in "123":
i = input("Choose a door - 1, 2 or 3: ")
self.chosen_door = self.three_doors[int(i)-1]
self.chosen_door.chosen = True

def monty_opens_a_door(self, silent=False):
""" Monty opens one of the remaining two doors, revealing ..."""
# The exact procedure here is crucial to the outcome of the problem.
# Some descriptions of the problem don't make it clear that Monty
# *knows* what is behind the doors and deliberately opens one that
# does not reveal the Grand Prize (whatever that might be)
if not silent:
print("\nMonty says 'Thank you for choosing a door. Your choice is shown by the ")
print("green light. Now I am going to open one of the two remaining doors ...")
remaining_doors = [door for door in self.three_doors if not door.chosen]
for i, door in enumerate(remaining_doors):
if door.contents == "FERRARI":           # so he opens the *other* door
remaining_doors[(i+1) % 2].status = "open"
return
remaining_doors[random.randint(0,1)].status = "open"

def stick_or_switch(self,silent=False):
""" Player chooses whether to switch choice of door. """
global switched, non_switched
if silent:
switch = random.choice([True,False])
else:
print("\nMonty says 'There are now two doors remaining closed. You have already chosen one of them.")
s = input("Would you like to switch your choice?' Y/N: ")
while s not in "YyNn":
s = input("Do you want to switch doors? Y/N: ")
if s in ["Yy"]:
switch = True
else:
switch = False

if switch:
self.stats = switched
for door in self.three_doors:
if door.status == "closed" and not door.chosen:
door.chosen = True
self.chosen_door.chosen = False
self.chosen_door = door
break
else:
self.stats = non_switched

def denoument(self,silent=False):
""" Heart-stopping stuff this. Did we win or not? """
if not silent:
print("\nMonty says 'You have made your choice. Come forward and open the door you have chosen.'")
print("... You walk unsteadily towards the door and reach for the handle ...")
self.chosen_door.status = "open"
self.stats.games += 1
if self.chosen_door.contents == "FERRARI":
self.stats.wins += 1
if not silent:
print("\n... LIGHTS FLASH! The audience SCREAMS!! You jump for joy!!!")
else:
if not silent:
print("\n... as the door swings open, you hear a disconsolate 'Baa' ...")

switched = Stats("Switched")
non_switched = Stats("Not switched")

def play_monty_hall(silent=False):
M = MontyHallProblem(silent=silent)
if not silent: M.look()
M.choose_door(silent=silent)
if not silent: M.look()
M.monty_opens_a_door(silent=silent)
if not silent: M.look()
M.stick_or_switch(silent=silent)
if not silent: M.look()
M.denoument(silent=silent)
if not silent: M.look()

play_monty_hall()

for i in range(10000):
play_monty_hall(silent=True)
switched.printit()
non_switched.printit()