Выборка элементов коллекции

В стандартной библиотеке Kotlin есть функции-расширения, которые предоставляют множество способов выбора элементов: явное перечисление их позиций, указание размера результата и пр.

Slice

Функция slice() возвращает список элементов коллекции по указанным индексам. Индексы могут передаваться как диапазон, либо как набор целочисленных значений.

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

Take и drop

Для получения определённого количества элементов, находящихся в начале коллекции (начиная с первого и далее), используйте функцию take(). В свою очередь функция takeLast() вернёт список, содержащий указанное количество последних элементов. При вызове этих функций с числом, превышающим размер коллекции, они обе вернут всю коллекцию целиком.

Для получения всех элементов, кроме указанного количества первых или последних элементов, используйте функции drop() и dropLast() соответственно.

fun main() {
    val numbers = listOf("one", "two", "three", "four", "five", "six")
    println(numbers.take(3)) // [one, two, three]
    println(numbers.takeLast(3)) // [four, five, six]
    println(numbers.drop(1)) // [two, three, four, five, six]
    println(numbers.dropLast(5)) // [one]
}

У вышеперечисленных функций есть аналоги, которые определяют количество элементов с помощью предиката:

  • takeWhile() - это аналог функции take(): возвращает первые элементы, соответствующие заданному предикату. Элементы отбираются с начала коллекции и до тех пор, пока не встретится элемент, несоответствующий предикату. Если первый элемент коллекции не соответствует предикату, то результат будет пустым.
  • takeLastWhile() - это аналог функции takeLast(): возвращает последние элементы, соответствующие заданному предикату. Элементы отбираются с конца коллекции и до тех пор, пока не встретится элемент, несоответствующий предикату. Если последний элемент коллекции не соответствует предикату, то результат будет пустым.
  • dropWhile() - противоположность takeWhile(): возвращает все элементы, кроме первых элементов, соответствующих заданному предикату.
  • dropLastWhile() - противоположность takeLastWhile(): возвращает все элементы, кроме последних элементов, соответствующих заданному предикату.
fun main() {
    val numbers = listOf("one", "two", "three", "four", "five", "six")
    println(numbers.takeWhile { !it.startsWith('f') }) // [one, two, three]
    println(numbers.takeLastWhile { it != "three" }) // [four, five, six]
    println(numbers.dropWhile { it.length == 3 }) // [three, four, five, six]
    println(numbers.dropLastWhile { it.contains('i') }) // [one, two, three, four]
}

Chunked

Функция chunked() позволяет разбить коллекцию на части (или чанки) заданного размера. Она принимает единственный аргумент size - размер каждой части - и возвращает List, состоящий из вложенных списков. Первая часть будет содержать в себе элементы, начиная с первого и далее, но количеством не более заданного размера; вторая часть - последующие элементы, но не более заданного размера и т.д. В последней части может быть меньше элементов, чем указанный размер (остаток).

fun main() {
    val numbers = (0..13).toList()
    println(numbers.chunked(3)) // [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13]]
}

Также функция chunked() позволяет сразу же преобразовывать полученные части, для чего ей необходимо передать функцию преобразования в виде лямбды. При этом каждая из частей является аргументом этой лямбды. Сами части в этом случае будут недолговечными объектами типа List и должны использоваться непосредственно в лямбде.

fun main() {
    val numbers = (0..13).toList()

    // `it` - это часть исходной коллекции
    println(numbers.chunked(3) { it.sum() }) // [3, 12, 21, 30, 25]
}

Windowed

С помощью функции windowed() вы можете получить все возможные диапазоны элементов коллекции. Представьте, что вы смотрите на коллекцию через "окно" определённого размера. Это окно может скользить от начала до конца коллекции, тем самым последовательно выделяя каждый возможный её диапазон. В отличие от chunked(), windowed() возвращает диапазоны (или окна), в которых первым элементом является каждый последующий элемент коллекции, а размер диапазона равен размеру "окна". Все диапазоны возвращаются в качестве элементов одного списка.

fun main() {
    val numbers = listOf("one", "two", "three", "four", "five")    
    println(numbers.windowed(3)) // [[one, two, three], [two, three, four], [three, four, five]]
}

У функции windowed() есть дополнительные параметры:

  • step - определяет расстояние между первыми элементами соседних диапазонов. По умолчанию равен 1, поэтому в этом случае результат будет содержать диапазоны, начинающиеся со всех элементов коллекции. Если вы зададите шаг равный 2, то получите диапазоны, начинающиеся только с нечётных элементов: 1, 3 и т.д.
  • partialWindows - позволяет включать в итоговый список диапазоны меньшего размера, которые начинаются с последних элементов коллекции. Например, вы хотите получить диапазоны из трёх элементов, а осталось всего два элемента. В этом случае partialWindows добавит ещё два списка размером 2 и 1.

Наконец, вы можете сразу же преобразовывать полученные диапазоны. Для этого передайте windowed() функцию преобразования в виде лямбды.

fun main() {
    val numbers = (1..10).toList()
    println(numbers.windowed(3, step = 2, partialWindows = true)) // [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
    println(numbers.windowed(3) { it.sum() }) // [6, 9, 12, 15, 18, 21, 24, 27]
}

Для создания диапазонов из двух элементов существует отдельная функция - zipWithNext(). Она создаёт пары из смежных элементов коллекции. Обратите внимание, что zipWithNext() не разбивает коллекцию на пары; она создаёт объекты Pair для каждого элемента, кроме последнего, поэтому результатом для [1, 2, 3, 4] будет [[1, 2], [2, 3], [3, 4]], а не [[1, 2], [3, 4]]. Также zipWithNext() можно вызывать с функцией преобразования, которая в качестве аргументов принимает два элемента коллекции.

fun main() {
    val numbers = listOf("one", "two", "three", "four", "five")    
    println(numbers.zipWithNext()) // [(one, two), (two, three), (three, four), (four, five)]
    println(numbers.zipWithNext() { s1, s2 -> s1.length > s2.length}) // [false, false, true, false]
}