Что делают 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

Алгоритм:

  1. acc = 1, value = 2 → 3
  2. acc = 3, value = 3 → 6
  3. 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

Алгоритм:

  1. acc = 10, num = 1 → 11
  2. acc = 11, num = 2 → 13
  3. acc = 13, num = 3 → 16

Также существует foldRight() — обработка справа налево, и foldIndexed() — с индексом.

Отличие reduce и fold

Особенность reduce fold
Начальное значение отсутствует задаётся вручную
--- --- ---
Безопасность может выбросить исключение безопасен при пустом списке
--- --- ---
Направление слева направо (reduce) слева направо (fold)
--- --- ---

groupBy

groupBy группирует элементы коллекции по ключу, вычисляемому через заданную функцию. Возвращает Map<K, List, где K — вычисленный ключ, V — элементы, попавшие в эту группу.

Сигнатура:

fun &lt;T, K&gt; Iterable&lt;T&gt;.groupBy(keySelector: (T) -> K): Map&lt;K, List<T&gt;>

Также можно указать вторую функцию, которая трансформирует значения:

fun &lt;T, K, V&gt; Iterable&lt;T&gt;.groupBy(
keySelector: (T) -> K,
valueTransform: (T) -> V
): Map&lt;K, List<V&gt;>

Пример:

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, позволяя эффективно обрабатывать коллекции декларативно и без ручного управления циклами.