Monty Hall 問題とは,アメリカの Let's make a deal というテレビ番組に由来する確率の問題である.詳しくは WikiPedia
の記事に詳しいのでそちらを参照するのが良いだろう.ここでは実はこのシミュレータを作ったというのが要点なので,問題に関しては簡潔に述べよう.
---
1. 3つのドア (A, B, C) があり,(車,ヤギ,ヤギ)がランダムに入っている.
2. プレイヤーはドアを1つ選ぶ.
3. プレイヤーがどのドアを選んだかにかかわらず、ホストは残りのドアのうち1つを必ず開ける。
4. ホストは景品のあるドアを知っていて、必ずヤギの入っているドアを開ける。
もし,両方ともヤギだった場合はプレイヤーの見えないところでコインを投げて決める.
(Wikipedia の上記の記事より引用)
---
この際,プレイヤーは最初の選択を変更した方が良いかどうかというのが問題である.
回答を言ってしまえば,最初の選択ではなく,常に switch した方が景品をもらえる可能性が二倍ある.
この blog の最後に上記の問題の定義に従った Monty Hall Problem Simulator を掲載する.ruby 言語で書かれているが,簡単なので他の言語で御自分で試さるのも良いだろう.(ところで,2 に関しての実装であるが,プレイヤーは equally distributed にランダムにドアを選ぶとした.またここでの乱数の実装は rubyの組み込みのものである.)
10000回試行した結果は,
Result: switch win ratio = 0.6689, switch lose ratio = 0.3311
であり,スイッチした方がほぼ 2/3 の可能性で勝っている.(ちなみに実装では乱数の系列を常に同じに初期化しているので,このプログラムを走らせれば読者も同じ結果が得られるはずである.ruby 1.8.7)
この問題がある雑誌で解説された際には沢山のそれは間違いであるという手紙が舞い込んだという話である.そのような手紙は大学の数学教授からも届いたという話である.多分,
「スイッチした方が景品をもらえる可能性が高い」
というのが直感的にどうも変な気がする.ということ
「問題の定義に仕方によってはスイッチしてもしなくても可能性は同じ」
というふうに問題を定義できること.そして,そちらの方が直感に合っている人が問題をそのように定義したこと.
などの条件によってそのようなことになったのであろう.雑誌の記事では語られていない(常識的ではあるが)仮定があったということである.
私は独立な事象というものに関してどうも直感が働かない.たとえば,正しいサイコロの目が 5 回続けて 1 だった場合,次に 1 に賭ける気が直感的にしないのである.独立な事象であれば,どれに賭けても同じである.サイコロが前回に何が出たかを覚えているわけがないのである.
また,今回のように何か情報が与えられた場合,条件付の確率が変化することに関しても私自身の直感は怪しい.今回の場合,極端な話,もし,外れの場所ではなく,当たりがどこかという情報を教えてもらえれば,どう確率論をこねまわしたところで,可能性どうこうという問題ではなくなる.つまり情報によって勝つ可能性は変化するのは明かであり,今回の問題ではその情報がある意味与えられている.
この確率に対する自身の直感への不信のために,私はこのような小さなシミュレータをいくつも書くことになる.シミュレータを書くことで条件に何が必要なのか,ここでは rand statement を書く時に,どんな条件が必要だったのかがわかるからだ.特にこのプログラムでの 82 行目の rand と,この付近のロジックが実際のものに近いのかがどうかによってこのシミュレーションの正当性は変化する.そのあたりの面白い話が Wikipedia の記事にはあるので,御覧頂くと良いだろう.
#! /usr/bin/ruby
#
# $Id$
# Copyright (C) 2010 Hitoshi
#
# Monty Hall Problem simulator 2010-1-22(Fri)
#
# The following is the problem rule definition. It seems that Monty
# didn't clearly define the problem, so, we employ some
# assumptions. If this rule is changed, the probability will change.
#
# 1. There are three doors (A, B, C) that has (car, donkey, donkey) and
# the assign is chosen equally distributed randomness.
#
# 2. Player choose one of the door
#
# 3. Host open one of the door that is not chosen by the player
# regardless which door is chosen by the player.
#
# 4. Host knows which door has a car (Host know the information). Host
# only open the door which has a donkey. If the both doors that is not
# chosen by the Player have donkey, Host choose one of them according
# to equally distributed random number.
#
# Actually, the rule 3. and 4. are not clear (Monty didn't mention
# this definition.) The rule 1 seems satisfied, but, could be
# violated. For example, the host can move the car, or they can put
# the car after the Player choose the door. If you change these rules,
# the probability will be changed. This makes interesting
# confusion. See WikiPedia, Monty Hall Problem.
#
# http://en.wikipedia.org/wiki/Monty_Hall_problem
# http://www.youtube.com/watch?gl=US&v=mhlc7peGlGg
#
#
require "getoptlong.rb"
MONTYHALLSIM_VERSION = "0.1.0"
#------------------------------------------------------------
# help
#------------------------------------------------------------
def print_help()
$stderr.print <<"HELP"
usage: montyhallsim.rb [-h|--help] [-v|--version] number_of_trials
Monty Hall Problem simulation.
-h, --help output this help.
-V, --version show version.
number_of_trials simulation number
By Hitoshi.
HELP
end
#------------------------------------------------------------
# verbose output
#------------------------------------------------------------
def verboseprint(mes)
if $OPT_VERBOSE then
$stderr.print mes
end
end
#------------------------------------------------------------
# class montyhallsim
#------------------------------------------------------------
class MontyHallSim
# constructor
# FIXME
def initialize()
# @foo is instance variable
# winning count when switch the choise
@switch_win_count = 0
end
# one trials and keep the results in member
def one_try()
# generate the car, donkey, donkey
car_pos = rand(3)
player_choice = rand(3)
if car_pos == player_choice then
# if player's initial choise is the car, choose one of the rest
# with 1/2 possiblity
open_choise = rand(2)
open_pos = (player_choice + (open_choise + 1)) % 3
else
# if player's initial choise is not the car, choose the only one
# possible donkey
if (player_choice + 1) % 3 == car_pos then
open_pos = (player_choice + 2) % 3
else
open_pos = (player_choice + 1) % 3
end
end
# show the status
doors = ['*', '*', '*' ]
doors[player_choice] = "P"
doors[open_pos] = "H"
# player position 'P' and Host open position 'H'
print "Player-Host: ";
doors.each { |x| print x.to_s + ' ' }
print "\n";
# and the car 'C'
print "Car: ";
doors[car_pos] = "C"
doors.each { |x| print x.to_s + ' ' }
# keep the record
if player_choice != car_pos then
@switch_win_count = @switch_win_count + 1
print " ... switching win!";
end
print "\n";
end
# run simulation
# \param _number_of_trials simulation number of trials
def run(_number_of_trials)
begin
# check number of trials
raise "0 number of trials" if _number_of_trials <= 0
# init random sequence (but always the same sequence in this
# implementation)
srand(0)
i = 0;
while i < _number_of_trials do
one_try()
i = i + 1
end
# output the result
print "Result: switch win count = " + @switch_win_count.to_s + "\n"
switch_win_ratio = @switch_win_count.to_f / _number_of_trials.to_f
print "Result: switch win ratio = " + switch_win_ratio.to_s +
", non-switch win ratio = " + (1 - switch_win_ratio).to_s + "\n"
rescue
# `$!' has the last exception object
print "Error! " + $!.message + "\n"
end
end
end
#------------------------------
# command line option parsing
#------------------------------
args = GetoptLong.new();
# ['--output', '-o', GetoptLong::REQUIRED_ARGUMENT],
args.set_options(
['--help', '-h', GetoptLong::NO_ARGUMENT],
['--version', '-v', GetoptLong::NO_ARGUMENT]
);
begin
args.each_option do |name, arg|
# print(name + ", " + arg + ":\n")
eval "$OPT_#{name.sub(/^--/, '').gsub(/-/, '_').upcase} = '#{arg}'"
end
rescue
exit(1)
end
#--- help
if $OPT_HELP
print_help
exit(1)
end
#--- show version
if $OPT_VERSION
$stderr.print(MONTYHALLSIM_VERSION + "\n")
exit(1)
end
#--- dotfile only option
is_dotfileonly = false
if $OPT_DOTFILEONLY
dotfileonly = true
end
#--- get number of trials
if ARGV.length == 0 then
number_of_trials = 100
elsif ARGV.length == 1
number_of_trials = ARGV[0].to_i
else
$stderr.print("Error! illegal number of arguments.\n")
print_help
exit(1);
end
# --- backup
dback = MontyHallSim.new()
dback.run(number_of_trials)
# --- end of montyhallsim.rb