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


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

Java Coffee Break

Solution for
Programming Exercise 3.4


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

Exercise 3.4: Write a program that reads one line of input text and breaks it up into words. The words should be output one per line. A word is defined to be a sequence of letters. Any characters in the input that are not letters should be discarded. For example, if the user inputs the line

          He said, "That's not a good idea."

then the output of the program should be

          He
          said
          that
          s
          not
          a
          good
          idea

An improved version of the program would list "that's" as a word. An apostrophe can be considered to be part of a word if there is a letter on each side of the apostrophe.

To test whether a character is a letter, you might use (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'). However, this only works in English and similar languages. A better choice is to call the standard function Character.isLetter(ch), which returns a boolean value of true if ch is a letter and false if it is not. This works for any Unicode character. For example, it counts an accented e, é, as a letter.


Discussion

There are many ways to approach this problem, and probably all of them are sort of tricky to get right. Here's a simple idea that almost works: Go through all the characters in the string. If the character is a letter, write it out. If it's not a letter, write a carriage return instead. If line is a String variable representing the line of text, this algorithm can be coded as

           for ( i = 0;  i < line.length(); i++ ) {
              ch = line.charAt(i);
              if ( Character.isLetter(ch) )
                 TextIO.put(ch);
              else
                 TextIO.putln();   
           }

This prints all the letters in a word on the same line of output. Since words in the string are separated by non-letter characters, and the computer prints a carriage return when it finds a non-letter, words in the output are separated by carriage returns. But there are is a problem with this: If two words are separated by several non-letters in the string, then there will be one or more blank lines between the words in the output. We don't want to output two carriage returns in a row. To avoid this, we can keep track of whether the previous output was a letter or a carriage return. When we find a non-letter, we will only output a carriage return if the previous output was not a carriage return. To keep track of the necessary information, I'll use a boolean variable named didCR. The value of this variable will be true if the previous output was a carriage return. I have to remember to set the value of didCR each time I output something. With this modification, the code becomes:

           for ( i = 0;  i < line.length(); i++ ) {
              ch = line.charAt(i);
              if ( Character.isLetter(ch) ) {
                 TextIO.put(ch);
                 didCR = false;  // previous output was not a CR
              }
              else {
                 if ( didCR == false ) {  // output CR if previous output was not a CR
                    TextIO.putln();
                    didCR = true;  // previous output was a CR
                 }
              }
           }

The program requires an initial value for didCR. In the program below, I output a carriage return before the for loop and set didCR to true. You should be able to follow the rest of the program.

An entirely different approach to this problem is given by the algorithm, "while there are more words in the string, get the next word and print it." This turns out to be even harder to implement than the above.


The Solution

    public class ListWordsInString {
      
       /*  This program will read one line of input typed by the user.
           It will print the words from the input, one word to a line.
           A word is defined to be a sequence of letters.  All non-letters
           in the input are discarded.
       */
    
       public static void main(String[] args) {
       
           String line;    // A line of text, typed in by the user.
           int i;          // Position in line, from 0 to line.length() - 1.
           char ch;        // One of the characters in line.
           boolean didCR;  // Set to true if the previous output was a carriage return.
           
           TextIO.putln("Enter a line of text.");
           TextIO.put("? ");
           line = TextIO.getln();
           
           TextIO.putln();
           didCR = true;
           
           for ( i = 0;  i < line.length();  i++ ) {
              ch = line.charAt(i);
              if ( Character.isLetter(ch) ) {
                 TextIO.put(ch);
                 didCR = false;
              }
              else {
                 if ( didCR == false ) {
                    TextIO.putln();
                    didCR = true;
                 }
              }
           }
           
           TextIO.putln();  // Make sure there's at least one carriage return at the end.
             
       }  // end main()

    }  // end class    


[ Exercises | Chapter Index | Main Index ]