Coding Test/JavaScript

[프로그래머스 FE 데브코스] 해시 문제 : 베스트 앨범

ea_jung 2023. 6. 8. 14:02

오늘은 오프 멘토님과의 첫 번째 커피챗이 있는 날이네요! 기대됩니다. 🤩
오늘 강사님께서 해시와 고차함수들을 이용한 문제와 linked list를 이용한 Queue 문제를 풀어주셨는데 저는 조금 어렵더라고요.
한번 차근차근 다시 풀어봐야겠어요. 제 것으로 만들면 다른 문제 풀 때에도 분명 도움이 많이 될 거 같아요.

+ 저녁에 커피챗 시간을 가졌는데 생각할게 많아졌어요.. 아직 너무 학생 같은 마인드로 강의 듣고 복습하고 하는 식의 공부만 했던 게 아닌가라는 생각이 들면서 좀 더 능동적으로 필요한 공부를 찾아서 해야겠다는 생각을 했어요. 지금 내가 부족하고 필요한 부분을 찾아서 책임지고 설명할 수 있을 정도로 깊게 공부하고 정리해야겠더라고요. 차근차근 지치지 말고 해 봐야겠습니다!
다음 커피챗도 기대됩니다!

 

🔗 해시 문제 : 베스트 앨범 

 

✔️ 입출력 예

 

✔️ 입출력 예 설명

classic 장르는 1,450회 재생되었으며, classic 노래는 다음과 같습니다.

  • 고유 번호 3: 800회 재생
  • 고유 번호 0: 500회 재생
  • 고유 번호 2: 150회 재생

 

pop 장르는 3,100회 재생되었으며, pop 노래는 다음과 같습니다.

  • 고유 번호 4: 2,500회 재생
  • 고유 번호 1: 600회 재생

 

따라서 pop 장르의 [4, 1] 번 노래를 먼저, classic 장르의 [3, 0] 번 노래를 그다음에 수록합니다. 

  • 장르 별로 가자 많이 재생된 노래를 최대 두 개 까지 모아 베스트 앨범을 출시하므로 2번 노래는 수록되지 않습니다. 

 

⭐️ 처음 읽었을때는 몰랐는데 입출력 예 설명 부분을 참고하면 문제 풀이 순서를 정하기 수월하더라고요. 
같은 장르끼리 묶고  ➡️  묶인 노래들을 재생 순으로 정렬하고  ➡️  노래를 2개까지 자릅니다. 

 

genres가 문자열 배열로 되어 있는데 매번 순회를 하면 느려질것이기에 필요한 정보를 하나로 묶기 위해 hash table을 사용하는 것이 좋아요! 자바스크립트에서는 Object나 Map를 사용할 수 있습니다.  이 문제에서는 Map()을 사용할 건데 이유는 기존 객체와는 다르게 key 값으로 객체나, 배열 같은 복잡한 타입도 key로 사용할 수 있고, 여러 편한 메서드를 제공해 주고, 순회도 편하게 해 주기 때문입니다. 

💡 묶는다는 키워드가 나오면 hash table를 생각해 보는 것이 좋습니다!

❓Hash table
key와 value를 받아 key를 해싱하여 나온 index에 값을 저장하는 선형 자료 구조 입니다. 
삽입은 O(1)이며 키를 알고 있다면 삭제, 탐색도 O(1)로 수행합니다. 

 

function solution(genres, plays) {
	const genreMap = new Map();
}

 

다음으로는 계산하기 편하도록 genres배열과 plays배열을 하나로 묶어 줍니다. 

 

function solution(genres, plays) {
	const genreMap = new Map();
    genres
    	.map((genre, index) => [genre, plays[index])
}

//실행한 결괏값
 [["classic",500],["pop",600],["classic",150],["classic",800],["pop",2500]]

 

이제 forEach를 이용해서 묶은 배열에서 데이터 뽑아볼게요.

비구조화 할당을 이용하면 더 간단한 코드를 사용 할 수 있습니다. 

genreMap에 set()를 이용해서 key : value 쌍을 넣어줍니다. 

data에 초기값이 없으면 undefined 에러가 뜨니까 data의 초기값도 설정해 줍니다. 

 

function solution(genres, plays) {
  const genreMap = new Map();
  genres
    .map((genre, index) => [genre, plays[index]])
    .forEach(([genre, play], index) => {
      const data = genreMap.get(genre) || {total: 0, songs: []};
      genreMap.set(genre, {
          total: data.total + play,
          songs: [...data.songs, {play, index}]
            .sort((a, b) => b.play - a.play)
            .slice(0, 2)
      })
  })
    console.log(genreMap.get('classic'))
} 

//genreMap의 classic 장르의 출력값을 확인해 봤어요.
{
  total: 1450,
  songs: [ { play: 800, index: 3 }, { play: 500, index: 0 } ]
}

 

저는 songs: [...data.songs, {play, index}] 부분의 {play, index} 부분이 조금 헷갈리더라구요.

이게 어떻게 출력될지 감이 안 잡힌달까...

[{ play: 800, index: 3 }, { play: 500, index: 0 }] 로 key: value으로 출력되더라고요? 

 

제가 헷갈렸던 이유가 객체로 묶었는데 배열처럼 생각했던게 문제였던 거 같아요. 

만약 songs: [...data.songs, [play, index]] 이 코드로 사용했다면 [ [ 800, 3 ], [ 500, 0 ] ] 와 같이 출력됐겠죠?

 

❗️ 하지만 저희는 객체로 묶어주고 구조분해할당을 사용했으므로 [{ play: 800, index: 3 }, { play: 500, index: 0 }] 이렇게 출력되는 것이 어쨌든 맞습니다. (배열로 풀어도 풀리기는 할 거 같아요.)

 

이제 genreMap 객체에서 데이터를 뽑아와서 return 해줘야 합니다.
genreMap 객체를 출력해서 한번 더 확인해 볼게요!
예쁘게 잘 정리되있네요!

 

Map(2) {
  'classic' => { total: 1450, songs: [ { play: 800, index: 3 }, { play: 500, index: 0 } ] },
  'pop' => { total: 3100, songs: [ { play: 2500, index: 4 }, { play: 600, index: 1 } ] }
}

 

그다음 Object.entries() 메서드를 이용하면  [key, value] 쌍의 배열이 반환됩니다. 

 

function solution(genres, plays) {
  const genreMap = new Map();
  genres
    .map((genre, index) => [genre, plays[index]])
    .forEach(([genre, play], index) => {
      const data = genreMap.get(genre) || {total: 0, songs: []};
      genreMap.set(genre, {
          total: data.total + play,
          songs: [...data.songs, {play, index}]
            .sort((a, b) => b.play - a.play)
            .slice(0, 2)
      })
  })
    console.log([...genreMap.entries()])
}

//출력 결과 
[
  [ 'classic', { total: 1450, songs: [ { play: 800, index: 3 }, { play: 500, index: 0 } ] } ],
  [ 'pop', { total: 3100, songs: [ { play: 2500, index: 4 }, { play: 600, index: 1 } ] } ]
]

 

 

이제 정렬만 하면 되는데 total 높은 장르인 pop이 먼저 정렬되게 하고, flatMap() 메서드를 이용해서 songs를 묶어서 정렬해 줍니다.

 

❗️flatMap() 이 아닌 map()을 사용해 버리면 배열(리스트) 안에 리스트가 들어간 너무 복잡한 형태가 되어 버려요.

 

//flatMap() 사용시
[
  { play: 2500, index: 4 },
  { play: 600, index: 1 },
  { play: 800, index: 3 },
  { play: 500, index: 0 }
]

//map() 사용시
[
  [ { play: 2500, index: 4 }, { play: 600, index: 1 } ],
  [ { play: 800, index: 3 }, { play: 500, index: 0 } ]
]

 

정말 마지막으로 index만 뽑아서 정렬해 주면 됩니다!

 

function solution(genres, plays) {
  const genreMap = new Map();
  genres
    .map((genre, index) => [genre, plays[index]])
    .forEach(([genre, play], index) => {
      const data = genreMap.get(genre) || {total: 0, songs: []};
      genreMap.set(genre, {
          total: data.total + play,
          songs: [...data.songs, {play, index}]
            .sort((a, b) => b.play - a.play)
            .slice(0, 2)
      })
  })
    return [...genreMap.entries()]
            .sort((a, b) => b[1].total - a[1].total)
            .flatMap(item => item[1].songs)
            .map(song => song.index)   
    
}

 

실행 결과 [4, 1, 3, 0] 이 잘 나오네요!

 

공부할 거리가 많았고 재밌는 문제 같습니다!

코테에서 만나지는 말자....

반응형