Removing by index
Many languages have a pop that removes the last element of an array. In Scala, an ArrayBuffer (from scala.collection.mutable, written with its full name in the lesson) has remove(index), which deletes the element at an index. The buffer shrinks by one, and the elements after the removed one shift to the left.
val arr = scala.collection.mutable.ArrayBuffer(1, 4, 6)
arr.remove(1)
println(arr) // ArrayBuffer(1, 6)
remove also returns the element it deleted, so you can keep it:
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val removed = fruits.remove(2)
println(removed) // grapefruit
println(fruits) // ArrayBuffer(mango, kiwi)
When the returned value isn't needed, as in the lesson, it is simply ignored.
Removing the last item
Because indices run from 0 to size - 1, the last element sits at index size - 1. So "pop" is written as:
buffer.remove(buffer.size - 1)
The expression buffer.size - 1 is evaluated before the removal, using the current size, so the same line works no matter how long the buffer is.
What happens to the other elements
Removing from the end is the cheap case: nothing needs to move. Removing from the front or the middle shifts every later element one place to the left, so the indices of those elements change. After remove(0) on ArrayBuffer(14, 32, 50, 21), the value 32 is at index 0 and the buffer is ArrayBuffer(32, 50, 21). Keep that in mind if you remove inside a loop over the same buffer: the indices move under you.
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)
- Remove the last fruit. The last index is
fruits.size - 1, which is2, so"grapefruit"goes and two fruits remain. - Remove the last two ages. The first call removes
21at index 3. Now the buffer holds three elements, so the same expressionages.size - 1gives2and the second call removes50. Writing the index as an expression rather than a fixed number is what lets the same line work twice.
val fruits = scala.collection.mutable.ArrayBuffer("mango", "kiwi", "grapefruit")
val ages = scala.collection.mutable.ArrayBuffer(14, 32, 50, 21)
fruits.remove(fruits.size - 1)
ages.remove(ages.size - 1)
ages.remove(ages.size - 1)
Afterwards fruits is ArrayBuffer(mango, kiwi) and ages is ArrayBuffer(14, 32).
Removing several or by value
Two related operations come up often. remove(index, count) deletes count elements starting at index, and -= deletes the first element equal to a given value:
val a = scala.collection.mutable.ArrayBuffer(1, 2, 3, 4)
a.remove(1, 2) // deletes the elements at indices 1 and 2
println(a) // ArrayBuffer(1, 4)
val b = scala.collection.mutable.ArrayBuffer(1, 2, 3, 2)
b -= 2 // only the first 2 is removed
println(b) // ArrayBuffer(1, 3, 2)
Mistakes to avoid
- Using
fruits.sizeas the index. It is one past the last element, andremovethrows anIndexOutOfBoundsException. - Removing from an empty buffer. The expression
size - 1is then-1, andremove(-1)throws anIndexOutOfBoundsException. Checkbuffer.nonEmptyfirst when the buffer might be empty. - Confusing index and value.
ages.remove(21)removes the element at index 21, not the value21, and fails on a short buffer. To remove a value, use-=:ages -= 21deletes the first element equal to21. - Trying it on a
Listor anArray. AListis immutable, and anArrayhas a fixed size, so neither has an in-placeremove. Use anArrayBuffer, or build a new collection, for examplelist.init, which is the list without its last element.
Key takeaways
buffer.remove(i)deletes the element at indexi, shifts the rest left and returns the removed element.- The last element is at
buffer.size - 1. - Removing from an empty buffer throws, so guard against it.
-=removes by value,removeremoves by index.
