-
[프로그래머스] 베스트앨범 (Kotlin)문제풀이/프로그래머스 2021. 6. 29. 22:45
문제 링크
내 코드 (failed)
파이썬으로 이미 풀어봤던 문제기 때문에 위풍당당하게 쭉쭉 코드를 적어나갔으나.... 여지없이 나를 붙잡는 UNSAFE_CALL 과 TYPE_MISMATCH.
일단 기록해둔 뒤 나중에 문제점을 살펴보겠습니다.
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { var answer = intArrayOf() val genre_play = HashMap<String, Int>() val genre_most = HashMap<String, Array<Int>>() for (i in 0 until genres.size) { genre_play[genres[i]]?.let { genre_play.put(genres[i], it + plays[i]) } ?: genre_play.put(genres[i], plays[i]) genre_most[genres[i]]?.let { if (plays[i] > plays[it[0]]) { genre_most[genres[i]] !!.set(1, it[0]) !!.set(0, i) } else { genre_most[genres[i]][1]?.let { if (plays[genre_most[genres[i]][1]] < plays[i] ) { genre_most[genres[i]]!!.set(1, i) } } ?: genre_most[genres[i]]!!.set(1, i) } } ?: genre_most.put(genres[i], arrayOfNulls<Int>(2).let { it.set(0, i); it }) } for (A in genre_play) { answer.add(genre_most[A[0]][0]) genre_most[A[0]][1]?.let { answer.add(it) } } return answer } }
다른 사람의 코드
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } .toList() .sortedByDescending { it.second.sumBy { plays[it] } } .map { it.second.sortedByDescending { plays[it] }.take(2) } .flatten() .toIntArray() } }
이것이 함수형 프로그래밍인가...? 😢
하나씩 뜯어봅시다.
genres: Array<String>과 plays: IntArray는 다음과 같은 형태로 입력됩니다.
(아래에서부터 이 예시로 입력된다고 가정하고 설명하겠습니다.)
genres => ["classic", "pop", "classic", "classic", "pop"]
plays => [500, 600, 150, 800, 2500]
그리고 그 뒤의 체인 프로퍼티&메서드를 하나씩 살펴보면 다음과 같습니다.
indices
val <T> Array<out T>.indices: IntRange
Collection, Array 클래스에 선언되어있는 IntRange형 프로퍼티입니다.
genres.indices는 genres의 크기만큼의 범위를 반환합니다.
즉, 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>> 인 메서드입니다.
지금과 같이 하나의 파라미터 genres[it]를 받을 시, 본인의 객체(it)의 원소들을 파라미터 genres[it]을 기준으로 분류하여 그룹화합니다.
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } // {classic=[0,2,3], pop=[1,4]} Type : Map .toList() .sortedByDescending { it.second.sumBy { plays[it] } } .map { it.second.sortedByDescending { plays[it] }.take(2) } .flatten() .toIntArray() } }
이는 즉 (0..4).groupBy { genres[it] } 이고, it에는 0부터 4까지의 정수가 순서대로 들어가게 됩니다.
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[3]는 "pop"이므로, map : { "classic" : [0, 2, 3], "pop" : [1, 4] } 가 됩니다.
최종적으로, { "classic" : [0, 2, 3], "pop" : [1, 4] } 의 map을 반환합니다.
toList()
fun <K, V> Map<out K, V>.toList(): List<Pair<K, V>>
toList() 메서드는 Array의 확장함수로 쓸 때와, Map의 확장함수로 쓸 때가 다르게 작용합니다.
Array.toList() 는 단순히 Array의 모든 원소를 복사하여 List타입으로 재구성합니다.
Map.toList() 는 각 Key와 Value를 Pair쌍으로 묶어서 List타입으로 재구성합니다.
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } // {classic=[0,2,3], pop=[1,4]} Type : Map .toList() // [(classic, [0,2,3]),(pop,[1,4])] Type : List .sortedByDescending { it.second.sumBy { plays[it] } } .map { it.second.sortedByDescending { plays[it] }.take(2) } .flatten() .toIntArray() } }
※ Pair란?
파이썬의 튜플과 같이 데이터를 쌍으로 묶은 데이터입니다. 실제로 Kotlin M3 이전 버전까지는 tuple 자료형이 있었으나, Pair와 Triple로 Migrate되어서 이제 tuple은 사용되고 있지 않습니다.
즉, { "classic" : [0, 2, 3], "pop" : [1, 4] }.toList() == [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ] (type: List<Pair<String, List<Int>>>) 가 됩니다.
sortedByDescending()
inline fun <T, R : Comparable<R>> Iterable<T>.sortedByDescending(crossinline selector: (T) -> R?): List<T>
이건 메서드 이름만 봐도 알 수 있죠. 파라미터를 기준으로 데이터를 내림차순으로 정렬합니다.
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } // {classic=[0,2,3], pop=[1,4]} Type : Map .toList() // [(classic, [0,2,3]),(pop,[1,4])] Type : List .sortedByDescending { it.second.sumBy { plays[it] } } // [(pop, [1,4]), (classic, [0,2,3])] Type : List .map { it.second.sortedByDescending { plays[it] }.take(2) } .flatten() .toIntArray() } }
파라미터로 쓰인 it.second.sumBy()는 앞 과정까지 구한 [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ] (type: List)의 두번째 element인 [0, 2, 3]과 [1, 4]가 각각 파라미터로 들어가 plays를 참조하여 합을 리턴합니다.
즉, [0, 2, 3]에 대해 500 + 150 + 800 = 1450, [1, 4]에 대해 600 + 2500 = 3100을 리턴합니다.
pop키의 데이터인 [1, 4]의 sumBy가 더 크므로, sortedByDescending 메서드를 통해 다음과 같이 정렬됩니다.
(정렬 전) [ ("classic", [0, 2, 3]), ("pop", [1, 4]) ] (type: List)
(정렬 후) [ ("pop", [1, 4]), ("classic", [0, 2, 3]) ] (type: List)
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } // {classic=[0,2,3], pop=[1,4]} Type : Map .toList() // [(classic, [0,2,3]),(pop,[1,4])] Type : List .sortedByDescending { it.second.sumBy { plays[it] } } // [(pop, [1,4]), (classic, [0,2,3])] Type : List .map { it.second.sortedByDescending { plays[it] }.take(2) } // [[4,1],[3,0]] Type : List .flatten() .toIntArray() } }
위에서 sortedByDescending과 sumBy를 이해하셨다면, 다음 line의 이해에도 무리가 없을 것입니다.
다만 map함수는 이름과 다르게, 객체의 각 iterable의 값을 변경하여 새로운 List를 반환합니다.
take함수는 인자의 개수만큼만 iterable 객체의 원소를 앞에서부터 List로 반환합니다.
즉, 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]] List<List<Int>>
flatten()
fun <T> Iterable<Iterable<T>>.flatten(): List<T>
List안에 List가 존재할 경우 이를 펼쳐놓아주는(flattening) 메서드입니다.
즉, [[4, 1], [3, 0]].flatten() == [4, 1, 3, 0]
답이 구해졌네요. 다만 지금 이 리스트의 타입이 List<Int>이므로, toIntArray를 통해 IntArray타입으로 변환해준 뒤 반환합니다.
class Solution { fun solution(genres: Array<String>, plays: IntArray): IntArray { return genres.indices.groupBy { genres[it] } // {classic=[0,2,3], pop=[1,4]} Type : Map .toList() // [(classic, [0,2,3]),(pop,[1,4])] Type : List .sortedByDescending { it.second.sumBy { plays[it] } } // [(pop, [1,4]), (classic, [0,2,3])] Type : List .map { it.second.sortedByDescending { plays[it] }.take(2) } // [[4,1],[3,0]] Type : List .flatten() // [4, 1, 3, 0] Type : List .toIntArray() // [4, 1, 3, 0] Type : IntArray } }