Chapter 14
IN THIS CHAPTER
Analyzing loop strategies
Diagnosing loop problems
Creating nested loops
If you’re an editor at John Wiley & Sons, Inc., please don’t read the next few paragraphs. In the next few paragraphs, I give away an important trade secret (something you really don’t want me to do).
I’m about to describe a surefire process for writing a best-selling For Dummies book. Here’s the process:
Write several words to create a sentence. Do this several times to create a paragraph.
Repeat the following to form a paragraph:
Repeat the following to form a sentence:
Write a word.
Repeat the previous instructions several times to make a section. Make several sections and then make several chapters.
Repeat the following to form a best-selling book in the For Dummies series:
Repeat the following to form a chapter:
Repeat the following to form a section:
Repeat the following to form a paragraph:
Repeat the following to form a sentence:
Write a word.
This process involves a loop within a loop within a loop within a loop within a loop. It’s like a verbal M.C. Escher print. Is it useful, or is it frivolous?
Well, in the world of computer programming, this kind of thing happens all the time. Most five-layered loops are hidden behind method calls, but two-layered loops within loops are everyday occurrences. So this chapter tells you how to compose a loop within a loop. It’s very useful stuff.
By the way, if you’re a Wiley editor, you can start reading again from this point onward.
The program in Listing 12-5 (over in Chapter 12) extracts a username from an email address. For example, the program reads
from the keyboard, and writes
John
to the screen. Let me tell you, in this book I have some pretty lame excuses for writing programs, but this simple email example tops the list! Why would you want to type something on the keyboard, only to have the computer display part of what you typed? There must be a better use for code of this kind.
Sure enough, there is. The BurdBrain.com network administrator has a list of 10,000 employees’ email addresses. More precisely, the administrator’s hard drive has a file named email.txt
. This file contains 10,000 email addresses, with one address on each line, as shown in Figure 14-1.
The company’s email software has an interesting feature. To send email within the company, you don’t need to type an entire email address. For example, to send email to John, you can type the username John
instead of [email protected]
. (This @BurdBrain.com
part is called the host name.)
The company’s network administrator wants to distill the content of the email.txt
file. She wants a new file, usernames.txt
, that contains usernames with no host names, as shown in Figure 14-2.
To solve the administrator’s problem, you need to modify the code in Listing 12-5. The new version gets an email address from a disk file and writes a username to another disk file. The new version is in Listing 14-1.
LISTING 14-1 From One File to Another
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
class ListOneUsername {
public static void main(String args[]) throws FileNotFoundException {
Scanner diskScanner = new Scanner(new File("email.txt"));
PrintStream diskWriter = new PrintStream("usernames.txt");
char symbol;
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
while (symbol != ’@’) {
diskWriter.print(symbol);
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
}
diskWriter.println();
diskScanner.close();
diskWriter.close();
}
}
Listing 14-1 does almost the same thing as its forerunner in Listing 12-5. The only difference is that the code in Listing 14-1 doesn’t interact with the user. Instead, the code in Listing 14-1 interacts with disk files.
Here’s how you run the code in Listing 14-1:
Create a file named email.txt
in your Eclipse project directory.
In the email.txt
file, put just one email address. Any address will do, as long as the address contains an @ sign.
ListOneUsername.java
file (the code from Listing 14-1) in your project’s src/(default package)
directory.Run the code in Listing 14-1.
When you run the code, you see nothing interesting in the Console view. What a pity!
View the contents of the usernames.txt
file.
If your email.txt
file contains [email protected]
, the usernames.txt
file contains John
.
For more details on any of these steps, see the discussion accompanying Listings 13-2, 13-3, and 13-4, over in Chapter 13. (The discussion is especially useful if you don’t know how to view the usernames.txt
file’s contents.)
The previous section describes a network administrator’s problem — creating a file filled with usernames from a file filled with email addresses. The code in Listing 14-1 solves part of the problem — it extracts just one email address. That’s a good start, but to get just one username, you don’t need a computer program. A pencil and paper do the trick.
Don’t keep the network administrator waiting any longer. In this section, you develop a program that processes dozens, hundreds, and even thousands of email addresses from a file on your hard drive.
First, you need a strategy to create the program. Take the statements in Listing 14-1 and run them over and over again. Better yet, have the statements run themselves over and over again. Fortunately, you already know how to do something over and over again: You use a loop. (See Chapter 12 for the basics on loops.)
Here’s the strategy: Take the statements in Listing 14-1 and enclose them in a larger loop:
while (not at the end of the email.txt file) {
Execute the statements in Listing 14-1
}
Looking back at the code in Listing 14-1, you see that the statements in that code have a while
loop of their own. So this strategy involves putting one loop inside another loop:
while (not at the end of the email.txt file) {
//Blah-blah
while (symbol != ’@’) {
//Blah-blah-blah
}
//Blah-blah-blah-blah
}
Because one loop is inside the other, they’re called nested loops. The old loop (the symbol != ’@’
loop) is the inner loop. The new loop (the end-of-file loop) is called the outer loop.
Now all you need is a way to test the loop’s condition. How do you know when you’re at the end of the email.txt
file?
The answer comes from Java’s Scanner
class. This class’s hasNext
method answers true
or false
to the following question:
Does the
email.txt
file have anything to read in it (beyond what you’ve already read)?
If the program’s findWithinHorizon
calls haven’t gobbled up all the characters in the email.txt
file, the value of diskScanner.hasNext()
is true
. So, to keep looping while you’re not at the end of the email.txt
file, you do the following:
while (diskScanner.hasNext()) {
Execute the statements in Listing 14-1
}
The first realization of this strategy is in Listing 14-2.
LISTING 14-2 The Mechanical Combining of Two Loops
/*
* This code does NOT work (but you learn from your mistakes).
*/
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
class ListAllUsernames {
public static void main(String args[]) throws FileNotFoundException {
Scanner diskScanner = new Scanner(new File("email.txt"));
PrintStream diskWriter = new PrintStream("usernames.txt");
char symbol;
while (diskScanner.hasNext()) {
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
while (symbol != ’@’) {
diskWriter.print(symbol);
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
}
diskWriter.println();
}
diskScanner.close();
diskWriter.close();
}
}
When you run the code in Listing 14-2, you get the disappointing response shown in Figure 14-3.
What’s wrong with the code in Listing 14-2? To find out, I role-play the computer. “If I were a computer, what would I do when I execute the code in Listing 14-2?”
The first several things that I’d do are pictured in Figure 14-4. I would read the J
in John
, and then write the J
in John
, and then read the letter o
(also in John
).
After a few trips through the inner loop, I’d get the @ sign in [email protected]
, as shown in Figure 14-5.
Finding this @ sign would jump me out of the inner loop and back to the top of the outer loop, as shown in Figure 14-6.
I’d get the B
in BurdBrain
and sail back into the inner loop. But then (horror of horrors!) I’d write that B
to the usernames.txt
file. (See Figure 14-7.)
There’s the error! You don’t want to write host names to the usernames.txt
file. When the computer found the @ sign, it should have skipped past the rest of John’s email address.
At this point, you have a choice. You can jump straight to the corrected code in Listing 14-3 (a couple of sections from here), or you can read on to find out about the error message in Figure 14-3.
Ah! You’re wondering why Figure 14-3 has that nasty error message.
I role-play the computer to help me figure out what’s going wrong. Imagine that I’ve already role-played the steps in Figure 14-7. I shouldn’t process the first letter B
(let alone the entire BurdBrain.com
host name) with the inner loop. But unfortunately, I do.
I keep running and processing more email addresses. When I get to the end of the last email address, I grab the m
in BurdBrain.com
and go back to test for an @ sign, as shown in Figure 14-8.
Now I’m in trouble. This last m
certainly isn’t an @ sign. So I jump into the inner loop and try to get yet another character. (See Figure 14-9.) The email.txt
file has no more characters, so Java sends an error message to the computer screen. (Refer to the NullPointerException
error message in Figure 14-3.)
Listing 14-3 has the solution to the problem described with Figures 14-1 and 14-2. The code in this listing is almost identical to the code in Listing 14-2. The only difference is the added call to nextLine
. When the computer reaches an @ sign, this nextLine
call swallows the rest of the input line without actually tasting it. (The nextLine
call gets the rest of the email address, but doesn’t output that part of the email address. The idea works because each email address is on its own separate line.) After gulping down @BurdBrain.com
, the computer moves gracefully to the next line of input.
LISTING 14-3 That’s Much Better!
/*
* This code is correct!!
*/
import java.util.Scanner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
class ListAllUsernames {
public static void main(String args[]) throws FileNotFoundException {
Scanner diskScanner = new Scanner(new File("email.txt"));
PrintStream diskWriter = new PrintStream("usernames.txt");
char symbol;
while (diskScanner.hasNext()) {
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
while (symbol != ’@’) {
diskWriter.print(symbol);
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
}
diskScanner.nextLine();
diskWriter.println();
}
diskScanner.close();
diskWriter.close();
}
}
To run the code in Listing 14-3, you need an email.txt
file — a file like the one shown earlier, in Figure 14-1. In the email.txt
file, type several email addresses. Any addresses will do, as long as each address contains an @ sign and each address is on its own separate line. Save the email.txt
file in your project directory along with the ListAllUsernames.java
file (the code from Listing 14-3). For more details, see the discussion accompanying Listings 13-2, 13-3, and 13-4 in Chapter 13.
With Listing 14-3, you’ve reached an important milestone. You’ve analyzed a delicate programming problem and found a complete, working solution. The tools you used included thinking about strategies and role-playing the computer. As time goes on, you can use these tools to solve bigger and better problems.
Try writing the code that I suggest in the next few paragraphs. Don’t be afraid to make lots of mistakes. If you get stuck, slow down, take a step back, and think about what the computer will do when it follows instructions to the letter.
The solutions are on my web page at www.allmycode.com/BeginProg
. But don’t jump to the solutions until you’ve experimented with lots of different ideas. Follow this tried-and-true formula:
Write some code;
Run your code;
while (your program doesn’t work correctly) {
Step through your code, one statement after another, keeping track
of the values of the variables and the computer’s output as
Java follows your instructions exactly as they’re written;
In the step by step execution of statements, notice the place where
Java does something that you don’t want it to do;
Ask yourself how you’d change the statements so that Java would do
what you want it to do;
Change the statements in your code;
Run your code again;
}
A file named input.txt
contains only four characters:
Java
What’s the output when you run the following code?
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class Main {
public static void main(String[] args) throws FileNotFoundException {
Scanner diskScanner = new Scanner(new File("input.txt"));
while (diskScanner.hasNext()) {
char symbol = diskScanner.findWithinHorizon(".", 0).charAt(0);
System.out.print(Character.toUpperCase(symbol));
}
diskScanner.close();
}
}
In this chapter’s “How it feels to be a computer” section, I examine each line of a program’s code and ask myself what the computer does when it executes that line. Do the same thing with the following program:
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
class ReadStars {
public static void main(String args[]) throws FileNotFoundException {
Scanner diskScanner = new Scanner(new File("input.txt"));
char symbol;
while (diskScanner.hasNext()) {
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
while (symbol == ’*’) {
System.out.print(symbol);
symbol = diskScanner.findWithinHorizon(".",0).charAt(0);
}
System.out.println();
}
diskScanner.close();
}
}
What happens when the input.txt
file contains the following characters?
*****x***y*****z
Make a chart to keep track of the changes to the values of i
and j
as Java executes the following code.
int i = 5;
int j;
while (i > 0) {
System.out.println(i);
i--;
j = 3;
while (j > 0) {
System.out.print(j);
j--;
}
System.out.println();
}
Based on the values in your chart, what will be the output of the code? Run the code in Eclipse to find out whether your prediction is correct.
Modify the code from the previous experiment ("Loop soup") so that the output has no lines containing the 321
digit sequence. In place of the 321
lines, the output has lines containing the 123
digit sequence.
This experiment comes in two parts. The first part requires only one loop. The second part requires nested loops.
How many stars? 5
*****
Write a program that repeatedly asks whether the user wants to see a row of stars. As long as the user replies with the letter y
, the program does what it did in the previous bullet. That is, the program asks the user how many stars to display and then displays that many stars. As soon as the user replies with the letter n
, the program stops running.
Here’s a sample run of the program:
Do you want a row of stars? (y/n) y
How many stars? 5
*****
Do you want a row of stars? (y/n) y
How many stars? 2
**
Do you want a row of stars? (y/n) y
How many stars? 8
********
Do you want a row of stars? (y/n) n
To create this program, take the code that you wrote in the previous bullet (Part 1 of “Seeing stars”) and surround some of that code inside a second loop.
In Figure 14-3, the run of a Java program throws a NullPointerException
. These NullPointerException
messages are never fun, but the more of these messages you encounter, the less frightening they are.
To help desensitize you to NullPointerException
messages, generate one of them intentionally. Type the following two lines, one after another, in the JShell window:
String name = null;
System.out.println(name.length());
3.144.30.178