Counting elements with size
Every Scala sequence, whether a List, an Array or an ArrayBuffer, reports how many elements it holds with size:
val ages = List(14, 32, 50, 21)
println(ages.size) // 4
The method length returns the same number, so ages.length works equally well. The count is the number of elements, not the last index. Because positions start at 0, a collection of size 4 has indexes 0, 1, 2 and 3.
For an ArrayBuffer the size can change over time. After buffer += 99 the size is one larger, and after buffer.remove(0) it is one smaller. Each time you read size, you get the current count.
Here is the size changing as a buffer grows and shrinks:
val buffer = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
println(buffer.size) // 4
buffer += 99
println(buffer.size) // 5
buffer.remove(0)
println(buffer.size) // 4
A List or an Array always keeps the size it was created with.
Using the size to reach the end
The last valid index is always size - 1, so you can read from the end without knowing the size in advance:
println(ages(ages.size - 1)) // last element
println(ages(ages.size - 2)) // second to last
Scala indexes with parentheses, so ages(i) is correct and ages[i] is not. Square brackets are for type parameters such as List[Int]. For the ages above, ages.size - 1 is 3, which is the last position, and ages.size - 2 is 2.
The exercise
The lesson starts with two ArrayBuffer variables:
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
Step 1: the size of fruits
println(fruits.size) prints 3.
Step 2: the last number of ages
The size is 4, so the last index is 3. println(ages(ages.size - 1)) prints 21.
Step 3: the second to last number
Subtract 2 to step one position further back: println(ages(ages.size - 2)) prints 50.
The finished program
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
println(fruits.size)
println(ages(ages.size - 1))
println(ages(ages.size - 2))
Its output:
3
21
50
Gotchas
- Size is not an index.
ages(ages.size)is one past the end and throws anIndexOutOfBoundsException. For the four ages, the message is4 is out of bounds (min 0, max 3). - Empty collections. When the size is 0,
size - 1is -1, which is not a valid index. Checkages.nonEmptyorages.size > 0before reading the last element. - Shortcuts.
ages.lastreturns the last element without any arithmetic. On an empty collection it throws aNoSuchElementException, just as an invalid index would fail. - Loops. A loop over all indexes uses
0 until ages.size. The range stops before the size, which keeps every index valid.
Why the last index matters
Code that depends on a typed-in number breaks when the data changes. Writing ages(3) works for four ages, but if a fifth is added at the end, it silently reads the fourth. The form ages(ages.size - 1) follows the collection, so it always reaches the real last element. For the same reason, loops use size as their bound instead of a literal.
size versus length
Both names exist because Scala sequences come from two traditions. length is the older name, used by Array and strings. size is the general name that works on every collection, including sets and maps, which have no positions at all. In this lesson's code either name is correct, so pick one and use it consistently. The collection type decides nothing here: fruits.size and fruits.length are both 3.
Key takeaways
sizeandlengthgive the number of elements.- The last index is
size - 1, the second to last issize - 2. - For an
ArrayBuffer, the size changes when you add or remove elements.
