/** Initializes the die so that it behaves as a fair, six-sided die. */ public NSidedDie() { ... } |
/** Initializes the die so that it behaves as a fair die having ** the specified number of sides. (If the specified number is ** not positive, an IllegalArgumentException is thrown.) */ public NSidedDie(int numSides) { ... } |
The third constructor is used to produce a (potentially) unfair die:
/** Initializes the die so that it behaves as one having N sides, ** where N is the length of the specified array, and where the ** relative likelihoods of its outcomes are specified by the ** values in the array elements. ** (If the array length is zero or any of its elements has a ** negative value, an IllegalArgumentException is thrown.) */ public NSidedDie(double[] relProb) { ... } |
The formal argument name relProb is meant to suggest the phrase "relative probability". The intent here is that, for example, execution of the code segment (in a client of NSidedDie)
double[] f = new double[] { 1.0, 2.0, 0.5, 1.0, 1.5, 2.0 }; NSidedDie die = new NSidedDie(f); |
will end with die's value being (a reference to) a six-sided die that behaves as described in this table:
Outcome | Probability |
---|---|
1 | 1/8 |
2 | 1/4 |
3 | 1/16 |
4 | 1/8 |
5 | 3/16 |
6 | 1/4 |
How did we calculate these probabilities? Each probability is the ratio of one of the array elements to the sum of all of them. More precisely, letting this sum be S, the probability of a die toss resulting in a face value of k is calculated to be f[k-1]/S. (Because die toss outcomes start at one and array indices start at zero, f[k-1] (rather than f[k]) is used in calculating the probability of tossing k.)
In our example, S = 1.0 + 2.0 + 0.5 + 1.0 + 1.5 + 2.0 = 8.0 and f[4] = 1.5, so the ratio f[4]/S is 1.5/8.0, or 3/16. In other words, the ratio between the relative probability of tossing 5 (i.e., f[4], with value 1.5) and the sum of all the relative probabilities (i.e., 8.0) is 3/16, which is why 5 should be tossed three-sixteenths of the time.
With an unfair die, simulating a toss is more difficult than with a fair die. This section outlines how it can be done.
Suppose that we have an N-sided die, where the intended probabilities of tossing 1, 2, ..., N, are P1, P2, ... PN, respectively. Then we partitition the interval [0,1) into N sub-intervals, which we call I1, I2, ..., IN, such that the length of Ij (for each j) is equal to Pj. To accomplish this, we take the lower bound of I1 to be to be zero and its upper bound to be P1. For each j>1, we take the lower bound of Ij to be the upper bound of Ij-1 and its upper bound to be that value plus Pj. Or, to put it more concisely, we take Ij to be the interval [Qj-1,Qj), where, for all i,
To toss a die, we call Math.random(), which, as you will recall, returns a pseudo-random number z (of type double) chosen (with approximately uniform distribution) from the interval [0,1). In order to determine the outcome of the die toss, we determine in which of the N sub-intervals z lies. If z lies in I4, for example, the outcome is taken to be 4.
Following our example from above, we would form the six subintervals
Seeing the sub-intervals on the number line might help:
0 1/8 3/8 7/16 9/16 3/4 1 +-------+---------------+---+-------+-----------+---------------+ |_______|_______________|___|_______|___________|_______________| I1 I2 I3 I4 I5 I6 |
Notice that each interval's length is equal to the probability of the corresponding die toss outcome. (E.g., I4 has length two-sixteenths (or one-eighth), corresponding to the intended probability of tossing 4 on the die.)
Question: How do we represent the sub-intervals? Answer: Put their upper bounds into an array of type double[]! For our example, the array (which we'll call upperBounds) would look like this:
0 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1/8 | 3/8 | 7/16 | 9/16 | 3/4 | 1 |
For the toss() method to simulate a toss of the die, it would do this:
so as to assign to z a pseudo-random number in the interval [0,1). Then it would search in the array upperBounds to find the smallest value of k satisfying the condition
The outcome of the die toss (i.e., the resulting face value) would then be k+1.
Submit your source code file (NSidedDie.java) from the course web page using the Submit/Review link that is adjacent to the link that brought you to this page. (Again, submit the .java file, not the .class file.) Make sure to include comments in your program identifying yourself, indicating that it is a solution to Prog. Assg. #2 in CMPS 144, acknowledging any persons who aided you in devloping your solution, and pointing out any flaws of which you are aware.
Be aware that you can submit more than one time. Hence, if, after submitting, you improve your program (e.g., by fixing logic errors), you should submit the newer version.