Skip to content

집계 연산

Kotlin 컬렉션에는 일반적으로 사용되는 _집계 연산_을 위한 함수가 포함되어 있습니다. 이는 컬렉션 내용에 기반하여 단일 값을 반환하는 연산입니다. 대부분의 함수는 잘 알려져 있으며 다른 언어에서와 동일하게 작동합니다.

  • minOrNull()maxOrNull()은 각각 가장 작은 요소와 가장 큰 요소를 반환합니다. 빈 컬렉션에서는 null을 반환합니다.
  • average()는 숫자 컬렉션 요소의 평균값을 반환합니다.
  • sum()은 숫자 컬렉션 요소의 합계를 반환합니다.
  • count()는 컬렉션 요소의 개수를 반환합니다.
kotlin

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을 발생시키는 대안도 있습니다.

kotlin

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

일반적인 sum() 외에도, 셀렉터 함수를 받아 모든 컬렉션 요소에 적용한 결과의 합계를 반환하는 고급 합계 함수인 sumOf()가 있습니다. 셀렉터는 Int, Long, Double, UInt, ULong (JVM에서는 BigIntegerBigDecimal도 포함)과 같은 다양한 숫자 타입을 반환할 수 있습니다.

kotlin

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

Fold와 reduce

더 특정한 경우를 위해, reduce()fold() 함수는 제공된 연산을 컬렉션 요소에 순차적으로 적용하고 누적된 결과를 반환합니다. 이 연산은 이전에 누적된 값과 컬렉션 요소를 두 개의 인수로 받습니다.

두 함수의 차이점은 fold()가 초기 값을 받아 첫 번째 단계에서 누적 값으로 사용하는 반면, reduce()의 첫 번째 단계는 첫 번째 요소와 두 번째 요소를 연산 인수로 사용한다는 것입니다.

kotlin
fun main() {
    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)
}

위 예시는 차이점을 보여줍니다. fold()는 두 배로 늘린 요소들의 합계를 계산하는 데 사용됩니다. 동일한 함수를 reduce()에 전달하면, reduce()는 첫 번째 단계에서 리스트의 첫 번째와 두 번째 요소를 인수로 사용하기 때문에 첫 번째 요소가 두 배가 되지 않아 다른 결과를 반환할 것입니다.

요소를 역순으로 함수에 적용하려면, reduceRight()foldRight() 함수를 사용하세요. 이 함수들은 fold()reduce()와 유사하게 작동하지만, 마지막 요소부터 시작하여 이전 요소로 계속 진행합니다. 참고로, 오른쪽에서 폴딩 또는 리듀싱할 때 연산 인수의 순서가 변경됩니다: 요소가 먼저 오고 그 다음에 누적된 값이 옵니다.

kotlin

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

요소 인덱스를 매개변수로 받는 연산도 적용할 수 있습니다. 이를 위해서는 연산의 첫 번째 인수로 요소 인덱스를 전달하는 reduceIndexed()foldIndexed() 함수를 사용하세요.

마지막으로, 컬렉션 요소에 오른쪽에서 왼쪽으로 이러한 연산을 적용하는 함수인 reduceRightIndexed()foldRightIndexed()도 있습니다.

kotlin

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)

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

모든 reduce 연산은 빈 컬렉션에서 예외를 발생시킵니다. 대신 null을 받으려면 *OrNull()에 해당하는 함수를 사용하세요.

중간 누적 값을 저장하고 싶은 경우에는 runningFold() (또는 동의어인 scan()) 및 runningReduce() 함수를 사용합니다.

kotlin

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("
", "Sum of first N elements with runningReduce:
"))
    println(runningFoldSum.mapIndexed(transform).joinToString("
", "Sum of first N elements with runningFold:
"))
}

연산 매개변수에 인덱스가 필요한 경우, runningFoldIndexed() 또는 runningReduceIndexed()를 사용하세요.