15

Through trying to explain the Monty Hall problem to a friend during class yesterday, we ended up coding it in Python to prove that if you always swap, you will win 2/3 times. We came up with this:

import random as r

#iterations = int(raw_input("How many iterations? >> "))
iterations = 100000

doors = ["goat", "goat", "car"]
wins = 0.0
losses = 0.0

for i in range(iterations):
    n = r.randrange(0,3)

    choice = doors[n]
    if n == 0:
        #print "You chose door 1."
        #print "Monty opens door 2. There is a goat behind this door."
        #print "You swapped to door 3."
        wins += 1
        #print "You won a " + doors[2] + "\n"
    elif n == 1:
        #print "You chose door 2."
        #print "Monty opens door 1. There is a goat behind this door."
        #print "You swapped to door 3."
        wins += 1
        #print "You won a " + doors[2] + "\n"
    elif n == 2:
        #print "You chose door 3."
        #print "Monty opens door 2. There is a goat behind this door."
        #print "You swapped to door 1."
        losses += 1
        #print "You won a " + doors[0] + "\n"
    else:
        print "You screwed up"

percentage = (wins/iterations) * 100
print "Wins: " + str(wins)
print "Losses: " + str(losses)
print "You won " + str(percentage) + "% of the time"

My friend thought this was a good way of going about it (and is a good simulation for it), but I have my doubts and concerns. Is it actually random enough?

The problem I have with it is that the all choices are kind of hard coded in.

Is this a good or bad 'simulation' for the Monty Hall problem? How come?

Can you come up with a better version?

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Josh Hunt
  • 14,225
  • 26
  • 79
  • 98

15 Answers15

38

Your solution is fine, but if you want a stricter simulation of the problem as posed (and somewhat higher-quality Python;-), try:

import random

iterations = 100000

doors = ["goat"] * 2 + ["car"]
change_wins = 0
change_loses = 0

for i in xrange(iterations):
    random.shuffle(doors)
    # you pick door n:
    n = random.randrange(3)
    # monty picks door k, k!=n and doors[k]!="car"
    sequence = range(3)
    random.shuffle(sequence)
    for k in sequence:
        if k == n or doors[k] == "car":
            continue
    # now if you change, you lose iff doors[n]=="car"
    if doors[n] == "car":
        change_loses += 1
    else:
        change_wins += 1

print "Changing has %s wins and %s losses" % (change_wins, change_loses)
perc = (100.0 * change_wins) / (change_wins + change_loses)
print "IOW, by changing you win %.1f%% of the time" % perc

a typical output is:

Changing has 66721 wins and 33279 losses
IOW, by changing you win 66.7% of the time
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 2
    I am not sure I understand why you have the for k in sequence part? You don't even select a k, and it doesn't matter which door monty picks... all that matters is "n", right? – Tom Aug 08 '09 at 03:50
  • 9
    @Tom, I'm simply trying to simulate the classical Monty Hall Problem statement very faithfully -- Monty picks a door (different from your original pick and not the one w/the car) and you either change or you don't. Yep, Monty's move within the given constraints is irrelevant (as shown by the fact that k doesn't appear in the tail of the loop;-), so that whole block might be removed, EXCEPT that the whole point of the executable pseudocode it presumably to help convince skeptics, so, the closer we stick to the letter of the problem, the better!-) – Alex Martelli Aug 08 '09 at 03:54
  • @Alex: you might be interested in reading my comments on Mitch Wheat's post :-). I attempt to explain why monty hall is intuitive. – Tom Aug 09 '09 at 16:02
  • 3
    @Tom, good try, but the rage against Marylin vos Savant when SHE gave the right answer -- including math PhDs railing against her -- proves empirically but irrefutably that "intuitive" is NOT a correct word to describe the interaction between probabilities and the human brain!-) – Alex Martelli Aug 09 '09 at 17:39
  • @Alex: Maybe intuitive was the wrong word... but I think it's a good way of explaining why it works out the way it does... without showing anyone a formula or a simulation. I've used this way of explaining it to people (using 100, 1000, or even 10^6 doors), and they seem to get it. But yes, it did indeed baffle many people, and I certainly didn't get it the first time I saw the problem... but it's nice to visit a problem after knowing the answer and come up with a simpler explanation - which is what I was trying to do. – Tom Aug 09 '09 at 18:18
  • @Tom, sure, but showing a precise, detailed simulation is one more way to help convince somebody THAT it works (while reasoning is better to show WHY it works;). I've met similar issues in the past, e.g. convincing bridge players about "restricted choice" (which follows very directly from Bayes' formula, actually;-). – Alex Martelli Aug 09 '09 at 19:06
  • There's a really beautiful aspect to this solution which allows you to trivially see what the result will be. Notice that when you're deciding whether to increment the wins or losses counter, you don't make reference to k, the door that Monty opened. The only information you require is whether or not the player picked the car at first or not. Stated more mathematically, you win iff you chose a goat initially -- probability 2/3. – Zach Conn Jun 26 '15 at 03:21
  • @AlexMartelli, Hello Alex. Can you confirm if the indentation is correct for block "for k in sequence:" ? It seems like the statements in this block are purposeless. Shouldn't the if-block "if doors[n] == "car":" be placed inside the prior block ? – hygoh2k Dec 05 '16 at 14:50
2

You mentioned that all the choices are hardcoded in. But if you look closer, you'll notice that what you think are 'choices' are actually not choices at all. Monty's decision is without loss of generality since he always chooses the door with the goat behind it. Your swapping is always determined by what Monty chooses, and since Monty's "choice" was actually not a choice, neither is yours. Your simulation gives the correct results..

thedayturns
  • 9,723
  • 5
  • 33
  • 41
  • In fact, if you start at a strict simulation, and optimize your way down, you'll end up with code that just returns true 67% of the time. This is perhaps one of the greatest ways to convince someone of this problem. Ask them to program the simulation. They'll see that they're writing silly and redundant code. – Cruncher Dec 13 '13 at 15:18
2

I like something like this.


#!/usr/bin/python                                                                                                            
import random
CAR   = 1
GOAT  = 0

def one_trial( doors, switch=False ):
    """One trial of the Monty Hall contest."""

    random.shuffle( doors )
    first_choice = doors.pop( )
    if switch==False:
        return first_choice
    elif doors.__contains__(CAR):
        return CAR
    else:
        return GOAT


def n_trials( switch=False, n=10 ):
    """Play the game N times and return some stats."""
    wins = 0
    for n in xrange(n):
        doors = [CAR, GOAT, GOAT]
        wins += one_trial( doors, switch=switch )

    print "won:", wins, "lost:", (n-wins), "avg:", (float(wins)/float(n))


if __name__=="__main__":
    import sys
    n_trials( switch=eval(sys.argv[1]), n=int(sys.argv[2]) )

$ ./montyhall.py True 10000
won: 6744 lost: 3255 avg: 0.674467446745
si28719e
  • 2,135
  • 5
  • 20
  • 22
2

Here's my version ...

import random

wins = 0

for n in range(1000):

    doors = [1, 2, 3]

    carDoor     = random.choice(doors)
    playerDoor  = random.choice(doors)
    hostDoor    = random.choice(list(set(doors) - set([carDoor, playerDoor])))

    # To stick, just comment out the next line.
    (playerDoor, ) = set(doors) - set([playerDoor, hostDoor]) # Player swaps doors.

    if playerDoor == carDoor:
        wins += 1

print str(round(wins / float(n) * 100, 2)) + '%'
Asclepius
  • 57,944
  • 17
  • 167
  • 143
peedurrr
  • 187
  • 16
1

My solution with list comprehension to simulate the problem

from random import randint

N = 1000

def simulate(N):

    car_gate=[randint(1,3) for x in range(N)]
    gate_sel=[randint(1,3) for x in range(N)]

    score = sum([True if car_gate[i] == gate_sel[i] or ([posible_gate for posible_gate in [1,2,3] if posible_gate != gate_sel[i]][randint(0,1)] == car_gate[i]) else False for i in range(N)])

    return 'you win %s of the time when you change your selection.' % (float(score) / float(N))

print simulate(N)

1

Not mine sample

# -*- coding: utf-8 -*-
#!/usr/bin/python -Ou
# Written by kocmuk.ru, 2008
import random

num = 10000  # number of games to play
win = 0      # init win count if donot change our first choice

for i in range(1, num):                            # play "num" games
    if random.randint(1,3) == random.randint(1,3): # if win at first choice 
        win +=1                                    # increasing win count

print "I donot change first choice and win:", win, " games"   
print "I change initial choice and win:", num-win, " games" # looses of "not_change_first_choice are wins if changing
Tard
  • 31
  • 4
1

I found this to be the most intuitive way of solving the problem.

import random

# game_show will return True/False if the participant wins/loses the car:
def game_show(knows_bayes):

    doors = [i for i in range(3)]

    # Let the car be behind this door
    car = random.choice(doors)

    # The participant chooses this door..
    choice = random.choice(doors)

    # ..so the host opens another (random) door with no car behind it
    open_door = random.choice([i for i in doors if i not in [car, choice]])

    # If the participant knows_bayes she will switch doors now
    if knows_bayes:
        choice = [i for i in doors if i not in [choice, open_door]][0]

    # Did the participant win a car?
    if choice == car:
        return True
    else:
        return False

# Let us run the game_show() for two participants. One knows_bayes and the other does not.
wins = [0, 0]
runs = 100000
for x in range(0, runs):
    if game_show(True):
        wins[0] += 1
    if game_show(False):
        wins[1] += 1

print "If the participant knows_bayes she wins %d %% of the time." % (float(wins[0])/runs*100)
print "If the participant does NOT knows_bayes she wins %d %% of the time." % (float(wins[1])/runs*100)

This outputs something like

If the participant knows_bayes she wins 66 % of the time.
If the participant does NOT knows_bayes she wins 33 % of the time.
user1965074
  • 367
  • 5
  • 16
1

Here is an interactive version:

from random import shuffle, choice
cars,goats,iters= 0, 0, 100
for i in range(iters):
    doors = ['goat A', 'goat B', 'car']
    shuffle(doors)
    moderator_door = 'car'
    #Turn 1:
    selected_door = choice(doors)
    print selected_door
    doors.remove(selected_door)
    print 'You have selected a door with an unknown object'
    #Turn 2:
    while moderator_door == 'car':
        moderator_door = choice(doors)
    doors.remove(moderator_door)
    print 'Moderator has opened a door with ', moderator_door
    #Turn 3:
    decision=raw_input('Wanna change your door? [yn]')
    if decision=='y':
        prise = doors[0]
        print 'You have a door with ', prise
    elif decision=='n':
        prise = selected_door
        print 'You have a door with ', prise
    else:
        prise = 'ERROR'
        iters += 1
        print 'ERROR:unknown command'
    if prise == 'car':
        cars += 1
    elif prise != 'ERROR':
        goats += 1
print '==============================='
print '          RESULTS              '
print '==============================='
print 'Goats:', goats
print 'Cars :', cars
psihodelia
  • 29,566
  • 35
  • 108
  • 157
1

Read a chapter about the famous Monty Hall problem today. This is my solution.

import random

def one_round():
    doors = [1,1,0] # 1==goat, 0=car
    random.shuffle(doors) # shuffle doors
    choice = random.randint(0,2) 
    return doors[choice] 
    #If a goat is chosen, it means the player loses if he/she does not change.
    #This method returns if the player wins or loses if he/she changes. win = 1, lose = 0

def hall():
    change_wins = 0
    N = 10000
    for index in range(0,N):
        change_wins +=  one_round()
    print change_wins

hall()
1

Updated solution

Update, this time using the enum module. Again, going for brevity while using the most expressive features of Python for the problem at hand:

from enum import auto, Enum
from random import randrange, shuffle

class Prize(Enum):
    GOAT = auto()
    CAR = auto()

items = [Prize.GOAT, Prize.GOAT, Prize.CAR]
num_trials = 100000
num_wins = 0

# Shuffle prizes behind doors. Player chooses a random door, and Monty chooses
# the first of the two remaining doors that is not a car. Then the player
# changes his choice to the remaining door that wasn't chosen yet.
# If it's a car, increment the win count.
for trial in range(num_trials):
    shuffle(items)
    player = randrange(len(items))
    monty = next(i for i, p in enumerate(items) if i != player and p != Prize.CAR)
    player = next(i for i in range(len(items)) if i not in (player, monty))
    num_wins += items[player] is Prize.CAR

print(f'{num_wins}/{num_trials} = {num_wins / num_trials * 100:.2f}% wins')

Previous solution

Yet another "proof," this time with Python 3. Note the use of generators to select 1) which door Monty opens, and 2) which door the player switches to.

import random

items = ['goat', 'goat', 'car']
num_trials = 100000
num_wins = 0

for trial in range(num_trials):
    random.shuffle(items)
    player = random.randrange(3)
    monty = next(i for i, v in enumerate(items) if i != player and v != 'car')
    player = next(x for x in range(3) if x not in (player, monty))
    if items[player] == 'car':
        num_wins += 1
        
print('{}/{} = {}'.format(num_wins, num_trials, num_wins / num_trials))
Velimir Mlaker
  • 10,664
  • 4
  • 46
  • 58
0

Monty never opens the door with the car - that's the whole point of the show (he isn't your friend an has knowledge of what is behind each door)

Martin Beckett
  • 94,801
  • 28
  • 188
  • 263
  • 7
    No--he never opens the door with the car! – Loren Pechtel Aug 08 '09 at 03:25
  • Sorry it's normally state the other way around - the important point (that is lost on most people using this in interviews) is that Monty isn't picking at ranom – Martin Beckett Aug 08 '09 at 18:43
  • even though it's a bit irrelevant to the question, I have always been bothered by this problem. The reason the results may be "surprising" is only because most people misunderstand the rules. Monty is eliminating a bad option because he knows the answer. If you can see that, then this problem isn't so interesting anymore. The problem is mostly in understanding its own rules, not understanding probability. – tenfour Sep 01 '10 at 12:21
0

Here is different variant I find most intuitive. Hope this helps!

import random

class MontyHall():
    """A Monty Hall game simulator."""
    def __init__(self):
        self.doors = ['Door #1', 'Door #2', 'Door #3']
        self.prize_door = random.choice(self.doors)
        self.contestant_choice = ""
        self.monty_show = ""
        self.contestant_switch = ""
        self.contestant_final_choice = ""
        self.outcome = ""

    def Contestant_Chooses(self):
        self.contestant_choice = random.choice(self.doors)

    def Monty_Shows(self):
        monty_choices = [door for door in self.doors if door not in [self.contestant_choice, self.prize_door]]
        self.monty_show = random.choice(monty_choices)

    def Contestant_Revises(self):
        self.contestant_switch = random.choice([True, False])
        if self.contestant_switch == True:
            self.contestant_final_choice = [door for door in self.doors if door not in [self.contestant_choice, self.monty_show]][0]
        else:
            self.contestant_final_choice = self.contestant_choice

    def Score(self):
        if self.contestant_final_choice == self.prize_door:
            self.outcome = "Win"
        else:
            self.outcome = "Lose"

    def _ShowState(self):
        print "-" * 50
        print "Doors                    %s" % self.doors
        print "Prize Door               %s" % self.prize_door
        print "Contestant Choice        %s" % self.contestant_choice
        print "Monty Show               %s" % self.monty_show
        print "Contestant Switch        %s" % self.contestant_switch
        print "Contestant Final Choice  %s" % self.contestant_final_choice
        print "Outcome                  %s" % self.outcome
        print "-" * 50



Switch_Wins = 0
NoSwitch_Wins = 0
Switch_Lose = 0
NoSwitch_Lose = 0

for x in range(100000):
    game = MontyHall()
    game.Contestant_Chooses()
    game.Monty_Shows()
    game.Contestant_Revises()
    game.Score()
    # Tally Up the Scores
    if game.contestant_switch  and game.outcome == "Win":  Switch_Wins = Switch_Wins + 1
    if not(game.contestant_switch) and game.outcome == "Win":  NoSwitch_Wins = NoSwitch_Wins + 1
    if game.contestant_switch  and game.outcome == "Lose": Switch_Lose = Switch_Lose + 1
    if not(game.contestant_switch) and game.outcome == "Lose": NoSwitch_Lose = NoSwitch_Lose + 1

print Switch_Wins * 1.0 / (Switch_Wins + Switch_Lose)
print NoSwitch_Wins * 1.0 / (NoSwitch_Wins + NoSwitch_Lose)

The learning is still the same, that switching increases your chances of winning, 0.665025416127 vs 0.33554730611 from the above run.

0

Here's one I made earlier:

import random

def game():
    """
    Set up three doors, one randomly with a car behind and two with
    goats behind. Choose a door randomly, then the presenter takes away
    one of the goats. Return the outcome based on whether you stuck with
    your original choice or switched to the other remaining closed door.
    """
    # Neither stick or switch has won yet, so set them both to False
    stick = switch = False
    # Set all of the doors to goats (zeroes)
    doors = [ 0, 0, 0 ]
    # Randomly change one of the goats for a car (one)
    doors[random.randint(0, 2)] = 1
    # Randomly choose one of the doors out of the three
    choice = doors[random.randint(0, 2)]
    # If our choice was a car (a one)
    if choice == 1:
        # Then stick wins
        stick = True
    else:
        # Otherwise, because the presenter would take away the other
        # goat, switching would always win.
        switch = True
    return (stick, switch)

I also had code to run the game many times, and stored this and the sample output in this repostory.

Ellis Percival
  • 4,632
  • 2
  • 24
  • 34
0

Here is my solution to the MontyHall problem implemented in python.

This solution makes use of numpy for speed, it also allows you to change the number of doors.

def montyhall(Trials:"Number of trials",Doors:"Amount of doors",P:"Output debug"):
    N = Trials # the amount of trial
    DoorSize = Doors+1
    Answer = (nprand.randint(1,DoorSize,N))

    OtherDoor = (nprand.randint(1,DoorSize,N))

    UserDoorChoice = (nprand.randint(1,DoorSize,N))

    # this will generate a second door that is not the user's selected door
    C = np.where( (UserDoorChoice==OtherDoor)>0 )[0]
    while (len(C)>0):
        OtherDoor[C] = nprand.randint(1,DoorSize,len(C))
        C = np.where( (UserDoorChoice==OtherDoor)>0 )[0]

    # place the car as the other choice for when the user got it wrong
    D = np.where( (UserDoorChoice!=Answer)>0 )[0]
    OtherDoor[D] = Answer[D]

    '''
    IfUserStays = 0
    IfUserChanges = 0
    for n in range(0,N):
        IfUserStays += 1 if Answer[n]==UserDoorChoice[n] else 0
        IfUserChanges += 1 if Answer[n]==OtherDoor[n] else 0
    '''
    IfUserStays = float(len( np.where((Answer==UserDoorChoice)>0)[0] ))
    IfUserChanges = float(len( np.where((Answer==OtherDoor)>0)[0] ))

    if P:
        print("Answer        ="+str(Answer))
        print("Other         ="+str(OtherDoor))
        print("UserDoorChoice="+str(UserDoorChoice))
        print("OtherDoor     ="+str(OtherDoor))
        print("results")
        print("UserDoorChoice="+str(UserDoorChoice==Answer)+" n="+str(IfUserStays)+" r="+str(IfUserStays/N))
        print("OtherDoor     ="+str(OtherDoor==Answer)+" n="+str(IfUserChanges)+" r="+str(IfUserChanges/N))

    return IfUserStays/N, IfUserChanges/N
barukasu
  • 41
  • 3
0

I just found that global ratio of winning is 50% and ratio of losing is 50%... It is how the proportion on winning or losing based on selected final option.

  • %Wins (staying): 16.692
  • %Wins (switching): 33.525
  • %Losses (staying) : 33.249
  • %Losses (switching) : 16.534

Here is my code, that differs from yours + with commented comments so you can run it with small iterations :

import random as r

#iterations = int(raw_input("How many iterations? >> "))
iterations = 100000

doors = ["goat", "goat", "car"]
wins_staying =  0
wins_switching = 0  
losses_staying =  0
losses_switching = 0  



for i in range(iterations):
    # Shuffle the options
    r.shuffle(doors)
    # print("Doors configuration: ", doors)

    # Host will always know where the car is 
    car_option = doors.index("car")
    # print("car is in Option: ", car_option)

    # We set the options for the user
    available_options = [0, 1 , 2]

    # The user selects an option
    user_option = r.choice(available_options)
    # print("User option is: ", user_option)

    # We remove an option
    if(user_option != car_option ) :
        # In the case the door is a goat door on the user
        # we just leave the car door and the user door
        available_options = [user_option, car_option]
    else:
        # In the case the door is the car door 
        # we try to get one random door to keep
        available_options.remove(available_options[car_option])
        goat_option = r.choice(available_options)
        available_options = [goat_option, car_option]


    new_user_option = r.choice(available_options)
    # print("User final decision is: ", new_user_option)

    if new_user_option == car_option :
        if(new_user_option == user_option) :
            wins_staying += 1
        else :
            wins_switching += 1    
    else :
        if(new_user_option == user_option) :
            losses_staying += 1
        else :
            losses_switching += 1 


print("%Wins (staying): " + str(wins_staying / iterations * 100))
print("%Wins (switching): " + str(wins_switching / iterations * 100))
print("%Losses (staying) : " + str(losses_staying / iterations * 100))
print("%Losses (switching) : " + str(losses_switching / iterations * 100))
leandr0garcia
  • 51
  • 1
  • 2