Section 6.2

Traversing by index

The counted for loop finally earns its keep — and the one-step-too-far shows up at run time.

When the index itself is what you are after, for … in is no longer enough: it hands you the values, never their position. This is the moment to pick the counted loop of section 4.2 back up.

@entry(start)
object MyProgram {
    start() {
        var temperatures := [12, 19, 7];

        for (var i := 0; i < temperatures.length; i := i + 1) {
            Console.print(text: $"reading {i + 1} of {temperatures.length}: {temperatures[i]}");
        }
    }
}

The three clauses, applied to an array

for (var i := 0; i < temperatures.length; i := i + 1) {

Each clause now says something precise:

  • var i := 0 — we start at the first index, which is zero;
  • i < temperatures.length — we carry on as long as we have not passed the last one;
  • i := i + 1 — we move on by one index.

Section 4.2 showed these three clauses on a run of numbers that served nothing but counting. Here is what they are really for: i is no longer a number, it is an index, and temperatures[i] is the element sitting there.

Notice that the printing uses both: {i + 1} to speak to the reader, who counts from 1, and temperatures[i] to reach the element, which counts from 0. The whole offset sits on one line.

< and not <=

This is the commonest mistake in the whole course, and it is worth stopping on.

Three elements, indexes 0, 1 and 2. The condition i < 3 lets 0, 1 and 2 through, then stops: exactly the three indexes that exist. Write i <= temperatures.length and the loop takes one turn more, with i at 3 — an index that does not exist.

The result is not a badly traversed array, it is a program that stops:

12
19
7
uncaught error — IndexOutOfRangeError : index 3 out of range for an array of length 3

The three lines print, then everything falls.

That is why section 4.3 said the commonest mistake of the counted for is stopping one step too early or one step too late. Here is the step too far, and what it costs.

Backwards, without counting

Walking an array back to front is such a common need that it takes no counted loop at all:

for reading in reverse temperatures {
    Console.print(text: $"{reading}");
}

The word reverse slips between in and what is walked, and the loop starts at the last element. Everything the previous subsection had you avoid — the starting index, the condition, the direction of the step — goes with it. An empty array takes no turn at all, either way.

reverse is not a reserved word for all that. It takes this meaning only here, right after the in of a for; anywhere else it is a name like any other, and nothing stops you calling a variable reverse.

Which to choose

The rule of section 4.3 still holds, and this subsection only shows its exception:

  • for … in whenever you are going through values, and for … in reverse whenever you go through them backwards. No index to write, so no index to get wrong — the loop cannot go out of bounds.
  • counted for when the index itself is of use: to print it, to compare an element with its neighbour, to go through only part of them.

When in doubt, for … in. An index you do not write is an index you cannot get wrong — and since reverse exists, going backwards is no longer a reason to write one.

Your turn

  • Replace < with <= and run. Three lines, then the error.
  • Go through it backwards by hand — var i := temperatures.length - 1, condition i >= 0, step i := i - 1 — then write the same thing as for reading in reverse temperatures. Three clauses against one word, for the same result.
  • Print only every other element, stepping by 2 instead of 1.
  • Rewrite the loop as for reading in temperatures and see what you lose: no more way to write “reading 2 of 3”.