Last active
August 29, 2015 14:19
-
-
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.
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
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