오늘 배운 것

24-03-26 Kotlin 컬렉션 집계하기

무무11 2024. 3. 26. 21:05

Kotlin으로 문제풀이를 하고 다른 풀이들을 보고있으면 내가 모르는게 참 많구나 하는 생각이 든다. fold와 같은 함수도 여러번 보았지만 정확한 사용법을 익히지 못해 아직 사용해보지 못했다. 코틀린의 공식문서에는 코틀린에서 컬렉션 요소들로 단일한 하나의 값을 도출해내는 집계 연산에 자주 사용되는 함수들에 대해 잘 정리되어 있다. 이 내용들과 직접 찾아본 내용들을 더해 자세히 정리해 보려고 한다.

 

코틀린 공식문서 링크

https://kotlinlang.org/docs/collection-aggregate.html

 

Aggregate operations | Kotlin

 

kotlinlang.org

1. 최대, 최소, 평균, 합, 개수

1-1. maxOrNull(), minOrNull(), max(), min()

 

컬렉션에서 최대, 최소를 구하는 함수는 maxOrNull(), minOrNull()이다. 컬렉션에서의 최대값, 최소값을 반환한다. 만약 컬렉션이 비어있다면 null을 반환한다.

 

그런데 max(), min()과 같은 함수도 있다. 둘의 정확한 차이를 알 수 없어 한참을 찾은 결과 다음과 같은 설명을 찾을 수 있었다.

 

https://kotlinlang.org/docs/whatsnew17.html#min-and-max-collection-functions-return-as-non-nullable

 

What's new in Kotlin 1.7.0 | Kotlin

 

kotlinlang.org

 

원래 min(), max()와 같은 이름이었던 함수들은 코틀린 1.4 버전에서는 minOrNull(), maxOrNull()로 변경되었다. 컬렉션이 비어있을 경우 null을 반환하는 함수의 특성을 반영해 함수의 이름을 재명명한것이다.

 

마찬가지로 4가지 함수가 OrNull을 붙여 minByOrNull(), maxByOrNull(), minWithOrNull(), maxWithOrNull()과 같은 이름으로 함수의 이름이 바뀌었다. 

 

그리고 1.7 버전에서 원래 함수들의 이름으로 함수가 다시 도입되었다. 대신 min(), max(), minBy(), maxBy(), minWith(), maxWith()는 이제 엄격하게 non-nullable(null이 될 수 없는) 반환 타입을 가진다. 이 함수들은 엄격하게 컬렉션 요소를 반환하거나 예외를 던진다.

fun main() {
    val ary = intArrayOf(1000,3333,4422,43111,32311,0)
    val numbers = listOf(6.4, 42.2, 10.1, 4.7)
    val empty = emptyList<Double>()

    println(ary.maxOrNull()) // 43111
    println(ary.max()) // 43111

    println(numbers.min()) // 4.7
    println(numbers.minOrNull()) // 4.7

    println(empty.max()) // 컴파일은 되지만 NoSuchElementException 예외가 발생
    println(empty.maxOrNull()) // null
}

 

컬렉션이 비어있다고 확신할 수 있으면 min(), max()를 사용할 수 있지만, 그렇지 않은 경우에 예외를 발생시키지 않는 minOrNull(), maxOrNull()을 사용해야 한다.

 

참고로 이 글을 작성하고 있는 현재 프로그래머스에서 코틀린으로 문제풀이를 하면 min(), max()는 사용할 수 없다. 1.7 버전에 추가되었기 때문에 현재 1.6버전으로 컴파일되는 프로그래머스에서는 반드시 minOrNull(), maxOrNull()을 사용해야한다.

1-2. average(), sum(), count()

 

컬렉션의 평균을 구할때는 average(), 합을 구할때는 sum(), 컬렉션 요소의 개수를 구할 때는 count()를 사용한다.

fun main() {
    val numbers = listOf(111, 222, 333, 444, 555)

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

/* 실행 결과
Average: 333.0
Sum: 1665
Count: 5
*/

 

 

1-3. maxByOrNull(), minByOrNull(), minOfOrNull(), maxOfWithOrNull(), maxWithOrNull(), minWithOrNull()

maxByOrNull(), minByOrNull()은 선택자 함수를 받아 함수가 최대값, 최소값을 반환하는 요소를 반환한다.

fun main() {
    val numbers = listOf(5, 42, 10, 4)
    val min3Remainder = numbers.minByOrNull { it % 3 }
    val max5Remainder = numbers.maxByOrNull { it % 5 }
    println(min3Remainder) // 42
    // 리스트에서 3으로 나눈 나머지가 가장 작은 값(42%3=0)을 가지는 요소(42)를 반환한다
    println(max5Remainder) // 4
    // 리스트에서 5로 나눈 나머지가 가장 큰 값(4%5=4)을 가지는 요소(4)를 반환한다
}

 

maxOfOrNull(), minOfOrNull()은 선택자 함수를 받아 함수가 최대값, 최소값을 가지는 선택자의 값(요소 아님)을 반환한다.

fun main() {
    val numbers = listOf(5, 42, 10, 4)
    val min3Remain = numbers.minOfOrNull { it % 3 }
    val max5Remain = numbers.maxOfOrNull { it % 5 }
    println(min3Remain) // 0
    // 리스트에서 3으로 나눈 나머지가 가장 작은 값(42%3=0)을 반환한다
    println(max5Remain) // 4
    // 리스트에서 5로 나눈 나머지가 가장 큰 값(4%5=4)을 반환한다
}

 

maxWithOrNull(), minWithOrNull()은 비교기(Comparator)를 인자로 받아 요소의 비교기 값이 최대, 최소인 요소를 반환한다.

fun main() {
    val strings = listOf("파인애플", "사과", "귤", "수박", "오렌지")
    val longestString = strings.maxWithOrNull(compareBy { it.length })
    val shortestString = strings.minWithOrNull(compareBy { it.length })
    println(longestString) // 파인애플
    // 비교기(요소의 문자열의 길이)로 비교해 비교기가 가장 큰 요소(파인애플)을 반환
    println(shortestString) // 귤
    // 비교기(요소의 문자열의 길이)로 비교해 비교기가 가장 작은 요소(귤)을 반환
}

 

1-4. sumOf()

sum()의 경우 Byte, Short, Int의 sum()은 Int타입으로 반환된다. 따라서 오버플로우가 발생할 수 있다.

sumOf()는 요소 타입을 다른 덧셈 가능한 타입으로 변환해주는 선택자 람다를 인자로 받아 합계의 타입을 지정해줄 수 있다.

fun main() {
    val numbers = listOf(5, 42, 10, 4)
    println(numbers.sumOf { it * 2 }) // 122
    // 각 요소에 2를 곱한 값을 모두 더한 값을 출력한다.
    println(numbers.sumOf { it.toDouble() / 2 }) // 30.5
    // 각 요소를 Double 타입으로 바꾼뒤 2로 나눈 값을 모두 더한 값을 출력한다.

    println(listOf(Int.MAX_VALUE, Int.MAX_VALUE).sumOf { it.toLong() })
    // 4294967294
    // Int 타입이 가질 수 있는 최대 값 두 개를 Long 타입으로 변환한 뒤 더한 값을 출력한다.
}

 

아직 내용이 한참 더 있는데 오늘은 이 정도만 알아보기로 했다. 내일 추가로 공부하고 정리하는 시간을 가져볼 예정이다.

2. 오늘 배운 것

- 컬렉션에서 집계하는 법 몇가지에 대해 알아보고 정리해보았다.

- 코딩 해보는 것, CS 공부, 이론 공부는 모두 병행해서 해야 이해도 잘 되고 시너지 효과가 나는 듯 하다. 더 열심히 해봐야겠다.