# [해시][Lv3] 베스트앨범 - 자바스크립트

# 프로그래머스 문제

https://programmers.co.kr/learn/courses/30/lessons/42579?language=javascript# (opens new window)

# 전체 코드

function solution(genres, plays) {
    var answer = [];
    const genresMap = new Map();
    const playsMap = new Map();
    
    for(var i=0; i<genres.length; i++){
        var isExist = genresMap.get(genres[i]);
        
        if(isExist){ // 이미 장르가 저장 된 경우
            genresMap.set(genres[i], plays[i]+isExist);
            playsMap.get(genres[i]).set(i, plays[i]);
        } else { // 새로 장르가 들어 가는 경우
            genresMap.set(genres[i], plays[i]);
            playsMap.set(genres[i], new Map( [[i, plays[i]]] ));
        }
    }
    
    // 1. 속한 노래가 많이 재생된 장르 genresMap 정렬
    // map -> object -> array -> 정렬 -> obejct
    let genresObjet = Object.fromEntries(genresMap);
    let genresObjet_sorted = Object.fromEntries(
        Object.entries(genresObjet).sort((a,b) => b[1]-a[1])
    );
    
    // 2. 장르별 가장 많이 재생된 노래 2개 고르기 playsMap
    Object.keys(genresObjet_sorted).forEach(v => {
        // 1. 해당 장르(v)의 재생횟수를 오름차순으로 나열
        var playObjet = Object.fromEntries(playsMap.get(v));
        var playObjet_sorted = Object.entries(playObjet).sort((a,b) => b[1]-a[1]);
        
        // 2. 상위 2개 선택
        var loopCnt = playObjet_sorted.length < 2 ? 1 : 2;
        for(var i=0; i<loopCnt; i++){
            answer.push(parseInt(playObjet_sorted.shift()[0]));
        }
        
    })

    return answer;
}

# 시도 1. 기본 틀 잡기

일단 해시 관련 문제라는 걸 알고있음 ⇒ map을 써야겠다 로 이어지니 첫 시작은 쉬웠음.

  1. 주어진 genres, plays배열을 이용해 해시맵 두개를 만든다.

    const genresMap = new Map();
    const playsMap = new Map();
    
    for(var i=0; i<genres.length; i++){
            var isExist = genresMap.get(genres[i]);
            
            if(isExist){ // 이미 장르가 저장 된 경우
                genresMap.set(genres[i], plays[i]+isExist);
                playsMap.get(genres[i]).set(i, plays[i]);
            } else { // 새로 장르가 들어 가는 경우
                genresMap.set(genres[i], plays[i]);
                playsMap.set(genres[i], new Map( [[i, plays[i]]] ));
            }
        }
    
    • genresMap : {key, value} ⇒ {장르명, plays 장르별로 합산한 값}

    • playsMap : {key, value} ⇒ {장르명, map(idx, plays) }

      ⇒ playsMap의 경우 value에 map을 한번 더 사용해줬다.

      밑처럼 생각했으나 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다. 규칙때문에 그냥 idx, plays 순으로 넣었다. 정렬은 genresMap를 정렬한 것 처럼 정렬함.

      Map.keys() 배열을 정렬하기 위해서 실행횟수(plays)를 키로 둔다. value 자리에 넣어도 Map.values()배열을 얻어서 정렬할 수 있긴하다. 하지만 가장 큰 값 2개를 얻은 후, 그 값의 고유번호(map(plays, idx)의 idx)를 얻기 위해선 key에 넣어두고 playMap.get(장르명).get(구해진 큰 값) 로 구하는 게 깔끔하다.

  2. genresMap을 재생수 순으로 정렬한다.

    let genresObjet = Object.fromEntries(genresMap);
    let genresObjet_sorted = Object.fromEntries(
        Object.entries(genresObjet).sort((a,b) => b[1]-a[1])
    );
    
    • Map 형식의 데이터를 sort 함수를 사용하기 위해선 array 형식으로 바꿔야한다.

      ⇒ 이 부분에서 forEach 를 사용해 배열로 만든 후 정렬할까 했지만.. 하나하나 하는 것보다 내장함수(?)를 사용해 보고 싶어서 검색을 했다. es10에 새로 나온 함수를 발견했고 Map → Object → Array → 정렬 → Object 순으로 진행했다. (사실 object로 변환을 한번 더 하지 않아도 되고 array[0]이렇게 사용해도 되지만.. 깔끔하게 가기위해..)

    • Object.fromEntries(iterable) : 반복가능한 키값 쌍의 목록을 객체로 반환(프로토타입이 Object)

    • Object.entries(obj) : [key, value] 쌍의 배열을 반환 (프로토타입이 Array)

      + 콘솔에서 확인해보기.
      
      > genresMap
      Map(2) {"classic" => 1450, "pop" => 3100}
      __proto__: Map
      
      > Object.fromEntries(genresMap)
      {classic: 1450, pop: 3100}
      __proto__: Object
      
      > Object.entries(genresMap)
      []
      __proto__: Array(0)
      
      > Object.entries(Object.fromEntries(genresMap));
      (2) [Array(2), Array(2)]
      0: (2) ["classic", 1450]
      1: (2) ["pop", 3100]
      __proto__: Array(0)
      
  3. 정렬된 genresMap를 for문으로 돌린다. for문안에서 장르별 많이 재생된 곡의 고유번호는 playsMap의 value에 위치하는 map을 이용한다.

    Object.keys(genresObjet_sorted).forEach(v => {
            // 1. 해당 장르(v)의 재생횟수를 오름차순으로 나열
            var playObjet = Object.fromEntries(playsMap.get(v));
            var playObjet_sorted = Object.entries(playObjet).sort((a,b) => b[1]-a[1]);
            
            // 2. 상위 2개 선택
            for(var i=0; i<2; i++){
                answer.push(parseInt(playObjet_sorted.shift()[0]));
            }
            
        })
    

# 결과

모든 테스트 케이스를 통과하지 못함! 당연하다. 왜냐면 일단 기본 틀을 잡는 데 주력하고, 문제에 주어진 예외사항들을 처리 안했음..

# 시도 2. 제한사항 처리하기

  1. 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.

    상위 2개 선택하는 부분에 loopCnt 변수를 둔다.

    // 2. 장르별 가장 많이 재생된 노래 2개 고르기 playsMap
        Object.keys(genresObjet_sorted).forEach(v => {
            // 1. 해당 장르(v)의 재생횟수를 오름차순으로 나열
            var playObjet = Object.fromEntries(playsMap.get(v));
            var playObjet_sorted = Object.entries(playObjet).sort((a,b) => b[1]-a[1]);
            
            // 2. 상위 2개 선택
            var loopCnt = playObjet_sorted.length < 2 ? 1 : 2;
            for(var i=0; i<loopCnt; i++){
                answer.push(parseInt(playObjet_sorted.shift()[0]));
            }
            
    })
    
    정확성  테스트
    테스트 1통과 (0.24ms, 30MB)
    테스트 2통과 (0.25ms, 30.1MB)
    테스트 3통과 (0.29ms, 30MB)
    테스트 4통과 (0.19ms, 29.9MB)
    테스트 5통과 (0.42ms, 30.1MB)
    테스트 6통과 (0.42ms, 30MB)
    테스트 7통과 (0.34ms, 30.1MB)
    테스트 8통과 (0.32ms, 30MB)
    테스트 9통과 (0.26ms, 30.3MB)
    테스트 10통과 (0.48ms, 29.9MB)
    테스트 11통과 (0.34ms, 30MB)
    테스트 12통과 (0.39ms, 30.1MB)
    테스트 13통과 (0.42ms, 30MB)
    테스트 14통과 (0.42ms, 30.1MB)
    테스트 15통과 (0.24ms, 30.2MB)
    채점 결과
    정확성: 100.0
    합계: 100.0 / 100.0
    

    끝! 개인적으로 level3 문제임에도 불구하고 이전에 풀었던 해시 문제(level1, 2)보다 가장 해시답고(?) 쉽고 재밌었다.. 이상한 수학 공식도 몰라도 되고....

    다른 사람의 답변을 둘러보니 일단 나는 sort 함수의 동작 원리를 더 정확히 이해할 수 있도록 공부해야 할 것 같다.

Last Updated: 8/11/2022, 5:17:02 AM