Что делают map, filter, reduce, fold, groupBy?
В Kotlin функции map, filter, reduce, fold, groupBy — это ключевые функции высшего порядка, применяемые к коллекциям. Они реализуют функциональный подход к трансформации, фильтрации, агрегации и группировке данных. Эти функции позволяют писать декларативный и лаконичный код, минимизируя побочные эффекты.
map
Функция map используется для преобразования каждого элемента коллекции. Она применяет заданную функцию ко всем элементам и возвращает новую коллекцию того же размера, но с преобразованными значениями.
Сигнатура:
fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R>
Пример:
val numbers = listOf(1, 2, 3)
val squares = numbers.map { it \* it } // \[1, 4, 9\]
Каждый элемент возводится в квадрат, результат — новый список. Исходная коллекция остаётся неизменной.
filter
filter отбирает элементы по заданному условию. Возвращает новую коллекцию с элементами, удовлетворяющими предикату.
Сигнатура:
fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>
Пример:
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.filter { it % 2 == 0 } // \[2, 4\]
Можно также использовать filterNot { ... } для противоположного условия.
reduce
reduce — это агрегация всех элементов коллекции в одно значение с использованием бинарной операции. Результат вычисляется на основе первого элемента и последовательно всех остальных.
Сигнатура:
fun <S, T: S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S
Пример:
val numbers = listOf(1, 2, 3, 4)
val sum = numbers.reduce { acc, value -> acc + value } // 10
Алгоритм:
- acc = 1, value = 2 → 3
- acc = 3, value = 3 → 6
- acc = 6, value = 4 → 10
Если коллекция пуста, reduce выбросит исключение UnsupportedOperationException.
fold
fold похожа на reduce, но требует начальное значение (initial) и возвращает результат, начиная с него. Она безопаснее, чем reduce, для пустых коллекций.
Сигнатура:
fun <T, R> Iterable<T>.fold(initial: R, operation: (acc: R, T) -> R): R
Пример:
val numbers = listOf(1, 2, 3)
val result = numbers.fold(10) { acc, num -> acc + num } // 16
Алгоритм:
- acc = 10, num = 1 → 11
- acc = 11, num = 2 → 13
- acc = 13, num = 3 → 16
Также существует foldRight() — обработка справа налево, и foldIndexed() — с индексом.
Отличие reduce и fold
Особенность | reduce | fold |
---|---|---|
Начальное значение | отсутствует | задаётся вручную |
--- | --- | --- |
Безопасность | может выбросить исключение | безопасен при пустом списке |
--- | --- | --- |
Направление | слева направо (reduce) | слева направо (fold) |
--- | --- | --- |
groupBy
groupBy группирует элементы коллекции по ключу, вычисляемому через заданную функцию. Возвращает Map<K, List
Сигнатура:
fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>>
Также можно указать вторую функцию, которая трансформирует значения:
fun <T, K, V> Iterable<T>.groupBy(
keySelector: (T) -> K,
valueTransform: (T) -> V
): Map<K, List<V>>
Пример:
val names = listOf("Tom", "Tim", "Bob", "Bill")
val grouped = names.groupBy { it.first() }
// Результат: {'T': \["Tom", "Tim"\], 'B': \["Bob", "Bill"\]}
С valueTransform:
val groupedLengths = names.groupBy(
keySelector = { it.first() },
valueTransform = { it.length }
)
// Результат: {'T': \[3, 3\], 'B': \[3, 4\]}
Комбинирование функций
Эти функции можно удобно комбинировать:
val result = listOf(1, 2, 3, 4, 5, 6)
.filter { it % 2 == 0 } // \[2, 4, 6\]
.map { it \* it } // \[4, 16, 36\]
.reduce { acc, i -> acc + i } // 56
Порядок важен: сначала фильтруются чётные, потом они возводятся в квадрат, затем суммируются.
Поведение и ленивая обработка
При использовании с List, все операции выполняются жадно (eagerly): каждый шаг возвращает новый список. Для ленивой (lazy) обработки используется asSequence():
val result = listOf(1, 2, 3, 4, 5, 6)
.asSequence()
.filter { it % 2 == 0 }
.map { it \* it }
.sum() // 56
asSequence() создаёт цепочку операций, которые выполняются по одному элементу, минимизируя накладные расходы и временное потребление памяти.
Другие полезные функции в том же стиле
- mapNotNull: фильтрует null и возвращает только ненулевые значения.
- filterNotNull: удаляет null из коллекции.
- count: возвращает количество элементов, удовлетворяющих условию.
- any, all, none: логические проверки условий на коллекции.
- partition: разбивает коллекцию на две части по условию (Pair<List
). - flatMap: разворачивает вложенные списки.
- distinctBy: удаляет дубликаты по ключу.
- associateBy, associateWith: создают Map из списка с заданным ключом или значением.
Эти функции образуют ядро функциональной обработки данных в Kotlin, позволяя эффективно обрабатывать коллекции декларативно и без ручного управления циклами.