fold, reduce и агрегация

Учимся сворачивать коллекцию в одно значение.

fold сворачивает коллекцию в одно значение, последовательно комбинируя элементы, начиная с заданного начального значения.

Часто из коллекции нужно получить одно число или строку: сумму, произведение, объединённый текст. Такую операцию называют свёрткой (агрегацией), и главный инструмент здесь — fold.

fold — свёртка с начальным значением

fold принимает начальное значение (аккумулятор) и лямбду из двух аргументов: текущий аккумулятор и очередной элемент. Результат лямбды становится новым аккумулятором.

fun main() {
    val numbers = listOf(1, 2, 3, 4)
    val sum = numbers.fold(0) { acc, n -> acc + n }
    println(sum)

    val product = numbers.fold(1) { acc, n -> acc * n }
    println(product)
}

Вывод:

10
24

reduce — свёртка без начального значения

reduce похож на fold, но за начальное значение берёт первый элемент коллекции. Поэтому на пустой коллекции reduce бросает исключение, а fold вернёт начальное значение.

fun main() {
    val numbers = listOf(3, 7, 2, 8)
    val max = numbers.reduce { acc, n -> if (n > acc) n else acc }
    println(max)
}

Вывод:

8

Готовые агрегаты

Для частых задач есть готовые функции — их не нужно писать через fold вручную.

fun main() {
    val numbers = listOf(5, 2, 9, 1)
    println(numbers.sum())
    println(numbers.max())
    println(numbers.min())
    println(numbers.average())
}

Вывод:

17
9
1
4.25

Группировка

groupBy распределяет элементы по группам, образуя Map: ключ — результат лямбды, значение — список элементов с этим ключом.

fun main() {
    val words = listOf("апельсин", "арбуз", "банан", "вишня")
    val byFirst = words.groupBy { it.first() }
    println(byFirst)
}

Вывод:

{а=[апельсин, арбуз], б=[банан], в=[вишня]}

Как работает под капотом

Внутри fold — обычный цикл, который перебирает элементы и на каждом шаге обновляет аккумулятор результатом лямбды. Тип аккумулятора может отличаться от типа элементов: например, можно свернуть список чисел в строку. Главное отличие fold от reduce — наличие явного начального значения, что делает fold безопасным для пустых коллекций.

Частые ошибки

  • Применять reduce к возможно пустой коллекции. На пустой коллекции он бросит исключение — берите fold с начальным значением.
  • Писать сумму через fold вручную. Для частых случаев есть sum(), max(), average().
  • Путать порядок аргументов лямбды. В fold первый аргумент — аккумулятор, второй — элемент.

Итог

  • fold сворачивает коллекцию в одно значение, начиная с заданного аккумулятора.
  • reduce берёт первый элемент за начальное значение и падает на пустой коллекции.
  • Для частых задач есть sum(), max(), average().
  • groupBy распределяет элементы по группам в Map.
Проверьте себя
1. Что вернёт listOf(1, 2, 3, 4).fold(0) { acc, n -> acc + n }?
A0
B10
C24
D4
2. Чем reduce отличается от fold?
Areduce работает только с числами
Breduce берёт первый элемент за начальное значение и падает на пустой коллекции
Creduce быстрее fold
DОни идентичны
3. Что делает groupBy?
AСортирует коллекцию
BРаспределяет элементы по группам в Map по ключу из лямбды
CУдаляет дубликаты
DСчитает сумму