Last active
May 27, 2023 06:28
-
-
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/6e4d429ef956c63bdc615daa16fafe017f9232a3
This file contains 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.3.0" | |
//> 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