다른 사람의 풀이에 정말 배울 가치가 있는 코드가 있어 하나씩 뜯어보며 공부해보려고 한다.
풀이
genres = ["classic", "pop", "classic", "classic", "pop"]
playes = [500, 600, 150, 800, 2500]
fun solution(genres: Array<String>, plays: IntArray): IntArray {
return genres.indices.groupBy { genres[it] }
.toList()
.sortedByDescending { pair ->
pair.second.sumOf { index ->
plays[index] }
}
.map { pair ->
pair.second.sortedByDescending { index ->
plays[index]
}.take(2)
}
.flatten()
.toIntArray()
}
📌 indices
val <T> Array<out T>.indices: IntRange
평소 안드로이드 개발을 할 때도 자주 쓰는 IntRange형 프로퍼티이다.
해당 Collection의 크기만큼의 범위를 반환한다.
따라서 첫 번째 줄의 genres.indices는 0..(genres.size - 1) (또는 0 until genres.size) 를 반환하게 된다.
📌 groupBy()
inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>>
Iterable 한 클래스들(IntRange, Array, Collection, List, ...)의 확장 함수로 사용될 수 있는, 반환타입 Map<K, List<T>> 인 메서드이다. 여기서 Iterable한 클래스란 무엇일까?
Classes that inherit from this interface can be represented as a sequence of elements that can be iterated over.
공식문서에는 반복될 수 있는 시퀀스로 나타낼 수 있는 클래스를 말한다고 한다.
예를 들어 (IntRange, Array, List .. 등이 있다.)
지금과 같이 하나의 파라미터 genres[it]를 받을 시, 본인의 객체(it)의 원소들을 파라미터 genres[it]을 기준으로 분류하여 그룹화한다.
지금까지의 코드를 보면 (0..4).groupBy { genres[it] } 이고 본인의 객체(it) 은 genres의 indices이다.
- genres[0]은 "classic"이므로, map : { "classic" : [0] }
- genres[1]은 "pop"이므로, map : { "classic" : [0], "pop" : [1] }
- genres[2]는 "classic"이므로, map : { "classic" : [0, 2], "pop" : [1] }
- genres[3]는 "classic"이므로, map : { "classic" : [0, 2, 3], "pop" : [1] }
- genres[4]는 "pop"이므로, map : { "classic" : [0, 2, 3], "pop" : [1, 4] }
따라서 최종적으로 Map 타입의 { "classic" : [0, 2, 3], "pop" : [1, 4] } 을 반환한다.
📌 toList()
toList() 메서드는 Array의 확장함수로 쓸 때와, Map의 확장 함수로 쓸 때가 다르다.
Array의 확장함수
fun IntArray.toList(): List<Int>
Array의 확장 함수로 쓰일 때는 그져 모든 원소를 그대로 List 타입으로 재구성한다.
Map의 확장함수
fun <K, V> Map<out K, V>.toList(): List<Pair<K, V>>
Map의 경우 조금 특이한데, key와 value를 Pair로 묶어서 각각의 Pair를 리스트로 재구성한다.
위 코드의 경우 즉, { "classic" : [0, 2, 3], "pop" : [1, 4] }.toList() -> [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ] 가 된다.
📌 sortedByDecending()
inline fun <T, R : Comparable<R>> Iterable<T>.sortedByDescending(crossinline selector: (T) -> R?): List<T>
단어로 봐도 쉽게 알 수 있듯이 파라미터를 기준으로 내림차순으로 정렬시키는 함수이다.
파라미터로 쓰인 it.second.sumOf()는 앞 과정까지 구한 [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ] 의 두 번째 element 즉 Pair의 두번째 원소인 [0, 2, 3]과 [1, 4]가 각각 파라미터로 들어가 plays를 참조하여 합을 리턴한다.
즉, [0, 2, 3]에 대해 500 + 150 + 800 = 1450, [1, 4]에 대해 600 + 2500 = 3100을 리턴한다.
pop키의 데이터인 [1, 4]의 sumOf가 3100으로 더 크므로, sortedByDescending 메서드를 통해 다음과 같이 정렬된다.
- (정렬 전) [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ]
- (정렬 후) [ ("pop", [1, 4]), ("classic", [0, 2, 3]) ]
📌 map
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
map함수 객체의 각 iterable의 값을 변경하여 새로운 List를 반환한다.
즉 map 함수는 각 원소를 원하는 형태로 변환하는 기능을 하며, 변환한 결과를 모아서 새로운 List로 반환하는 것이다.
/* 각 원소의 제곱으로 모인 리스트를 만드는 map 예제 */
>>> val list = listOf(1, 2, 3, 4)
>>> println(list.map { it * it }) //제곱 만들기 (1x1, 2x2, 3x3, 4x4)
[1, 4, 9, 16]
즉 위의 코드에서는 [[4, 1], [3, 0, 2]] 가 반환된다.
📌 take()
public fun <T> Iterable<T>.take(n: Int): List<T>
앞에서부터 n개를 가지고 있는 리스트를 반환한다.
즉, it.second.sortedByDescending { plays[it] } == [4,1] , [3, 0, 2]
[4, 1].take(2) == [4, 1]
[3, 0, 2].take(2) == [3, 0]
map { [4, 1] and [3, 0] } == [[4, 1], [3, 0]]
📌 flatten()
fun <T> Iterable<Iterable<T>>.flatten(): List<T>
List안에 List가 존재할 경우 이를 펼쳐놓아주는(flattening) 함수이다.
즉, [[4, 1], [3, 0]].flatten() == [4, 1, 3, 0]로 바꾸어 준다.
'Algorithm' 카테고리의 다른 글
[코틀린] 백준 - 약수의 합2 (0) | 2022.10.21 |
---|---|
[코틀린] 프로그래머스 - 거리두기 확인하기 (0) | 2022.09.23 |
[파이썬] 프로그래머스 - 구명보트 (0) | 2022.06.04 |
[파이썬] 프로그래머스 - 이중우선순위큐 (0) | 2022.05.17 |
[파이썬] 프로그래머스 - 더 맵게 (0) | 2022.05.10 |