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.