본문 바로가기

알고리즘

[프로그래머스] 베스트앨범 #코틀린 #kotlin #hash #level3

반응형

https://programmers.co.kr/learn/courses/30/lessons/42579?language=kotlin 

 

코딩테스트 연습 - 베스트앨범

스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다. 속한 노래가

programmers.co.kr

풀이

문제에서 하라는대로 해봅시다. 먼저, 곡을 장르 별로 구분합니다.

plays.withIndex().groupBy { genres[it.index] }

zip() 메소드를 사용해도 좋겠지만, 이후에 노래의 고유 번호가 필요하므로, withIndex() 메소드를 사용하여 index를 사용해 plays와 genres를 매칭시킵니다. withIndex()를 사용하면 이후 원소는 IndexedValue<*> 타입이 되며, 멤버변수 index와 value를 가집니다. 이제 groupBy() 메소드로 {장르: [노래, ...], ...}와 같은 데이터를 완성할 수 있습니다.

해당 장르가 많이 재생된 순으로 정렬합니다.

plays.withIndex().groupBy { genres[it.index] }.values
	.sortedByDescending { v -> v.sumBy { it.value } }

이후에는 key 값(= genre)이 사용되지 않으므로 .values를 이용하여 key 값을 버려도 좋습니다. 이후 이를 sumBy, 즉 장르를 재생한 시간을 기준으로 큰 순으로 정렬합니다. 이때 클로저에서 v의 타입은 다음과 같습니다.

 

장르 내에서 많이 재생된 노래 순으로 다시 정렬합니다.

plays.withIndex().groupBy { genres[it.index] }.values
	.sortedByDescending { v -> v.sumBy { it.value } }
	.map { v -> v.sortedByDescending { it.value }.map { it.index } }

각 장르 별로 큰 순으로 정렬하며, 이후 재생 시간은 사용되지 않기 때문에 .map { it.index }를 통해 노래의 고유 번호만을 남깁니다. (장르 내에서 재생 횟수가 같은 노래 중 고유 번호가 낮은 노래를 먼저 수록합니다.) 이 조건은 고유 번호가 이미 낮은 순으로 정렬되어 있었기 때문에 자동으로 달성되는 조건입니다.

이제 각 장르 별로 두 곡 씩 앨범에 수록해봅시다. 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.

plays.withIndex().groupBy { genres[it.index] }.values
	.sortedByDescending { v -> v.sumBy { it.value } }
	.map { v -> v.sortedByDescending { it.value }.map { it.index } }
	.fold(intArrayOf()) { acc, v -> acc + v.subList(0, min(2, v.size)) }

.fold() 메소드를 통해 초기값을 지정하여 reduce를 실행할 수 있습니다. .subList 메소드의 경우 범위를 넘어서는 인덱스를 입력하면 Out Of Bounds 에러를 발생시키니 다음과 같이 min을 활용하여 에러 발생 요소를 원천 차단합니다.

import java.lang.Integer.min

class Solution {
    fun solution(genres: Array<String>, plays: IntArray) =
        plays.withIndex().groupBy { genres[it.index] }.values
            .sortedByDescending { v -> v.sumBy { it.value } }
            .map { v -> v.sortedByDescending { it.value }.map { it.index } }
            .fold(intArrayOf()) { acc, v -> acc + v.subList(0, min(2, v.size)) }
}

완성된 코드입니다. 통과 시간은 대략 23ms ~ 35ms 사이로, 파이썬의 0.02ms ~ 0.10ms에 비하면 너무나도 느린 것 같습니다.

코틀린이 원래 이렇게 느린건지, 코드가 비효율적인건지 모르겠습니다. 다른 사람의 풀이에서 제일 처음 나오는 코드는 조금 더 빠를까요? 다음은 해당 코드입니다. subList(0, min(2, v.size)) 대신 take(2)를, fold() 대신 flatten()을 사용하여 복잡한 작업을 줄였습니다. 시간은 줄어들었을까요?

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()
    }
}

비슷비슷하나 전반적으로 30ms를 넘는 것이.. 제가 짠 코드가 조금이나마 더 빠른 것 같습니다. 어찌 되었든, 테스트 통과를 표시하는데 걸리는 시간이 늘어나므로 코틀린으로 코딩 테스트를 치면 파이썬보다 불리해보입니다. 특히, 코딩 테스트의 막바지에는 다수의 사람들이 강박적으로 코드를 실행하여 전반적인 테스트 속도가 느려지므로 뜻하지 않게 불이익을 볼 수 있을 것 같네요... 물론 서버가 어떻게 구성되었느냐에 따라 다르겠지만요.

그럼에도 코틀린을 사용하여 코딩 테스트를 연습해야 하는 이유는 분명 존재합니다. 면접 대비 때문입니다. 신입 공채, 블라인드 인턴 등 대규모로 시험을 보는 상황이라면 면접 시 코딩 테스트 결과를 묻지 않겠지만, 상시 채용의 경우 코딩 테스트 때 작성한 코드의 동작 원리를 묻거나, 언어의 숙련도를 확인하는 등의 경우가 생깁니다. 특히, 면접관들 앞에서 라이브 코딩 테스트를 치는 경우에는 이러한 부분이 아쉬운 점이 될 수 있겠죠

또한, 라이브 코딩 테스트의 경우 코틀린을 실행할 수 있는 환경을 점검하는 것도 필요합니다. 화면이 공유되지 않는 상태라면 마음껏 인터넷 검색을 활용할 수 있겠지만, 면접관 앞에서 인터넷으로 레퍼런스를 찾는다? 무조건 마이너스입니다. 그렇다고 안드로이드 스튜디오를 켜서 코틀린을 실행했다가 에뮬레이터를 키기라도 하면 얼마나 민망합니까. 이러한 부분을 생각하면 미리미리 문법 체크, 자동 완성 등의 기능을 제공하고, 바로바로 실행 및 확인할 수 있는 환경을 구축하는 부분 또한 중요해보입니다.

반응형