John Doe
John Doe

Reputation: 13

Monty Hall simulation not working as intended

I've been trying out to solve the monty hall problem in Python in order to advance in coding, which is why I tried to randomize everything. The thing is: I've been running into some trouble. As most of you probably know the monty problem is supposed to show that changing the door has a higher winrate (66%) than staying on the chosen door (33%). For some odd reason though my simulation shows a 33% winrate for both cases and I am not really sure why.

Here's the code:

from random import *


def doorPriceRandomizer():
    door1 = randint(0,2) #If a door is defined 0, it has a price in it
    door2 = randint(0,2) #If a door is defined either 1 or 2, it has a goat in it.
    door3 = randint(0,2)
    while door2 == door1:
        door2 = randint(0,2)
    while door3 == door2 or door3 == door1:
        door3 = randint(0,2)
    return door1,door2,door3 #This random placement generator seems to be working fine.


while True:
    loopStart = 0
    amountWin = 0
    amountLose = 0
    try:
        loopEnd = int(input("How often would you like to run this simulation: "))
        if loopEnd < 0:
            raise ValueError
        doorChangeUser = int(input("[0] = Do not change door; [1] = Change door: "))
        if doorChangeUser not in range(0,2):
            raise ValueError
    except ValueError:
            print("Invalid input. Try again.\n")
    else:
        while loopStart != loopEnd:
            gameDoors = doorPriceRandomizer()
            inputUser = randint(0,2)
            if doorChangeUser == 0:
                if gameDoors[inputUser] == 0:
                    amountWin += 1
                    loopStart += 1
                else:
                    amountLose += 1
                    loopStart += 1
            elif doorChangeUser == 1:
                ChangeRandom = 0
                while gameDoors[ChangeRandom] == gameDoors[inputUser]:
                    ChangeRandom = randint(0,2)
                if gameDoors[ChangeRandom] == 0:
                    amountWin += 1
                    loopStart += 1
                else:
                    amountLose += 1
                    loopStart += 1

    print("Win amount: ",amountWin,"\tLose amount: ",amountLose)

What am I doing wrong? I really appreciate all help! Thanks in advance!

Upvotes: 1

Views: 303

Answers (2)

Adam Smith
Adam Smith

Reputation: 54183

ChangeRandom = 0
while gameDoors[ChangeRandom] == gameDoors[inputUser]:
    ChangeRandom = randint(0,2)

This doesn't do what you think it does. Instead of checking if the ChangeRandom door is the same as the inputUser door, this checks if the ChangeRandom door and the inputUser door have the same value -- that is to say they're either both winners or both losers.

That said, that's not even what you want to do. What you want to do is to find a door that's not the user's input that IS a loser door, then switch to the OTHER one that isn't the user's input. This could be implemented with minimal change to your code as:

other_wrong_door = next(c for c, v in enumerate(gameDoors) if v != 0 and c != inputUser)
new_door = next(c for c, _ in enumerate(gameDoors) if c != inputUser and c != other_wrong_door)

But honestly this merits a re-examining of your code's structure. Give me a few minutes to work something up, and I'll edit this answer to give you an idea of how I'd implement this.

import random

DOORS = [1, 0, 0]

def runonce(switch=False):
    user_choice = random.choice(DOORS)
    if user_choice == 1:
        # immediate winner
        if switch:
            # if you won before and switch doors, you must lose now
            return False
        else:
            new_doors = [0, 0]  # remove the user-selected winner
            new_doors = [0]     # remove another loser
            return bool(random.choice(new_doors))
            # of course, this is always `0`, but
            # sometimes it helps to show it. In production you
            # wouldn't bother writing the extra lines and just return False
    else:
        if switch:
            new_doors = [1, 0]  # remove the user-selected loser
            new_doors = [1]     # remove another loser
            return bool(random.choice(new_doors))
            # as above: this is always True, but....
        else:
            return False  # if you lost before and don't switch, well, you lost.

num_trials = int(input("How many trials?"))
no_switch_raw = [run_once(switch=False) for _ in range(num_trials)]
switch_raw = [run_once(switch=True) for _ in range(num_trials)]

no_switch_wins = sum(1 for r in no_switch_raw if r)
switch_wins = sum(1 for r in switch_raw if r)

no_switch_prob = no_switch_wins / num_trials * 100.0
switch_prob = switch_wins / num_trials * 100.0

print( "         WINS    LOSSES   %\n"
      f"SWITCH:  {switch_wins:>4}    {num_trials-switch_wins:>6}  {switch_prob:.02f}\n"
      f"NOSWITCH:{no_switch_wins:>4}    {num_trials-no_switch_wins:>6}  {no_switch_prob:.02f}")

Upvotes: 1

Shawn Mehan
Shawn Mehan

Reputation: 4568

You have gotten the mechanics of the problem wrong so you are getting the wrong result. I have rewritten the choice mechanics, but I am leaving the user input stuff to you so that you can continue to learn python. This is one of many ways to solve the problem, but hopefully it demonstrates some things to you.

def get_choices():
    valid_choices = [0, 1, 2] # these are the values for a valid sample
    shuffle(valid_choices)    # now randomly shuffle that list
    return valid_choices      # return the shuffled list

def get_door(user_choice):
    return user_choice.index(0)


def monty_sim(n, kind):
    """

    :param n: number of runs in this simulation
    :param kind: whether to change the door or not, 0 - don't change, 1 = change door
    :return: (win_rate, 1 - win_rate)
    """
    wins = 0
    for i in range(0, n):
        game_doors = get_choices()
        user_choice = get_door(get_choices()) # use the same method and find user door choice
        # so there are two branches.
        # In both, a door with a goat (game_door = 1) is chosen, which reduce the result to
        # a choice between two doors, rather than 3.
        if kind == 0:
            if user_choice == game_doors.index(0):
                wins += 1
        elif kind == 1:
            # so now, the user chooses to change the door
            if user_choice != game_doors.index(0):
                wins += 1
            # Because the original choice wasn't the right one, then the new
            # must be correct because the host already chose the other wrong one.

    win_rate = (wins / n) * 100
    return win_rate, 100 - win_rate


if __name__ == '__main__':
    n = 1000
    kind = 1
    wins, loses = monty_sim(n, kind)
    print(f'In a simulation of {n} experiments, of type {kind} user won {wins:02f} of the time, lost {loses:02f} of the time')

Upvotes: 0

Related Questions