
Collection processing in Kotlin: drop and take
This is a chapter from the book Functional Kotlin. You can find it on LeanPub or Amazon. It is also available as a course.
When you need to take or get rid of a certain number of elements, the take, takeLast, drop and dropLast functions are at your service:
take(n)- returns a collection with only the firstnelements (or returns the unchanged collection if it has less thannelements).takeLast(n)- returns a collection with only the lastnelements (or returns the unchanged collection if it has less thannelements).drop(n)- returns a collection without the firstnelements.dropLast(n)- returns a collection without the lastnelements.
fun main() { val chars = ('a'..'z').toList() println(chars.take(10)) // [a, b, c, d, e, f, g, h, i, j] println(chars.takeLast(10)) // [q, r, s, t, u, v, w, x, y, z] println(chars.drop(10)) // [k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z] println(chars.dropLast(10)) // [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p] }




Kotlin by design doesn't have aliases for
head(to take the first element) ortail(to drop the first element) methods, that are well known in other functional languages. Instead, we usefirst()anddrop(1).
Most collection processing functions, including take and drop, are extension functions on the Iterable interface, but takeLast and dropLast are extension functions on List. Such design is needed for efficiency.
If we know the size of our collection, these methods can be used interchangeably:
l.take(n)gives the same result asl.dropLast(l.size - n),l.takeLast(n)gives the same result asl.drop(l.size - n),l.drop(n)gives the same result asl.takeLast(l.size - n),l.dropLast(n)gives the same result asl.take(l.size - n),
fun main() { val c = ('a'..'z').toList() println(c.take(10) == c.dropLast(c.size - 10)) // true println(c.takeLast(10) == c.drop(c.size - 10)) // true println(c.drop(10) == c.takeLast(c.size - 10)) // true println(c.dropLast(10) == c.take(c.size - 10)) // true }
If we are operating on a List, all these methods can be replaced with the more universal subList, which expects as arguments the start index (inclusive) and the end index (exclusive), so:
l.take(n)gives the same result asl.subList(0, n),l.takeLast(n)gives the same result asl.subList(l.size - n, l.size),l.drop(n)gives the same result asl.subList(n, l.size),l.dropLast(n)gives the same result asl.subList(0, l.size - n),
fun main() { val c = ('a'..'z').toList() val n = 10 val s = c.size println(c.take(n) == c.subList(0, n)) // true println(c.takeLast(n) == c.subList(s - n, s)) // true println(c.drop(n) == c.subList(n, s)) // true println(c.dropLast(n) == c.subList(0, s - n)) // true }

I find take, takeLast, drop and dropLast much more readable than subList, which requires unintuitive operations on indexes. They are also safer - when we ask to drop more elements than there are in the collection, the result is an empty collection, when we try to take more than there is the result is the collection with as many elements as possible, when we call subList with an incorrect value, it throws an exception.
fun main() { val letters = listOf("a", "b", "c") println(letters.take(100)) // [a, b, c] println(letters.takeLast(100)) // [a, b, c] println(letters.drop(100)) // [] println(letters.dropLast(100)) // [] letters.subList(0, 4) // throws IndexOutOfBoundsException }
