The Monty Hall problem in Ruby
4 min read
The Monty Hall Problem is a probability brain teaser based on the game show "Let's Make a Deal", where Monty Hall is the host.
The basic premise is the host, Monty Hall, presents 3 closed doors to a contestant, one of which hides a new car, and the other 2 doors contain goats. The goal for the contestant is to choose the door hiding the new car.
To start the game, the contestant chooses 1 of the 3 doors. But before they can see what's inside, the host opens one of the remaining doors where this is a goat. The host then offers the contenstant the option to switch their door selection to the other remaining door or to stick with their first choice.
The brain teaser: Is it better for the contestant to stay, switch, or does it not matter?
To find out, I wrote a Ruby script, and added in a bit of Lord of the Rings by changing goats to orcs and setting the prize as farthings.
Ruby script with Lord of the Rings theme
def green_dragon
stay_wins = 0
switch_wins = 0
show_door = nil
switch_door = nil
tries = 10000
tries.times do
initial_guess = rand(3)
door_items = ["500 farthings", "1 orc", "1 orc"].shuffle!
door_items.each_with_index do |item, door_index|
if door_index != initial_guess and item != "500 farthings"
show_door = door_index
end
end
switch_door = ((0..2).to_a - [initial_guess, show_door])[0]
if door_items[initial_guess] == "500 farthings"
stay_wins += 1
end
if door_items[switch_door] == "500 farthings"
switch_wins += 1
end
end
stay_wins = (stay_wins.to_f / tries.to_f) * 100
switch_wins = (switch_wins.to_f / tries.to_f) * 100
results = {"Stay Wins" => stay_wins, "Switch Wins" => switch_wins}
end
Code breakdown
def green_dragon
I define a method and give it a name - in this case green_dragon
stay_wins = 0 and switch_wins = 0
To get a count of wins won by staying and a count of wins won by switching, I add the two variables stay_wins and switch_wins, and set their starting count to 0.
show_door = nil
switch_door = nil
show_door
Represents one of the doors containing an orc that the hosts shows the contestant after their initial selection.
switch_door
Represents the door the hosts offers the contestant to switch to instead of staying with their initial door selection.
= nil
While each door will hold a single index value (0,1,2) during each iteration, they have no value at the start, and begin with nil as their initial value.
tries = 10000
This variable represents the number of times the do / end statement will run. 10000 will give us a good enough sample to draw a conclusion, but can be any integer (50000, 1230000).
tries.times
times is a public ruby method for iterating over a block a certain number of times - in this case, 1000 times as defined by the tries variable.
initial_guess = rand(3)
initial_guess is the guest's first door selection. rand sets the index value within initial_guess to random through each iteration of the loop. (3) gives us three index values for the three doors (0, 1, 2)
door_items = ["500 farthings, "1 orc", "1 orc"].shuffle!
The door_items array ["500 farthings", "1 orc", "1 orc"] gives us the items behind the doors.
shuffle is a method that shuffles items in an array through each iteration.
! (often referred to as bang) modifies the object it's called on. It is ruby convention to use ! at the end of a method when something will be modified in your code.
door_items.each_with_index do |item, door_index|
This will iterate through each index of the door items.
The variables in the pipe |item, door_index| are placeholders. item represents the item string (such as "1 orc"), and door_index represents the index number of the item (0, 1, or 2). Naming it door_index makes it clear the value corresponds to a door position.
if door_index != initial_guess and item != "500 farthings"
show_door = door_index
end
This is an if conditional statement. It states if the index does not equal the initial_guess and if the item does not equal the desired prize ("farthings"), then that index belongs to the show_door. The 1st end ends the conditional statement, and the second end ends the each do loop.
switch_door = ((0..2).to_a - [initial_guess, show_door])[0]
This statement finds the switch_door. (0..2).to_a takes the potential index value of the three doors (0,1,2) and puts them in an array [0,1,2]. - [intial_guess, show_door] subtracts the indices belonging to initial_guess and show_door, leaving you with the index value of the switch_door in an array. Now we need the index value out of array form, and that's where [0] comes in. This grabs the value of the 0 index position of an array.
if door_items[initial_guess] == "500 farthings"
stay_wins += 1
end
if door_items[switch_door] == "500 farthings"
switch_wins += 1
end
The first conditional statement finds out if the initial_guess item is farthings, and if so add 1 win to stay_wins. The second conditional find out if the switch_door contains the farthings and if so add 1 win to switch_wins. The 1st two ends conclude the conditional statements, and the last end take us out of the loop.
stay_wins = (stay_wins.to_f / tries.to_f) * 100
switch_wins = (switch_wins.to_f / tries.to_f) * 100
results = {"Stay Wins" => stay_wins, "Switch Wins" => switch_wins}
stay_wins and switch_wins now contain how many times a contestant sticking with their initial selection won over how many times they played, then converts the answer to a percentage.
To calculate the variables, they need to be in float form .to_f
results = {"Stay Wins" => stay_wins, "Switch Wins" => switch_wins}
We need to put stay_wins and switch_wins into a hash, otherwise the value of the variables will come up as nil. A hash is an associative array of key, value pairs. While an array has default index integer values (0,1,2,3...) to reference an item within the array, hashes allow you to use any object type.
For example if I wrote this hash: animals = {"monkeys" => "primates", "sharks" => "fish"}. I could type: puts animals["monkeys"] and I would get the string "primates" as the result.
The conclusion
It is best for the contestant to choose switch over stay. Consistently the results showed choosing to stay meant 1/3 odds you will win, while switching gives you 2/3 odds.