Monty Hall problem
What is the Monty Hall problem?
Suppose you’re on a game show, and you’re given the choice of three doors:
- Behind one door is a car;
- behind the others, goats.
You pick a door, say No. 1, and the host, who knows what’s behind the doors, opens another door, say No. 3, which has a goat. He then says to you, “Do you want to pick door No. 2?” Is it to your advantage to switch your choice?
Lets run a few rounds and see?
The plan is to simulate the game and test the results. We need to model the game, the host knowing the door and the contestant picking a choice. Then just calculate the number of times the contestant wins.
defmodule MontyHall do
defstruct door_1: nil, door_2: nil, door_3: nil, initial_guess: nil
end
# A implement an inspect method so we can print the game in a pretty way
defimpl Inspect, for: MontyHall do
def inspect(%MontyHall{door_1: d1, door_2: d2, door_3: d3}, _) do
"""
\
"""
end
defp draw_symbol(d) when d == :car do
"🏎"
end
defp draw_symbol(d) when d == :open do
"🪤"
end
defp draw_symbol(_d) do
"🐐"
end
end
%MontyHall{door_1: :goat, door_2: :car, door_3: :goat}
We have a way of drawing the game, so lets look at generating a random game board.
defmodule GenerateMH do
def gen_random do
case Enum.random([1, 2, 3]) do
1 -> %MontyHall{door_1: :car, door_2: :goat, door_3: :goat}
2 -> %MontyHall{door_1: :goat, door_2: :car, door_3: :goat}
3 -> %MontyHall{door_1: :goat, door_2: :goat, door_3: :car}
end
end
end
GenerateMH.gen_random()
We now have a way of generating lots of games.
We now need a function that can run alot of games, take a contestants guess and return the new game when the host has opened the door.
defmodule PlayMH do
def guess(game) do
guess(game, Enum.random([1, 2, 3]))
end
def guess(game, guess) when guess == 1 do
{da, db} = open_door(game.door_2, game.door_3)
%MontyHall{door_1: game.door_1, door_2: da, door_3: db, initial_guess: 1}
end
def guess(game, guess) when guess == 2 do
{da, db} = open_door(game.door_1, game.door_3)
%MontyHall{door_1: da, door_2: game.door_2, door_3: db, initial_guess: 2}
end
def guess(game, guess) when guess == 3 do
{da, db} = open_door(game.door_1, game.door_2)
%MontyHall{door_1: da, door_2: db, door_3: game.door_3, initial_guess: 3}
end
# So there are 3 possibilites
# A - Goat, B - Car
# A - Car, B - Goat
# A - Goat, B - Goat
defp open_door(da, db) when da == :goat and db == :car do
{:open, db}
end
defp open_door(da, db) when da == :car and db == :goat do
{da, :open}
end
defp open_door(_da, db) do
{:open, db}
end
end
# Lets give it a shot, we should expect that Door 1 is never opened, but either 2 or 3 are.
GenerateMH.gen_random() |> PlayMH.guess(1)
We have the basis for a test - we can now look at how many times the contestant would win if they stick with their choice and how often they win when they change.
defmodule EvaluateGame do
def calc_percent(games) do
won = games |> Enum.count(fn game -> eval(game) == true end)
won / length(games)
end
def eval(game) do
case game.initial_guess do
1 -> would_win(game.door_1)
2 -> would_win(game.door_2)
3 -> would_win(game.door_3)
end
end
def would_win(door) when door == :car do
true
end
def would_win(_door) do
false
end
end
1..1000
|> Enum.map(fn _x -> GenerateMH.gen_random() |> PlayMH.guess() end)
|> EvaluateGame.calc_percent()
WTF??
So we seem to be converging on a probability of 1/3 (the more games you run, the close it will get to a third)
Does this make intuitive sense? What is going on?
A good description can be found here
> You pick a door with a goat originally and you switch. What do you have? A car. Suppose you don’t switch. What do you have? A goat. > So if you pick a goat originally, then switching gets a car and not switching gets a goat. > The only relevant question then, is what is the probability that you picked a goat originally? (2/3rds)