The Monty Hall problem is a problem of probability as follows.
There are three closed doors.
Beyond one of the doors is a new prize car, and beyond the other two doors is a goat, which means lost. Players can get a new car by opening the door with the new car.
When the player chooses one door, the moderator Monty Hall opens one of the remaining two doors with the goat to show the goat.
Here the player can change from the first door he chooses to another door that hasn't been opened yet.
Now, should the player change the door? (Which one is more likely to hit a new car?)
Those who thought, "Since the moderator opened the door of the lost goat, there are only two doors left, and the probability is 1/2 with or without changing the door?" Actually, this is an incorrect answer.
The correct answer is, "The more you change the door, the more likely you are to win." If you do not change the door, you have a 1/3 chance of hitting, and if you change the door, you have a 2/3 chance of hitting.
The following video explains why this is the result.
Twitter survey has surprising results! "Monty Hall Problem Modified Version"
Now, even if you know the answer, some may still not be convinced that it really is.
So, I tried 1 million times with and without changing the door in the middle, and wrote a program to verify whether the result really meets that probability.
If you want to know only the result, please jump to "Verification result".
The program I actually wrote is as follows. Implemented in Ruby.
monty_hall_problem.rb
# frozen_string_literal: true
require 'optparse'
require 'securerandom'
options = {}
OptionParser.new do |opt|
opt.on('-d NUMBER', '--doors=NUMBER') { |v| options[:doors_count] = v }
opt.on('-t NUMBER', '--trials=NUMBER') { |v| options[:trials] = v }
opt.parse!(ARGV)
end
DOORS_COUNT = options[:doors_count].to_i >= 3 ? options[:doors_count].to_i : 3
TRIALS = options[:trials].to_i >= 1 ? options[:trials].to_i : 100
def main
correct_count = { changed: 0, never: 0 }
TRIALS.times do
correct_count[:changed] += 1 if try(changed: true)
end
TRIALS.times do
correct_count[:never] += 1 if try(changed: false)
end
puts "Number of doors: #{DOORS_COUNT} doors"
puts "Number of trials: #{TRIALS} times each"
puts
puts "Changed: #{correct_count[:changed]} times (Actual: #{((correct_count[:changed].to_f / TRIALS.to_f) * 100).round(2)}%, Expected: #{(((DOORS_COUNT - 1).to_f / DOORS_COUNT.to_f) * 100).round(2)}%)"
puts "Never: #{correct_count[:never]} times (Actual: #{((correct_count[:never].to_f / TRIALS.to_f) * 100).round(2)}%, Expected: #{((1.0 / DOORS_COUNT.to_f) * 100).round(2)}%)"
end
def try(changed:)
doors = Array.new(DOORS_COUNT, false)
doors[SecureRandom.random_number(DOORS_COUNT)] = true
first_choice = SecureRandom.random_number(DOORS_COUNT)
doors = reveal(doors, first_choice)
doors[second_choice(doors, first_choice, changed: changed)]
end
def reveal(doors, first_choice)
if doors[first_choice] # the first choice is correct
leftover = nil
loop do
leftover = SecureRandom.random_number(DOORS_COUNT)
break if leftover != first_choice
end
doors.map.with_index { |door, index| door if door || index == leftover }
else # the first choice is incorrect
doors.map.with_index { |door, index| door if door || index == first_choice }
end
end
def second_choice(doors, first_choice, changed:)
return first_choice unless changed
second_choice = nil
doors.each_with_index do |door, index|
unless door.nil? || index == first_choice
second_choice = index
break
end
end
second_choice
end
main
The original Monty Hall problem is 3 doors, but it is a program like [^ 1] that can be verified even if it is 10 doors or 100 doors. DOORS_COUNT
represents the number of doors. The default is 3.
[^ 1]: If there are 100 doors, the moderator will open the door with the remaining 98 goats after the player chooses one.
You can also change the number of trials. TRIALS
represents the number of trials. The default is 100.
You can change the number of doors by putting -d NUMBER
in the argument. NUMBER
is a number. You can also change the number of trials by entering -t NUMBER
. For example, -t 100000
will try 100,000 times.
main It is a method that outputs the result of trying several times separately when the door is changed and when it is not changed in the middle.
Put the result (the number of hits subtracted) in correct_count
. correct_count [: changed]
is the number of hits when the door is changed in the middle, and correct_count [: never]
is the number of hits when the door is not changed in the middle.
try
The method to try. If you pull the winning door, it will return true
, and if you pull the lost door, it will return false
.
Prepare an array doors
with all elements false
, and randomly put true
somewhere in it. true
is the door with the new car and false
is the door with the goat.
Randomly put the possible subscript values of the array doors
into first_choice
. This is the state in which the player has selected one door. Of course, the player doesn't know the answer, so he gets the value randomly.
reveal A method in which the moderator opens the remaining door with the goat.
Returns an array with all but the door chosen by the player (the element specified by first_choice
) and the door with the new car (the element of true
) as nil
.
However, if the player has selected the door with the new car (the element of true
) from the beginning, it does not matter if all the doors are opened, so in that case the unselected doors (all) Randomly choose one of the (goat doors) doors, leave that element alone, and leave all other elements (except, of course, the player's chosen door) as nil
.
The element containing nil
means the [^ 2] door where the moderator has revealed that there is a goat (missing).
[^ 2]: "reveal" means "reveal".
second_choice
A method that returns the door the player chooses for the second time. Returns the possible subscript values of the array doors
.
The changed
received as an argument can be true
or false
. true
means that you changed the door in the middle, and false
means that you did not change the door.
If you do not change the door, it is the same as first_choice
, so the value of first_choice
is returned as it is.
If you change the door, it is an element that is not nil
and returns a different value than first_choice
. After the moderator opens the door with the goat, there are only two doors, one that he chose first and one that hasn't been opened yet, so it's unique to change.
Let's actually verify it. I will try it a million times.
$ ruby monty_hall_problem.rb -t 1000000
Number of doors: 3 doors
Number of trials: 1000000 times each
Changed: 666764 times (Actual: 66.68%, Expected: 66.67%)
Never: 333181 times (Actual: 33.32%, Expected: 33.33%)
The table below is easy to understand.
If you change the door on the way | If you do not change the door on the way | |
---|---|---|
Number of hits(1 million times) | 666,764 times | 333,181 times |
Correct answer rate(Round to the third decimal place) | 66.68% | 33.32% |
Real probability(Round to the third decimal place) | 66.67% | 33.33% |
If you changed the door, there was almost a 2/3 chance that you would get it right, and if you didn't change the door, you had a good chance of getting it right about 1/3.
So, it turns out that the probability of 50% with or without changing the door is wrong, and that changing it does increase the accuracy rate.
There are two types of most languages: (just) pseudo-random numbers and cryptographically secure pseudo-random numbers.
Ruby, of course, has both.
Pseudo-random number
irb(main):001:0> rand
=> 0.3204722194100945
Cryptographically secure pseudo-random numbers
irb(main):001:0> require 'securerandom'
=> true
irb(main):002:0> SecureRandom.rand
=> 0.6596803273103891
In this program, we used cryptographically secure pseudo-random numbers (SecureRandom.random_number
) instead of just pseudo-random numbers. The reason is ** [^ 3] because there is a possibility of bias if it is just a pseudo-random number.
That doesn't mean that cryptographically secure pseudo-random numbers are truly unbiased random numbers, but because they are used in cryptography, they are unpredictably unbiased random numbers. That said.
This time, it was a verification to find the mathematical probability, so I decided that it would be better to use random numbers that are not biased as much as possible, so I used cryptographic pseudo-random numbers.
Reference: How to make really safe random numbers in each language
[^ 3]: Pseudo-random numbers-Wikipedia
Recommended Posts