Spec-Zone.ru › Kotlin 1.6

Операции агрегирования

Kotlin-коллекции содержат функции для часто используемых операций агрегирования – операций, возвращающих одно значение на основе содержимого коллекции. Большинство из них хорошо известны и работают так же, как и в других языках:

  • minOrNull() и maxOrNull() возвращают наименьший и наибольший элемент соответственно. В пустых коллекциях они возвращают null.

  • average() возвращает среднее значение элементов в коллекции чисел.

  • sum() возвращает сумму элементов в коллекции чисел.

  • count() возвращает количество элементов в коллекции.

fun main() {
    val numbers = listOf(6, 42, 10, 4)

    println("Count: ${numbers.count()}")
    println("Max: ${numbers.maxOrNull()}")
    println("Min: ${numbers.minOrNull()}")
    println("Average: ${numbers.average()}")
    println("Sum: ${numbers.sum()}")
}

Также существуют функции для получения наименьшего и наибольшего элементов по определенной функции-селектору или настраиваемому Comparator:

  • maxByOrNull() и minByOrNull() принимают функцию-селектор и возвращают элемент, для которого она возвращает наибольшее или наименьшее значение.

  • maxWithOrNull() и minWithOrNull() принимают объект Comparator и возвращают наибольший или наименьший элемент в соответствии с этим Comparator.

  • maxOfOrNull() и minOfOrNull() принимают функцию-селектор и возвращают наибольшее или наименьшее возвращаемое значение самой функции-селектора.

  • maxOfWithOrNull() и minOfWithOrNull() принимают объект Comparator и возвращают наибольшее или наименьшее возвращаемое значение функции-селектора в соответствии с этим Comparator.

Эти функции возвращают null в пустых коллекциях. Также существуют альтернативы – maxOf, minOf, maxOfWith и minOfWith – которые делают то же самое, что и их аналоги, но выбрасывают исключение NoSuchElementException в пустых коллекциях.

fun main() {
//sampleStart
    val numbers = listOf(5, 42, 10, 4)
    val min3Remainder = numbers.minByOrNull { it % 3 }
    println(min3Remainder)

    val strings = listOf("one", "two", "three", "four")
    val longestString = strings.maxWithOrNull(compareBy { it.length })
    println(longestString)
//sampleEnd
}

Помимо обычного sum(), существует расширенная функция суммирования sumOf(), которая принимает функцию-селектор и возвращает сумму ее применения ко всем элементам коллекции. Функция-селектор может возвращать различные числовые типы: Int, Long, Double, UInt, и ULong (также BigInteger и BigDecimal на JVM).

fun main() {
//sampleStart
    val numbers = listOf(5, 42, 10, 4)
    println(numbers.sumOf { it * 2 })
    println(numbers.sumOf { it.toDouble() / 2 })
//sampleEnd
}

Свертка и редукция

Для более специфических случаев существуют функции reduce() и fold(), которые последовательно применяют предоставленную операцию к элементам коллекции и возвращают накопленный результат. Операция принимает два аргумента: ранее накопленное значение и элемент коллекции.

Различие между этими двумя функциями заключается в том, что fold() принимает начальное значение и использует его как накопленное значение на первом шаге, тогда как первый шаг reduce() использует первый и второй элементы в качестве аргументов операции на первом шаге.

fun main() {
//sampleStart
    val numbers = listOf(5, 2, 10, 4)

    val simpleSum = numbers.reduce { sum, element -> sum + element }
    println(simpleSum)
    val sumDoubled = numbers.fold(0) { sum, element -> sum + element * 2 }
    println(sumDoubled)

    //incorrect: the first element isn't doubled in the result
    //val sumDoubledReduce = numbers.reduce { sum, element -> sum + element * 2 } 
    //println(sumDoubledReduce)
//sampleEnd
}

Приведенный выше пример демонстрирует разницу: fold() используется для вычисления суммы удвоенных элементов. Если вы передадите ту же функцию в reduce(), она вернет другой результат, потому что она использует первый и второй элементы списка в качестве аргументов на первом шаге, поэтому первый элемент не будет удвоен.

Для применения функции к элементам в обратном порядке используйте функции reduceRight() и foldRight(). Они работают аналогично fold() и reduce(), но начинают с последнего элемента, а затем продолжают к предыдущим. Обратите внимание, что при свертке или редукции справа аргументы операции меняют свой порядок: сначала идет элемент, а затем накопленное значение.

fun main() {
//sampleStart
    val numbers = listOf(5, 2, 10, 4)
    val sumDoubledRight = numbers.foldRight(0) { element, sum -> sum + element * 2 }
    println(sumDoubledRight)
//sampleEnd
}

Вы также можете применять операции, принимающие индексы элементов в качестве параметров. Для этого используйте функции reduceIndexed() и foldIndexed(), передавая индекс элемента в качестве первого аргумента операции.

Наконец, существуют функции, которые применяют такие операции к элементам коллекции справа налево - reduceRightIndexed() и foldRightIndexed().

fun main() {
//sampleStart
    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)

    val sumEvenRight = numbers.foldRightIndexed(0) { idx, element, sum -> if (idx % 2 == 0) sum + element else sum }
    println(sumEvenRight)
//sampleEnd
}

Все операции редукции выбрасывают исключение при пустых коллекциях. Чтобы получить null вместо этого, используйте их *OrNull() аналоги:

  • reduceOrNull()

  • reduceRightOrNull()

  • reduceIndexedOrNull()

  • reduceRightIndexedOrNull()

Для случаев, когда вы хотите сохранить промежуточные значения аккумулятора, существуют функции runningFold() (или ее синоним scan()) и runningReduce().

fun main() {
//sampleStart
    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 }
//sampleEnd
    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"))
}

Если вам нужен индекс в параметре операции, используйте runningFoldIndexed() или runningReduceIndexed().

Последнее изменение: 07 апреля 2022
Сортировка Операции записи в коллекцию

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/collection-aggregate.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API