Solution for Programming Exercise 4.1
This page contains a sample solution to one of the exercises from Introduction to Programming Using Java.
Exercise 4.1:
To "capitalize" a string means to change the first letter of each word in the string to upper case (if it is not already upper case). For example, a capitalized version of "Now is the time to act!" is "Now Is The Time To Act!". Write a subroutine named printCapitalized that will print a capitalized version of a string to standard output. The string to be printed should be a parameter to the subroutine. Test your subroutine with a main() routine that gets a line of input from the user and applies the subroutine to it.
Note that a letter is the first letter of a word if it is not immediately preceded in the string by another letter. Recall from Exercise 3.4 that there is a standard boolean-valued function Character.isLetter(char) that can be used to test whether its parameter is a letter. There is another standard char-valued function, Character.toUpperCase(char), that returns a capitalized version of the single character passed to it as a parameter. That is, if the parameter is a letter, it returns the upper-case version. If the parameter is not a letter, it just returns a copy of the parameter.
We are told the name of the subroutine and that it has one parameter of type String. The name of the parameter is not specified. I will use str. The return type is void because the subroutine does not return a value. (It displays a value to the user, but to return a value means to return it to the line in the program where the function is called. The value returned by a function is generally not displayed to the user by the function.) The first line of the subroutine definition will be:
static void printCapitalized( String str )
The subroutine must look at each character in str and decide whether to capitalize it or not. An algorithm for the subroutine is
for each character in str: if the character is the first letter of a word: Print a capitalized version of the character else: Print the character Print a carriage return to end the line of output
The test as to whether a character is the first letter of a word is surprisingly complicated. A test that almost works is: "If the character is a letter and the preceding character is not a letter." The problem is that if the character is the first character is the string, then there is no preceding character! If the character is str.charAt(i), then the preceding character would be str.charAt(i-1), but str.charAt(i-1) doesn't exist if i is 0. You should notice the problem when you look at str.charAt(i-1) and remember that the charAt() method has the precondition that its parameter must be greater than or equal to zero. When i is zero in str.charAt(i-1), that precondition is not satisfied.
Let's look at Java code that suffers from this bug. Recall that the operator "!" stands for "not."
for ( i = 0; i < str.length(); i++ ) { // BUGGY CODE!
ch = str.charAt( i );
if ( Character.isLetter(ch) && ! Character.isLetter(str.charAt(i-1)) )
System.out.print( Character.toUpperCase(ch) );
else
System.out.print( ch );
}
System.out.println();
This will crash when i is zero, if the first character in the string is indeed a letter. There are several ways to work around the problem, and all of them are techniques that are worth knowing. The first is to use a more complicated test in the if statement: "if the character is a letter and either it's the first character in the string or the previous character is not a letter". In Java, this is:
if (Character.isLetter(ch) && (i==0 || ! Character.isLetter(str.charAt(i-1))))
This avoids testing str.charAt(i-1) in the case when i is zero. But it can be difficult to get such a complicated test right. Another possibility is a bit sneaky: Add an extra character onto the beginning of str, and then start the for loop with i=1. Any character will do, as long as it's not a letter. For example, you could say "str = "." + str;" Since the for loop starts at i=1, the "." is not copied to output, and the problem of i == 0 doesn't arise. The method that I will use is similar, but it doesn't require any modification of str. I'll use another variable to represent the preceding character in the string, except that at the beginning of the string, I'll set it to the arbitrary value, '.'. At the end of the loop, the character that we have just processed becomes the "previous character" in the next iteration of the loop. Here is the complete subroutine, using this method:
static void printCapitalized( String str ) { char ch; // One of the characters in str. char prevCh; // The character that comes before ch in the string. int i; // A position in str, from 0 to str.length()-1. prevCh = '.'; // Prime the loop with any non-letter character. for ( i = 0; i < str.length(); i++ ) { ch = str.charAt(i); if ( Character.isLetter(ch) && ! Character.isLetter(prevCh) ) System.out.print( Character.toUpperCase(ch) ); else System.out.print( ch ); prevCh = ch; // prevCh for next iteration is ch. } System.out.println(); }
Keeping track of a previous value in a loop is a very common programming pattern. This doesn't exhaust the possibilities. Another idea, for example, would be to use a boolean variable to keep track of whether the previous character was a letter.
Finally, we should add a Javadoc comment to document the subroutine. Writing a main() routine to test this subroutine on a line of input is easy.
/** * This program will get a line of input from the user and will print a copy * of the line in which the first character of each word has been changed to * upper case. The program was written to test the printCapitalized * subroutine. It depends on the non-standard TextIO class. */ public class CapitalizeOneString { public static void main(String[] args) { String line; // Line of text entered by user. System.out.println("Enter a line of text."); line = TextIO.getln(); System.out.println(); System.out.println("Capitalized version:"); printCapitalized( line ); } /** * Print a copy of a string to standard output, with the first letter * of each word in the string changed to upper case. * @param str the string that is to be output in capitalized form */ static void printCapitalized( String str ) { char ch; // One of the characters in str. char prevCh; // The character that comes before ch in the string. int i; // A position in str, from 0 to str.length()-1. prevCh = '.'; // Prime the loop with any non-letter character. for ( i = 0; i < str.length(); i++ ) { ch = str.charAt(i); if ( Character.isLetter(ch) && ! Character.isLetter(prevCh) ) System.out.print( Character.toUpperCase(ch) ); else System.out.print( ch ); prevCh = ch; // prevCh for next iteration is ch. } System.out.println(); } } // end CapitalizeOneString