Haskell

[小ネタ] Haskellでモンティ・ホール問題を解いてみよう

今回はHaskellでモンティ・ホール問題を解いてみようと思います。すごいH本を読破した方であれば理解可能なコードになったと思います。

モンティー・ホール問題とは

Wikipediaより

モンティ・ホール問題(モンティ・ホールもんだい、英: Monty Hall problem)とは、確率論の問題で、ベイズの定理における事後確率、あるいは主観確率の例題のひとつとなっている。モンティ・ホール(英語版) (Monty Hall, 本名 Monte Halperin) が司会者を務めるアメリカのゲームショー番組、「Let's make a deal(英語版)[1]」の中で行われたゲームに関する論争に由来する。

概要

クイズの概要は至ってシンプルです

「プレーヤーの前に閉まった3つのドアがあって、1つのドアの後ろには景品の新車が、2つのドアの後ろには、はずれを意味するヤギがいる。プレーヤーは新車のドアを当てると新車がもらえる。プレーヤーが1つのドアを選択した後、司会のモンティが残りのドアのうちヤギがいるドアを開けてヤギを見せる。ここでプレーヤーは、最初に選んだドアを、残っている開けられていないドアに変更してもよいと言われる。プレーヤーはドアを変更すべきだろうか?」

ここで処理の流れをまとめると:
1. ヤギ2頭、車の順番を1つランダムに選択する。
2. プレイヤーはランダムに扉を1つ選択する
3. モンティーは残った2つの扉からヤギのものを選択し、開ける
4. 残り1つの扉(すなわち変更したときの扉)が車であるか調べる

メモ

  • ヤギ、車はPrize型を定義し、それを用いて表現する
  • 2~4に関してはリスト内包表記を用いればエレガントに求めることができる
  • クイズをn回反復して行うにはreplicateMを用いる
  • 確率はそのまんまです。(扉を変えたときに車であった回数 / クイズを行った総数)
module Lib
    ( montyHall
    , runMontyHall
    ) where

import           Control.Monad (replicateM)
import           Data.Semigroup ((<>))
import           System.Random (randomRIO)

-- | Given an list, picks an element
pick :: [a] -> IO a
pick xs = do
    ele <- randomRIO (0, length xs - 1)
    return $ xs !! ele

-- | Prizes from Monty Hall
data Prize =
      Goat
    | Car
    deriving (Eq, Show)

-- | Run monty hall
montyHall :: IO Bool
montyHall = do
    prize  <- pick [[Car, Goat, Goat], [Goat, Car, Goat], [Goat, Goat, Car]]
    choose <- pick [0 .. 2]
    monty  <- pick [ i | i <- [0 .. 2], i /= choose, prize !! i == Goat ]
    let switch = head [ i | i <- [0 .. 2], i /= choose, i /= monty ]
    return $ prize !! switch == Car

-- | Run @'montyHall'@ given number of times and compute it's winning percentage
runMontyHall :: Int -> IO ()
runMontyHall num = do
    putStrLn $ "Running monty hall " <> show num <> " times"

    -- Run the computation
    xs <- replicateM num montyHall
    let winning = filter (== True) xs
    -- Compute the winning percentage
    let winningPercentage = fromIntegral (length winning) / fromIntegral num * 1000
    let winningPercentageRound = fromIntegral (ceiling winningPercentage) / 10

    -- Print the results
    putStrLn "Computation is done!"
    putStrLn $ "Chance of winning a car is " <> show winningPercentageRound <> "%"

扉を変えたほうがいいのか

それではモンティ・ホールを10000回ほど行い、その確率を求めてみましょう。

*Main Lib> runMontyHall 100000
Running monty hall 100000 times
Computation is done!
Chance of winning a car is 66.7%

ちなみに扉を変えなかった場合に当たる確率は約33%でした。よって扉を変えたほうが車が当たる確率は変えなかったときに比べて約2倍になります。