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 … inwhenever you are going through values, andfor … in reversewhenever you go through them backwards. No index to write, so no index to get wrong — the loop cannot go out of bounds.- counted
forwhen 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, conditioni >= 0, stepi := i - 1— then write the same thing asfor reading in reverse temperatures. Three clauses against one word, for the same result. - Print only every other element, stepping by
2instead of1. - Rewrite the loop as
for reading in temperaturesand see what you lose: no more way to write “reading 2 of 3”.