Why a List can't do this
Many languages have an array that you can grow with push or append. In Scala, the two everyday sequence types differ in an important way:
- A
Listis immutable. It never changes after it is created. Operations like:+build and return a new list and leave the original alone. - An
Arrayis mutable but has a fixed size, so it can't grow. - An
ArrayBuffer, fromscala.collection.mutable, is mutable and growable, so it plays the role of a push-able array.
The lesson uses the full name scala.collection.mutable.ArrayBuffer because the type is not imported by default.
Appending with +=
The += operator adds one element at the end of a buffer, changing the buffer itself:
val arr = scala.collection.mutable.ArrayBuffer(1, 2, 3)
arr += 4
println(arr) // ArrayBuffer(1, 2, 3, 4)
Notice that arr is declared with val. That is fine, because val only prevents reassigning the name arr. The buffer it refers to can still change. Appending is always at the end, so the new element gets the index size - 1.
Other ways to add elements to a buffer:
val arr = scala.collection.mutable.ArrayBuffer(1, 2, 3)
arr.append(4) // same as arr += 4
arr ++= List(5, 6) // append several elements at once
arr.prepend(0) // add at the front
arr.insert(1, 99) // add at index 1, shifting later elements right
println(arr) // ArrayBuffer(0, 99, 1, 2, 3, 4, 5, 6)
How += reads
On a mutable buffer, arr += 4 is a method call that changes arr and gives back the same buffer. It is not shorthand for arr = arr + 4, which is why it works with a val. The buffer is stored as one growable block of memory, so adding at the end is cheap on average, while adding at the front or the middle with prepend or insert has to shift the elements after it.
Elements are added one at a time and keep their order, so after arr += 4 followed by arr += 5 the buffer ends with 4, 5.
The exercise
The lesson starts with two buffers:
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
- Append
"apple"tofruits. Afterfruits += "apple"it holds four fruits and"apple"is last. - Append
10and then23toages.+=takes one value, so it is used twice, in that order. The order matters because each append goes to the end:10goes first and23after it, so23is last.
The complete solution:
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
fruits += "apple"
ages += 10
ages += 23
Afterwards fruits is ArrayBuffer(mango, kiwi, grapefruit, apple) and ages is ArrayBuffer(14, 32, 50, 21, 10, 23).
Mistakes to avoid
- Using
+=on an immutableList. AListhas no+=that works on aval, and the compiler reports that+=is not a member ofList[Int]. To "add" to a list, build a new one with:+:val bigger = list :+ 4. The original list is unchanged. With avar,list = list :+ 4works, but each append copies the list, which is slow for long lists. - Expecting
:+to change a buffer. On a buffer it also returns a new collection and leaves the buffer as it was. Use+=to change the buffer itself. - Appending the wrong type. The buffer's element type is fixed by its first elements. A buffer of
Intrejectsages += "ten"when the program is compiled.
Key takeaways
- Use
scala.collection.mutable.ArrayBufferwhen you need to grow a sequence in place. buffer += valueappends one element at the end.valfixes the name, not the contents of a mutable collection.- A
Listis immutable: "adding" gives a new list.
