Loops and ranges
Repeating work four ways — for-in over a range or list, each over a list, times for a count, while for an unknown number of passes — and how to leave early.
Ranges, and for-in
1..5 is a range: every whole number from one to five, both ends included, and 1..<5 stops before five. A range can be walked, collected, turned into a list, and used as a switch case — it is a value, not a loop. That is why (1..5).contains(3) is a question you can ask without writing a loop, and why printing a range shows [1, 2, 3, 4, 5] rather than the two dots.
for (n in 1..5) walks it, one pass per number. The same syntax walks a list: for (name in names). When the number of passes is known and each pass needs the item, this is the plainest loop there is and it reads like the sentence it is.
each, and times
list.each { ... } hands every item to a closure. The block is the body, and the current item arrives as it unless you name it yourself. It reads well for "do this to everything", and it is the method the next chapter builds on. It also answers the collection it walked rather than the block’s last value, so a line that assigns the result of each gets the original list back.
3.times { ... } runs a block three times, counting from zero, and eachWithIndex gives you both the item and its position. These are not two languages: each and times are ordinary methods with a closure argument, which is why they can be chained and why they cannot be steered with break.
while, and leaving early
while (fuel > 0) checks before each pass and is the right loop when the count is not known at the start — something burns down until it runs out. The body has to change whatever the condition watches, or the loop never ends. A while that never ends shows up as a Run that never returns, and the fix is always the same: find the value the condition watches and change it inside the body.
break leaves the loop, continue skips to the next pass, and both work in for and while. They do not work inside each: the block is a closure, not a loop body, so a break there is refused at compile time with "break statement is only allowed inside loops or switches". When a loop must exit early, use for.
Four loops, one exit
for (i in 1..3) {
println 'pass ' + i
}
def totals = [2, 4, 6]
totals.each { println it * 10 }
2.times { println 'again' }
def n = 0
while (n < 3) {
n = n + 1
}
println 'n = ' + n
for (c in ['a', 'b', 'c']) {
if (c == 'b') continue
print c
}
println()A range is a value
1..5 is an IntRange, an object with methods: sum(), collect { }, contains(3), toList(). That is why it can be a switch case and why (1..10).sum() needs no loop. for (n in 1..5) walks it with an iterator, and each walks it with a closure; both visit the same numbers.
1..<5 is the same object with the right end excluded. The half-open form is the one to reach for when the end is a length: 0..<list.size() visits every valid index exactly once, while 0..list.size() visits one too many and throws.
Loops and closures are different tools
for and while are statements. They can break, continue and return from the enclosing method, and the compiler knows they are loops. each, times and eachWithIndex are methods that call a closure; the closure is a separate piece of code, so break and continue have nothing to attach to.
That difference decides which one to use. If the loop must stop early, or skip based on a condition, use for. If the job is to do something to every item, each reads better and chains with the collection methods. times is for a count with no collection behind it.
Three loop mistakes: 1..list.size() is one past the last index, so use 0..<list.size(); break inside each is a compile error, because the block is not a loop; and removing items from a list while each walks it skips the next item. Collect the removals and apply them after the loop.
Try these next
- Print the sum of
1..100withsum()and again with awhileloop. - Walk a list with
eachWithIndexand number the lines from 1. - Write a nested loop that prints a 3 by 3 grid of products.
Count, walk, repeat, drain
Run it and match each block to the output. Then change the first range to `1..<3` and see what stops happening, and make the `while` loop count up from 0 to 3 instead of down from 3.
A table, a running total, a numbered list
Run it and check one row of products by hand. Then make the table go to 5 instead of 3, and change `eachWithIndex` so the count starts at 0.
A loop that will not end is not a mystery: the condition is still true after the body, so something in the body was supposed to change it. Print that value inside the loop and the answer is on the screen.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is the difference between 1..3 and 1..<3?
