Computer Science 105 Lab 8: Recursion, Palindromes, and Graphics
Due: November 20, 2001
Objectives and Overview: This lab provides experience with recursion
in problem solving, along with a review of String variables and an introduction
to the Graphics class. The programming problem gives you a chance for
writing a recursive methods and then using those methods for displaying the
favorite palindromes and photos of your classmates.
Part 1 - Recursion: the Towers of Hanoi Problem
We saw a brief example of recursion in the last lab, where the method
findMinimum was written both recursively and iteratively (using
a loop). This was not a particularly compelling example, since using
a loop might be a more intuitive strategy for solving that problem.
Below is an abbreviated version of a program in your text (pages 479-481)
that solves another problem, called "Towers of Hanoi." A copy of
this program is available for your use in the file TowersOfHanoi.java
in the CS105 (Tucker) folder. This problem is one whose
recursive solution is much more easily designed than if we had tried to use
a loop.
The Towers of Hanoi problem starts with some number (n) of disks, all with
different diameters, stacked in a pile on the first of three pegs (labeled
1, 2, and 3), with the largest disk on the bottom of the pile. The
goal is to discover a series of 1-disk moves in which the entire pile is transferred
to the third peg, again with the largest disk on the bottom. No move
in this process can leave a larger disk on top of a smaller disk on the same
peg.
To solve any problem recursively, we must divide the problem into two cases:
- a "base case", in which the solution is immediate, and
-
a "recursive case", in which the solution can be expressed in terms
of the solution to a slightly simpler version of the original problem.
This approach is the essence of the so-called "Divide and Conquer" strategy
of problem solving.
For Towers of Hanoi, the base case is the 1-disk problem (n=1), in which
we simply move the disk from peg 1 to peg 3. The recursive case is
the n-disk problem (n>1), whose solution can be expressed in three steps,
assuming that we have a solution to the slightly simpler n-1 disk problem.
That is, the n-disk solution is achieved recursively by:
a. Solving the n-1 disk problem from peg 1 to
peg 2.
b. Solving the 1 disk problem from peg 1 to peg
3.
c. Solving the n-1 disk problem (again) from peg 2
to peg 3.
This recursive solution is expressed below in the method moveTower
, which distinguishes the base case from the recursive case using an
if-else structure. The recursive step (the else part) involves a call
of moveTower to itself, passing a slightly smaller value (n-1)
as an argument in the call. Notice that the three parameters start
, end , and temp identify the three pegs for a single
solution - for the original problem, these are 1, 3, and 2. However,
these parameters change roles every time the method moveTower is
called recursively. The program below is in the file TowersOfHanoi.java
on the CS105 server.
public class TowersOfHanoi {
static void moveTower (int numDisks, int start, int end, int temp)
{
if (numDisks == 1) // base case -- solve the 1-disk
problem
moveOneDisk (start, end);
else { // recursive
call -- solve the n-disk problem
// by using solutions to the (n-1)-disk problem
moveTower (numDisks - 1, start, temp, end);
moveOneDisk (start, end);
moveTower(numDisks - 1, temp, end, start );
}
}
static void moveOneDisk (int start, int end) {
System.out.println( " Move disk from " + start + "
to " + end);
}
public static void main (String argv[]) {
int totalDisks; // total number of disks for the
problem
System.out.print( "Enter number of disks: " );
totalDisks = Keyboard.readInt();
while(totalDisks > 0) {
moveTower (totalDisks, 1, 3, 2 );
System.out.print( "\nEnter number of disks (or 0 to end):
" );
totalDisks = Keyboard.readInt();
}
}
}
After running this program several times for some different values of
totalDisks, answer the following questions about its behavior.
- For totalDisks = 3, make a table that has one row for each
call to the method moveTower, and in that row the values of the
parameters numDisks, start, end, and temp
are shown.
- How many times is the method moveTower called for totalDisks
= 0? for totalDisks = 4? for totalDisks = 5?
- What about the general case? That is, can you derive an expression
for the number of calls to method moveTower for totalDisks
= n (where n is any nonnegative integer)?
Part 2 - Strings and Recursion
The Java class String can be used to support programming in a wide
range of text processing software applications, like word processors and
spelling checkers. Sometimes string methods can be written recursively
in a way that clearly identifies their function.
String Constants and variables
A String constant is any sequence of characters enclosed in single double
quotes ("). In our Java programs, all messages displayed on the screen,
like the message
"Enter number of disks: "
are String constants.
A String variable is any variable declared with the type String. The following
are String variables:
String s, t, u;
The value of a String variable can be any String constant, including
the empty String "" (the one that has no characters between the quotes).
The String class has a number of methods that allow programs to manipulate
String values in different ways. The table on page 326 of your text summarizes
many of these methods. The discussion below illustrates how these methods
can be used by showing some simple examples.
String Length, Assignment, Concatenation, and Comparison
The length of a String is the number of characters that appear between
the quotes, including blanks and all other special characters. String
variables can be assigned values, just like numerical variables. This can
be done with an assignment statement or a Keyboard.readString()
statement. The following statement assigns a String value to the variable
s declared above:
s = "Hello World!";
The length of this String value of s is 12, and the method length
returns the length of any String. Thus, the expression s.length()
returns 12 in this example.
The ith character of a String variable can be isolated by using the
charAt method. String values can be viewed as arrays of characters
in this sense, so s.charAt(0) returns the first character
'H' , s.charAt(1) returns the second character 'e'
, and so forth. [Note that single characters are enclosed in single quotes
(') rather than double quotes ("), which enclose strings. Thus the character
H is written as 'H', while the 1-character String H is written as "H".]
Two String values can be concatenated (joined together) to form a single
String, either using the + sign or using the concat method, which
is part of the String class. Either of the following statements leaves
the variable u with the value "Hello World! Hello!".
u = s + " Hello!";
u = s.concat(" Hello!");
Two String values can be compared for equality or nonequality using
the equals method. Equality for String values means that one is an identical
copy of the other, letter for letter. So we might write
if (s.equals("Hello!")) ...
to test whether or not the current value of s is the String "Hello!"
.
Substring Extraction, Insertion, and Deletion
A String can be formed by extracting a copy of an embedded String, called
a substring, from another string. For example, the following statement
extracts a copy of "World" from the String s and assigns it to
the String t::
t = s.substring(6, 11);
Here, the 6 designates the starting position of the substring to be
copied, and the 11 designates the position of the next character in s
following the substring (counting the first character at position 0, not
1). Note that the value of s itself is not affected by this action.
If we wanted to delete that same substring from s, we would need to concatenate
the beginning part of s with that part which follows that substring. That
is, we would write
s = s.substring(0, 5) + s.substring(11);
to obtain the string "Hello" and assign it to s. Note here that the expression
s.substring(11) takes that substring which begins at position
11 and ends at the end of s. That is, it is the entire righthand end of
s.
Finally, to insert a new substring within an existing String, we use concatenation
and substring in a different way. For instance, to insert the word "Cruel"
inside the string s = "Hello World!" to form s = "Hello Cruel World!"
we would write the following assignment:
s = s.substring(0, 5) + " Cruel " + s.substring(6);
String Searching and Replacement
Two other useful String methods search a String for the position of a
particular character or substring that may occur within it, and replace a
character within a String by another character. The first is called indexOf
, and the second is called replace. Here are two examples
(assuming i and j are integer variables, and s has the value "Hello
Cruel World!" ).
i = s.indexOf("Cruel");
s = s.replace(s.charAt(i), 'c');
The resulting value of i in this example is 6 (the position of the
first character of the leftmost occurrence of the substring "Cruel"
within s). The resulting value of s is "Hello cruel World!" since
s.charAt(i) returns the character 'C'.
Designing Recursive String Methods
Writing a String method recursively requires first identifying the "base
case" and the "recursive case", just as in the Towers of Hanoi problem. For
example, suppose we want to write a method (called mylength ) that
computes the length of a String (assuming for the moment that there is no
"length" function available in the String class. For this problem,
we can begin by writing the basic recursive prototype:
public int mylength (String s) {
if // base case
// compute and return base case result
else
// compute and return recursive case result
(after calling mylength
// with a slightly simpler problem - substring of
s)
}
Now we can get at these two cases by noting
1. that the length of the
empty string ("") is 0 - that's the base case, and
2. that the length of any
nonempty string is 1 greater than the length of a string that is one character
shorter - that's
the recursive case.
In Java, this would be written as follows:
public int mylength (String s) {
if (s.equals("")
return 0;
else
return 1 + mylength(substring(s, 1));
}
How does this work? Let's consider the call mylength("bob")
, hoping to establish that it returns the result 3. Since "bob"
doesn't satisfy the base case, we must compute 1+mylength("ob")
, in which the method recursively calls itself with a slightly smaller string.
Since "ob" doesn't satisfy the base case, another recursive call is
initiated inside the expression 1+mylength("b"). One final
call is initiated inside the expression 1+mylength(""), in which
the base case is finally achieved and the result 0 is returned to the previous
call. Working backwards, the result 1+0 = 1 is now returned to the
call mylength("b") , which computes 1+1=2 and returns that result
to the all mylength("ob") . This call now computes 1+2=3
and returns that result to the original call, mylength("bob").
With this information, answer the following questions:
-
Write a Java statement that locates the position of the first blank
character in String u and assigns it to the integer variable i.
- Using this value of i, write another statement that extracts the first
word from u and assigns it to t. That is, if u = "Hello Hello Cruel World",
your statement should leave t = "Hello" and u = "Hello Cruel World".
- Design a recursive method called deBlank(String u)
that returns the String that results from removing all blanks from u. For
instance, the call deBlank("Hello cruel world") should return
the string "Hellocruelworld".
- Design a recursive method Reverse that reverses the
order of the characters in a String. For example, Reverse("Hello")
would return the String "olleH".
Part 3 - Discovering Palindromes
A palindrome is a word or phrase that is spelled the same when written
backwards or forwards. For example, each of the following Strings is a palindrome:
"abba"
"abbadabba"
"abbadabbadabba"
"ablewasiereisawelba"
"amanaplanacanalpanama"
Design a recursive method called Palindrome(String s)
which returns the boolean value true or false, depending
respectively on whether or not the argument s is a palindrome. Start
by identifying a solution for the base case, and then find a solution for
the recursive case.
Optional Extra Credit: Add features to this method by which the
following are also accepted as palindromes -- that is, spaces and capitalized
letters are ignored in the determination. Hint: consider using the
deBlank method you designed in an earlier question.
"abba dabba dabba"
"able was i ere i saw elba"
"a man a plan a canal panama"
"Able was I ere I saw Elba"
Part 4 - Applets, Interaction, and Graphics
Read the beginning of the the document
Interactive Java Programming
, but only through the discussion of graphics programming. Also
read the introduction to Applets given on pages 92-100 of your text, including
the summary of the Graphics class on page 99. While reading this
discussion, use a copy of the folder myApplet from the CS105
(Tucker) server on the desktop, and Make and Run the
project that is inside that folder.
When you finish this exercise, replace the files Scribble.java and Scribble.html
inside that project by the files MirroredPictures.java and MirroredPictures.html,
which are variations of the program and discussion in Section 11.4 of your
text (pages 482-484). Run this program several times.
Your job is to develop a modification of this program so that it displays
all 16 photos from our class in a 4x4 grid, each accompanied by its corresponding
caption from the file "captions" (that file is also known to contain 16
different captions on separate lines) below it. The program should
also determine and display which (if any) of these captions are palindromes.
A skeleton program for this modification is already provided in the
files lab8.java and lab8.html. You should start with this skeleton
and complete the following additional tasks:
- Change the method drawPictures so that it calls your Palindrome
method from Part 3 to determine whether or not each caption is a palindrome,
and then displays an appropriate message.
- Change the method drawPictures so that it is recursive.
- Refine your deBlank method from Part 2 so that it removes not only
blanks but also other punctuation marks from a string. For instance,
deblank("May a yam!") should return "Mayayam". Including
this refinement should discover all 16 captions as palindromes in
your modified program.
The desired output for this program is shown in the file lab8.JPG, which
you should use as a guide for debugging.
Lab Deliverables:
Answer all questions in parts 1 and 2, and hand in a hard copy of
your answers and your complete program fo part4. Also submit a copy
of your complete program for part 4, which should be labeled lab8yourname
.java, by dragging it to the Drop Box in the CS105 (Tucker)
folder.
This is a natural team project. If you work in teams of 2 or 3 persons,
divide the work evenlyl and turn in one set of answers to the questions and
one program that identifies all your team members.