Monty Hall Simulation

last modified: February 28, 2005

A little program in RubyLanguage to simulate the MontyHallProblem. We were going to do it in PythonLanguage, but then we'd have to call it MontyPython.

def randomDoor
        return rand(3)
end

def otherDoorThan(*forbiddenDoors)
        allowedDoors = [0,1,2] - forbiddenDoors
        return allowedDoors[rand(allowedDoors.size)]
end

stayingWasRight  = 0
changingWasRight = 0

1_000_000.times do 
        prizeDoor = randomDoor()
        firstChosenDoor = randomDoor()
        montysDoor = otherDoorThan(prizeDoor, firstChosenDoor)
        secondChosenDoor = otherDoorThan(montysDoor, firstChosenDoor)
        case prizeDoor
        when firstChosenDoor  then  stayingWasRight  += 1
        when secondChosenDoor then  changingWasRight += 1
                                else  puts "Monty cheated!"
        end
end

totalRuns = stayingWasRight + changingWasRight

puts "Absolute Numbers: 
        Staying was right in  #{stayingWasRight}, cases
        Changing was right in #{changingWasRight}, cases
        Total number of runs: #{totalRuns},
Relative Numbers:  
        Staying was right in  #{100.0 * stayingWasRight  / totalRuns},% cases
        Changing was right in #{100.0 * changingWasRight / totalRuns},% cases"

A run produced the following output:

Absolute Numbers: 
        Staying was right in  333810 cases
        Changing was right in 666190 cases

        Total number of runs: 1000000

Relative Numbers:  
        Staying was right in  33.381% cases
        Changing was right in 66.619% cases

So this supports the notion, that changing is right in 2/3 of the cases. Or can you spot any problems in this program, similar to the problems some of the tables on MontyHallProblem have?


Writing my own MontyHallSimulation made the solution clearer to me. I was able to simplify it to something like this:

prizeDoor = randomDoor();
firstChosenDoor = randomDoor();

if prizeDoor == firstChosenDoor then
        stayingWasRight += 1
else
        changingWasRight += 1
end

And clearly, prizeDoor == firstChosenDoor only 1/3 of the time.

This is correct, but doesn't really show why the problem is different from NotTheMontyHallProblem. For that, see NotMontyHallSimulation -- AndrewMcGuinness

The only reason you could reduce the problem to above, is because you had done the analysis. So it isn't really a simulation, per se.

Here is a simulation done in lisp. It is somewhat redundant, as no analysis of the choices is done:

(defun monty-hall (N)
  "simulate the 'Monty Hall' problem for N iterations"
  (defun pick-one (list)
    (nth (random (length list)) list))  
  (let ((stay-success 0)
        (switch-success 0))
    (dotimes (i N t)
      (let* ((doors (list 0 1 2))
             (prize-door (pick-one doors))
             (your-door  (pick-one doors))
             (monty-door  (pick-one (remove prize-door (remove your-door doors))))
             (switch-door (pick-one (remove monty-door (remove your-door doors)))))
        (cond ((equal your-door prize-door) (incf stay-success))
              ((equal switch-door prize-door) (incf switch-success)))))
    (list stay-success switch-success)))

and output for a run:

* (monty-hall 100000000)
(33337404 66662596)

A Java applet (with source code) is available here: http://www.rdrop.com/~half/Creations/Puzzles/LetsMakeADeal/monty.hall.applet.html

This applet lets you play the game manually, or the applet can play itself. There are three-door and six-door versions, and running win/lose statistics are displayed as you play.


Here's a version in Perl, loosely based on the Ruby version above, but altered slightly to allow for more than three doors (if desired).

use strict;
use warnings;

my $number_of_doors = 3;
my $total_trials = 1_000_000;

sub random_door {
    return int rand $number_of_doors;
},

sub door_other_than {
    my @forbidden = @_;
    my %doors;
    @doors{ 0 .. $number_of_doors - 1 }, = ();
    for my $door (@forbidden) {
        delete $doors{$door},;
    },        
    my @choose_from = keys %doors;
    return $choose_from[ int rand @choose_from ];
},

my $staying_was_right  = 0;
my $changing_was_right = 0;

for (1 .. $total_trials) {
    my $prize_door         = random_door();
    my $first_chosen_door  = random_door();
    my $montys_door        = door_other_than($prize_door,  $first_chosen_door);
    my $second_chosen_door = door_other_than($montys_door, $first_chosen_door);
    if ($prize_door == $first_chosen_door) {
        $staying_was_right++
    },
    elsif ($prize_door == $second_chosen_door) {
        $changing_was_right++;
    },
},

my $stay_percent   = 100 * $staying_was_right  / $total_trials;
my $change_percent = 100 * $changing_was_right / $total_trials; 

print <<END_TEXT;
Total trials: $total_trials

Absolute numbers:
    Staying  was right in $staying_was_right cases.
    Changing was right in $changing_was_right cases.
Relative numbers:
    Staying was right in  $stay_percent% cases.
    Changing was right in $change_percent% cases.
END_TEXT

Loading...