Taking part of a list
Sometimes you need only part of a list, like the first few items. subList(i, j) gives the items from index i up to, but not including, index j. So letters.subList(1, 4) holds the items at indexes 1, 2 and 3, and its length is 4 - 1 = 3.
val letters = mutableListOf("a", "b", "c", "d", "e")
val middle = letters.subList(1, 4)
println(middle)
println(letters)
[b, c, d]
[a, b, c, d, e]
The original list keeps all its items. To go to the very end, use the size as the end index:
println(letters.subList(3, letters.size))
[d, e]
When both indexes are equal, the result is empty: letters.subList(2, 2) prints [].
subList is a view, not a copy
The result doesn't hold its own items. It shows a range of the original list, so a change through one is a change in the other:
middle[0] = "X"
println(letters)
[a, X, c, d, e]
When you need a separate list, call toMutableList() on the slice. Changing the copy then leaves the original alone:
val copy = letters.subList(0, 2).toMutableList()
copy[0] = "Z"
println(letters)
println(copy)
[a, X, c, d, e]
[Z, X]
Traps
- The end index is excluded. To include index 3, the end is 4.
- Bad bounds throw. On the five-letter list,
subList(3, 9)throws anIndexOutOfBoundsException(toIndex = 9), andsubList(3, 1)throws anIllegalArgumentException(fromIndex(3) > toIndex(1)). - Changing the size of the original. If you add to or remove from the original list after taking a view, reading the view afterwards throws a
ConcurrentModificationException. Copy the slice first if the original will keep changing.
Common slices
With a number n and the list letters (five items again), three slices cover most needs:
val n = 2
println(letters.subList(0, n))
println(letters.subList(letters.size - n, letters.size))
println(letters.subList(1, letters.size - 1))
[a, b]
[d, e]
[b, c, d]
These are the first n items, the last n items, and everything except the two ends. Kotlin also has take(n) and drop(n), which return new lists rather than views, but this lesson practises subList.
Why the end is left out
Leaving the end index out looks odd at first, but it makes slices easy to combine. subList(0, 3) and subList(3, size) meet exactly at index 3: nothing is repeated and nothing is missed, which is just what the exercise needs. It also means an empty slice is natural: subList(0, 0), or subList(size, size) at the very end, is simply [].
Because a slice is a view, it can even change the original. Adding "Z" to the view letters.subList(1, 4) inserts it into letters right after the view's last item, giving [a, b, c, d, Z, e], and removing the view's first item removes b from letters too. Most of the time you only read a slice, and then none of this matters.
The exercise
runners holds five names in finishing order. The podium is the first three, so it is subList(0, 3). The rest start at index 3 and run to the end, so the end index is runners.size. Nothing is removed from runners, which is why its size still prints 5.
val runners = mutableListOf("Ana", "Ben", "Cleo", "Dan", "Eve")
val podium = runners.subList(0, 3)
val rest = runners.subList(3, runners.size)
println(podium[0] + " " + podium[1] + " " + podium[2])
println(rest[0] + " " + rest[1])
println(runners.size)
Ana Ben Cleo
Dan Eve
5
Both slices here are views of runners, which is fine because the code only reads them. If it had to change podium without touching the race results, you would add .toMutableList() to the first line.
Key takeaways
subList(i, j)covers indexesitoj - 1.- Use the size as the end index to reach the last item.
- The slice is a view of the original; add
toMutableList()for a real copy.
