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:

  1. a "base case", in which the solution is immediate, and
  2. 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.
  1. 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.
  2. How many times is the method moveTower called for totalDisks = 0? for totalDisks = 4? for totalDisks = 5?
  3. 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:
  1. Write a Java statement that locates the position of the first blank
    character in String u and assigns it to the integer variable i.
  2. 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".
  3. 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".
  4. 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:

  1. 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.
  2. Change the method drawPictures so that it is recursive. 
  3. 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.