New to Java? We'll help you get started with our revised beginner's tutorial, or our free online textbook.


Get the latest Java books
h t t p : / /w w w . j a v a c o f f e e b r e a k . c o m /

Java Coffee Break

Solution for
Programming Exercise 5.3


THIS PAGE DISCUSSES ONE POSSIBLE SOLUTION to the following exercise from this on-line Java textbook.

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 are rolled before a given total comes up. It repeats this experiment 10000 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.")

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 );
               stats.enter( rollCountThisExperiment );
           }

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 the version of TextIO.put() that has a column-width specification. For example, "TextIO.put(total,6);" 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.put(total, 6);
               TextIO.put(stats.getMean(), 18);
               TextIO.put(stats.getStandardDeviation(), 19);
               TextIO.put(stats.getMax(), 14);
               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 into a single dataset.


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 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.put(total, 6);
          TextIO.put(stats.getMean(), 18);
          TextIO.put(stats.getStandardDeviation(), 19);
          TextIO.put(stats.getMax(), 14);
          TextIO.putln();
       }
       

Note in particular that not every object variable needs to be initialized with a new object. In this case, an object is computed elsewhere. The variable, stats, is set to refer to that existing object.


The Solution


     public class DiceRollStats2 {
     
         /*
             This program preforms the following type of experiment:
             Given a desired total roll, such as 7, roll a pair of
             dice until th given total comes up, and count how many
             rolls are necessary.  Now do the 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.
         */
     
        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.put(total, 6);
               TextIO.put(stats.getMean(), 18);
               TextIO.put(stats.getStandardDeviation(), 19);
               TextIO.put(stats.getMax(), 14);
               TextIO.putln();
            }
            
        } // end main
        
     
        static int rollFor( int N ) {
                 // 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.
            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 ]