patternpythonMinor
Monty Hall simulation with any number of doors
Viewed 0 times
numbersimulationwithanydoorsmontyhall
Problem
After answering Monty hall python simulation I wondered what the outcome were to be if there were more than three doors, say four or seven. And so I decided to modify the problem slightly to adjust for this.
There are \$x\$ amount of doors. Behind one door is a car, the others are goats. You pick a door, and the host reveals a goat in a different door. You're then given the choice to change from the selected door to any door except the open door, or the already selected door.
And so I decided to find out how many times a person is likely to win if they always pick a random door when asked if they want to switch door. And so programmed the following:
I then decided to optimize the above code, and came up with the following ways to do this:
There are \$x\$ amount of doors. Behind one door is a car, the others are goats. You pick a door, and the host reveals a goat in a different door. You're then given the choice to change from the selected door to any door except the open door, or the already selected door.
And so I decided to find out how many times a person is likely to win if they always pick a random door when asked if they want to switch door. And so programmed the following:
import random
def monty_hall(amount, doors=3):
if doors < 3:
raise ValueError(f'doors must be greater than three, not {doors}')
wins = 0
for _ in range(amount):
player_choice = random.randrange(doors)
car_placement = random.randrange(doors)
other_doors = set(range(doors)) - {player_choice, car_placement}
shown_door = random.choice(list(other_doors))
swap_doors = set(range(doors)) - {player_choice, shown_door}
final_choice = random.choice(list(swap_doors))
wins += final_choice == car_placement
return wins
print(monty_hall(1000000, 3))
print(monty_hall(1000000, 4))
print(monty_hall(1000000, 5))I then decided to optimize the above code, and came up with the following ways to do this:
- Change
player_choicetodoors - 1. The first player choice doesn't have to be random.
- Change
other_doorsandswap_doorsto account for (1). Rather than removing an item that will always be in the set, remove it from the range and the second set. And soother_doorsbecomes:set(range(doors - 1)) - {car_placement}.
- Remove
player_choiceas it's no longer used.
- Change
shown_doorto always be the first door. However if the car is the first door, show the second.shown_door = car_placement == 0.
- Remove
other_doorsas it
Solution
I would have used an helper function in the first version so that your
This also applies to the second version but removes some of the speed optimizations you made there; so not sure if it’s worth keeping.
However, in the first version, I would use list-comprehensions to reduce a bit the memory needed. It would also exhibit the actions more like a real player would do. Since creating the
For the second version, if you were to keep two functions that could lead to more explanatory stuff like:
I’m using
monty_hall function is:def monty_hall(amount, doors=3):
if doors < 3:
raise ValueError(f'doors must be greater than three, not {doors}')
return sum(player_wins_a_car(doors) for _ in range(amount))This also applies to the second version but removes some of the speed optimizations you made there; so not sure if it’s worth keeping.
However, in the first version, I would use list-comprehensions to reduce a bit the memory needed. It would also exhibit the actions more like a real player would do. Since creating the
set from range will already iterate over each elements, using the list-comp won't be much more costy:import random
def player_wins_a_car(doors):
player_choice = random.randrange(doors)
car_placement = random.randrange(doors)
shown_door = random.choice([door for door in range(doors)
if door not in (player_choice, car_placement)])
final_choice = random.choice([door for door in range(doors)
if door not in (player_choice, shown_door)])
return final_choice == car_placementFor the second version, if you were to keep two functions that could lead to more explanatory stuff like:
def player_wins_a_car(doors):
# Choose a door that hide a car. Car is never behind door 0,
# if it were behind door 0, consider door 0 and 1 to be
# swapped so that host always open door 0.
car_placement = random.randrange(doors) or 1
# Consider an initial choice of door `doors - 1` and host
# showing door 0 unconditionaly. Always choosing a random
# door out of these two.
final_choice = random.randrange(1, doors-1)
return car_placement == final_choiceI’m using
or here as the only value triggering max would be 0, not sure which one performs better.Code Snippets
def monty_hall(amount, doors=3):
if doors < 3:
raise ValueError(f'doors must be greater than three, not {doors}')
return sum(player_wins_a_car(doors) for _ in range(amount))import random
def player_wins_a_car(doors):
player_choice = random.randrange(doors)
car_placement = random.randrange(doors)
shown_door = random.choice([door for door in range(doors)
if door not in (player_choice, car_placement)])
final_choice = random.choice([door for door in range(doors)
if door not in (player_choice, shown_door)])
return final_choice == car_placementdef player_wins_a_car(doors):
# Choose a door that hide a car. Car is never behind door 0,
# if it were behind door 0, consider door 0 and 1 to be
# swapped so that host always open door 0.
car_placement = random.randrange(doors) or 1
# Consider an initial choice of door `doors - 1` and host
# showing door 0 unconditionaly. Always choosing a random
# door out of these two.
final_choice = random.randrange(1, doors-1)
return car_placement == final_choiceContext
StackExchange Code Review Q#161088, answer score: 5
Revisions (0)
No revisions yet.