Retrieving Collection Parts

The Kotlin standard library contains extension functions for retrieving parts of a collection. These functions provide a variety of ways to select elements for the result collection: listing their positions explicitly, specifying the result size, and others.

Slice

slice() returns a list of the collection elements with given indices. The indices may be passed either as a range or as a collection of integer values.

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four", "five", "six")
  4. println(numbers.slice(1..3))
  5. println(numbers.slice(0..4 step 2))
  6. println(numbers.slice(setOf(3, 5, 0)))
  7. //sampleEnd
  8. }

Take and drop

To get the specified number of elements starting from the first, use the take() function. For getting the last elements, use takeLast(). When called with a number larger than the collection size, both functions return the whole collection.

To take all the elements except a given number of first or last elements, call the drop() and dropLast() functions respectively.

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four", "five", "six")
  4. println(numbers.take(3))
  5. println(numbers.takeLast(3))
  6. println(numbers.drop(1))
  7. println(numbers.dropLast(5))
  8. //sampleEnd
  9. }

You can also use predicates to define the number of elements for taking or dropping. There are four functions similar to the ones described above:

  • takeWhile() is take() with a predicate: it takes the elements up to but excluding the first one not matching the predicate. If the first collection element doesn’t match the predicate, the result is empty.
  • takeLastWhile() is similar to takeLast(): it takes the range of elements matching the predicate from the end of the collection. The first element of the range is the element next to the last element not matching the predicate. If the last collection element doesn’t match the predicate, the result is empty;
  • dropWhile() is the opposite to takeWhile() with the same predicate: it returns the elements from the first one not matching the predicate to the end.
  • dropLastWhile() is the opposite to takeLastWhile() with the same predicate: it returns the elements from the beginning to the last one not matching the predicate.
  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four", "five", "six")
  4. println(numbers.takeWhile { !it.startsWith('f') })
  5. println(numbers.takeLastWhile { it != "three" })
  6. println(numbers.dropWhile { it.length == 3 })
  7. println(numbers.dropLastWhile { it.contains('i') })
  8. //sampleEnd
  9. }

Chunked

To break a collection onto parts of a given size, use the chunked() function. chunked() takes a single argument – the size of the chunk – and returns a List of Lists of the given size. The first chunk starts from the first element and contains the size elements, the second chunk holds the next size elements, and so on. The last chunk may have a smaller size.

  1. fun main() {
  2. //sampleStart
  3. val numbers = (0..13).toList()
  4. println(numbers.chunked(3))
  5. //sampleEnd
  6. }

You can also apply a transformation for the returned chunks right away. To do this, provide the transformation as a lambda function when calling chunked(). The lambda argument is a chunk of the collection. When chunked() is called with a transformation, the chunks are short-living Lists that should be consumed right in that lambda.

  1. fun main() {
  2. //sampleStart
  3. val numbers = (0..13).toList()
  4. println(numbers.chunked(3) { it.sum() }) // `it` is a chunk of the original collection
  5. //sampleEnd
  6. }

Windowed

You can retrieve all possible ranges of the collection elements of a given size. The function for getting them is called windowed(): it returns a list of element ranges that you would see if you were looking at the collection through a sliding window of the given size. Unlike chunked(), windowed() returns element ranges (windows) starting from each collection element. All the windows are returned as elements of a single List.

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four", "five")
  4. println(numbers.windowed(3))
  5. //sampleEnd
  6. }

windowed() provides more flexibility with optional parameters:

  • step defines a distance between first elements of two adjacent windows. By default the value is 1, so the result contains windows starting from all elements. If you increase the step to 2, you will receive only windows starting from odd elements: first, third, an so on.
  • partialWindows includes windows of smaller sizes that start from the elements at the end of the collection. For example, if you request windows of three elements, you can’t build them for the last two elements. Enabling partialWindows in this case includes two more lists of sizes 2 and 1.

Finally, you can apply a transformation to the returned ranges right away. To do this, provide the transformation as a lambda function when calling windowed().

  1. fun main() {
  2. //sampleStart
  3. val numbers = (1..10).toList()
  4. println(numbers.windowed(3, step = 2, partialWindows = true))
  5. println(numbers.windowed(3) { it.sum() })
  6. //sampleEnd
  7. }

To build two-element windows, there is a separate function - zipWithNext(). It creates pairs of adjacent elements of the receiver collection. Note that zipWithNext() doesn’t break the collection into pairs; it creates a Pair for each element except the last one, so its result on [1, 2, 3, 4] is [[1, 2], [2, 3], [3, 4]], not [[1, 2], [3, 4]]. zipWithNext() can be called with a transformation function as well; it should take two elements of the receiver collection as arguments.

  1. fun main() {
  2. //sampleStart
  3. val numbers = listOf("one", "two", "three", "four", "five")
  4. println(numbers.zipWithNext())
  5. println(numbers.zipWithNext() { s1, s2 -> s1.length > s2.length})
  6. //sampleEnd
  7. }