September 15, 2023

# Simulate the Monty Hall problem in Python πππ

There's a classic probability puzzle based on the TV game show "Let's Make a Deal" and named after its host, Monty Hall. Here's the puzzle:

You are a contestant on a game show. In front of you are three closed doors. Behind one of the doors is a car, and behind the other two doors are goats. Your goal is to pick the door with the car.

The host asks you to choose a door. You tell the host your choice. Instead of telling you whether your choice was correct, the host (who knows which door contains the car) opens one of the two doors you didn't choose and reveals a goat.

You now have the opportunity to keep your original choice or switch your choice to the door that is still closed. Which should you choose?

For example, let's pretend that you started by choosing door #1. The host opens door #3 to reveal that it contains a goat. Should you keep your original choice of door #1 or switch your choice to door #2?

## Use simulations for problem-solving

One of the "superpowers" of being able to write code is that you can use simulations in order to solve problems like these. Before reading my solution below, I'm challenging you to write Python code to simulate this problem!

Specifically, I want you to simulate that you are a contestant on this show 1000 times. Each time, you pick a random door as your first choice, let the host open a door that reveals a goat, and then switch your choice to the door that the host didn't open. With that strategy (known as the "always switch" strategy), how often do you win the car?

Here are a few details that I want to be clear about:

• Before starting each game, the host randomly selects which door contains the car.
• After you make your initial choice, the host always opens one door that contains a goat, and it will always be a door that you did not initially choose. (If the host has two options for which door to open to reveal a goat, he will randomly select which of those two doors to open.)
• After the host opens a door that contains a goat, you will always be given the option to switch your choice to the door that is still closed, and you will always accept that option.

The ideal simulation code is concise, easy-to-read, and represents the data in an elegant way. Feel free to share your code with me in the comments section at the bottom! π

Below you'll find my solution, as well as some excellent solutions submitted by readers of my newsletter!

## My solution code

I'll explain this code piece-by-piece:

Since we need to simulate random selections, the usual choice is Python's `random` module.

I set the number of games to simulate, a counter to track the number of wins, and a list to represent the three doors.

This loop simulates 1000 independent games.

During each game, the host randomly selects one of the three doors for the car, and the player randomly selects one of the three doors as their first pick for where the car might be. To do this, the `random.choice()` function selects one of the three elements from the doors list.

The host must open one of the closed doors, and I need to make sure that they're not opening the door with the car or the door that the player already chose.

To do this, I convert the doors list to a set, convert the car's location and the player's first pick to another set, and then take the difference. Thus, `host_can_open` is a set of all of the doors that don't contain the car and weren't picked by the player.

`host_can_open` contains either one or two doors, and I use `random.choice()` to select which door the host actually opens and store that in `host_opens`. (Note: `random.choice()` doesn't work with sets, which is why I converted it to a list.)

Next, we need the player to switch their pick to the one door that is still closed. Again, we use a set difference operation. But why is the `min()` function there?

Well, the set difference operation returns a set that contains a single integer, such as `{3}`. In order to access just the integer, the most elegant solution is to take the min (or max) of that set!

Finally, I check whether the player's second pick matches the location of the car. If so, we increment the number of wins.

This is just a bit of debugging code. It runs during the first five games, and shows me the value of four of the objects. This allows me to double-check that the values being selected obey the rules of the game.

These are called "self-documenting expressions", which were added in Python 3.8. Because of the equals sign, the f-string will print `first_pick=2` (for example). Without the equals sign, the f-string would just print `2`, and I'd have to remember what each number represents.

Finally, we print the win percentage, which is calculated by dividing the number of wins by the number of games.

I start the f-string with a `\n` in order to add a line break, and I add the `:.1%` at the end to format the result as a percentage with 1 digit after the decimal.

## How often does the player win the car?

Here's an example of the output of my code:

To be clear, the first five lines represent the first five games (out of 1000). In those five games, the player won the car three times.

The win percentage is how often the player won the car by using the "always switch" strategy across 1000 games. In this case it was 68.8%, but you'll find that this percentage hovers around 66.6%. If you want to confirm this for yourself, you can run my code online.

This result is counter-intuitive to many people (including mathematicians and Nobel Prize winners!), but the "always switch" strategy is twice as likely to win as the "never switch" strategy! (To be clear, this result only holds if we follow all of the rules I specified at the beginning of this post.)

One of my favorite solutions was from FΓ‘bio C., who received help from Bing AI when writing this code:

Here's what I like about FΓ‘bio's code:

• To select which door the host opens, he uses a while loop that generates a random integer until it doesn't match the location of the car or the player's first pick. Brilliant!
• To select which door the player changes to, he subtracts the player's first pick and the door the host opens from 6. Why? Because those three values (1, 2, and 3) must add up to 6!

Another solution I really enjoyed was from Aaron S.:

Here's what I like about Aaron's code:

• He uses a list of emoji (which are valid characters) to represent the prizes, and then uses `random.shuffle()` to shuffle their locations.
• To select which door the host opens, he uses a clever list comprehension that zips together the doors and prizes and checks for two conditions.
• To count the number of wins, he runs the function 1000 times using a generator expression and then sums the results. (This works because his function returns `False` for a loss and `True` for a win, and these get converted to 0's and 1's by the `sum()` function.)

JosΓ© P. performed an increasing number of simulations to demonstrate that as the number of games increases, the result converges towards a winning percentage of 66.6%:

Finally, Conor G. sent me a link to his collection of Monty Hall simulations written in Lua, Perl, C, Fortran, COBOL, Pascal, PHP, and (of course) Python!

Thanks so much to everyone who submitted a solution! Subscribe to my weekly newsletter to participate in future code challenges like this! π