Skip to content

Instantly share code, notes, and snippets.

@benhardy
Last active August 29, 2015 14:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benhardy/671ee9a90b9b6105a9df to your computer and use it in GitHub Desktop.
Save benhardy/671ee9a90b9b6105a9df to your computer and use it in GitHub Desktop.
In case you thought the Monty Hall Problem hadn't been done to death.
package monty;
import static com.google.common.collect.ImmutableSet.of;
import static com.google.common.collect.Sets.difference;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.assertEquals;
import java.util.List;
import java.util.Set;
import org.junit.Test;
/**
* This class runs three complete Monty Hall simulations as unit tests, each
* with a different strategy for selecting the second door.
*
* The output of this test ends up being as follows:
*
* win ratio for monty knows, always swapping is 66.6%
* win ratio for monty knows, never swapping is 33.3%
* win ratio for monty knows, random swapping is 50.0%
* win ratio for monty doesn't know, always swapping is 66.6%
* win ratio for monty doesn't know, never swapping is 66.6%
* win ratio for monty doesn't know, random swapping is 66.6%
*/
public class MontyHallSimulationTest {
final static Set<String> ALL_THE_DOORS = of("A", "B", "C");
/**
* Run a bunch of rounds with the same picking strategy to measure
* how often that strategy wins.
* @param description - a word describing the picking strategy
* @param pickingStrategy - how we choose the second door (more about that below)
*
* FYI you will not need these parameters in R. I've just got this here
* so I could compare different strategies.
*
* @param howToReveal - how Monty picks which door to reveal
* @return a percentage of wins
*/
private double runSimulation(String description, Picker pickingStrategy, MontyRevealStrategy howToReveal) {
final int rounds = 100000;
int wins = 0;
for (int round = 0; round < rounds; round++) {
boolean won = runOneRound(pickingStrategy, howToReveal);
if (won)
wins++;
}
final double winRatio = wins * 100.0 / rounds;
System.out.printf("win ratio for %s swapping is %.1f%%\n", description, winRatio);
return winRatio;
}
/**
* Perform one round of the simulation.
*
* @param pickingStrategy - what picking strategy to use (won't need this if you're only
* interested in the one winning strategy)
* @return true if we won, otherwise false
*/
private boolean runOneRound(Picker pickingStrategy, MontyRevealStrategy howToReveal) {
String prizeDoor = sample(ALL_THE_DOORS);
String firstPick = sample(ALL_THE_DOORS);
String montyRevealed = howToReveal.reveal(firstPick, prizeDoor);
if (montyRevealed.equals(prizeDoor)) {
return true; // bail out
}
// Player could pick this
String alternate = aDoorNotAmongst(firstPick, montyRevealed);
// Player actually picks this, according to their strategy
String secondPick = pickingStrategy.pick(firstPick, alternate);
boolean winning = secondPick.equals(prizeDoor);
return winning;
}
/**
* Randomly pick a door that isn't doorA or doorB.
* DoorA and doorB could be the same door, or different, we don't care
* as long as neither's picked.
*/
private static String aDoorNotAmongst(String doorA, String doorB) {
Set<String> unavailable = of(doorA, doorB);
Set<String> available = difference(ALL_THE_DOORS, unavailable);
return sample(available);
}
/**
* select one item, completely at random, from a set of strings
* (had to hack this together in Java since it has no such thing)
*
* n.b. the value returned is *not* a subset containing the selected
* item - it IS the item
*/
public static String sample(Set<String> items) {
List<String> things = items.stream().collect(toList());
int length = things.size();
final int selectedIndex = (int) (length * Math.random());
return things.get(selectedIndex);
}
/**
* Strategy of player picking between their first choice and an offered alternate
*/
@FunctionalInterface
interface Picker {
String pick(String firstChoice, String alternate);
}
public static final Picker NEVER_SWAP = (firstPick, alternate) -> firstPick;
public static final Picker ALWAYS_SWAP = (firstPick, alternate) -> alternate;
public static final Picker RANDOMLY_SWAP = (firstPick, alternate) -> sample(of(firstPick, alternate));
/**
* Strategy of Monty picking which door to reveal. He might know where the prize is, but in another
* version of the problem, he does not.
*/
@FunctionalInterface
interface MontyRevealStrategy {
String reveal(String prizeLocation, String firstPick);
}
public static final MontyRevealStrategy MONTY_DOES_KNOW = (firstPick, prizeDoor) -> aDoorNotAmongst(firstPick, prizeDoor);
public static final MontyRevealStrategy MONTY_DONT_KNOW = (firstPick, prizeDoor) -> aDoorNotAmongst(firstPick, firstPick);
private static final double ERROR_MARGIN = 1.0;
@Test
public void neverSwappingWinsAThirdIfMontyKnows() {
double ratio = runSimulation("monty knows, never", NEVER_SWAP, MONTY_DOES_KNOW);
assertEquals("Never swap should win about 33% of the time if Monty knows", 33.3, ratio, ERROR_MARGIN);
}
@Test
public void alwaysSwapWinsTwoThirdsIfMontyKnows() {
double ratio = runSimulation("monty knows, always", ALWAYS_SWAP, MONTY_DOES_KNOW);
assertEquals("Always swap should win about 67% of the time if Monty knows", 66.7, ratio, ERROR_MARGIN);
}
@Test
public void randomlySwapWinsAHalfIfMontyKnows() {
double ratio = runSimulation("monty knows, random", RANDOMLY_SWAP, MONTY_DOES_KNOW);
assertEquals("Random swap should win about 50% of the time if Monty knows", 50.0, ratio, ERROR_MARGIN);
}
@Test
public void neverSwappingWinsTwoThirdsIfMontyDoesntKnow() {
double ratio = runSimulation("monty doesn't know, never", NEVER_SWAP, MONTY_DONT_KNOW);
assertEquals("Never swap should win about 67% of the time if Monty doesn't know", 66.7, ratio, ERROR_MARGIN);
}
@Test
public void alwaysSwapWinsTwoThirdsIfMontyDoesntKnow() {
double ratio = runSimulation("monty doesn't know, always", ALWAYS_SWAP, MONTY_DONT_KNOW);
assertEquals("Always swap should win about 67% of the time if Monty doesn't know", 66.7, ratio, ERROR_MARGIN);
}
@Test
public void randomlySwapWinsTwoThirdsIfMontDoesntKnow() {
double ratio = runSimulation("monty doesn't know, random", RANDOMLY_SWAP, MONTY_DONT_KNOW);
assertEquals("Random swap should win about 67% of the time if Monty doesn't know", 66.7, ratio, ERROR_MARGIN);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment