Skip to content
Topos Uranos

Summary

The lesson Loops taught how to write a repetition; this one teaches how to write it well and how to know why it is right. It begins with the patterns from which most loops are built (the counter, the accumulator, the maximum and the minimum, the sentinel and the flag), each defined by what its variable means after every iteration. That observation leads to the central concept: the invariant, a statement about the variables that holds before the first iteration and after each one, and that, together with the exit condition, implies the result. It is an induction on the number of iterations, and with it a loop is designed backwards: first what must hold at the end, then the invariant, and finally the body. Termination is justified with the variant, already introduced in the lesson From the problem to the algorithm, and the lesson shows three loops that do not terminate: one with a real-valued condition, one with an unsigned counter and one whose signed counter overflows, which is undefined behavior. It closes with a first contact with the Visual Studio Code debugger, which stops the program at a line and shows its variables iteration by iteration.

Learning objectives

  1. Recognize in a loop the counter, accumulator, maximum and sentinel patterns, and use them to design new loops.
  2. State the invariant of a simple loop and check that it is maintained in an arbitrary iteration.
  3. Justify the termination of a loop with an integer quantity that decreases and is bounded.
  4. Follow the execution of a loop with the Visual Studio Code debugger, using a breakpoint and the variables view.

Patterns

What a variable means

Most loops are not invented: they are assembled from a few pieces that reappear in every program. Each piece is a variable with an initialization before the loop, an update inside it and, above all, a meaning that holds after every iteration. Knowing the pieces by their meaning, and not by their form, is what allows them to be combined without error.

  • A counter holds how many data items have been processed (or how many satisfy a condition). It is initialized to 0, because before the first iteration none has been processed, and it is updated with ++count, only when there is something to count.
  • An accumulator holds the combination of the processed data: their sum, their product. It is initialized to the identity element of the operation (0 for the sum, 1 for the product), because the sum of no data is 0 and the product of no data is 1.
  • A maximum holds the largest of the processed data, and a minimum, the smallest. The maximum of no data does not exist: that is why it is initialized with the first data item, and not with a number chosen at random.
  • A sentinel is an input value that is not a data item but the signal that the data have ended. The pattern requires reading once before the loop and again at the end of the body, as the lesson Loops did when summing up to zero, so that the condition examines each value before it is processed, and the sentinel is never counted as data.
  • A flag is a bool that tells whether something has ever happened among the processed data. It is initialized to false and becomes true the first time it happens; no later iteration sets it back to false.

An example with all the pieces

A rainfall log records the millimeters that fall each day. The program first reads a threshold, and then the measurements of several days, terminated by a negative number, which is not a possible measurement and serves as a sentinel; it writes how many measurements there are, their average, the maximum, how many days exceeded the threshold and whether there was any day without rain.

C++ program
#include <iostream>#include <print>​int main(){    double threshold{};    std::cin >> threshold;    int count{0};          // counter: measurements processed    int aboveCount{0};     // counter: measurements above the threshold    double total{0.0};     // accumulator: sum of the measurements    double largest{0.0};   // maximum: meaningful only if count > 0    bool dryDay{false};    // flag: there was a day without rain    double value{};    std::cin >> value;    while (std::cin && value >= 0.0) {        if (count == 0 || value > largest) {            largest = value;        }        ++count;        total += value;        if (value > threshold) {            ++aboveCount;        }        if (value == 0.0) {            dryDay = true;        }        std::cin >> value;    }    if (count == 0) {        std::println("No measurements.");    } else {        std::println("Measurements: {}; average: {:.2f} mm", count, total / count);        std::println("Maximum: {:.1f} mm; days above {:.1f} mm: {}", largest, threshold, aboveCount);        std::println("Days without rain: {}", dryDay ? "yes" : "no");    }}
Program input
104.5 0 12.5 21.5 7.5 -1
Output
Measurements: 5; average: 9.20 mmMaximum: 21.5 mm; days above 10.0 mm: 2Days without rain: yes

Four decisions deserve comment. The condition count == 0 || value > largest initializes the maximum with the first data item without a separate read: in the first iteration the counter is 0 and the assignment always happens; in the following ones, only if the data item exceeds the maximum. The comparison value == 0.0 is exact, because the value was read and not computed: the digit 0 is converted into the exact zero, and the caution of the lesson Floating-point numbers against == refers to results of operations. The loop condition includes std::cin, so that an input that ends without a sentinel also ends the loop. And the average is computed only if there are measurements: with count equal to 0, total / count would be 0/00/0, which in floating point is not an error but the value “not a number”, which the program would write without warning.

ExampleThe variables iteration by iteration

Proof

  1. Before the loop: the threshold is 10, the counters and the accumulator are 0, the flag is false, and the first measurement, 4.5, has already been read. The maximum is 0, but that value means nothing while count is 0.

  2. First iteration: count is 0, so 4.5 becomes the maximum; the counter rises to 1 and the total to 4.5. Then 0 is read.

  3. Second iteration: 0 does not exceed the maximum, but it raises the flag. Then 12.5 is read.

  4. Third iteration: 12.5 is the new maximum and exceeds the threshold: aboveCount rises to 1. Then 21.5 is read.

  5. Fourth iteration: 21.5 is the new maximum and also exceeds the threshold. Then 7.5 is read.

  6. Fifth iteration: 7.5 changes neither the maximum nor aboveCount; the total reaches 46. Then −1-1 is read.

  7. The condition examines −1-1, which is negative: the loop ends without processing it. The average is 46/5=9.246 / 5 = 9.2.

Note that in each row of the figure every variable fulfills its meaning with respect to the measurements processed so far: total is their sum, largest their maximum, dryDay tells whether there was a zero among them. That property, which holds after every iteration, has a name.