Операции объединения
Коллекции Kotlin содержат функции для часто используемых операций объединения (ориг. aggregate operations), которые возвращают одно значение на основе содержимого коллекции. Большинство из них хорошо известны и работают так же, как и на других языках:
- minOrNull()и- maxOrNull()возвращают наименьший и наибольший элемент коллекции соответственно. Если коллекция пуста, то вернётся- null.
- average()возвращает среднее значение элементов в коллекции чисел.
- sum()возвращает сумму элементов в коллекции чисел.
- count()возвращает количество элементов в коллекции.
fun main() {
    val numbers = listOf(6, 42, 10, 4)
    println("Count: ${numbers.count()}") // Count: 4
    println("Max: ${numbers.maxOrNull()}") // Max: 42
    println("Min: ${numbers.minOrNull()}") // Min: 4
    println("Average: ${numbers.average()}") // Average: 15.5
    println("Sum: ${numbers.sum()}") // Sum: 62
}
Также существуют функции для получения самых маленьких и самых больших элементов в коллекции с помощью определённой функции-селектора или пользовательского Comparator:
- maxByOrNull()и- minByOrNull()принимают функцию-селектор и возвращают элемент, для которого эта функция возвращает наибольшее или наименьшее значение.
- maxWithOrNull()и- minWithOrNull()принимают объект- Comparatorи возвращают наибольший или наименьший элемент, соответствующий этому- Comparator.
Если коллекция пуста, то эти функции вернут null. У функций maxByOrNull() и minByOrNull() есть аналоги:
maxOf() и
minOf(), которые работают аналогично, но бросают исключение NoSuchElementException, если коллекция пуста.
fun main() {
    val numbers = listOf(5, 42, 10, 4)
    val min3Remainder = numbers.minByOrNull { it % 3 }
    println(min3Remainder) // 42
    val strings = listOf("one", "two", "three", "four")
    val longestString = strings.maxWithOrNull(compareBy { it.length })
    println(longestString) // three
}
Помимо обычного sum(), существует расширенная функция
sumOf(). Она принимает функцию-селектор, которая применяет заданную операцию к каждому элементу коллекции, и возвращает сумму всех элементов с учётом этих изменений. Функция-селектор может возвращать различные числовые типы: Int, Long, Double, UInt, и ULong (а также BigInteger и BigDecimal из JVM).
fun main() {
    val numbers = listOf(5, 42, 10, 4)
    println(numbers.sumOf { it * 2 }) // 122
    println(numbers.sumOf { it.toDouble() / 2 }) // 30.5
}
Fold и reduce
Для более специфичных случаев существуют функции
reduce() и
fold(), которые последовательно применяют предоставленную операцию к элементам коллекции и возвращают накопленный результат. Операция принимает два аргумента: ранее накопленное значение и элемент коллекции.
Разница между этими двумя функциями состоит в том, что fold() принимает начальное значение и использует его как накопленное значение на первом шаге, тогда как reduce() на первом шаге в качестве аргументов операции использует первый и второй элементы.
fun main() {
    val numbers = listOf(5, 2, 10, 4)
    val simpleSum = numbers.reduce { sum, element -> sum + element }
    println(simpleSum) // 21
    val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
    println(sumDoubled) // 42
    // некорректно: первый элемент не будет удвоен
    // val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 }
    // println(sumDoubledReduce)
}
В примере выше показана разница: fold() используется для вычисления суммы удвоенных элементов. Если вы передадите ту же функцию в reduce(), она вернёт другой результат, поскольку он использует первый и второй элементы списка в качестве аргументов на первом шаге, в связи с чем первый элемент не будет удвоен.
Существуют функции reduceRight() и
foldRight(), которые работают аналогично fold() и reduce(), но в обратном порядке: начинают с последнего элемента, а затем переходят к предыдущему. Обратите внимание, что при использовании foldRight() или reduceRight() аргументы операции меняются местами: сначала идёт элемент, а затем накопленное значение.
fun main() {
    val numbers = listOf(5, 2, 10, 4)
    val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 }
    println(sumDoubledRight) // 42
}
Также вы можете использовать функции
reduceIndexed() и
foldIndexed(), которые дополнительно дают доступ к индексам элементов в качестве первого аргумента операции. Либо функции
reduceRightIndexed()
и foldRightIndexed(), которые работают аналогично, но в обратном порядке.
fun main() {
    val numbers = listOf(5, 2, 10, 4)
    val sumEven = numbers.foldIndexed(0) { idx, sum, element -> if (idx % 2 == 0) sum + element else sum }
    println(sumEven) // 15
    val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
    println(sumEvenRight) // 15
}
Все reduce-операции бросают исключение, если коллекция пуста. Чтобы вместо исключения возвращался null, используйте их аналоги с постфиксом *OrNull():
* reduceOrNull()
* reduceRightOrNull()
* reduceIndexedOrNull()
* reduceRightIndexedOrNull()
Для случаев, когда вы хотите сохранить промежуточное накопленное значение, существуют функции
runningFold() (или её синоним
scan()) и
runningReduce().
fun main() {
    val numbers = listOf(0, 1, 2, 3, 4, 5)
    val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
    val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }
    val transform = { index: Int, element: Int -> "N = ${index + 1}: $element" }
    println(runningReduceSum.mapIndexed(transform).joinToString("\n", "Sum of first N elements with runningReduce:\n"))
    println(runningFoldSum.mapIndexed(transform).joinToString("\n", "Sum of first N elements with runningFold:\n"))
    // В логе будет:
    // Sum of first N elements with runningReduce:
    // N = 1: 0
    // N = 2: 1
    // N = 3: 3
    // N = 4: 6
    // N = 5: 10
    // N = 6: 15
    // Sum of first N elements with runningFold:
    // N = 1: 10
    // N = 2: 10
    // N = 3: 11
    // N = 4: 13
    // N = 5: 16
    // N = 6: 20
    // N = 7: 25
}
Если вам нужен индекс в качестве параметра операции, используйте
runningFoldIndexed()
или runningReduceIndexed().