본문 바로가기
알고리즘

[프로그래머스] Swift - 베스트앨범

by 바등쪼 2023. 7. 10.

https://school.programmers.co.kr/learn/courses/30/lessons/42579

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 

2023.07.10 기준 Level 3

 

알고리즘 공부용으로 풀고 기록하는 글입니다!
참고만 해주시고 더 좋은 풀이법이 있다면 알려주세요!

 

아이디어

문제 자체의 요구 사항은 간단합니다!

 

  1. 가장 많이 재생된 장르를 찾는다. (정렬한다)
  2. 각 장르에서 가장 많이 재생된 노래를 정렬하고 상위 2개만 뽑아낸다.
  3. 1번과 2번의 작업을 거쳐 나온 노래들을 어레이로 리턴한다.

결국 장르와 노래를 재생 수로 정렬하는 게 핵심입니다.

 

풀이

Genre 모델 생성

fileprivate class Genre {
    let name: String
    var playsCnt = [Int: Int]()
    
    var cnt: Int {
        playsCnt.values.reduce(0, +)
    }
    
    init(name: String) {
        self.name = name
    }
    
    func getTop2Plays() -> [Int] {
        let top2 = playsCnt
            .sorted {
                if $0.value == $1.value {
                    return $0.key < $1.key
                } else {
                    return $0.value > $1.value
                }
            }
            .map { $0.key }
            .prefix(2)
        return Array(top2)
    }
}

다양한 풀이법이 있겠으나 저는 약간의 객체지향적인 관점에서 풀기 위해 장르 모델(객체)를 생성했습니다.

장르 이름과 노래들의 재생 카운트를 기록하는 딕셔너리인 playsCnt를 가집니다.

playsCnt의 key는 노래의 고유번호(인덱스)이고 value는 재생된 횟수입니다.

 

해당 장르에서 가장 많이 재생된 노래 Top2를 구하는 함수인 getTop2Plays()를 메서드로 만들었습니다.

playsCnt 딕셔너리를 value값을 기준으로 정렬하고 문제 조건에 따라 재생된 횟수를 기준으로 정렬 + 재생 횟수가 같다면 key를 오름차순으로 정렬하도록 했습니다.

그리고 정렬된 데이터를 key만 남기도록 매핑하고 상위 2곡만 필요하기 때문에 prefix(_ maxLength)로 잘라냅니다.

우리가 필요한 타입이 어레이기 때문에 어레이로 타입 변환하고 리턴하도록 했습니다.

 

solution 함수 구현

fileprivate func solution(_ genres:[String], _ plays:[Int]) -> [Int] {
    var genresDict = [String: Genre]()
    
    for i in genres.indices {
        let genreName = genres[i]
        let play = plays[i]
        
        if let genre = genresDict[genreName] {
            genre.playsCnt[i, default: 0] += play
        } else {
            let genreModel = Genre(name: genreName)
            genreModel.playsCnt[i] = play
            genresDict[genreName] = genreModel
        }
    }
    
    let genresSorted = genresDict.values.sorted(by: { $0.cnt > $1.cnt })
    let result = genresSorted.map { $0.getTop2Plays() }.flatMap { $0 }
    
    return result
}

genresDict라는 이름으로 Genre 객체를 담는 딕셔너리를 생성하고 genres를 반복문을 돌며 정보를 업데이트합니다.

 

가장 많이 재생된 장르의 순서대로 Sorting하고 고차함수인 map과 flatMap을 활용하여 각 장르의 Top 2 노래로 변환하고 이것을 일차원 어레이로 바꾸도록 했습니다.

 

결과적으로 result 변수는 다음과 같은 형태로 변환되어 우리가 원하는 값이 들어가게 됩니다.

장르들을 재생순으로 정렬 ➡️ 해당 장르에서 노래를 재생순으로 정렬 ➡️ Top 2개만 뽑아냄 ➡️ 1차원 어레이 형태로 변환

 

마무리

딕셔너리를 사용한 구현 문제였습니다.

각 장르, 노래별 재생 횟수를 기록해야 하기 때문에 딕셔너리가 적합한 자료구조입니다.

Genre 클래스를 생성하여 가독성을 높이고자 했습니다.

(그냥 Solution함수에서 원시 타입으로만 구현한 사례도 많지만 그렇게 하면 나중에 다시 읽었을 때 이해하기 쉽지 않습니다.)

고차함수를 꽤 많이 사용해서 코드의 길이를 줄일 수 있었습니다.

 

댓글