집계 연산
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() {
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에서는 BigInteger
및 BigDecimal
도 포함)과 같은 다양한 숫자 타입을 반환할 수 있습니다.
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()
의 첫 번째 단계는 첫 번째 요소와 두 번째 요소를 연산 인수로 사용한다는 것입니다.
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()
와 유사하게 작동하지만, 마지막 요소부터 시작하여 이전 요소로 계속 진행합니다. 참고로, 오른쪽에서 폴딩 또는 리듀싱할 때 연산 인수의 순서가 변경됩니다: 요소가 먼저 오고 그 다음에 누적된 값이 옵니다.
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()
도 있습니다.
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()
함수를 사용합니다.
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()
를 사용하세요.