🎉

モンティ・ホール問題をGoでも実装してみた。

に公開

今回は、以前Pythonでモンティ・ホール問題を実装した記事を出したのですが、Goの勉強も兼ねて今回はGoでの実装を試してみました。モンティ・ホール問題って何?という方やPythonでの実装が気になる方はぜひこちらを参照ください。

https://zenn.dev/akasan/articles/90b205bc9bca23

それでは早速実装していく

プロジェクト初期化

今回もこちらの記事のようにmiseを使ってプロジェクトを初期化していきます。
https://zenn.dev/akasan/articles/7ef47bf9cd599b

mise use go@1.24.3
mkdir monty_golang
cd monty_golang
go mod init monty_golang

コード実装

早速コードを実装してみましょう。前回のPython実装を元に移植しています。ただし、以下のように条件を変えています。

package main

import (
	"fmt"
	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotter"
	"gonum.org/v1/plot/plotutil"
	"gonum.org/v1/plot/vg"
	"math/rand"
)

const (
	Car int = iota
	Goat
)

type MontyHallProblem struct {
	DoorPrize     map[int]int
	SelectedIndex *int
	OpenedIndex   *int
}

func GetPrize() map[int]int {
	doorPrize := map[int]int{0: Goat, 1: Goat, 2: Goat}
	prizeTargetIndex := rand.Intn(3)
	doorPrize[prizeTargetIndex] = Car
	return doorPrize

}

func InitializeProblem() MontyHallProblem {
	problem := MontyHallProblem{}
	problem.Reset()
	return problem
}

func (p *MontyHallProblem) Reset() {
	p.DoorPrize = GetPrize()
	p.SelectedIndex = nil
	p.OpenedIndex = nil
}

func (p *MontyHallProblem) SelectFirst(firstIndex int) {
	p.SelectedIndex = IntPtr(firstIndex)
}

func IntPtr(i int) *int {
	return &i
}

func (p *MontyHallProblem) ChangeCandidate() {
	switch {
	case *p.SelectedIndex == 0 && *p.OpenedIndex == 1:
		p.SelectedIndex = IntPtr(2)
	case *p.SelectedIndex == 0 && *p.OpenedIndex == 2:
		p.SelectedIndex = IntPtr(1)
	case *p.SelectedIndex == 1 && *p.OpenedIndex == 2:
		p.SelectedIndex = IntPtr(0)
	}
}

func (p MontyHallProblem) IsCorrect() bool {
	return p.DoorPrize[*p.SelectedIndex] == Car
}

func (p *MontyHallProblem) OpenGoatDoor() {
	for idx := range p.DoorPrize {
		if idx == *p.SelectedIndex {
			continue
		}
		if p.DoorPrize[idx] == Goat {
			p.OpenedIndex = IntPtr(idx)
			break
		}
	}
}

func main() {
	ITERATION := 1000
	problem := InitializeProblem()
	changedResult := 0.0
	unchangedResult := 0.0
	changedResults := make(plotter.XYs, ITERATION)
	unchangedResults := make(plotter.XYs, ITERATION)

	for i := 0; i < ITERATION; i++ {
		problem.Reset()
		problem.SelectFirst(0)
		problem.OpenGoatDoor()
		if problem.IsCorrect() {
			unchangedResult += 1
		}
		problem.ChangeCandidate()
		if problem.IsCorrect() {
			changedResult += 1
		}
		fmt.Println(unchangedResult, changedResult)
		changedResults[i].X = float64(i)
		changedResults[i].Y = changedResult / float64(i+1)
		unchangedResults[i].X = float64(i)
		unchangedResults[i].Y = unchangedResult / float64(i+1)
	}

	p := plot.New()
	p.Title.Text = "Month-Hall Problem"
	p.X.Label.Text = "Iteration"
	p.Y.Label.Text = "Probability"
	plotutil.AddLinePoints(p, "changed", changedResults, "unchanged", unchangedResults)
	if err := p.Save(4*vg.Inch, 4*vg.Inch, "monty_hall_problem_result.png"); err != nil {
		panic(err)
	}
}

グラフを書くために、Gonum Plotを利用しました。Gonum Plotの使い方についてはこちらの記事を参照ください。

https://zenn.dev/akasan/articles/7ef47bf9cd599b

こちらを実行すると、以下のような結果を得ることができます。結果を確認すると、前回の検証と同じく、ドアを変更した場合がおよそ66%程度、そのままの場合が33%程度と予想していた通りの結果になりました。

まとめ

今回はモンティ・ホール問題をGoで実装してみました。個人的にGoを使い始めで構造体の使い方とか慣れないところではありますが、前回のPython実装と同様の結果が再現できてよかったです。また、昨日記事化したGonum Plotについても早速応用的に使うことができてよかったです。

今後はもっとGoを使った実装に挑戦していこうと思います。もし何か質問やコメントあればどんどん残してくれると嬉しいです。

Discussion