Getting the item and its index
You can loop over an index with for (i <- meals.indices) and then read meals(i). Scala has a shorter way to get both at once. The method zipWithIndex pairs each item with its index, and the loop unpacks each pair into two names, as val (name, age) = pet does for a tuple:
for ((meal, i) <- meals.zipWithIndex) {
println(s"$i $meal")
}
For a buffer ArrayBuffer("Ana", "Ben", "Cleo"), zipWithIndex builds ArrayBuffer((Ana,0), (Ben,1), (Cleo,2)): a buffer of pairs.
Mind the order. The item comes first and the index second. If you write (i, meal) instead, the names are swapped: i holds the item and meal holds the number. With strings, i + 1 then compiles, because + joins a string and a number, and the output comes out wrong. For ArrayBuffer("apple", "bread") a loop written as for ((i, item) <- items.zipWithIndex) printing ${i + 1}. $item gives apple1. 0 and bread1. 1.
The index starts at 0, so print i + 1 when you want a numbered list that starts at 1.
Walking two buffers together
a.zip(b) pairs the first item of a with the first item of b, then the second items, and so on. Each pair is a tuple:
for ((fruit, price) <- fruits.zip(prices)) {
println(s"$fruit costs $price")
}
zip stops at the end of the shorter buffer. The extra items are skipped without any error: zipping three names with two numbers gives two pairs.
Using both together
You can zip first and then number the pairs with zipWithIndex. Each item is then a pair together with an index, so the names nest in the loop:
for (((fruit, price), i) <- fruits.zip(prices).zipWithIndex) {
println(s"${i + 1}. $fruit costs $price")
}
With the players and scores of the exercise below, a loop like this that prints ${i + 1}. $player has $points gives 1. Ana has 12, 2. Ben has 9 and 3. Cleo has 15. The inner parentheses hold the pair from zip, and the outer ones hold that pair and the index.
The exercise
A quiz show keeps its players in the buffer players, and their points in scores, in the same order. First print the players as a numbered list from 1, like 1. Ana, with zipWithIndex. Then print each player with their points, like Ana has 12 points, with zip.
class Solution {
def mainFunction(): Unit = {
val players = scala.collection.mutable.ArrayBuffer("Ana", "Ben", "Cleo")
val scores = scala.collection.mutable.ArrayBuffer(12, 9, 15)
for ((player, i) <- players.zipWithIndex) {
println(s"${i + 1}. $player")
}
for ((player, points) <- players.zip(scores)) {
println(s"$player has $points points")
}
}
}
The output is:
1. Ana
2. Ben
3. Cleo
Ana has 12 points
Ben has 9 points
Cleo has 15 points
The second loop doesn't need an index, because zip already lines up each player with the points at the same position.
Why not index into both buffers?
You could write for (i <- players.indices) and read players(i) and scores(i). That gives the same output, but it asks you to keep the index in step by hand, and a typo in one of the two reads goes unnoticed. With zip the pairing is done for you, and each name in the loop says what it is. Use the index form when you really need the position, for example to look at the item before or after the current one.
Common mistakes
- Swapping the names so the index comes first.
- Forgetting that the index starts at 0 and printing
0. Ana. - Zipping buffers of different lengths and not noticing that the longer one is cut short.
- Putting the parentheses wrong when nesting:
zip(...).zipWithIndexgives((item, price), i), not(item, price, i).
Key takeaways
zipWithIndexgives(item, index)pairs, and the index starts at 0.a.zip(b)pairs items by position and stops at the shorter buffer.- Unpack the pairs right in the
forloop.
