[ Exercises | Chapter Index | Main Index ]

Solution for Programming Exercise 5.3


This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.


Exercise 5.3:

This problem uses the PairOfDice class from Exercise 5.1 and the StatCalc class from Exercise 5.2.

The program in Exercise 4.4 performs the experiment of counting how many times a pair of dice is rolled before a given total comes up. It repeats this experiment 10000 times and then reports the average number of rolls. It does this whole process for each possible total (2, 3, ..., 12).

Redo that exercise. But instead of just reporting the average number of rolls, you should also report the standard deviation and the maximum number of rolls. Use a PairOfDice object to represent the dice. Use a StatCalc object to compute the statistics. (You'll need a new StatCalc object for each possible total, 2, 3, ..., 12. You can use a new pair of dice if you want, but it's not necessary.)


Discussion

The program from Exercise 4.4 defines a function, rollFor(N), that performs the basic experiment once. It rolls the dice until the total on the dice is N, and it returns the number of rolls. Using a PairOfDice object, dice, the body of this subroutine becomes

int rollCt = 0;  // Number of rolls made.
do {
   dice.roll();
   rollCt++;
} while ( dice.getTotal() != N );
return rollCt;

This is significantly simpler than the original version. But where does the dice object come from? One possibility is to create a new PairOfDice object at the beginning of the function. This will work, but then a new object is created each time the function is called. In the program we are writing, the function is called 110,000 times. It seems a waste to manufacture 110,000 pairs of dice when one would do! To avoid this, I create the dice as a static member variable:

private static PairOfDice dice = new PairOfDice();

The variable must be static since it is used in the static function, rollFor. Since dice is a static member variable, it is created and initialized when the class is first loaded and it exists as long as the program is running. The rollFor() method always uses this one pair of dice. (Some people might prefer to create the dice as a local variable in the main() routine. The dice could be passed as a parameter to the rollFor() method. Then, rollFor(N,dice) would mean "roll for a total of N using this pair of dice." But in my opinion, the dice are not logically a part of the interface of this subroutine, and I prefer to use the member variable.)

The original program also had a method called getAverageRollCount() to find the average number of rolls, when the basic experiment is repeated 10000 times. We could rename this to getRollCountStats and use it to compute all the statistics, not just the average. The actual computation is to be done by a StatCalc object. Let stats be a variable that refers to that object. The results of each experiment will be fed into this object, something like this:

for ( int i = 0;  i < 10000;  i++ ) {
      // Assume "total" is the number we are rolling for.
    rollCountThisExperiment = rollFor( total );  // Do one experiment.
    stats.enter( rollCountThisExperiment );      // Enter the data.
}

At the end of this process, stats is ready to report the statistics. All you have to do is call its functions, such as stats.getMean().

In my program, I use the named constant NUMBER_OF_EXPERIMENTS instead of the literal number, 10000. I abbreviate the for loop to

for ( int i = 0;  i < NUMBER_OF_EXPERIMENTS;  i++ ) 
    stats.enter( rollFor(total) );

and, since it has become so short, I deleted the subroutine and moved the for loop into the main() routine. The main() routine prints the output in neat columns, using formatted output. For example, "TextIO.putf("%6d",total);" prints the value of total in a column of width 6. (The hardest part was figuring out how wide to make the columns!). After printing out some headings for the columns, the main() routine says

for ( int total = 2;  total <= 12;  total++ ) {
   StatCalc stats;  // An object that will compute the statistics.
   stats = new StatCalc();
   for ( int i = 0; i < NUMBER_OF_EXPERIMENTS; i++ ) {
         // Do the experiment of counting the number of rolls
         // required to roll the desired total, and enter the
         // number of rolls into stats' dataset.
      stats.enter( rollFor(total) );
   }
   TextIO.putf("%6d", total);
   TextIO.putf("%18.3f", stats.getMean());
   TextIO.putf("%19.3f", stats.getStandardDeviation());
   TextIO.putf("%14.3f", stats.getMax());
   TextIO.putln();
}

The body of the for loop processes one of the possible totals on a pair of dice, and it produces one line of output with the statistics for that total. A new StatCalc object is created for each execution of the for loop. This is necessary because we want separate statistics for each total. A single StatCalc object would just accumulate all the data for all the possible values of total into a single dataset. It might be worth noting that the five output statements in this method could be written as a single statement:

TextIO.putf( "%6d%18.3f%19.3f%14.3f\n", total, stats.getMean(), 
        stats.getStandardDeveialtion(), stats.getMax() );

By the way, you might wonder what would happen if I had not eliminated the getRollCountStats() subroutine? In that case, the statistics data is generated in the subroutine and is used in the main() routine. So, the same StatCalc object has to be used in both routines. There are several ways to handle this. The variable, stats, could be a static member variable. Then it could simply be used in both routines. Alternatively, the StatCalc object could be passed as a parameter to the subroutine. Or, as a final and best alternative, the object could be created in the subroutine and sent back to the main() routine as a return value. Let's look at this last possibility. The subroutine would be:

static StatCalc getRollCountStats( int total ) {
    StatCalc calc;         // An object to compute the statistics.
    calc = new StatCalc();
    for ( int i = 0; i < NUMBER_OF_EXPERIMENTS; i++ )
        calc.enter( rollFor(total) );
    return calc;  // Send back the object, with the statistics.
}

In the main program, this would be used as follows:

for ( int total = 2; total <= 12; total++ ) {
   StatCalc stats;  // The stats for this total
   stats = getRollCountStats( total ); // Get stats from subroutine.
   TextIO.putf("%6d", total);
   TextIO.putf("%18.3f", stats.getMean());
   TextIO.putf("%19.3f", stats.getStandardDeviation());
   TextIO.putf("%14.3f", stats.getMax());
   TextIO.putln();
}

Note in particular that not every object variable needs to be initialized with a new object. In the case of the variable calc in the main() routine, an object is computed elsewhere. The variable, calc, is set to refer to that existing object.


The Solution

/**
 * This program performs the following type of experiment:
 * Given a desired total roll, such as 7, roll a pair of
 * dice until the given total comes up, and count how many
 * rolls are necessary.  Now do the experiment over and over,
 * and find the average number of rolls.  The number of times
 * the experiment is repeated is given by the constant,
 * NUMBER_OF_EXPERIMENTS.  Several statistics are computed and
 * printed out for each possible roll = 2, 3, ..., 12:
 * the average number of rolls, the standard deviation,
 * and the maximum number of rolls.
 */

public class DiceRollStats2 {

   static final int NUMBER_OF_EXPERIMENTS = 10000;
   
   private static PairOfDice dice = new PairOfDice();
            // A single pair of dice, which will be used for all
            // the experiments.

   
   public static void main(String[] args) {
   
       TextIO.putln("Dice Total   Avg # of Rolls   Stand. Deviation   Max # of Rolls");
       TextIO.putln("----------   --------------   ----------------   --------------");

       for ( int total = 2;  total <= 12;  total++ ) {
          StatCalc stats;  // An object that will compute the statistics.
          stats = new StatCalc();
          for ( int i = 0; i < NUMBER_OF_EXPERIMENTS; i++ ) {
                // Do the experiment of counting the number of rolls
                // required to roll the desired total, and enter the
                // number of rolls into stats' dataset.
             stats.enter( rollFor(total) );
          }
          TextIO.putf("%6d", total);
          TextIO.putf("%18.3f", stats.getMean());
          TextIO.putf("%19.3f", stats.getStandardDeviation());
          TextIO.putf("%14.3f", stats.getMax());
          TextIO.putln();
       }
       
   } // end main
   
   /**
    * Roll the dice repeatedly until the total on the
    * two dice comes up to be N.  N MUST be one of the numbers
    * 2, 3, ..., 12.  (If not, this routine will go into an
    * infinite loop!).  The number of rolls is returned.
    */
   static int rollFor( int N ) {
       int rollCt = 0;  // Number of rolls made.
       do {
          dice.roll();
          rollCt++;
       } while ( dice.getTotal() != N );
       return rollCt;
   }

   
}  // end class DiceRollStats2

[ Exercises | Chapter Index | Main Index ]