Algorithm

[코틀린] 프로그래머스 - 베스트앨범

🤖 Play with Android 🤖 2022. 7. 25. 21:03
728x90


다른 사람의 풀이에 정말 배울 가치가 있는 코드가 있어 하나씩 뜯어보며 공부해보려고 한다.

 

풀이

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]로 바꾸어 준다.