# [해시][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