В чем разница между fold и reduce в Kotlin и когда что использовать?
Ключевое отличие: начальное значение и работа с пустыми коллекциями
Вы уже заметили главное различие: fold требует начальное значение (аккумулятор), а reduce использует первый элемент коллекции в качестве начального аккумулятора. Это приводит к нескольким важным последствиям.
1. Тип результата
reduce всегда возвращает тот же тип, что и элементы коллекции, потому что аккумулятор изначально равен первому элементу. fold может возвращать любой тип, который вы укажете через начальное значение.
val numbers = listOf(1, 2, 3)
// Reduce: тип результата = Int (как у элементов)
val sum = numbers.reduce { acc, i -> acc + i } // Int
// Fold: тип результата = String (не как у элементов!)
val asString = numbers.fold("") { acc, i -> "$acc$i" } // "123"
Это очень полезно, когда нужно преобразовать коллекцию в другой тип данных.
2. Работа с пустыми коллекциями
reduce выбросит UnsupportedOperationException на пустой коллекции, потому что не из чего взять первый элемент. fold просто вернёт начальное значение.
val emptyList = listOf()
// Ошибка!
// emptyList.reduce { acc, i -> acc + i }
// Безопасно: вернёт 0
val safeSum = emptyList.fold(0) { acc, i -> acc + i } // 0
Если вы не уверены, что коллекция не пуста, используйте fold или проверяйте ifEmpty перед reduce.
3. Производительность и читаемость
Поскольку reduce не создаёт дополнительный объект для начального значения, он может быть чуть быстрее на больших коллекциях. Но разница обычно незначительна. Главное — читаемость кода.
- Используйте
reduce, когда:- Коллекция гарантированно не пуста.
- Результат должен быть того же типа, что и элементы.
- Логика аккумуляции естественно начинается с первого элемента (например, поиск максимума).
- Используйте
fold, когда:- Коллекция может быть пустой.
- Тип результата отличается от типа элементов.
- Нужно задать явное начальное значение (например, для конкатенации строк с префиксом).
Реальные примеры
data class Product(val name: String, val price: Double)
val products = listOf(
Product("Phone", 500.0),
Product("Laptop", 1200.0),
Product("Mouse", 25.0)
)
// Reduce: сумма цен (тип результата = Double)
val totalPrice = products.reduce { acc, product ->
Product("total", acc.price + product.price)
}.price
// Fold: строка с названиями через запятую (тип результата = String)
val names = products.fold("Products: ") { acc, product ->
"$acc${product.name}, "
}
println(names) // "Products: Phone, Laptop, Mouse, "
Подводные камни
- Порядок выполнения: обе функции обрабатывают элементы слева направо. Для обратного порядка есть
foldRightиreduceRight. - Изменяемые аккумуляторы: старайтесь не мутировать аккумулятор внутри лямбды. Используйте неизменяемые объекты или
runблок для локальных мутаций. - Производительность: на больших коллекциях (миллионы элементов) разница между fold и reduce может быть заметна из-за создания лишних объектов. В таких случаях рассмотрите
foldIndexedили циклfor.
Итог
Если кратко: fold — это универсальный инструмент с явным начальным значением, reduce — частный случай для непустых коллекций с тем же типом результата. Начинайте с fold, если не уверены — он безопаснее. Переходите на reduce, когда хотите подчеркнуть, что коллекция точно не пуста и тип результата совпадает с типом элементов.
Дополнение: foldIndexed и reduceIndexed
В Kotlin есть также версии с индексом: foldIndexed и reduceIndexed. Они передают в лямбду не только аккумулятор и элемент, но и его индекс. Это удобно, когда нужно учитывать позицию элемента.
val numbers = listOf(10, 20, 30)
// foldIndexed: добавляем индекс к каждому элементу
val withIndex = numbers.foldIndexed(mutableListOf()) { index, acc, value ->
acc.add("[$index] $value")
acc
}
println(withIndex) // [[0] 10, [1] 20, [2] 30]
// reduceIndexed: проверяем, что все элементы положительные
val allPositive = numbers.reduceIndexed { index, acc, value ->
if (value <= 0) throw IllegalArgumentException("Negative at $index")
acc + value
}
Когда это полезно?
- При работе с матрицами или таблицами, где важна позиция.
- Для создания отчётов с номерами строк.
- Для валидации данных с указанием индекса ошибки.
Совет: не злоупотребляйте индексными версиями — они усложняют код. Используйте их только когда индекс действительно нужен для логики аккумуляции.
Ответов пока нет.
Войдите чтобы оставить ответ