Unit 5: Iteration with Loops

Topics covered in this unit

After completing this unit you will:

5.0. Overview

There are two abilities we need to be able to write programs, and those are handled by two corresponding control structures that we need to write our programs. We need the ability to branch (handled by if-else conditionals), and the ability to iterate (handled by while and for loops).

Up this point we've dealt with two things that have each had their own challenges. The first challenge was developing an understanding of boolean statements: the if statement, the if-else statement, and variations on these, including a switch-style series of alternatives, and nested if-else statements that allow us to drill down through multiple options.

The second challenge was navigating "object-oriented design", and learning to write programs that implemented classes, methods, and testers.

In this unit, we turn our attention to loops. Computers specialize in repetitive operations which can be repeated very quickly, so understanding how to write loops—and how to write loops well—is at the very heart of programming.

Let's get started.

  1. while loops, do loops, Debugging
  2. for loops
  3. Sentinel Values, Common Loop Algorithms
  4. Nested Loops
  5. Random Numbers, Simulations

5.1. Overview: while loops, do loops, Debugging

There are two major types of techniques for repeating, or iterating, sections of code. The first, and the most powerful, is the while loop.

5.1.1. Intro to Iteration

The if statement has to do with making decisions, and it's the first kind of control structure that we have in Java to alter the flow of our instructions.

The second kind of control structure we're going to have in our toolbox is the loop, which allows us to make our computers repeat sequences of code, many many times, very very fast. Once we have our branching and our looping control structures in place, we are going to be able to write some amazing code!

5.1.2. The while loop

There are basically three types of loops that we're going to work with in Java, although technically, any programming language only needs one. The most fundamental type of loop—the most powerful, the most versatile—is the while loop.

The while loop

The while loop operates under a boolean condition that has a value of true:

while (<boolean condition>) { <statement;> <statement;> <statement;> }

Example: A simple loop:

int i = 0; while (i < 4) { System.out.println(i); i++; }

How many times does the loop repeat? What is the first value that is printed? What is the last value that is printed?

The while-loop is sometimes called a conditional loop—it continues to repeat as long as a condition evaluates as true.

Here's another example of a conditional loop.

Scanner in = new Scanner(System.in); System.out.print("Wanna play a game (y/n)?"); String response = in.nextLine(); while (!response.equalsIgnoreCase("n")) { int rand = (int) (Math.random() * 3); if (rand == 0) { System.out.println("You're amazing!"); } else if (rand == 1) { System.out.println("I think you're awesome."); } else { System.out.println("I know you can do this!"); } System.out.print("Wanna play again (y/n)?"); response = in.nextLine(); } System.out.println("Goodbye");

5.1.3. Off-by-One Errors

Off-by-One Errors

"Off-by-one" errors occur when a loop is incorrectly written, causing the loop to repeat one time more or less than it should.

There are a number of important aspects to programming a loop that we have control over. In the example below, for example, we design the loop based on:

These factors all affect the loop's operation.

int i = 0; while (i < 10000) { System.out.println(i); i++; }

Off-by-one errors typically happen during the first iteration or the last iteration of the loop, so it's important to check the status of variables at those times especially carefully. If you do find that there's a problem with the loop, you're strongly discouraged from just randomly altering your loop parameters. A program that doesn't behave as expected reveals that you don't perfectly understand how it works—better to make the effort to analyze it and make a repair that you understand rather than just changing something and "seeing what happens."

Which one goes to 11?

Take a moment to analyze the loops here. Which ones count from 1 to 11?

// loopA int i = 0; while (i < 11) { i++; System.out.println(i); }
// loopB int i = 1; while (i <= 11) { System.out.println(i); i++; }
// loopC int i = 1; while (i < 12) { System.out.println(i); i++; }
// loopD int i = 0; while (i < 11) { System.out.println(i + 1); i++; }

Show/hide answer

Answer: They all do! Each of these loops demonstrates a different way of counting from 1 to 11. The most typical way for a programmer to write this is loopC, with the initial value clearly stated (i = 1;), the final value one less than the 12 given, and the body of the loop performed before the index variable i is incremented.

This is a pattern that you'll see over and over, so being comfortable with it will help you avoid off-by-one errors.

5.1.4. The Investment class

Continuing with out interest in things financial, take a moment to make sure you understand the Investment class here.

Identify the instance variables, the constructors, the accessor methods, and the mutator methods. Run through the class by hand to make sure you understand how the program works.

/** * A class to monitor the growth of an invetment that accumulates * interest at a fixed annual rate. * * @author Horstmann * @version 2005? */ public class Investment { private double initialBalance; private double balance; private int years; /** * Constructs an Investment object with a given starting balance * @param aBalance the starting balance * @param aRate the interest rate in percent */ public Investment(double theInitialBalance) { initialBalance = theInitialBalance; } /** * Accumulates interest until a target balance is reached * @param targetBalance the desired balance */ public void waitForBalance(double rate, double targetBalance) { years = 0; balance = initialBalance; while (balance < targetBalance) { years++; double thisYearsInterest = balance * rate / 100; balance += thisYearsInterest; System.out.println(balance); } } /** * Gets the current investment balance * @return the current balance */ public double getBalance() { return balance; } /** * Gets number of years investment has accumulated interest * @return the number of years since investment started */ public int getYears() { return years; } }

5.1.5. Using a Debugger

Three things you need to know when using a debugger.

Sometimes it's hard to figure out what's going on with a program, and you get confused by what values might be stored in what variables. Old school debugging says "put a bunch of print statements in your program," and you can certainly do that in Java.

A better strategy, if you're using an IDE, is to use the included Debugger. Here's the basic process, in 3 steps.

  1. Set a breakpoint
    In BlueJ, click on the line number of a statement so that a Stop appears at that location. Your program will run normally until you hit that breakpoint, and you can Inspect the state of various variables at that point. When you're ready to continue, click Continue.
    In a complex program, you may wish to set multiple breakpoints to inspect what's happening at various parts of your program.
  2. Step
    If you've hit a breakpoint, rather than hitting Continue you can choose to Step through various instructions. This has the effect of executing the instructions in that call/module one at a time, so you can see what's happening to your variables as you go.
    But what if those instructions include method calls?
  3. Step into
    Click on Step into to descend into a method call to see what's happening there.

5.1.6. Debugging Strategy

You don't want to use a debugger all the time, every time, to go through a program. That takes too much time. But if you're stuck and can't figure something out, the debugger allows you to follow the flow of your program and quickly find problems.

The best strategy is to set a breakpoint just before where you think your program is blowing up. Run the program, and then Step through statements until you see the failure. Then do it all again, but Step Into the method that you think might be causing you problems. This "divide-and-conquer" strategy should help you nail down your problem pretty quickly.

5.1.7. do loops

A standard while loop checks the condition before doing something, but sometimes you want to do something first, and then check some boolean condition.

The do loop

The do loop is simply a variation on the while loop:

do { <statement;> <statement;> <statement;> } while (<boolean expression>);

Example: Get a user's input and then see if it's valid. The loop below will continue asking a user's age until he/she enters an age that is a positive value.

do { System.out.print("Enter your age: "); userAge = in.nextInt(); } while (userAge <= 0);

The do loop can be useful and logical to use in certain situations, but most people seem to prefer writing a standard while loop with a structure that is equivalent to the do loop's operation.

Four different conditional loops

Compare these program segments:

double value; // *** Conditional One *** do { System.out.print("Enter a positive number: "); value = in.nextDouble(); } while (value <= 0); // *** Conditional Two *** System.out.print("Enter a positive number: "); value = in.nextDouble(); while (value <= 0) { System.out.print("Error: Please enter a positive number: "); value = in.nextDouble(); } // *** Conditional Three *** boolean done = false; while (!done) { System.out.print("Enter a positive number: "); value = in.nextDouble(); if (value > 0) done = true; } // *** Conditional Four *** while (true) { System.out.print("Enter a positive number: "); value = in.nextDouble(); if (value > 0) break; }

Do you have a preference for one style over the other?

5.2. Overview - for loops

Although a conditional loop like the while loop is all that is necessary to write programs, we often need a loop that will count through some series of values. The for loop is another type of loop that is very handy for creating unconditional loops that iterate a value over some known range of values.

5.2.1. The for loop

As we've said, the while loop is sufficient for any looping needs you might have, but there's one type of while loop that is so common, most languages have an additional structure for it: the for loop. If you need to count up or down from some value, to a terminal value, a for loop is what you should probably use.

We've already seen how you can count with a while loop:

// while-loop to count from 0 to 99 int i = 0; while (i < 100) { System.out.println(i); i++; }

Here's how you'd do the same thing with a for loop:

// for-loop to count from 0 to 99 for (int i = 0; i < 100; i++) { System.out.println(i); }

Note that if the counting variable i has already been declared, you don't have to declare it in the body of the loop.

The for loop

A for loop is used to repeat the execution of a code block a given number of types, with an index variable used to track the iteration of the loop.

int i; for (i = 0; i < n; i++) { <statement;> <statement;> <statement;> }

In this example, i is the index variable, and it will have the value 0 on the first execution of the loop. The second expression in the parentheses, i < n; is a boolean expression: the loop will continue running as long as this expression is true. The third expression in the parentheses, i++ in this example, indicates how much the value of the index variable should increase every time the loop repeats.

Note that if the index variable has not been declared prior to the loop instruction, it may be declared in the for statement itself, like this:

for (int i = 1; i < n; i++) { <statement;> <statement;> <statement;> }

Adding up lots of numbers

Write a program that uses a for loop to add the numbers from 1 to 1000000 (one million). Will the int type be sufficient for holding the sum of these values?

Show/hide answer

This code attempts to add the numbers:

public class MillionAdder { public static void main(String[] args) { int sum = 0; for (int i = 1; i <= 1000000; i++) sum = sum + i; System.out.println(sum); } }

The result produced is 1784293664, which might be the right answer? It has the look of an overflow, though. What is the maximum value that an int variable can store?

If we're concerned that there might be an overflow error, consider declaring sum to be a long type. Run the program again and see if the answer is the same.

For really big integers, you'll need to investigate the slow but powerful BigInteger class.

More for loops

There are variations on the syntax for the for loop that make it even more versatile. Other examples:

Just as with while loops, you'll need to be careful to avoid off-by-one errors when writing your for loops!

5.2.2. Using loops for different objectives

Up to this point, we've played a bit with the two main types of loops in Java: the while loop, and the for loop.

The while loop

The while loop may be used for for any situation, but especially situations when we maybe don't know exactly how many times the loop is going to need to repeat. It's often referred to as a conditional loop or an indefinite loop because in some situations, we don't know exactly how many times the loop will repeat.

Useful for:

The for loop

The for loop is typically used for counting through a known set of values, or repeating something a known number of times. It is sometimes called an unconditional loop or a definite loop because we can usually identify how many times the loop is going to run in advance.

Useful for:

Classic loop processing

Write a short snippet that could be used to count the number of vowels in the String word. Don't worry about the letter 'y'.

Show/hide answer

int vowelCount = 0; for (int i = 0; i < word.length(); i++) { if (word.substring(i, i+1).equals("a") || word.substring(i, i+1).equals("e") || word.substring(i, i+1).equals("i") || word.substring(i, i+1).equals("o") || word.substring(i, i+1).equals("u")) vowelCount++; } System.out.println(vowelCount);

5.3. Overview - Sentinel Values, Common Loop Algorithms

We've discussed while loops and for loops, and seen a few simple examples of how they might be used. Let's take a look at some of the ways that loops are commonly used.

5.3.1. Sentinel values

A sentinel keeps watch, and sentinel values are values that are watched for in a loop to find out when the loop should complete.

We've already looked at one way to do this. Here, the sentinel value is "Q":

System.out.print("Enter a value, Q to quit: "); String input = in.next(); // get the value while (!input.equals("Q")) { int numericValue = Integer.parseInt(input); ... System.out.print("Enter another value, Q to quit: "); input = in.next(); // get the value } System.out.println("Done with the loop!");

5.3.2. The break and continue statements

The idea of checking values in the middle of the loop in the "loop-and-a-half" structure above strikes some people as inelegant, and not the cleanest way to code these situations.

One useful alternative involves a break statement in the middle of the loop:

while (true) { System.out.print("Enter a value, Q to quit: "); String input = in.next(); // get the value if (input.equalsIgnoreCase("Q")) break; // used to immediately exit a while, for, or do loop else { int numericValue = Integer.parseInt(input); ... } }

Our textbook doesn't use break statements this way, but it's a very common strategy for lots of coders, and allowed by Java.

5.3.3. Common Loop Algorithms

As you might expect, loops are used for a lot of different "standard tasks" in program. Here are some of the most common.

5.3.3.1. Keeping a Running Total of Values

We often need to sum up a series of values. A variable called sum here does just that:

Scanner in = new Scanner(System.in); double sum = 0; System.out.print("Enter a numeric value ('q' to quit): "); String value = in.next(); while (!value.equalsIgnoreCase('q')) { sum = sum + Double.parseDouble(value); System.out.print("Enter another value ('q' to quit): "); value = in.next(); } System.out.println("The sum of your values: " + sum);

5.3.3.2. Counting Matching Data

Sometimes we need to identify which items in a sequence match a certain requirement—counting vowels, for example:

String greeting = "Hello, World!"; int vowelCount = 0; for (int i = 0; i < greeting.length(); i++) { if (greeting.substring(i, i+1).equals("a") || greeting.substring(i, i+1).equals("e") || greeting.substring(i, i+1).equals("i") || greeting.substring(i, i+1).equals("o") || greeting.substring(i, i+1).equals("u")) { vowelCount++; } } System.out.println("There were " + vowelCount + vowels.");

5.3.3.3. Data Validation / Error-Trapping

Sometimes we need to validate data that the user has entered, or make sure that the user hasn't entered data that will cause our program to crash.

System.out.print("Enter a positive integer: "); int value = in.nextInt(); while (value < 0) { System.out.print("Error--Please enter a value greater than 0: "); value = in.nextInt(); } System.out.println("You entered the value " + value + ". Thank you!");

5.3.3.4. Finding the Maximum or Minimum in a Series of Values

Being able to find the largest or smallest value in a series turns out to be enormously useful.

Scanner in = new Scanner(System.in); int maximum = Integer.MIN_VALUE; # This smallest possible value System.out.print("Enter a numeric value ('q' to quit): "); String value = in.next(); while (!value.equalsIgnoreCase('q')) { if (Integer.parseInt(value) > maximum) maximum = Integer.parseInt(value); System.out.print("Enter another value ('q' to quit): "); value = in.next(); } System.out.println("The largest value was: " + maximum);

5.3.4. Sequential Data Analysis

Take a look at this DataSet class that calculates information about a set of values entered by the user.

/** DataSet.java Computes the average of a set of data values. @author Cay Horstmann @version Java Concepts 5th Edition */ public class DataSet { private double maximum; private int count; private double sum; /** Constructs an empty data set. */ public DataSet() { sum = 0; count = 0; maximum = 0; } /** Adds a data value to the data set @param x a data value */ public void add(double x) { sum = sum + x; if (count == 0 || maximum < x) maximum = x; count++; } /** Gets the average of the added data. @return the average or 0 if no data has been added */ public double getAverage() { if (count == 0) return 0; else return sum / count; } /** Gets the largest of the added data. @return the maximum or 0 if no data has been added */ public double getMaximum() { return maximum; } }

Analyzing the code, we can see how it works. Let's see how a runner program might interact with this class, getting input from the user via a Scanner, and using a sentinel value to manage that interaction:

/** Compute the average and maximum of a set of input values. */ import java.util.Scanner; public class DataAnalyzer { public static void main(String[] args) { Scanner in = new Scanner(System.in); DataSet data = new DataSet(); boolean done = false; while(!done) { System.out.print("Enter value, Q to quit: "); String input = in.next(); if (input.equalsIgnoreCase("Q")) { done = true; } else { double x = Double.parseDouble(input); data.add(x); } } System.out.println("Average: " + data.getAverage()); System.out.println("Maximum: " + data.getMaximum()); } }

5.3.5. Finding primes

Exercise P6.7. Prime numbers.

Write a program PrimeRunner that prompts the user for an integer and then prints out all prime numbers up to that integer. For example, when the user enters 20, the program should print

2 3 5 7 11 13 17 19

Recall that a number is a prime number if it is not divisible by any number except 1 and itself. Your program PrimeRunner should call a class PrimeGenerator that you will write. PrimeGenerator contains a method nextPrime that returns the next prime each time it is called.

Example:

// Code from PrimeRunner PrimeGenerator pGen = new PrimeGenerator(); // constructs new PrimeGenerator System.out.println(pGen.nextPrime()); // prints out 2 System.out.println(pGen.nextPrime()); // prints out 3 for (int i = 0; i < 5; i++) // will run 5 times { System.out.println(pGen.nextPrime()); // prints 5, 7, 11, 13, 17 }

Show/hide answer

RESTATED:
We need a program that the user will enter information into: (a Main program)

If we know that 5 is a prime, how do we go about figuring out what the next prime is? (Add one, start trying to divide by every previous number; not very efficient but that's all we have at this point. If one of the numbers we're testing is evenly divisible, then we know this candidate is not a prime. If we do make it all the way through the list without it dividing evenly, we know it must be a prime.)

We need a class PrimeGenerator that starts "at the beginning". Every time the method .nextPrime() is called, PrimeGenerator will see what the last prime it found was, and find the next prime after that.

How? By adding one to the last prime it found, and running through all the numbers from 2 to the number we're checking (or the square root of that number if we want to be a little more efficient), and seeing if any of those numbers divide into our candidate. If they don't, the candidate is our new prime number—we need to remember it and return it.

No, I just want the answer

public class PrimeGenerator { // instance variables private int candidate; /** * Constructor for objects of class PrimeGenerator */ public PrimeGenerator() { // initialise instance variables candidate = 1; // not really, but seeds the method } /** * nextPrime identifies the next prime in the sequence, * and returns it * @return the next prime in the sequence */ public int nextPrime() { boolean isPrime = false; // seed while (!isPrime) { isPrime = true; // assume positive candidate++; // start checking at next value for (int i = 2; i <= Math.sqrt(candidate); i++) { if (candidate % i == 0) { isPrime = false; break; } } } return candidate; } }

5.4. Overview - Nested Loops

We've had a good chance to look at how we can use loops to quickly work through a series of values.

Often we'll need to use a looping structure as the body of a larger, outer loop. Such loops are said to be nested.

5.4.1. Nested loops

Just as we can nest if-else statements inside other if-else statements to work on multiple levels of a problem, we can nest loops inside other loops.

Pseudocode example: Going through a document word by word

for (each word in sentence) { for (each letter in this word) { // do something } }

Pseudocode example: finding the total assets of a bank

double sum = 0; for (each client at bank) { for (each account for this client) { sum = sum + this account } }

5.4.2. Nested counting loops

One very common type of loop pattern involves using two loops to work through a 2-dimensional grid or table.

Generally:

for (each row in the table) { for (each column in a row) { // do something } }

The Boxy class

Take a look at the Boxy class shown here, and see how the col and row classes are set up.

/** * Boxy class creates a box of a given dimension. * * @author Richard White * @version 2013-10-24 */ public class Boxy { private int dimension; // instance variable /** * Constructor for objects of class Boxy * @param theDimension the width and height of the box */ public Boxy(int theDimension) { dimension = theDimension; } /** * printIt method displays a text version of the box */ public void printIt() { for (int row = 0; row < dimension; row++) { for (int col = 0; col < dimension; col++) { System.out.print("[]"); } System.out.println(); // go down to next line } } /** * toString method returns a text version of the box * @return a string encoding of the box */ public String toString() { String theBox = ""; for (int row = 0; row < dimension; row++) { for (int col = 0; col < dimension; col++) { theBox += "[]"; } theBox += "\n"; // go down to next line } return theBox; } }

Note that there are two methods in the Boxy class that create a visual representation of the box. One displays it on the screen, and one composes and returns a string that can be used by the "caller" to display the box on screen.

5.5. Overview - Random numbers, Simulations

We've had a good chance to look at how we can use the two types of loops—conditional (while loops) and unconditional (for loops).

Now let's look at a new type of program that will make good use of what we've learned: models, or simulations.

5.5.1. Random Numbers and Simulators

Monte Carlo Simulation

In a Monte Carlo simulation you repeatedly generate pseudo-random numbers and use them to simulate an activity.

5.5.2. Getting Random Integers

To get pseudo-random numbers in a Java program, there are two common strategies.

5.5.2.1. The Math.random() method

Math.random()

Java's Math library includes the .random() method, which returns a random double value between 0 (inclusive) and 1 (exclusive).

System.out.println(Math.random()); → 0.341889604189878 System.out.println(Math.random()); → 0.5569208907430896

In many simulations we need to create an integer value within some range of values: 1-6 for a dice roll, for example. We can return any desired range of values by manipulating the result returned by Math.random():

int dieRoll = (int) (Math.random() * 6) + 1;

What's going on here?

  1. Math.random() creates a random double between 0 and 0.9999999999999999.
  2. That value is multiplied by 6 to yield a double number between 0 and 5.999999999999994.
  3. We add 1 to that value to get a double number between 1 and 6.999999999999994.
  4. We cast that value as an int to get an integer between 1 and 6 that is stored in the int variable dieRoll.

This strategy for creating integers within a given range is very common.

5.5.2.2. The Random package

The Random package contains a more extensive collection of utilities that allow you to work with random and pseudorandom values. Use of that package is not required for this class, although you'll see it used in examples in our textbook, so it helps to know how to create a Random object.

Use of the Random package

import java.util.Random; // The Random package // create a random number generator instance Random generator = new Random(); // get either a random integer in the range 0 to n-1: int randNum1 = generator.nextInt(n); // ... or a random Double in the between 0 (inclusive) and 1 (exclusive) double randNum2 = generator.nextDouble();

What statements would you use to simulate rolling a six-sided die?

int aDieRoll = generator.nextInt(6) + 1;

5.5.3. Writing a simulation

NumberGuessingGame.java

In class, write a program NumberGuessingGame.java. This single main program plays a number guessing game with the user. The program should:

  1. create a random number between 1 and 10 inclusive.
  2. ask the user to guess the number
  3. if the user gets it right, congratulate them, and the game is over
  4. if the user doesn't guess the number, tell them if they guessed too high or too low
  5. give the user three tries to guess the number
  6. if they get don't get it right in three tries, tell them what the number was, and the game is over
  7. ask them if they want to play again, and keep playing the game until they indicate they want to quit.

Show/hide clarifying questions

How many loops does this program require? Are they going to be counting loops (with a known number of iterations) or conditional loops (that require checking a value)? If conditional, what are the conditions going to be on the loops?

What are the Big Pieces of this program that we need to figure out?

  1. Create a random number for them to guess.
  2. Get their guess
  3. Compare guess to random number and give appropriate feedback
  4. Keep track of how many guesses they've made (what kind of loop? counting? or conditional?)
  5. Ask them if they want to play again (what kind of loop? counting? or conditional?)