Last active
May 25, 2024 10:19
-
-
Save dacr/7e7e3a8e4cd942e14110fa709ca0dd4a to your computer and use it in GitHub Desktop.
Monty Hall paradox simulation (probability puzzle) / published by https://github.com/dacr/code-examples-manager #57afcd4b-a9bc-4204-82f8-d28a8338ee69/219f1cfa4bbffed30a3e0c66d379579cc54f2976
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// summary : Monty Hall paradox simulation (probability puzzle) | |
// keywords : scala, math, probability, puzzle, paradox, simulation, monty-hall, @testable | |
// publish : gist | |
// authors : David Crosson | |
// license : Apache NON-AI License Version 2.0 (https://raw.githubusercontent.com/non-ai-licenses/non-ai-licenses/main/NON-AI-APACHE2) | |
// id : 57afcd4b-a9bc-4204-82f8-d28a8338ee69 | |
// created-on : 2020-12-29T21:01:25Z | |
// managed-by : https://github.com/dacr/code-examples-manager | |
// run-with : scala-cli $file | |
// --------------------- | |
//> using scala "3.4.2" | |
//> using dep "org.scalatest::scalatest:3.2.16" | |
//> using objectWrapper | |
// --------------------- | |
/* | |
Monty Hall paradox wikipedia : https://en.wikipedia.org/wiki/Monty_Hall_problem | |
*/ | |
import org.scalatest._ | |
import flatspec._ | |
import matchers._ | |
import scala.annotation.tailrec | |
import scala.math._ | |
import scala.util.Random | |
object MontyHall { | |
type Door = Int | |
sealed trait BehindDoor | |
object Goat extends BehindDoor | |
object NewCar extends BehindDoor | |
object Game { | |
def apply(doorsCount: Int = 3): Game = { | |
val state = | |
1.to(doorsCount) | |
.map(n => n -> Goat) | |
.toMap | |
.updated(Random.nextInt(doorsCount) + 1, NewCar) | |
new Game(state,None) | |
} | |
} | |
case class Game(state: Map[Door, BehindDoor], played:Option[Door]) { | |
val doors = state.keys.toSet | |
def chooseDoor(door:Door):Game = this.copy(played=Some(door)) | |
def play(door:Door):Boolean = state.get(door).exists(_ == NewCar) | |
def revealDoor(): Door = { | |
val doors = state.collect{case (door,Goat) if played.isEmpty || played.get != door => door}.toArray | |
doors(Random.nextInt(doors.size)) | |
} | |
} | |
def simulate(rounds:Int=100_000)(strategy: (Door,Door,Set[Door])=>Door):Double = { | |
@tailrec | |
def play(round:Int, winCount:Int):Int = { | |
if (round==0) winCount else { | |
val gameInitialState = Game() | |
val doors = gameInitialState.doors | |
val firstChosenDoor = doors.to(Array)(Random.nextInt(doors.size)) | |
val gameAfterGivenChoice = gameInitialState.chooseDoor(firstChosenDoor) | |
val revealedDoor = gameAfterGivenChoice.revealDoor() | |
val finalChoice = strategy(firstChosenDoor, revealedDoor, doors) | |
val result = gameAfterGivenChoice.play(finalChoice) | |
play(round-1, winCount + (if (result) 1 else 0)) | |
} | |
} | |
play(rounds,0).toDouble/rounds | |
} | |
def simulateChangeMind(rounds:Int=100_000):Double = simulate(rounds) { | |
(firstChosenDoor, revealedDoor, doors) => | |
(doors - firstChosenDoor - revealedDoor).head | |
} | |
def simulateKeepChoice(rounds:Int=100_000):Double = simulate(rounds) { | |
(firstChosenDoor, revealedDoor, doors) => firstChosenDoor | |
} | |
} | |
class MontyHallTest extends AnyFlatSpec with should.Matchers { | |
override def suiteName: String = "MontyHallTest" | |
import MontyHall._ | |
"Monty Hall paradox" should "give 33% of win chance if I keep my initial choice" in { | |
simulateKeepChoice(500_000) shouldBe 0.33d +- 0.01d | |
} | |
it should "give 66% of win chance if I change my mind" in { | |
simulateChangeMind(500_000) shouldBe 0.66d +- 0.01d | |
} | |
} | |
org.scalatest.tools.Runner.main(Array("-oDF", "-s", classOf[MontyHallTest].getName)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment