뚝딱햄 탈출기

[JS][TIL] reduce()를 사용해보자 : 프로그래머스 베스트앨범 본문

Algorithm & Data structure/알고리즘 문제 풀이

[JS][TIL] reduce()를 사용해보자 : 프로그래머스 베스트앨범

hyrmzz1 2024. 4. 3. 21:42

[프로그래머스][level 3] 베스트앨범

접근 방식

  1. 입력값이 두 배열 genres와 plays로 나뉘어 들어오지만, 두 배열의 같은 인덱스를 가진 값들은 하나의 노래에 대한 값이다.
  2. 따라서 한 객체에 노래에 대한 정보들을 모아야겠다고 생각했다.
  3. 먼저 Object에 노래의 고유번호(= 인덱스), 장르, 재생 횟수를 담는다.
  4. 반환값인 베스트앨범의 첫 번째 기준은 '속한 노래의 재생 횟수가 많은 장르 먼저 수록' 이므로
    장르별로 해당 장르 노래들의 재생 횟수를 모두 더하고, 총 재생 횟수를 기준으로 장르를 내림차순 정렬해야겠다고 생각했다.
  5. 베스트앨범의 두 번째 기준은 '장르 내에서 많이 재생 횟수가 많은 노래 수록' 이다.
    장르별로 속한 노래들을 재생 횟수를 기준으로 내림차순 정렬해야겠다고 생각했다.
  6. 장르를 (4)에서 구한 총 재생 횟수 기준으로 내림차순 정렬하고,
    순회하며 장르별로 재생 횟수가 top1, 2인 노래의 인덱스를 출력하자.
    1. 각 장르당 2개 이하의 노래를 담아야 한다. -> 카운트해야 함!
    2. 인덱스는 배열에 담아 출력해야 한다.

Object의 각 요소에 대해 1) 장르별로 분류하고 2) 재생 횟수를 더해야 하는데,
복잡한 방식밖에 떠오르지 않아 내 사랑 gemini에게 자문한 결과 reduce()를 사용하라는 답변을 받았다.

🤔 reduce()란?

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

리듀서 함수는 네 개의 인자를 가집니다.
1. 누산기 (acc)
2. 현재 값 (cur)
3. 현재 인덱스 (idx)
4. 원본 배열 (src)

리듀서 함수의 반환 값은 누산기에 할당되고, 누산기는 순회 중 유지되므로 결국 최종 결과는 하나의 값이 됩니다.
콜백의 최초 호출 때 accumulator와 currentValue는 다음 두 가지 값 중 하나를 가질 수 있습니다.
1. initialValue를 제공한 경우, accumulator는 initialValue와 같고 currentValue는 배열의 첫 번째 값과 같습니다. 
2. initialValue를 제공하지 않았다면, accumulator는 배열의 첫 번째 값과 같고 currentValue는 두 번째와 같습니다.

1. 두 개의 인자를 가질 때

[0, 1, 2, 3, 4].reduce((prev, curr) => prev + curr);
// 1번째 콜백 호출) prev: 0, curr: 1. 반환 값 = 1
// 2번째 콜백 호출) prev: 1, curr: 2. 반환 값 = 3
// 3번째 콜백 호출) prev: 3, curr: 3. 반환 값 = 6
// 4번째 콜백 호출) prev: 6, curr: 4. 반환 값 = 10
// 10 반환

첫 번째 인자인 prev는 acc로, 누적되는 값이다.

두 번째 인자인 curr는 cur으로, 현재 처리하고 있는 요소이다. (현재 순회 중인 요소)

 

이 코드에는 initialValue가 제공되지 않았으므로 콜백의 최초 호출 때 prev에 배열의 첫 번째 값인 0을 할당하고, curr에는 두 번째 값인 1을 할당한다.

두번째 호출부턴 prev에 이전 호출의 반환값이 들어가고, curr에는 배열의 나머지 원소인 2, 3, 4가 순차적으로 들어간다.

결과적으로 reduce()는 마지막 콜백 호출의 반환값을 반환한다.

2. 초기값이 주어졌을 때

초기값이 주어졌다면 acc는 초기값과 같고 cur은 배열의 첫 번째 값과 같다.

[0, 1, 2, 3].reduce(
  (accumulator, currentValue) => accumulator + currentValue,
  10,
);

10이라는 초기값이 주어졌으니 콜백의 최초 호출 때 accumulator에 10이, currentValue에 0이 들어간다.

반환값인 10 + 0, 즉 10이 두번째 콜백 호출 때 accumulator에 들어가고, 배열의 다음 원소인 1이 currentValue에 들어간다.

3. 객체로 이루어진 배열에 reduce()를 사용하려면?

객체로 이루어진 배열에 들어있는 값을 합산하려면 반드시 초기값이 필요하다고 한다.

초기값으로 {}만 할당할 수도 있다!

 

코드를 작성하며 객체에 적용하는 법을 알아보자.

✅ Solution

장르에 대한 총 누적값(재생 횟수)을 구해야 한다. 초기값은 필요 없다고 생각해 {}를 할당했다.

 

누적값 acc를 배열로도 취급할 수 있다!

각 장르별로 누적값을 구해야 하므로 acc[song.genre]에 song.play를 더해주었다.

function solution(genres, plays) {
    let songs = genres.map((genre, idx) => {
        return {
            idx: idx,
            genre: genre,
            play: plays[idx],
        };
    })

    // 장르별 재생 횟수
    let playByGenre = songs.reduce((acc, song) => {
        if (!acc[song.genre]) { // 초기값 설정
            acc[song.genre] = 0;
        }
        
        acc[song.genre] += song.play;
        return acc;
    }, {});

    // 총 재생횟수 높은 순으로 장르 정렬
    playByGenre = Object.entries(playByGenre).sort((a, b) => b[1] - a[1]); 

    // 재생횟수 높은 순으로 모든 노래 정렬
    songs.sort((a, b) => b.play - a.play);

    // 기준을 만족하는 노래의 인덱스 넣기
    const answer = [];
    playByGenre.forEach((genre) => {
        let limit = 0;

        songs.forEach((song) => {
            if (song.genre === genre[0] && limit < 2) {
                answer.push(song.idx);
                limit++;
            }
        })
    })

    return answer;
}

총 재생 횟수 높은 순으로 장르 정렬 단계 전에 playByGenre를 출력해 보면 {"classic":1450,"pop":3100} 와 같다.

object인 playByGenre를 play(1450, 3100) 기준으로 내림차순 정렬하기 위해 Object.entries()를 통해 [key, value] 쌍의 배열로 만들었다. 배열이 됐으므로 인덱스를 통해 play를 기준으로 정렬할 수 있다. .sort((a, b) => b[1] - a[1]); 

 

배열로 만든 덕에 answer에 노래의 인덱스를 넣기 위한 forEach문을 더 수월하게 작성할 수 있었다!

 


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

 

프로그래머스

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

programmers.co.kr

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce

 

Array.prototype.reduce() - JavaScript | MDN

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

developer.mozilla.org

 

Comments