CH 7 V12
CH 7 V12
CH 7 V12
7.4 Nontermination
7.6 For-Statements
7.10 Recursion
7.13 Summary
Repeating an action over and over is called repetition. This Chapter introduces
techniques and applications of repetition in programming. The Chapter is organized
into three distinct parts:
7.1. REPETITION 305
• The first part, Sections 7.1-7.8, introduce the while- and for-statements for
writing standard and classic patterns of repetition, called iteration.
• The second part, Section 7.9, applies repetition in a case study of designing and
building an animation
• The third part, Sections 7.10-7.12, promotes another form of repetition, recur-
sive method invocation, as a technique for solving problems in terms of repeat-
edly solving simpler subproblems.
The first part of the Chapter is essential reading; the second is strongly recommended;
and the third can be omitted on first reading, if desired.
After studying the Chapter, the reader should be able to identify when a pro-
gramming problem should be solved by means of repetitive computation, apply the
appropriate repetitive pattern, and write the pattern in Java.
7.1 Repetition
Some jobs must be solved by repeating some step over and over:
• When replacing a flat tire with a spare, you must “place a lug nut at the end
of each bolt, and as long as the nut is loose, rotate it clockwise over and over
until it is finally secure against the wheel.”
• When searching for your favorite show on the television, you must, “while you
haven’t yet found your show, press the channel button repeatedly.”
Both of these “algorithms” rely on repetition to achieve their goals.
Computers became popular partly because a computer program will unfailingly
repeat a tedious task over and over. For example, perhaps you must know the decimal
values of the fractions (reciprocals) 1/2, 1/3, 1/4, and so on, up to 1/20. A painful
way of programming these calculations is manually repeating a division statement 19
times:
public static void main(String[] args)
{ System.out.println("1/2 = " + (1.0/2));
System.out.println("1/3 = " + (1.0/3));
System.out.println("1/4 = " + (1.0/4));
...
System.out.println("1/20 = " + (1.0/20));
}
The computer readily computes the reciprocals, but we should find a simpler way
to request the computations than the “copy and paste” coding technique above. A
better solution is built with a control structure called a while loop.
306
Step 1 initializes the variable that acts as the loop’s counter; Step 2 prints the recip-
rocals, one by one, as the loop’s counter increases by ones. Here is how the algorithm
is written in Java:
public static void main(String[] args)
{ int denominator = 2;
while ( denominator <= 20 )
{ System.out.println("1/" + denominator + " = " + (1.0 / denominator));
denominator = denominator + 1;
}
}
The while-loop prints the fractional representations of 1/2, 1/3, ..., 1/20 and stops.
Here is a more precise explanation of how the while-loop, while ( TEST ) { BODY
}, executes:
2. If TEST computes to true, then the BODY executes and the process repeats, restart-
ing at Step 1.
3. If TEST computes to false, then the BODY is ignored, and the loop terminates.
Repetition by means of a while-loop is called iteration, and one repetition of the loop’s
body is called an iteration; when the loop repeats its body over and over, we say that
it iterates.
Here is the flowchart representation of a a while-loop—it is a graph with a cycle:
TEST?
true false
BODY
Exercises
1. What will these while-loops print?
assigns 5 to i.)
(b) int countdown = 10;
while ( countdown != 0 )
{ System.out.println(i);
countdown = countdown - 1;
}
308
2. Write a while-loop to print the characters, ’a’ to ’z’, one per line. (Hint:
recall that Java allows you to initialize a variable as char c = ’a’ and to do
arithmetic with it, e.g., c = c + 1.)
3. Write a while-loop to print all the divisors of 1000 that fall within the range 2
to 50. (Recall that an integer, i, divides another integer, j, if j % i equals 0.)
(Hint: insert an if-statement in the body of the loop.)
The ellipsis in the formula suggests that we should repeatedly sum the scores of the
exams until all N scores are totalled; then we do the division. Here is an algorithm
that uses several variables, along with a while-loop, to sum the exams:
1. Assume that variable how many holds the quantity of test scores to be read.
7.3. DEFINITE ITERATION 309
2. Declare a variable total points = 0 to remember the total of all the exams,
and declare variable, count = 0, to remember how many exam scores have been
read already.
• Ask the user for the next exam score and add it to total points.
• Increment count by 1.
By reading the printed trace information, we see that at the beginning (and the
end) of each iteration, the value in total points indeed equals the sum of all the
exam scores read so far, that is,
This crucial fact is called the loop’s invariant property or invariant, for short. The in-
variant explains what the loop has accomplished with its iterations so far. Because of
their value in helping us understand a loop’s secrets, we will state invariant properties
for the loops we study.
Because of the loop’s invariant property, we know that when the loop stops with
count equal to how many, then it must be the case that total points holds all the
total points for all exams. From here, it is a small step to compute the average.
In the example, variables count and total points play crucial roles in remem-
bering the loop’s progress—the former acts as the loop’s counter, and the latter
remembers a running total.
The loop’s test evaluates to true, so execution moves into the body:
The user types 8 as the first exam score; the loop’s body revises the variables’ values:
At this point, the while-loop “restarts”—the control marker, >, returns to the
beginning of the loop, and the process repeats. Of course, the variables retain their
updated values:
Again, the test is evaluated and the result is true, so the body of the loop is entered
for yet another repetition, and the second score, 5, is read:
The loop repeats twice more, reading the last two scores, and we reach the configu-
ration where count’s cell holds 4:
This loop is an example of definite iteration, because once the loop is started, the
number of iterations is completely decided; here, it is how many iterations.
Definite iteration loops almost always use
• a test expression that examines the loop counter to see if all the iterations are
completed,
For a loop counter, count, the pattern of definite iteration often looks like this:
Exercises
1. Implement an application that computes upon exam averages:
(a) First, place the computeAverage method in a new class you write, called
class ExamStatistics. Next, write a main method whose algorithm goes
like this:
i. construct a new ExamStatistics object;
ii. construct a dialog that asks the user to enter a positive number for
the number of exams
iii. invoke the computeAverage method in the ExamStatistics object
iv. construct a dialog that shows the result returned by computeAverage.
(b) Modify computeAverage so that if its argument is a nonpositive integer,
the method shows a dialog that announces an error and returns 0.0 as its
answer.
(c) Next, write a method, computeMaxScore:
/** computeMaxScore reads a sequence test scores submitted by a user and
* returns the highest score read
* @param how_many - the quantity of test scores to read; must be nonnegative
* @return the maximum test score */
public double computeMaxScore(int how_many)
Add this method to class ExamStatistics and modify the main method
you just wrote to invoke computeMaxScore instead.
(d) Write this method and include it within class ExamStatistics:
/** computeBetterAverage computes the average of test scores submitted
* by a user _but discards_ the lowest score in the computation.
* @param how_many - the quantity of test scores to read; must be > 1
* @return the average of the best (how_many - 1) test scores */
int t = 4;
int count = 2;
while ( count <= 4 )
{ t = t * 2;
count = count + 1;
}
3. Use the pattern for definite iteration to write a loop that displays this output,
all on one line: 88 77 66 55 44 33 22 11
314
(a) uses a loop to ask the user to type four words, one word at a time, e.g.,
I
like
my
dog
(b) shows a dialog that displays the words listed in reverse order on one line,
e.g.,
dog my like I
(Note: if b > a holds true, then define product(a, b) = 1.) For example,
product(3, 6) is 3 * 4 * 5 * 6 = 360.
(c) A famous variation on iterated product is factorial; for a nonnegative
integer, m, its factorial, m!, is defined as follows:
0! = 1
n! = 1 * 2 * ... * n, for positive n
i. /** sine calculates the sine value of its argument, using the formula
* sin(x) = x - (x^3/3!) + (x^5/5!) - (x^7/7!) + ... - (x^n/19!)
* @param x - the value, in radians, whose sine is desired
* (i.e., sine(0)=0, sine(pi/2)=1, sine(pi)=0, sine(3pi/2)=-1, etc.)
* @return the sine as calculated by the formula */
public double sine(double x)
(Note: use Math.pow(a,b) to compute ab .) Compare the answers your
method produces to the ones produced by the method, Math.sin(...).
ii. /** cosine calculates the cosine value of its parameter, using the formula
* cosin(x) = 1 - (x^2/2!) + (x^4/4!) - (x^6/6!) + ... - (x^20/20!)
* @param x - the value, in radians, whose cosine is desired
* @return the cosine as calculated by the formula */
public double cosine(double x)
Assuming that the size of the bulls-eye is also set by the user, we can use the definite
iteration pattern to paint each stripe, one at a time, from the outermost to the
innermost. Three variables will be needed to control the painting:
• count, which remembers how many circles have been drawn;
• color, which remembers the color of the next circle to paint;
• diameter, which remembers the diameter of the next circle to paint.
316
Why do we require these three variables? Obviously, to paint one ring, we must know
the ring’s diameter and color. If we wish to paint multiple rings, we must remember
how many rings we have painted already, so that we know when it is time to stop.
The three variables are used in the painting algorithm:
1. Set count equal to 0, set color equal to red, and set diameter equal to the
bulls-eye’s size.
2. While count not yet equals rings (the total number of rings desired), do the
following:
• In the center of the graphics window, paint a filled circle of the appropriate
diameter with the current color.
• Revise all variables: increment count by one, make the diameter smaller
(so that the next ring is painted smaller than this one), and reset the color
(if it’s red, make it white; if it’s white, make it red).
These are the basic steps, although the details for calculating the rings’ exact positions
are omitted for the moment.
Perhaps we write the algorithm as a method, so that the method can be used at
will to paint bulls-eyes at whatever position, number of rings, and diameter that we
desire: Figure 2 displays the Java method that is parameterized on these arguments.
The method in Figure 2 is used as a helper method in the class that displays the
graphics window—see Figure 3. To use class BullsEyeWriter, we would construct a
new object such as
Exercises
1. Construct this object:
Explain why it is important that the bulls-eye’s circles are painted from the
largest to the smallest; explain why the innermost ring (a circle, actually) has
a width that is almost twice as large as all the other rings. (Hint: insert a
System.out.println(diameter) statement into the paintBullsEye method to
monitor the drawing of the rings.)
7.4 Nontermination
Recall the computeAverage method in Figure 1, which read a sequence of exam scores,
summed them, and computed their average. What happens when we invoke the
method with a negative integer, e.g., computeAverage(-1)? Indeed, the invocation
requests exam scores forever, summing the scores without ceasing, and we will see an
indefinite produced,
count = 1; total = 8
count = 2; total = 13
count = 3; total = 19
count = 4; total = 26
count = 5; total = 30
...
assuming that the user is patient enough to submit a never-ending sequence of scores!
The loop inside computeAverage iterates indefinitely because its test expression is
always true. Such behavior is called nontermination, or more crudely, infinite looping
or just “looping.” A nonterminating loop prevents the remaining statements in the
program from executing, and in this case, the method from computing the average
and returning an answer.
Although the method’s header comment tells us to supply only nonnegative pa-
rameters to computeAverage, the method might defend itself with a conditional state-
ment that checks the parameter’s value:
/** computeAverage computes the average of test scores submitted by a user
* @param how_many - the quantity of test scores to read; must be nonnegative
* @return the average of the test scores
* @throw RuntimeException, if how_many is negative */
public double computeAverage(int how_many)
320
{ if ( how_many <= 0 )
{ throw new RuntimeException("computeAverage error: negative quantity"); }
double total_points = 0.0; // the total of all test scores
int count = 0; // the quantity of tests read so far
while ( count != how_many )
{ ... see Figure 1 for the loop’s body ... }
return (total_points / how_many);
}
The initial conditional statement throws an exception to prevent looping.
Often, nontermination is an unwanted behavior, and unfortunately there is no
mechanical technique that can verify that a given loop must terminate, but the section
titled “Loop Termination,” included as an optional section at the end of the Chapter,
presents a technique that helps one prove loop termination in many cases.
If one of your Java applications appears to have infinite looping, you can terminate
it: When using an IDE, select the Stop or Stop Program button; when using the JDK,
press the control and c keys simultaneously.
Finally, we should note that a while-loop’s test can sometimes be written cleverly
so that looping becomes impossible. Consider again the loop in Figure 1, and say
that we rewrite the loop’s test so that the Figure reads as follows:
public double modifiedComputeAverage(int how_many)
{ double total_points = 0.0;
int count = 0;
while ( count < how_many )
{ String input = JOptionPane.showInputDialog("Type next exam score:");
int score = new Integer(input).intValue();
total_points = total_points + score;
count = count + 1;
System.out.println("count = " + count + "; total = " + total_points);
}
return (total_points / how_many);
}
Now, if how many receives a nonpositive value, then the loop’s test fails immediately,
and the loop executes zero iterations.
But the simple alteration is imperfect, because it allows the method to continue
its execution with an improper value for how many and compute an erroneous result:
If how many holds a negative number, then the method computes that the exam av-
erage is 0.0, which is nonsensical, and if how many holds zero, the result is even more
surprising—try modifiedComputeAverage(0) and see.
Perhaps looping is unwanted, but under no conditions do we want a method that
returns a wrong answer, either. For this reason, you should take care when altering a
while-loop’s test to “ensure” termination; the alteration might cause a wrong answer
to be computed, travel to another part of the program, and do serious damage.
7.5. INDEFINITE ITERATION: INPUT PROCESSING 321
Exercises
1. Write this method, which is is designed to execute forever:
Now, write an application that invokes it. Test the application for as long as
you have the patience to do so.
• If the user pressed Cancel, then note this and do no further computation
within the loop;
• else the user entered an exam score, so add it to the total and add one to
the count of exams read.
To write the loop’s test expression, we will use a boolean variable, processing, to
remember whether the user has pressed the Cancel button, signalling the desire to
quit. This extra variable gives us a clever way of writing the loop:
This pattern was used in Figure 4 and can be profitably used for most input-processing
applications. The loop is an example of indefinite iteration, because when the loop
starts, it is not decided how many iterations will occur until termination. Note that
indefinite iteration is different from nontermination—assuming that there are finitely
many input transactions, the loop will terminate!
Exercises
1. Explain what this method does:
int total = 0;
while ( processing )
{ String s = JOptionPane.showInputDialog("Type an int:");
int i = new Integer(s);
if ( i < 0 )
{ processing = false; }
else { total = total + i; }
}
JOptionPane.showMessageDialog(null, total);
}
The user types the lines, one at a time, into input dialogs. When the user types
presses Cancel or when the user presses just the Enter key by itself (with no
text typed), the program prints in the command window all the lines the user
has typed and halts. (Hint: You can save complete lines of text in a string
variable as follows:
String s = "";
...
s = s + a_line_of_text + "\n";
String s = "abcde";
int i = s.length();
String s = "abcde";
char c = s.charAt(3);
1. Set index to 0.
2. While ( c is not yet found, and there are still unexamined characters within s
), do the following:
The loop’s test consists of two boolean expressions, combined by conjunction (&&),
and the loop terminates when either of the conjuncts becomes false.
This algorithm is realized in Figure 5.
The loop’s invariant property tells us how the loop operates: As long as found is
false, c is not found in the searched prefix of s; when found becomes true, the search
has succeeded.
The loop might terminate one of two ways:
• !found becomes false, that is, variable found has value true. By Clause (1) of
the invariant, we know that c is found at position index in s.
• !found stays true, but index < s.length() becomes false. By Clause (2) of
the invariant, we know that c is not in the entire length of string s.
These facts are crucial to returning the correct answer at the end of the function;
study the above until you understand it thoroughly.
A string is a kind of “collection” or “set” of characters. In the general case, one
searches a set of items with the following pattern of while-loop:
boolean item_found = false;
DETERMINE THE FIRST ‘‘ITEM’’ TO EXAMINE FROM THE ‘‘SET’’;
while ( !item_found && ITEMS REMAIN IN THE SET TO SEARCH )
{ EXAMINE AN ITEM;
if ( THE ITEM IS THE DESIRED ONE )
{ item_found = true; }
else { DETERMINE THE NEXT ITEM TO EXAMINE FROM THE SET; }
}
326
• !item found evaluates to false. This means the search succeeded, and the
desired item is the last ITEM examined.
• ITEMS REMAIN evaluates to false. This means the search examined the entire
set and failed to locate the desired item.
The search through the set, {n/2, (n/2) - 1, ..., 3, 2}, terminates if we find a divisor
that produces a remainder of 0 or if we search the entire set and fail. Figure 6 shows
the completed method.
Exercises
1. Modify method findChar in Figure 3 so that it locates the rightmost occurrence
of character c in string s. Remember to revise the loop’s invariant.
328
7.6 For-Statements
Definite-iteration loops pervade computer programming. When we use this pattern
of definite iteration,
there is a special loop statement in Java, called a for-statement, that we can use to
tersely code the above pattern; it looks like this:
The semantics of the for-statement is exactly the semantics of the definite iteration
pattern, but there is a small technical point: The scope of the declaration of i extends
only as far as the for-statement’s body—the statements following the for-statement
cannot examine i’s value. If it is important that i’s value be available after the loop
terminates, then an extra declaration must be prefixed to the loop:
int i;
for ( i = INITIAL VALUE; TEST ON i; INCREMENT i; )
{ EXECUTE LOOP BODY; }
// because i is declared before the loop starts, i’s value can be read here
Here is the for-loop that corresponds to the while-loop we saw at the beginning
of this Chapter, which prints the decimal values of the reciprocals, 1/2 to 1/20:
int denominator = 2;
while ( denominator <= 20 )
{ System.out.println("1/" + denominator + " = " + (1.0 / denominator));
denominator = denominator + 1;
}
There are two advantages in using the for-statement for definite iteration:
• The for-statement suggests to the reader that the loop is a definite iteration.
Exercises
1. Rewrite the computeAverage method in Figure 1 so that it uses a for-statement.
4. Rewrite into for-loops the while-loops you wrote in the answers to the Exercises
for the “Definite Iteration” Section
The “for” keyword in the algorithm suggests that a for-statement is the best way to
write the required loop. Similarly, printing the multiplications, i * 0, i * 1, ... i *
4, can be done by varying the second operand over the range, 0..4:
The completed algorithm uses its outer loop to count and print the rows of mul-
tiplications and uses its inner loop to print the individual multiplications on each
row:
Note the uses of print and println to format the rows of the table.
7.7. NESTED LOOPS 331
Drawing a Chessboard
How might we paint an n-by-n chessboard in a graphics window?
This task is also a table-printing problem, where the “values” in the table are red
and white squares. To understand which squares should be red and which should be
white, consider this numbering scheme for the squares, which was suggested by the
multiplication table seen earlier:
0, 0 0, 1 0, 2 ... 0, n-1
1, 0 1, 1 1, 2 ... 1, n-1
...
n-1, 0 n-1, 1 n-1, 2 ... n-1, n-1
Assuming that the board’s upper-left corner (the one numbered by 0,0) is a red square,
then it is easy to calculate the color of every square: If the square’s number is i,j,
then the square is red when i + j is even-valued; otherwise, it is white (when i + j
is odd-valued).
We write the algorithm for painting the board by imitating the algorithm for
printing the multiplication table:
• for j varying from 0 to n-1, do the following: paint the square numbered
i, j: If i + j is even-valued, paint a red square; otherwise, paint a white
square.
332
Alphabetizing a String
When a set of items must be arranged or reordered, nested loops often give a solu-
tion. Given a string, s, say that we must construct a new string that has all of s’s
characters but rearranged in alphabetical order. For example, if s is butterfly, then
its alphabetized form is beflrttuy.
The algorithm for alphabetization will copy the characters, one by one, from string
s into a new, alphabetized string, which we call answer. Here is an initial algorithm:
2. Working from left to right, for each character in s, copy the character into its
proper position in answer.
7.7. NESTED LOOPS 333
The use of “for” in Step 2 suggests that a for-statement might be used to examine
and extract the characters in s, say, as s.charAt(0), s.charAt(1), and so on:
answer = "";
for ( int i = 0; i != s.length(); i = i + 1 )
// invariant: characters at indices 0..i-1 have been inserted into answer
{ insertAlphabetically(s.charAt(i), answer); }
The body of the loop contains a helper method, insertAlphabetically, which will
place its first argument, the character, into the correct position within its second
argument, answer, so that answer remains alphabetized.
What is the algorithm for insertAlphabetically? Let’s consider the general
probem of inserting a character, c, into its proper position in an alphabetized string,
alpha. This is in fact a searching problem, where we search alpha from left to right
until we find a character that appears later in the alphabet than does c. We adapt
the searching pattern for loops seen earlier in this Chapter:
2. While ( searching for c position and there are still characters in alpha to
examine ), do the following
3. Insert c into alpha in front of the character at position index within alpha.
Figure 8 shows the final versions of the two methods developed for generating the
complete, alphabetized string. The public method, alphabetize, uses a for-statement
to systematically copy each character in the orginal string into the alphabetized string.
Helper method insertAlphabetically uses a searching loop to find the correct
position to insert each character into the alphabetized string. When the searching
loop finishes, local variable index marks the position where the character should be
inserted. The insertion is done by the last statement within insertAlphabetically,
which makes clever use of the substring method:
return alpha.substring(0, index) + c + alpha.substring(index, alpha.length());
That is, alpha.substring(0, index) is the front half of string alpha, from character
0 up to character index, and alpha.substring(index, alpha.length()) is the back
half of alpha, from character index to the end.
Note also within insertAlphabetically that the <= operation is used to compare
two characters—for example, ’a’ <= ’b’ computes to true but ’b’ <= ’a’ is false.
334
Exercises
1. Write nested loops that generate the addition table for 0+0 up to 5+5.
0 0
1 0 1 1
2 0 2 1 2 2
3 0 3 1 3 2 3 3
0 3 0 2 0 1 0 0
1 3 1 2 0 1
2 3 2 2
3 3
int i = 2;
while ( i != 0 );
{ i = i - 1; }
fails to terminate because the semicolon after the test causes the Java compiler to read
the code as while ( i != 0 ) {}; { i = i - 1; }—the loop has an empty body.
Problems also arise when a loop’s test is carelessly formulated. For example, this
attempt to compute the sum, 1 + 2 + ... + n, fails,
int total = 0;
int i = 0;
while ( i <= n )
{ i = i + 1;
total = total + i;
}
because the loop iterates one time too many. This form of error, where a loop iterates
one time too many or one time too few, is commonly made, so be alert for it when
testing the loops you write.
A related problem is an improper starting value for the loop counter, e.g.,
336
int total = 0;
int i = 1;
while ( i <= n )
{ i = i + 1;
total = total + i;
}
double d = 0.0;
while ( d != 1.0 )
{ d = d + (1.0 / 13);
System.out.println(d);
}
should terminate in 13 iterations but in reality does not due to computer arithmetic,
where fractional numbers like 1/13 are imperfectly represented as fractions in decimal
format.
Formulating test cases for loops is more difficult than formulating test cases for
conditionals, because it is not enough to merely ensure that each statement in the
loop body is executed at least once by some test case. A loop encodes a potentially
infinite number of distinct execution paths, implying that an infinite number of test
cases might be needed. Obviously, no testing strategy can be this exhaustive.
In practice, one narrows loop testing to these test cases:
• Which test cases should cause the loop to terminate with zero iterations?
• Which test cases should cause the loop to terminate with exactly one iteration?
• Which “typical” test cases should cause the loop to iterate multiple times and
terminate?
• Which test cases might cause the loop to iterate forever or produce undesirable
behavior?
Test cases of all four forms are applied to the loop code.
One more time, this attempt to compute the summation 1 + 2 + ... + n,
7.8. WRITING AND TESTING LOOPS 337
might be tested with n set to 0 (which we believe should cause immediate termination),
set to 1 (should cause termination in one iteration), set to, say, 4 (a typical case that
requires multiple iterations), and set to a negative number, say, -1, (which might cause
unwanted behavior). These test cases quickly expose that the test expression forces
termination one iteration too soon. Subtle errors arise when a loop’s test expression
stops the loop one iteration too soon or one iteration too late; testing should try to
expose these “boundary cases.”
A more powerful version of loop testing is invariant monitoring: If you wrote an
invariant for the loop, you can monitor whether the invariant is holding true while the
loop iterates. To do this, insert inside the loop one or more println statements that
display the values of the variables used by the loop. (Or, use an IDE to halt the loop
at each iteration and display the values of its variables.) Use the variables’s values to
validate that the loop’s invariant is holding true. If the invariant is remaining true,
this gives you great confidence that the loop is making proper progress towards the
correct answer.
For example, consider Figure 1, which displays a loop that sums a sequence of
exam scores. The loop’s invariant,
tells us what behavior to observe at the start (and the end) of each loop iteration.
The println statement placed at the end of the loop in Figure 1 lets us verify that
the invariant property that we believed to be true is in fact holding true during the
execution.
If we monitor a loop’s invariant, like the one in Figure 1, and we find that the
invariant goes false at some interation, this is an indication that either the loop’s code
or the invariant is incorrectly written. In the former case, repair is clearly needed; in
the latter case, we do not understand what our loop should do and we must study
further.
Although loop testing can never be exhaustive, please remember that any testing
is preferable to none—the confidence that one has in one’s program increases by the
number of test cases that the program has successfully processed.
338
(Although the printed page cannot show it, the red ball is travelling from side to side
within the frame.)
A program written in the Model-View-Controller architectural style does best:
The animation is modelled, in this case, by objects that represent the ball and box. A
view paints the model’s current state on the display. Finally a controller monitors the
passage of time and tells the model when to update the ball’s position and repaint it
on the display.
Figure 9 shows the architecture of the animation program. The controller contains
a loop that executes an iteration for each unit of time that passes. At each unit of
time, the loop’s body tells the model to update its state, and it tells the view to
repaint the model; the repeated paintings look like movement. The model and view
have methods that respond to the controller’s requests. Also, the view must ask the
model for its current state each time the model must be painted.
Recall once more the steps we take to design and solve a programming problem:
1. State the program’s behavior, from the perspective of the program’s user.
2. Select a software architecture that can implement the behavior.
3. For each of the architecture’s components, specify classes with appropriate at-
tributes and methods.
7.9. CASE STUDY: BOUNCING BALL ANIMATION 339
Controller
run() View
{ while (true) paintModel()
{ wait one time unit; { model.getState();
model.updateState(); repaint();
view.paintModel(); }
}
}
Model
getState()
updateState()
5. Integrate the classes into the architecture and test the system.
Steps (1) and (2) are not a challenge for building the moving-ball animation: The
behavior of the animation has already been indicated—there is nothing for the user
to do but watch—and the architecture in Figure 9 is a good start towards implement-
ing the desired behavior. The architecture shows that the application has distinct
model, view, and controller subassemblies, so we develop each subassembly separately,
beginning with the model.
We begin with the model subassembly of the animation—What are the model’s com-
ponents? They are of course the ball and the box in which the ball moves. Our first
attempt at specifying the model might place ball and box together in one class, like
this:
340
2. Ask the box that contains the ball whether the ball has come into contact
with one of the box’s horizontal or vertical walls; if it has, then change the
ball’s direction. (For example, if the ball makes contact with a horizontal wall,
then its horizontal direction must reverse; this is done by negating the ball’s
horizontal velocity.)
Because class MovingBall depends on class Box, we test the classes together,
say, by writing a testing program that constructs a box and places the ball in the
middle of the box. Then, we tell the ball to move and we print its new position. Of
course, we should move the ball until it comes into contact with a wall so that we
can see how the ball’s direction changes as a result. Here is some testing code:
Box box = new Box(50); // 50 pixels by 50 pixels in size
MovingBall ball = new MovingBall(25, 25, 10, box); // radius is 10 pixels
while ( true )
{ ball.move(1); // 1 unit of elapsed time
System.out.println("x = " + ball.xPosition()
+ "; y = " + ball.yPosition());
}
344
AnimationWriter
paintComponent(Graphics g)
MovingBall BallWriter { box.paint(g);
(see Table 10) paint(Graphics g) ball.paint(g);
}
Box BoxWriter
(see Table 11) paint(Graphics g)
When the animation must be painted, an AnimationWriter is asked to paint the entire
picture on a panel. This class asks the BoxWriter to paint the box and it asks the
BallWriter to paint a ball. The latter two classes query the accessor methods of their
respective model components for the state of the box and ball.
There is little more to specify about the view classes, so we study their codings.
Figure 14 presents class AnimationWriter; notice how this class gives its graphics
pen to class BoxWriter and then to class BallWriter, presented in Figure 15, so
that the box and ball are painted on the window with which the graphics pen is
associated.
We can test the view classes with the model we have already built and tested.
Figure 16 gives the coding of this loop and also the coding of the start-up class that
contructs all the animation’s objects.
The while-loop in the run method of class BounceController is intended not to
terminate, hence its test is merely true. To present an illusion of movement, the same
7.9. CASE STUDY: BOUNCING BALL ANIMATION 345
import java.awt.*;
/** BallWriter displays a moving ball */
public class BallWriter
{ private MovingBall ball; // the (address of the) ball object displayed
private Color balls color; // the ball’s color
technique from motion pictures is used: Method delay pauses the program slightly
(here, 20 milliseconds, but this should be adjusted to look realistic for the processor’s
speed) before advancing the ball one more step. The method uses a built-in method,
Thread.sleep, which must be enclosed by an exception handler. These details are
unimportant, and the delay method may be copied and used whereever needed.
The moving-ball animation is a good start towards building more complex anima-
tions, such as a billiard table or a pinball machine. Indeed, here is a practical tip: If
you are building a complex animation, it helps to build a simplistic first version, like
the one here, that implements only some of the program’s basic behaviors. Once the
first version performs correctly, then add the remaining features one by one.
For example, if our goal is indeed to build an animated pinball machine, we should
design the complete pinball machine but then design and code an initial version that
is nothing more than an empty machine (a box) with a moving pinball inside it. Once
the prototype operates correctly, then add to the machine one or more of its internal
“bumpers” (obstacles that the ball hits to score points). Once the pinball correctly
hits and deflects from the bumpers, then add the scoring mechanism and other parts.
Each feature you add to the animation causes you to implement more and more of
the attributes and methods of the components. By adding features one by one, you
will not be overwhelmed by the overall complexity of the animation. The Exercises
that follow give a small example of this approach.
Exercises
1. Execute the moving ball animation. Occasionally, you will observe that the
ball appears to bounce off a wall “too late,” that is, the ball changes direction
after it intersects the wall. Find the source of this problem and suggest ways to
improve the animation.
2. Revise the moving-ball animation so that there are two balls in the box. (Do
not worry about collisions of the two balls.)
3. If you completed the previous exercise, rebuild the animation so that a colli-
sion of the two balls causes both balls to reverse their horizontal and vertical
directions.
4. Place a small barrier in the center of the box so that the ball(s) must bounce
off the barrier.
7.10 Recursion
No doubt, you have seen a “recursive picture,” that is, a picture that contains a
smaller copy of itself that contains a smaller copy of itself that contains.... (You can
348
simulate this by hanging two large mirrors on opposite walls, standing in the middle,
and looking at one of the mirrors.) It is self-reference that characterizes recursion,
and when a method contains a statement that invokes (restarts) itself, we say that
the method is recursively defined.
In the bank-accounting example in Chapter 6. we used recursion to restart a
method, but now we study a more sophisticated use of recursion, where a complex
problem is solved by solving simpler instances of the same problem.
Here is an informal example of recursive problem solving: How do you make a
three-layer cake? A curt answer is, “make a two-layer cake and top it with one more
layer!” In one sense, this answer begs the question, because it requires that we know
already how to make a cake in order to make a cake, but in another sense, the answer
says we should learn how to make first a simpler version of the cake and use the result
to solve the more complex problem.
If we take seriously the latter philosophy, we might write this recipe for making a
cake with some nonzero number of layers, say, N+1:
To make an (N+1)-layer cake, make an N-layer cake and top it with one more layer.
The reciple simplifies (N+1)-layer cake making into the problem of N-layer cake making.
But something is missing—where do we begin cake making? The answer is startlingly
simple:
350
The solution to the original problem becomes clearer—to make a three-layer cake,
we must first make a two layer cake (and then top it with one more layer); but to
make a two-layer cake, we must first make a one-layer cake (and then top it with one
more layer); but to make a one-layer cake, we must make a zero-layer cake (and then
top it with one more layer); and we make a “zero-layer cake” from just an empty
pan. Perhaps this explanation is a bit wordy, but all the steps are listed. A diagram
displays the steps more pleasantly:
make a 3-layer cake
8
1 make a 2-layer cake
and top it with one more layer 7
2 make a 1-layer cake
and top it with one more layer 6
3 make a 0-layer cake
and top it with one more layer 5
4 place an empty cake pan on the table
By following the arrows, we understand how the problem of making a multi-layer case
is decomposed into simpler problems (see the downwards arrows) and the result is
assembled from from the solutions—the pan and layers (see the upwards arrows).
The strategy of solving a task by first solving a simpler version of the task and
building on the simpler solution is called problem solving by recursion or just recursion,
for short. When we solve a problem by recursion, we can approach it as a task of
writing simultaneous algebraic equations, like these, for cake making:
bakeACake(0) = ...
bakeACake(N + 1) = ... bakeACake(N) ..., where N is nonnegative
The first equation states how a 0-layer cake is made, and the second explains how an
N+1-layer cake is made in terms of making an N-layer one. Based on the narrative for
cake making, we would finish the equations like this:
The algorithm can be used to generate the steps for a 3-layer cake:
bakeACake(3)
=> bakeACake(2)
and top it with one more layer
=> (bakeACake(1)
and top it with one more layer)
7.10. RECURSION 351
A recursive algorithm can be reformatted into a Java method that uses recursion.
Perhaps there is a Java data type, named Cake, such that new Cake() constructs a
0-layer cake. Perhaps Cake objects have a method, named addALayer, which places
one more layer on a cake. For fun, a version of class Cake is displayed in Figure 17.
Now, we can use class Cake to reformat the two algebraic equations for cake
making into the Java method displayed in Figure 18.
Now, the assignment,
Cake c = bakeACake(0);
because this triggers bakeACake(2), which invokes bakeACake(1), which starts bakeACake(0).
The last invocation constructs a new cake object, which is then topped by three layers
352
bakeACake
{ int layers == 3 Cake result == ?
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
The test marked by >1??? computes to false, meaning that the next step is a
7.10. RECURSION 353
recursive invocation:
bakeACake
{ int layers == 3 Cake result == ?
. . .
else { > result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
bakeACake
{ int layers == 3 Cake result == ?
. . .
else { > result = AWAIT RESULT
result.addALayer();
}
return bakeACake
} Cake result == ?
{ int layers == 2
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
This shows that a recursive method invocation works like any other invocation: actual
parameters are bound to formal parameters, and a fresh copy of the method’s body
executes.
this configuration:
bakeACake
{ int layers == 3 Cake result == ?
bakeACake
{ int layers == 2 Cake result == ?
bakeACake
{ int layers == 1 Cake result == ?
bakeACake
{ int layers == 0 Cake result == ?
> if ( layers == 0 )
{ result = new Cake(); }
else { result = bakeACake(layers - 1);
result.addALayer();
}
return result;
}
Because the conditional’s test evaluates to true, the conditional’s then-arm constructs
a new Cake object and places its address, a1, in the local variable, result.
a1 : Cake
bakeACake
{ int layers == 3 Cake result == ?
bakeACake
{ int layers == 2 Cake result == ?
bakeACake
{ int layers == 1 Cake result == ?
bakeACake
{ int layers == 0 Cake result == a1
. . .
> return result;
}
7.10. RECURSION 355
The 0-layer cake is returned as the result, and the activation record for the completed
invocation disappears.
a1 : Cake
bakeACake
{ int layers == 3 Cake result == ?
bakeACake
{ int layers == 2 Cake result == ?
bakeACake
{ int layers == 1 Cake result == a1
. . .
> result.addALayer();
}
return result;
}
Next, result.addALayer() adds a layer to the cake, and the one-layer cake is returned
as the result:
a1 : Cake
bakeACake
{ int layers == 3 Cake result == ?
bakeACake
{ int layers == 2 Cake result == a1
. . .
> result.addALayer();
}
return result;
}
This process repeats until the result variable at the initial invocation receives the
cake object at address a1 and places the third layer on it.
356
The example shows how each recursive invocation decreases the actual parameter
by 1, halting when the parameter has 0. When an invoked method returns its result,
the result is used by the caller to build its result. This matches our intuition that
recursion is best used to solve a problem in terms of solving a simpler or smaller one.
Admittedly, the cake-making example is contrived, and perhaps you have already
noticed that, given class Cake, we can “make” an N-layer cake with this for-loop:
But the style of thinking used when writing the for-loop is different than the style
used when writing the recursively defined method.
The loop in the previous paragraph should remind you that an incorrectly written
repetitive computation might not terminate—this holds true for loops and it holds
true for recursively defined methods as well. It is not an accident that the cake-making
example used recursive invocations with an argument that counted downward from
an nonnegative integer, N, to 0. As a rule:
A recursively defined method should use a parameter whose value decreases at each
recursive invocation until the parameter obtains a value that stops the recursive
invocations.
The “counting-down” pattern suggested above will protect us from making the foolish
mistake of writing a recursively defined method that restarts itself forever.
• You must visit three cities, a, b, and c, next week. What are the possible orders
in which you can visit the cities, and which of these orders produces the shortest
or least-expensive trip?
• You must complete three courses to finish your college degree; what are the
possible orders in which the courses might be taken, and which ordering allows
you to apply material learned in an earlier course to the courses that follow?
7.11. COUNTING WITH RECURSION 357
• Three courses that are taught at the same hour must be scheduled in three
classrooms. What are the possible assignments of courses to classrooms, and
which assignments will accommodate all students who wish to sit the courses?
• A printer must print three files; what are the orders in which the files can be
printed, and which orderings ensure that the shorter files print before the longer
files?
It is valuable for us to know how to generate and count permutations. To study
this problem, say that we have n distinct letters, and we want to count all the per-
mutations of the letters. The following observation holds the secret to the solution:
By magic, say that we already know the quantity of permutations of n-1 distinct
letters—say there are m of them, that is, there are distinct words, word 1, word
2, ..., word m, where each word is n-1 letters in length.
Next, given the very last, nth letter, we ask, “how many permutations can we
make from the m words using one more letter?” The answer is, we can insert the
new letter it into all possible positions in each of the m words: We take word
1, which has length n-1, and we insert the new letter in all n possible positions
in word 1. This generates n distinct permutations of length n. We do the same
for word 2, giving us n more permutations. If we do this for all m words, we
generate m sets of n permutations each, that is, a total of n * m permutations
of length n. This is the answer.
Finally, we note that when we start with an alphabet of one single letter, there is
exactly one permutation.
The above insights tell us how to count the quantity of permutations:
For an alphabet of just one letter, there is just one permutation:
number_of_permutations_of(1) = 1
If the alphabet has n+1 letters, we count the permutations made from n letters, and
we use the technique described above to count the permutations generated by the
addition of one more letter:
number_of_permutations_of(n+1) = (n + 1) * number_of_permutations_of(n)
These two equations define an algorithm for computing the quantity of permu-
tations. We can use this algorithm to quickly count the permutations of 4 distinct
letters:
number_of_permutations_of(4)
= 4 * number_of_permutations_of(3)
= 4 * ( 3 * number_of_permutations_of(2) )
= 4 * ( 3 * ( 2 * number_of_permutations_of(1) ))
= 4 * 3 * 2 * 1 = 24
358
Calculations with this algorithm quickly convince us that even small alphabets gen-
erate huge quantities of permutations.
Not surprisingly, permutation counting occurs often in probability and statistics
theory, where it is known as the factorial function. It is traditional to write the
factorial of a nonnegative integer, n, as n!, and calculate it with these equations:
0! = 1
In practice, many counting problems are best solved with recursive techniques.
If the recursive solution has a simple form, like those seen in the cake-making and
factorial examples, then it may be possible to rewrite the recursive implementation
into a loop. But the next section shows counting problems where solutions with
multiple recursions are needed; such solutions do not always convert to loop code.
that is, the number of flies produced by a newly hatched fly is just the fly itself.
These two equations generate a Java method that contains two recursions:
public int fliesAtGeneration(int n)
{ int answer;
if ( n < 0 )
{ throw new RuntimeException("error: negative argument"); }
else { if ( n == 0 ) // is it a newly hatched fly?
{ answer = 1; }
else { int first_egg_produces = fliesAtGeneration(n - 1);
int second_egg_produces = fliesAtGeneration(n - 1);
answer = first_egg_produces + second_egg_produces + 1;
}
return answer;
}
7.11. COUNTING WITH RECURSION 361
When executing the innermost else-clause, the computer computes the first recursion
completely to its integer result before it starts the second recursive invocation. Be-
cause each recursive invocation uses an argument that is smaller than the one given
to the caller method, all the recursions will terminate.
Now that we have used recursive problem solving, we can readily see that the two
recursions in the method’s innermost else-clause can be replaced by one:
This simplifies the method and even allows us to rewrite the method’s body into a
loop, if we so desire.
Although the fly-counting example is a bit contrived, there is a related counting
problem that is not: the Fibonacci function. The Fibonacci number of a nonnegative
integer is defined in the following way:
Fib(0) = 1
Fib(1) = 1
This recursively defined algorithm was proposed in the 13th century for counting
the number of pairs of rabbits produced by one initial pair, assuming that a pair of
rabbits takes one month to mature and from then on the pair produces one more pair
of rabbits every month thereafter! Since that time, Fibonacci numbers have arisen in
surprising places in problems in biology, botany, and mathematics.
The algorithm for the Fibonacci function has an easy coding as a method that
uses two recursions in its body. It is a fascinating project to rewrite the method so
that it uses only one recursion.
Exercises
1. Write an application that prints the values of 3!, 6!, 9!, ..., 18!.
2. Remove from the factorial method the first conditional, the one that tests n <
0 || n > 20. Then, try to compute factorial(20), factorial(21), factorial(99),
and factorial(-1).
3. With a bit of thought, you can use a while-loop to program the recursive equa-
tions for factorial. Do this.
4. Implement the Fibonacci function with a method that contains two recursions,
and try your function with the actual parameters 20; 30; 35; 40. Why does the
computation go so slowly for the test cases?
362
(Incidentally, the Fibonacci function was proposed in the 13th century for count-
ing the number of pairs of rabbits produced by one initial pair, assuming that a
pair of rabbits takes one month to mature and from then on the pair produces
one more pair of rabbits every month thereafter!)
add(0, b) = b
add(a, b) = add(a-1, b) + 1, when a > 0
A(0, n) = n + 1
A(m, 0) = A(m-1, 1), when m > 0
A(m, n) = A(m-1, A(m, n-1)), when m > 0 and n > 0
8. Say that a word is sorted if all its letters are distinct and are listed in alphabetical
order, e.g., "bdez" is sorted but "ba" and "bbd" are not. Write a recursive
algorithm that calculates the number of sorted words of length n or less that
one can create from n distinct letters.
7.12. DRAWING RECURSIVE PICTURES 363
9. Solve this modified version of the fly-generation problem: Say that an “ordi-
nary” fly lays exactly two eggs. One of the eggs produces another “ordinary”
fly, but the other egg produces a “queen” fly, which can lay, for every generation
thereafter, two eggs that hatch into ordinary flies.
Starting from one ordinary fly, how many flies are produced from it in n gener-
ations? Starting from one queen fly, how many flies are produced from it in n
generations?
(Hint: You will need one recursive algorithm that counts the flies produced
from one ordinary fly and one recursive algorithm that counts the number of
children flies produced from one queen fly.)
You might give a painter a bucket of paint, a brush, and these recursive instructions:
Paint a slightly smaller egg-picture in the background and next paint a big egg in the
front!
More precisely stated, the algorithm goes as follows:
1. First, paint a completed an egg-picture, sized slightly smaller than the desired
size of the final picture. (If this makes the background eggs have zero size, then
don’t bother with it.)
364
2. Next, paint the largest egg, of the desired size, in the foreground.
3. Finally, draw a border around the largest egg and its background.
The work is done by method paintEggField, which paints two triangular fields of
eggs, one to the left rear and one to the right rear, of a lead egg. The recursively
defined method, paintEggField, uses parameter layer to count downwards by one
each time the rear layers of eggs are painted. Because the left rear egg field is painted
after the right rear field, the left field’s eggs rest on the top of the right field’s.
Exercises
1. Revise class RecursivePictureWriter by removing the border around each pic-
ture; next, move the eggs so that they rest in the left corner of the picture rather
than the right corner.
7.12. DRAWING RECURSIVE PICTURES 365
2. Write a class that generates a circle of diameter 200 pixels, in which there is
another circle of 0.8 size of the first, in which there is another circle of 0.8 size
of the second, ..., until the circles shrink to size 0.
3. To better understand class EggFieldWriter, do the following. First, replace
method paintAnEgg with this version, which draws a transparent egg:
Retry EggFieldWriter.
4. Write a recursive definition, like that for the factorial and Fibonacci functions,
that defines precisely how many eggs are drawn in an egg field of n layers. How
many eggs are drawn by the output view in Figure 6?
7.13 Summary
This chapter presented concepts about repetition:
7.13. SUMMARY 367
New Constructions
• while-statement (from Figure 1):
int total = 0;
int count = 0;
while ( count != n )
{ System.out.println("count = " + count + "; total = " + total);
count = count + 1;
total = total + count;
}
• for-statement:
int total = 0;
for ( int count = 1; count <= n; count = count + 1 )
{ total = total + count;
System.out.println("count = " + count + "; total = " + total);
}
New Terminology
• loop: a statement (or sequence of statements) that repeats computation, such
as a while-statement
• iteration: repetitive computation performed by a loop; also refers to one execu-
tion of a loop’s body
• definite iteration: iteration where the number of the loop’s iterations is known
the moment the loop is started
• indefinite iteration: iteration that is not definite
• nontermination: iteration that never terminates; also called “infinite looping”
• invariant property: a logical (true-false) fact that holds true at the start and at
the end of each iteration of a loop; used to explain the workings of a loop and
argue the loop’s correctness
• loop counter: a variable whose value remembers the number of times a loop has
iterated; typically used in the loop’s test to terminate iteration. Also called a
loop control variable.
• recursive definition: a method that invokes itself, directly or indirectly; used to
solve a problem by recursive invocations to first solve simpler versions of the
problem and next combine the results to compute overall result.
7.13. SUMMARY 369
Points to Remember
• While-loops are used in two forms:
– definite iteration, where the number of times the loop repeats is known
when the loop is started. A standard pattern for definite iteration reads,
int count = INITIAL VALUE;
while ( TEST ON count )
{ EXECUTE LOOP BODY;
INCREMENT count;
}
where count is the loop control variable. The for-statement is used to
tersely code the above pattern:
for ( int count = INITIAL VALUE; TEST ON count; INCREMENT count; )
{ EXECUTE LOOP BODY (DO NOT ALTER count); }
• Inside the design of every loop is an invariant property, which states what the
loop is accomplishing as it progresses towards completion. When you design a
loop, you should also write what you believe is the loop’s invariant property;
this clarifies your thinking and provides excellent documentation of the loop.
• Use a recursively defined method when a problem is solved by using the solution
to a slightly simpler/smaller-valued version of the same problem. Write the
370
2. Write an application that reads a series of nonnegative integers and prints the
list of all the nonzero divisors of the input integer. (Recall that b is a divisor of
a if a divided by b yields a remainder of zero.) The program terminates when
a negative integer is typed; it should behave like this:
3. Write an application that calculates the odds of a person winning the lottery
by guessing correctly all m numbers drawn from a range of 1..n. The formula
that calculates the probability is
probability = m! / product((n-m)+1, n)
7.14. PROGRAMMING PROJECTS 371
(Footnote: Here is the rationale for the probability formula. Consider the task
of matching 3 chosen numbers in the range 1..10; the chances of matching on the
first number drawn at the lottery is of course 3 in 10, or 3/10, because we have
3 picks and there are 10 possible values for the first number. Assuming success
with the first draw, then our chances for matching the second number drawn are
2/9, because we have 2 numbers left and the second number is drawn from the
9 remaining. The chance of matching the third number drawn is 1/8. In total,
our chances of matching all three numbers are (3/10) * (2/9) * (1/8), which
we reformat as (1*2*3) / (8*9*10). A pattern appears: The odds of picking m
numbers out of the range 1..n are (1*2*...*m ) / ((n-m)+1 * (n-m)+2 *...*n)
which is succinctly coded as the formula presented above. End Footnote.)
4. Write an application that can translate between Arabic numbers (ordinary non-
negative integers) and Roman numerals. (Recall that Roman numeral I denotes
1, V denotes 5, X denotes 10, L denotes 50, and C denotes 100. The symbols of
a numeral should be in nonincreasing order, e.g. LXXVIII is 78, but an out-of-
order symbol can be used to subtract from the symbol that follows it, e.g., XCIV
is 94.)
The application must be able to translate any Arabic numeral in the range 1
to 399 into a Roman numeral and vice versa. (Recall that no letter in a roman
number can appear more than three times consecutively, e.g., you must write
IX rather than VIIII for 9.)
s == a b c d e f g
p == c d e
current position == 0
The “current position” remembers the index where the pattern is aligned. Since
the match fails, the search moves one character:
372
s == a b c d e f g
p == c d e
current position == 1
The process proceeds until a match succeeds or the pattern cannot be shifted
further.
7. (a) Design a calendar program, which takes two inputs: a month (an integer
in range 1..12) and a year (for simplicity, we require an integer of 1900
or larger) and presents as its output the calendar for the month and year
specified. We might use the program to see the calendar for December,
2010:
Su Mo Tu We Th Fr Sa
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
The application’s model will need methods to calculate how many days are
in a given month (within a given year), on what day of the week a given
month, year begins, whether a year is a leap year, etc.
(b) Next, revise the application so that, on demand, the calendar prints a
proper French calendar, that is, the days are presented Monday-Tuesday-
...-Sunday (lundi-mardi-mercredi-jeudi-vendredi-dimanche) with the labels,
lu ma me je ve sa di
8. Write a program that implements the word game, “hangman.” Two people
play: The first inserts a “secret word” into the game, and the second tries to
guess the secret word, one letter at a time, before six wrong guesses are made.
After the first player has typed the secret word (e.g., apple), the second sees
the following on the console:
Pick a letter:
7.14. PROGRAMMING PROJECTS 373
The second player guesses a letter, e.g., e. The program replies whether or not
the guess was correct:
Pick a letter:e
Correct!
__
| |
|
|
|
_ _ _ _ e
The program displays the partially guessed word and a “hangman’s pole” upon
which the wrong guesses, drawn as a stick figure, are compiled. The game
continues with the second player making guesses, one by one, until either the
word is guessed correctly, or the six-piece stick figure is displayed:
Pick a letter:x
__
| |
| 0
| /|\
| /\
_ p p _ e
You lose--the word was "apple"!
(Note: this program can be written with judicious use of the charAt and
substring methods for strings; see Table 9, Chapter 3, for details about these
methods.)
9. Euclid’s algorithm for calculating the greatest common divisor of two nonneg-
ative integers is based on these laws:
• GCD(x,y) = x, if x equals y
• GCD(x,y) = GCD(x-y,y), if x > y
• GCD(x,y) = GCD(x,y-x), if y > x
10. Newton’s approximation method for square roots goes as follows: The square
root of a double, n, can be approximated as the limit of the sequence of values,
ni , where
374
• n0 = n
• ni+1 = ((n/ni ) + ni )/2
Write a method that takes as its parameters two doubles: the number n and
a precision, epsilon. The method prints the values of ni until the condition
|nk+1 − nk | < epsilon holds for some k > 0. The result of the method is nk+1 .
(Hint: the Java operation Math.abs(E) computes |E|, the absolute value of E.)
Insert this method into a test program and try it with various values of epsilon.
11. Recall yet again this formula for the monthly payment on a loan of principal,
p (double), at annual interest rate, i (double), for a loan of duration of y (int)
years:
(1 + i)y ∗ p ∗ i
annual payment =
(1 + i)y − 1
monthly payment = annual payment/12.0
The monthly payment history on a loan of principal, p, at annual interest rate,
i, is computed as follows:
• p0 = p
• pj+1 = ((1 + (i/12))pj ) − monthly payment
for j >= 0.
Use these formulas to write an application that that calculates the monthly
payment for a loan and prints the corresponding payment history. Test your
application on various inputs, and explain why the principal is completely repaid
a bit sooner than exactly y full years.
12. Write an output-view class that contains these two methods for formatting
numbers. The first is
The second method is also called format and uses the following interface:
7.14. PROGRAMMING PROJECTS 375
Insert the two methods in a class called Formatter and use the class to print
nicely formatted output, e.g.,
13. Write a program that lets two players play a game of tic-tac-toe (noughts and
crosses). (Hint: the model simulates the the game board, whose internal state
can be represented by three strings, representing the three rows of the board.
Write methods that can insert a move into the board and check for three-in-
a-row. (Note: use the charAt and substring methods for strings; see Table 9,
Chapter 3 for details about substring.)
It is simplest to build the game for only one player at first; then add a second
player. Next, revise the game so that the computer can compete against a
human player.
14. Program an animation of a falling egg. The animation will show, at correct
376
velocity, an egg falling from a great height and hitting the ground.
height = I0 − (0.5 ∗ g ∗ t2 )
where I0 is the egg’s initial height (in meters), t is the time (in seconds) that
the egg has been falling, and g is the acceleration due to gravity, which is
9.81meters/second2 .
7.14. PROGRAMMING PROJECTS 377
The animation should let its user specify the initial height from which the egg
is dropped. When the egg reaches height 0, it must “smash” into the ground.
15. Program an animation of a cannon ball shot into the air: The position, x,y,
of the cannon ball in the air after t seconds have elapsed is defined by these
formulas:
x = initial velocity ∗ cosine(radians angle) ∗ t
y = (initial velocity ∗ sine(radians angle) ∗ t) − ((gravity ∗ t2 )/2)
where initial velocity is the velocity of the ball when it first leaves the cannon,
gravity is the pull of gravity, and radians angle is computed as radians angle =
(degrees ∗ P I)/180 degrees is the angle that the cannon was pointed when it
shot the cannon ball.
Your application should let the user experiment with different initial velocities,
degrees angles, and gravitational constants.
16. Make the cannon-ball animation into a game by drawing a bucket (pail) at a
random spot near the end of the graphics window and letting the user adjust
the velocity and angle of the cannon to try to make her cannon ball fall into
the bucket.
17. Program an animation of a clock—every second, the clock redisplays the time.
(To start, program a digital clock. Next, program a clock with a face and three
moving hands; see Chapter 4 for advice at drawing clock hands.)
|
| v |
| |
-----+ +----
->
-----+ +----
| |
| |
For simplicity, assume that traffic on one street travels only from north to south
and traffic on the other street travels only from west to east. The simulation
maintains at most one car at a time travelling from north to south and at most
one car at a time travelling from east to west. When one car disappears from
view, another car, travelling in the same direction, may appear at the other
end of the street. All cars travel at the same velocity of 10 meters (pixels) per
second.
378
Assume that both roadway are 90 meters in length and that the intersection
has size 10-by-10 meters.
Build the simulation in stages:
(a) First, generate north-south cars only: A new north-south car appears
within a random period of 0-2 seconds after the existing north-south car
disappears from view. Next, add a counter object that counts how many
cars complete their journey across the intersection in one minute.
(b) Next, generate east-west cars: A new east-west car appears within a ran-
dom period of 0-5 seconds after the existing one disappears. Count these
cars as well. (Do not worry about collisions at the intersection.)
(c) Now, program the cars so that a car must stop 20 meters (pixels) before
the intersection if a car travelling in another direction is 20 meters or
closer to entering the intersection. (If both cars are equi-distant from the
intersection, the east-west car—the car on the “right”—has right-of-way.)
Count the total flow of cars in one minute.
(d) Experiment with stop signs at the intersection: A stop sign causes a car to
stop for 1 second, and if another car is within 20 meters of the intersection
at the end of the 1 second, the stopped car must wait until the moving car
crosses the intersection. Place a stop sign on just the east-west street and
count the total traffic flow for one minute. Do the same for a stop sign
just on the north-south street.
(e) Finish the simulation with two stop signs; count cars.
These optional sections expand upon the materials in the chapter. In particular,
correctness properties of loops and recursively defined methods are studied in depth.
7.15. BEYOND THE BASICS 379
A loop that does indefinite iteration must terminate at some point, and often the
decision to terminate is made in the middle of the loop’s body. Java provides a
statement named break that makes a loop quit at the point where the termination
decision is made.
For example, Figure 5 showed how the findChar method used indefinite iteration
to search for the leftmost occurrence of a character in a string. Once the character
is located, a boolean variable, found, is set to true, causing the loop to quit at the
beginning of the next iteration. We can terminate the loop immediately by using a
break statement instead of the assignment, found = true:
public int findChar(char c, String s)
{ int index = 0; // where to look within s for c
while ( index < s.length() )
// invariant: at this program point,
// c is not any of chars 0..(index-1) in s
{ if ( s.charAt(index) == c )
{ break; } // exit loop immediately
else { index = index + 1; }
}
// If the break executed, this means index < s.length() is still true
// at this point and that s.charAt(index) == c.
if ( !(index < s.length()) ) // did the loop terminate normally?
{ index = -1; } // then c is not found in s
return index;
}
The break statement causes the flow of control to move immediately from the middle
of the loop to the first statement that follows the loop. For this reason, the found
variable no longer controls loop termination. This simplifies the loop’s test but forces
us to write a more complex conditional that follows the loop.
When a break statement is inserted into a loop, it means that the loop has more
than one way of exiting—it can exit by failure of its test and by execution of the
break. For this reason, a break statement should be used sparingly and with caution:
insert a break only in a position where the reason for loop exit is perfectly clear
In the above example, the break occurs exactly when s.charAt(index) == c, which
is a crucial property for describing what the loop does. This property is crucial to
the the conditional statement that follows the loop. If you are not completely certain
about inserting a break statement into a loop, then don’t do it.
Finally, note that a break exits only one loop’s body. For example, the following
nested loop appears to make the values of variables i and j vary from 0 to 3. But
the break statement in the body of the inner loop causes that loop to terminate
prematurely:
380
int i = 0;
while ( i != 4 )
{ int j = 0;
while ( j != 4 )
{ if ( i + j == 3 )
{ break; }
j = j + 1;
}
System.out.println("i = " + i + ", j = " + j);
i = i + 1;
}
The loops print
i = 0, j = 3
i = 1, j = 2
i = 2, j = 1
i = 3, j = 0
The above example should make clear that a break statement can make a program
more difficult to understand.
Answer the question: Say that the loop has been iterating for some time; what has it
accomplished so far? An informal but accurate answer is, “the loop has been printing
squares.” Indeed, this is an invariant, and it is useful to document the loop exactly
this way:
while ( i != (n + 1) )
// the loop has been printing squares
{ System.out.println(i * i);
i = i + 1;
}
Someone who is more mathematically inclined might formulate a more precise answer:
The loop has been printing the squares 0*0, 1*1, 2*2, etc. Indeed, the value of its
counter variable, i, states precisely how many squares have been printed:
382
while ( i != (n + 1) )
// the loop has printed the squares 0*0, 1*1, ...up to... (i-1)*(i-1)
{ System.out.println(i * i);
i = i + 1;
}
This is also an invariant, because whether the loop has iterated 2 times, 20 times,
etc., the invariant states exactly what has happened. For example, after 20 iterations,
i has value 21, and indeed the loop has printed from 0*0 up to 20*20. (If the loop
has iterated zero times, i has value 0 and the invariant says the loop has printed from
0*0 up to -1*-1, that is, it has printed nothing so far.)
When the loop terminates, its test, i != (n + 1), evaluates to false. Hence, i has
the value, n+1. But we know that the invariant is still true, and by substituting n+1
for i, we discover that the loop has printed exactly the squares from 0*0 up to n*n.
This exposes the ultimate goal of the loop.
Next, reconsider the summation example and its proposed invariant:
1. basis step: we show the invariant is true the very first time the loop is encoun-
tered.
2. inductive step: We assume the invariant is already holding true at the start of an
arbitrary iteration of the loop, and we show that when the iteration completes
the invariant is still true.
Such a proof style is known as proof by induction on the number of iterations of the
loop.
The proof of the invariant for the summation loop is an induction:
1. When the loop is first encountered, both total and i have value 0, therefore it
is true that total == 0 + ...up to... + 0.
and we show that the statements in the loop’s body update the invariant so
that it holds true again at the end of the iteration:
We use V start to represent the value of variable V at the start of the iteration
and V next to represent the new value V receives during the iteration.
Here is the proof:
We conclude, no matter how long the loop iterates, the invariant remains true.
The previous examples were of definite iteration loops, where the pattern of in-
variant takes the form
In the case of input processing, the natural invariant for the transaction-processing
loop is simply,
Searching loops often have invariants in two parts, because the loops can exit in
two different ways; the general format is
384
Clauses 1 and 2 are necessary because the value of item found can possibly change
from false to true within an iteration.
If you write a loop and you “know” what the loop does, but you find it difficult
to state its invariant, then you should generate execution traces that exhibit values
of the loop’s variables and examine the relationships between the variable’s values at
the various iterations. These relationships often suggest an invariant. For example,
say that we believe this loop computes multiplication of two nonnegative integers a
and b via repeated additions:
int i = 0;
int answer = 0;
while ( i != a )
{ answer = answer + b;
i = i + 1;
}
The question is: Say that the loop has been iterating for some time; what has it
accomplished so far? We can use the debugging tool of an IDE to print an execution
trace, or we might make the loop generate its own trace with a println statement:
int i = 0;
int answer = 0;
while ( i != a )
{ System.out.println("i=" + i + " a=" + a + " b=" + b
+ " c=" + c + " answer=" + answer);
answer = answer + b;
i = i + 1;
}
When a is initialized to 3 and b is initialized to 2, we get this trace:
i=0 a=3 b=2 answer=0
i=1 a=3 b=2 answer=2
i=2 a=3 b=2 answer=4
i=3 a=3 b=2 answer=6
7.15. BEYOND THE BASICS 385
We see that i * b = answer holds at the start of each iteration. This is indeed the
crucial property of the loop, and when the loop terminates, we conclude that i = a
and therefore a * b = answer.
These examples hint that one can use loop invariants and elementary symbolic
logic to construct formal proofs of correctness of computer programs. This is indeed
the case and unfortunately this topic goes well beyond the scope of this text, but a
practical consequence of invariants does not: A programmer who truly understands
her program will be capable of stating invariants of the loops it contains. As a matter of
policy, the loops displayed in this text will be accompanied by invariants. Sometimes
the invariant will be stated in mathematical symbols, and often it will be an English
description, but in either case it will state the property that remains true as the
iterations of the loop work towards the loop’s goal.
Exercises
State invariants for these loops and argue as best you can that the invariants remain
true each time the loop does an additional iteration:
1. Multiplication:
2. Division:
3. Exponentiation:
386
String s = ... ;
boolean item_found = false;
int index = 0;
while ( !item_found && (index < s.length()) )
{ if ( s.charAt(index) == ’A’ )
{ item_found = true; }
else { index = index + 1; }
}
5. String reverse:
String s = ...;
String t = "";
int i = 0;
while ( i != s.length() )
{ t = s.charAt(i) + t;
i = i + 1;
}
The termination expression is a - i, because each time the loop completes an iter-
ation, the value of a - i decreases. Since a is a nonnegative number, at some point
i’s value will equal a’s, and the termination expression’s value reaches 0, and at this
very moment, the loop does indeed terminate. (Again, we require that a and b are
nonnegative integers.)
Definite iteration loops use termination expressions that compare the value of the
loop counter to a stopping value, because the increment of the loop counter within
the loop’s body makes the counter move closer to the stopping value.
Searching loops ensure termination by limiting their search to a finite collection.
In the case of the example in Figure 4, where the loop searches for divisors, the termi-
nation expression is current - 1, because variable current is searching for possible
divisors by counting from n/2 down to 1. When the termination expression reaches
0, it forces the loop’s test to go false.
Unfortunately, not all loops have such termination expressions; a loop that imple-
ments a divergent infinite series clearly lacks such an “alarm clock.” An even simpler
example is a loop that reads a sequence of transactions submitted by a user at the
keyboard—there is no termination expression because there is no guarantee that the
user will tire and terminate the sequence of inputs.
Exercises
For each of the loops in the previous exercise set, state the conditions under which
each is guaranteed to terminate and explain why.
• Remove the header line from the constructor method and replace it by public
void init().
• Remove the enclosing frame and all invocations of setSize, setTitle, and
setVisible.
If the constructor method uses parameters, then you are out of luck. (But
Chapter 10 presents an awkward way of binding strings to variables within
init.)
We must work harder to make the animation example into an applet—the start-up
and controller classes must be squeezed into the output-view class, AnimationWriter.
The unfortunate result appears in Figure 22. The controller must be moved into the
applet’s paint method, because a newly created applet first executes its init method,
followed by paint. The “real” painting method, that of the output view, is renamed
paintAnimation and is invoked from within paint.
In Chapter 10, we learn how to avoid such surgeries.