While conceptually simple, loops actually turn out to be a bit tricky to get right. Unfortunately stepping through the execution of a loop line by line can be tedious due to the repetitionβimagine if you had a loop that ran a million times; you wouldnβt want to step through that! So itβs useful to have a few techniques for analyzing loops a bit more abstractly.
Often the main question we have about a loop is simply how many times will the body run? Sometimes this is in order to do run-time analysis in order to get an estimate of how long a program will take. (A loop that runs a billion times is going to take a lot longer than one that runs ten times even though, textually, the code of the two loops might be identical.) More often we know how many times the loop is supposed to run and we want to make sure weβve written the loop correctly.
In particular, a common bug that occurs in loops is called a fencepost error or an off-by-one error which is when a loop iterates one fewer times (or occasionally one more) than it should. The name comes from the mistake that many people make when asked how many fencepostsβthe uprights in a fenceβyou need to build a hundred foot fence with fenceposts every ten feet. If you think the answer is ten you have a fencepost error because the correct answer is eleven.
If all else fails, sometimes it is necessary to trace through some or all the iterations of loop. If so weβll want to use a techniue called a trace table to keep track of things.
After ensuring those features are present, any Java programmer should be able to look at this loop and almost instantly respond to the question, βHow many times will the body run?β with the answer, βn times.β In this case n is a variable but it could be any expression.
If it was a literal number like 10 the answer would be βten timesβ. And if it was some other expression like s.length() where s was a String, the answer would be βonce for every character in sβ.
Note that it is possible for code in the loop body to cause the loop to complete early. For instance, a return statement causes an immediate return from the method, breaking out of the loop early. But for purposes of analyzing loops, we typically assume nothing like that happens in the body.
You may have a fencepost error. While the largest value i will take on in the body of the loop is 99, there are one hundred numbers between 0-99, inclusive.
For instance if a loop uses <= rather than < we can change the <= to a < if we add one to the limit. And assuming weβre only trying to figure out how many times the loop body will run weβre can add or subtract the same amount from both the initial value and the limit to get a canonical loop that will run the same number of times (though obviously with different values for the loop variable).
We can also use this trick to analyze loops that run backwards. To convert a backwards loop to a canonical forwards loop we first convert it to an equivalent (except for the order, obviously) forward loop and put that forward loop into canonical form.
To turn around a backwards loop, write a forwards loop where the initial value of the loop variable is value of the limit in the originial loop if it is written with >= and one greater than the limit if it is written with >. And the limit in the forwards loop is one greater than the initial value in the backwards loop.
You may have a fencepost error. While the largest value i will take on in the body of the loop is 20 and the smallest is 11, there are ten numbers between 11-20, counting 11 and 20 themselves.
How many stars are printed out in this loop? Depends how many iterations the loop runs. See if you can convert the loop to a canonical loop in your head or on a piece of paper to predecict how many iterations it will run before you run the code.
Translating between non-canonical and canonical loops is a good skill to have. In general we should write loops in canonical form whenever we can so that people reading our code (including ourselves!) can see at a glance what is going on. That will also make the times when we have to write a non-canonical loop because something tricky is going on stand out so we know to pay extra careful attention to that code.
But we can further distill the process of converting to a canonical loop if all we care about is how many times the loop body will run. For any loop all we really need to know is the smallest value the loop variable will take on and the limit, which is one greater than the largest value. I.e. these are the initial value and the value to the right of the < in the condition of a canonical loop. Then the number of times the loop will run is just \(limit - smallestValue\text{.}\)
Recall that nested loops are just loops within the body of another loop. Since the whole inner loop runs all of its iterations every time the outer loopβs body runs once, the total number of times the body of a nested loop runs is the number of times the outer loop runs multiplied by the number of times the inner loop runs. Here is an example of a nested loop that prints a rectangle of stars:
Analyzing the nested loops using the formula from above, we see that the outer loop has a smallest value of 0 and a limit of 5 so it runs \(5 - 0 = 5\) times. And the inner loop has a smallest value of 0 and a limit of 10 so it runs \(10 - 0 = 10\) times. And five times ten gives us fifty total stars.
Because they are hard enough to get right even when theyβre simple, itβs a good idea to keep loops as simple as possible. But sometimes we come across loop that contain multiple variables that all change in the body of the loop and which may all be involved in the loopβs condition which makes all our simpler loop analysis techniques less useful. In those situations it may be time to pull out a piece of scratch paper and create a trace table.
A trace table is a table that we use to track the values of the variables in a loop at each iteration of the loop. Each row of the table represents one iteration of the loop and each column holds the value for one variable. To see how to build a trace table weβll use this example loop.
To make a trace table, the first step is to count the variables that are assigned values in the body of the loop Make a table with as many columns as variables plus one. Label the first column βiterationβ. Then label the remaining columns with variable names in the order that they are assigned new values in the loop body. The header row for a trace table for this loop would look like this:
Now to fill out the table add row for iteration 0, i.e. before the loop body has run and fill in the initial value of each variable in the appropriate column.
Then add rows to the table, Before you add a new row, check whether the loop condition given the values of the variables recorded in the current row. If it is false the loop ends and the table is complete. But if itβs true, add a row, increasing the number in the iteration column, and then mentally executing the code in the loop to determine the new value of each variable.
If one variable depends on a variable to its left in the table, use the value in the current row; otherwiseβincluding if the variable depends on itselfβuse the value from the previous row. (This is why itβs important to put the variables into columns in the right order.)
From the last row of a completed trace table we can read off how many times the body was executed from the iteration column and the final values of all the variables. Note it even works if if the loopβs condition is never true and the body never executes. In that case the last row will be for iteration 0 and the final values of all the variables will be whatever they were initialized to.
Make sure you understand how the table was filled out. You may want to get a piece of paper and try to make your own table and see if you get the same answer as above. Or you can click the Code Lens button to trace the code below or add print statements like System.out.println("var1: " + var1 + " var2: " + var2); before, inside, and after the loop and Run the code to see how the variables change.
The loop stopped because var2 = 0. After the first execution of the loop var1 = 1 and var2 = 1. After the second execution of the loop var1 = 2 and var2 = 0. This stops the loop and doesnβt execute the second part of the complex conditional.
This would be true if the and (&&) was an or (||) instead. But in a complex conditional joined with and (&&) both conditions must be true for the condition to be true.
Here is a particularly hairy loop. Get a piece of scratch paper and make a trace table. When you are done, click βAnswerβ and compare to see if you got it right.
Itβs useful to know how to build trace tables and actually making them is good practice for following a mechanical procedure precisely which is good practice for thinking about how code works.
In actual programming practice, trace tables are of limited use. They have the virtue that, similar to boolean truth tables, once you master the mechanics you can reliably crank them out without having to think too hard. On the other hand, code tracing is only possible if we know the initial values. And if we knew the initial values we could just run the program to find out what it does. Or write a program to print out the trace table! To actually understand a loop via tracing you might need to trace it multiple times with different initial values. Which is even more tedious than making just one.
However there are times (particularly on the AP exam) when a trace table can quickly and reliably answer certain questions about a piece of code. If you ever find yourself confronting as hairy a loop as the one in ActivityΒ 5.5.9Β Tracing a hairy loop, you way want to make a trace table to help yourself understand whatβs going on so you can rewrite it to be simpler. Unless the loop is on the AP exam in which case just shake your fist at the AP exam writers and then answer the question.
We encourage you to do this activity as a POGIL (Process Oriented Guided Inquiry Learning) group activity. POGIL groups are self-managed teams of up to 4 students where everyone has a POGIL role and works together to solve the problems, making sure that everyone in the team participates and learns.
Do the following exercises in your group. Make sure you draw the trace tables keeping track of all the variables in the loops. Use the formulas to determine how many times the loops run. If your group finishes early, do some of the multiple-choice problems in the Practice and Summary section of this unit.
(AP 2.12.A.1) A statement execution count indicates the number of times a statement is executed by the program. Statement execution counts are often calculated informally through tracing and analysis of the iterative statements.
The number of times a loop executes can be calculated by largestValue - smallestValue + 1 where these are the largest and smallest values of the loop counter variable possible in the body of the loop.
In non-rectangular loops, the number of times the inner loop runs can be calculated with the sum of natural numbers formula n(n+1)/2 where n is the number of times the outer loop runs or the maximum number of times the inner loop runs.