7.2

Lecture 24: While loops

While loops for unbounded iteration

24.1While loops

In the last few lectures we’ve been discussing looping constructs, which let us repeat a block of statements some number of times. The for-each loop let us repeat the block once per item in an ArrayList, while the counted-for loop let us repeat the block for each value of some index variable as it counted from a starting value to a final value. There is one more loop construct in Java, which is more flexible than either the for-each loop or the counted-for loop, known as a while loop.

A while loop simply repeats a block of code for as long as its termination condition remains true:

 while (someBooleanExpression()) { ...body of loop... }

The execution of this loop is completely straightforward: first, it evaluates whether someBooleanExpression() is true. If it is, it executes the body. Then it repeats. As soon as the condition evaluates to false, the loop stops. This means that if the condition immediately evaluates to false, the loop never runs. For example, this code does not produce an error:
 while(false) { doSomethingWith(1 / 0); }
Since the condition is false, the body never executes, and in particular the division-by-zero does not happen.

To show that while loops are more flexible than the other forms of loops, we first show how to use this loop to express the exact same computation as a counted-for loop. Then, we give examples of loops that cannot be expressed as counted-for loops.

24.1.1Translating a counted-for loop into a while loop

Consider a typical counted-for loop:
 for (int idxVar = start; idxVar < end; idxVar = idxVar + 1) { ...body... }
(As we saw in the last lecture, the termination condition could be more flexible; the update statement could count by numbers other than 1, etc., but the general structure of the loop is relatively typical.)

How could we express this as a while loop? For a start, the meaning of the termination condition of a counted-for loop matches the meaning of the termination condition of a while loop: as soon as they’re false, the loops terminate. So we can start our translation by sketching out
 ... while (idxVar < end) { ... } ...
Every time the counted-for loop iterates, it first executes the termination condition, then the body, then the update statement. This implies we can refine our sketch as:
 ... while (idxVar < end) { ...body... idxVar = idxVar + 1; } ...
The only other piece to a counted-for loop is the initialization statement, which we can just write before our while loop. There is nothing particular to a counted-for loop that happens after the loop terminates, so our final translation of the loop is
 int idxVar = start; while (idxVar < end) { ...body... idxVar = idxVar + 1; }

All four pieces of the counted-for loop are present (initialization, termination condition, body, and update statement), and they serve exactly the same purpose within this style of while loop as they do in a counted-for loop.

Do Now!

Translate the code of findMin to use a while loop instead of a counted-for loop.

But when can while loops be more general?

24.1.2The 3n+1 problem

This puzzle is properly known as the Collatz conjecture, and is an unsolved problem in pure mathematics.

Consider the following mathematical function, defined on positive integers:

\begin{equation*}f(n) = \begin{cases} n / 2 &\text{when $n$ is even} \\ 3n + 1 &\text{when $n$ is odd} \end{cases}\end{equation*}

For any given number, say $$9$$, compute $$f(9)$$, $$f(f(9))$$, etc. The puzzle is this: Will this process always reach $$1$$ for every value of $$n$$? In this example,
 9 -> 28 -> 14 -> 7 -> 22 -> 11 -> 34 -> 17 -> 52 -> 26 -> 13 -> 40 -> 20 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1
so we do reach $$1$$. Eventually. The number of steps it takes to reach $$1$$ is largely unrelated to the size of $$n$$ (it takes longer to get to $$1$$ from $$9$$ than it does from $$11$$), and can grow enormously even for small numbers (try starting with 27).

How might we implement this in Java? Clearly, some form of looping is going on, but which form of loop? We certainly can’t use a for-each loop, since we don’t have an ArrayList of elements to iterate over. And we can’t use a counted-for loop, because we don’t know in advance how many times the loop needs to iterate. But a while loop looks perfect for the job:
 // In some Utils class boolean getsToOne(int n) { while (n > 1) { if (n % 2 == 0) { n = n / 2; } else { n = 3 * n + 1; } } return true; }
Notice that we never return false...but we’re also never guaranteed to actually reach the return true statement. (To properly return false from this function, we’d need to keep track of the list of all numbers we’ve seen, and if we see a number more than once, return false because we’ve entered a cycle.)

Exercise

Try elaborating the function above to keep track of this extra information.

24.1.3Big-bang

How does the big-bang library work? Conceptually, it takes an initial world and invokes the makeImage method to create an image from it, then invokes the onTick method to get the next work, and then repeats these two steps until the worldEnds method returns true. This sounds like looping behavior, but it cannot possibly be a counted-for loop, since the game can last an indefinite amount of time, and we’re not counting any particular index, but rather updating a world from one value to the next. This is a perfect use of a while loop. In fact, this style of using a while loop to repeatedly handle events (such as keypresses or mouse clicks) is at the core of almost every operating system, browser, game console, or other system that deals with interactivity.

24.2Discussion

When should we use each of these three loop types? After all, if every for-each loop over ArrayLists can be rewritten to use a counted-for loop, and every counted-for loop can be rewritten to use while loops, why bother with the other two loops?

To a large extent, this is an aesthetic choice, and programmers largely have come to consensus on when it is appropriate to use each of these loops. The shortest guideline is, “use the simplest form loop that works”. If a problem can be expressed as a computation over the particular items of an ArrayList, use a for-each loop. If the problem requires specifically manipulating indices, or counting for whatever reason, use a counted-for loop. If the number of iterations of the loop is not known a priori, use a while loop.

Additionally, as we will see in the next lecture, for-each loops are in fact more general than just working with ArrayLists, and we will encounter additional reasons to use them.