Unit 5: Iteration with Loops
Topics covered in this unit
After completing this unit you will:
- know the basic syntax for constructing a conditional (while) loop
- be able to demonstrate various debugging strategies in an IDE
- know how to use conditional loop in common algorithms
- know the basic syntax for constructing an unconditional (for) loop
- know how to use counter, sum, and sentinel values in loops
- know how to use break and continue statements as needed
- know how to use nested loops as a problem-solving strategy
- be able to use Math.random() to create a pseudorandom value in a given range
- be able to use random values for a simple simulation or game
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.
- while loops, do loops, Debugging
- for loops
- Sentinel Values, Common Loop Algorithms
- Nested Loops
- 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:
Example: A simple loop:
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.
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:
- the initial value of i? (0, or 1?)
- the comparison operator (<, or <=?)
- the final value (10000? 10001?)
- the order of the statements in the loop (do I print i first and then increment, or do I increment first and then print?)
These factors all affect the loop's operation.
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?
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.
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.
- 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. - 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? - 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:
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.
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:
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:
Here's how you'd do the same thing with a for loop:
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.
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:
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?
This code attempts to add the numbers:
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:
- Counting from 1 to 100:for (i = 1; i < 101; i++)
- Counting from 1 to 100:for (i = 1; i <= 100; i++)
- Counting from 0 to 100 by 2s:for (i = 0; i < 101; i=i+2)
- Counting backwards; what number do you think it will stop at? for (i = 100; i > 0; i--)
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:
- getting user input that is error-checked
- playing a game until the user wants to quit
- doing calculations until a certain value is reached
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:
- counting numbers
- counting through letters in a word or sentence
- counting through a list of bank accounts and doing calculations on all of them
- counting through any series of objects and manipulating them somehow
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'.
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":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:
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:
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:
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.
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.
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.
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:
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
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:
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.
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
Pseudocode example: finding the total assets of a bank
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:
The Boxy class
Take a look at the Boxy class shown here, and see how the col and row classes are set up.
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).
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():
What's going on here?
- Math.random() creates a random double between 0 and 0.9999999999999999.
- That value is multiplied by 6 to yield a double number between 0 and 5.999999999999994.
- We add 1 to that value to get a double number between 1 and 6.999999999999994.
- 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
What statements would you use to simulate rolling a six-sided die?
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:
- create a random number between 1 and 10 inclusive.
- ask the user to guess the number
- if the user gets it right, congratulate them, and the game is over
- if the user doesn't guess the number, tell them if they guessed too high or too low
- give the user three tries to guess the number
- if they get don't get it right in three tries, tell them what the number was, and the game is over
- 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?
- Create a random number for them to guess.
- Get their guess
- Compare guess to random number and give appropriate feedback
- Keep track of how many guesses they've made (what kind of loop? counting? or conditional?)
- Ask them if they want to play again (what kind of loop? counting? or conditional?)