import java.util.Random; /** Keys.java - No, not the Florida Keys. This is the 4-keys problem. * On page 89 of "Guilty Not Guilty" by Felix Francis, the protagonist is * given a set of 4 keys that open 4 doors. But he has to figure out * which key opens which lock. How many mistakes could he make? * In the best case, 0. In the worst case, 3+2+1 = 6. * Let's make random trials to see how often each number 0-6 comes up, * so that we can calculate the "expected value" of the number of mistakes. * * If you wanted to find the expected value by hand, note that each door * can be handled independently. If you still have K keys to match, then * you could now make 0 to K-1 mistakes. Each possibility has probability 1/K. * * Approach in this program: Think of a 2-d finite automaton, in the shape * of an array, with cells numbered 1..N, 1..N. The row number is the * door you are trying. The columns indicate mistakes. Begin at 1,1. * The simulation is over once you reach row n. You are in row i and col j. * Try a key by selecting a random number from j to N. If correct, you * can go to cell (i+1, i+1). It's not (i+1, 1) because you have fewer keys * to try as you continue. If wrong, go right to (i, j+1). During each * trial keep track of the total number of mistakes. */ public class Keys { // Given a number of keys, find the maximum possible number of mistakes // we could make. It is the sum of the integers from 0 to n-1. // The formula is n(n-1)/2. public static int findMax(int n) { return n * (n - 1) / 2; } public static void main(String [] args) { Random gen = new Random(); int N = 4; int numTrials = 100000000; int maxPossibleMistakes = findMax(N); int [] mistakeDist = new int[maxPossibleMistakes + 1]; // Oops: increment with ++trial, not ++i! for (int trial = 1; trial <= numTrials; ++trial) { // Don't forget to initialize i & j to 1 HERE, not before trial loop. int i = 1; int j = 1; int numMistakes = 0; while (i < N) { // If you have reached column j, then there is only 1 key left, // so you can't make a mistake. Go to next door. if (j == N) { ++i; j = i; continue; } // We are at i,j. Pick a number from j to N and see if it equals N, // to simulate the possibility of guessing right. int guess = gen.nextInt(N - j + 1) + j; if (guess == N) { ++i; j = i; } else { ++numMistakes; ++j; } } // At this point we observe how many mistakes we made, and increment // the appropriate cell in the statistical distribution. ++mistakeDist[numMistakes]; } // Now the experiment is over. From the statistical distribution of the // number of mistakes across all trials, we can easily calculate the // empirical probability of making each possible number of mistakes. // Multiply each by the number of mistakes to determine the expected value. int sumProduct = 0; for (int i = 0; i <= maxPossibleMistakes; ++i) { System.out.printf("%2d mistakes occurred %8d times.\n", i, mistakeDist[i]); sumProduct += i * mistakeDist[i]; } double expectedValue = 1.0 * sumProduct / numTrials; System.out.printf("Out of %d trials, expected # mistakes is %.2f\n", numTrials, expectedValue); } } /* 100,000,000 trials take about 8 seconds. The result looks like a "normal" distribution. Example output: 0 mistakes occurred 4163725 times. 1 mistakes occurred 12498837 times. 2 mistakes occurred 20828805 times. 3 mistakes occurred 25001916 times. 4 mistakes occurred 20834394 times. 5 mistakes occurred 12502989 times. 6 mistakes occurred 4169334 times. Out of 100000000 trials, expected # mistakes is 3.00 */