PROGRAMMERS - 베스트 앨범 [Level 3]

GI JUNG·2023년 9월 1일
2

algorithm

목록 보기
17/28
post-thumbnail
post-custom-banner

🍀 베스트 앨범

중간에 힘들일이 있어 살짝 슬럼프??가 있어서 베트남 여행과 서핑을 통해 기분전환 후 다시 블로그를 시작한다. 정진!! 이 문제를 자신만만하게 접근했는데 내가 받아들이기에는 문제의 조건이 애매하게 적힌것 같은 느낌이었다. 속한 노래가 많이 재생된 장르를 먼저 수록합니다.라는 조건을 내 멋대로???인지는 모르겠지만 이해가 가지 않아 TC를 보고 나서 최대 재생수를 기준으로 장르를 수록하는 줄 알았다. 하지만, 이렇게 풀고나서 TC 5~14번이 통과가 되지 않아서 재생수를 총 합친 것이 큰 기준으로 장르를 수록하는 코드로 바꾸어 보았는데 통과했다.

문제를 푼 로직은 아래와 같다.

  • 1️⃣ 장르별 재생수의 총합이 큰 기준으로 내림차순 정렬
  • 2️⃣ 장르 내에서 재생수가 큰 것을 기준으로 내림차순 정렬
    • 3️⃣ 이 때 재생수가 같다면 고유번호는 오름차순 정렬
  • 4️⃣ 최 대 2곡만 뽑는다

1️⃣ Python

❌ python - 실패코드(5 ~ 14)

# 실패코드 5 ~ 14
def solution(genres, plays):
    answer = []
    
    max_d = dict()
    genre_play_d = dict()
    
    # 장르별 재생수 총합 구하기 👉🏻 1️⃣
    for genre, play in zip(genres, plays):
        if genre not in max_d:
            max_d[genre] = play
            continue
            
        if max_d[genre] < play:
            max_d[genre] = play
            
    # 재생수 총합 기준으로 내림차순 정렬 👉🏻 1️⃣
    max_genres = list(map(lambda x: x[0], sorted(max_d.items(), key=lambda x: -x[1])))
    
    for idx, [genre, play] in enumerate(zip(genres, plays)):
        if genre not in genre_play_d:
            genre_play_d[genre] = []
        
        genre_play_d[genre].append([idx, play])
        
    for genre in max_genres:
        # 재생수 -> 내림차순, 고유번호 -> 오름차순 👉🏻 2️⃣ & 3️⃣
        sorted_by_play_unique = sorted(genre_play_d[genre], key=lambda x: (-x[1], x[0]))
        # 장르별 최대 곡의 수는 2개 이므로 👉🏻 4️⃣
        max_two_plays = sorted_by_play_unique[:2]
        answer.extend(list(map(lambda x: x[0], max_two_plays)))

        
    return answer

위의 코드가 실패 코드이다.... 🥲 문제파악을 다시하고 코드를 짜는데 1시간은 더 걸린 것 같다 ㅜㅜ

✅ python - 성공코드

def solution(genres, plays):
    answer = []
    
    # 장르별 재생수 총합 구하기 👉🏻 1️⃣
    max_d = dict()
    genre_play_d = dict()
    
    for genre, play in zip(genres, plays):
        if genre not in max_d:
            max_d[genre] = 0
            
        max_d[genre] += play
        
    # 재생수 총합 기준으로 내림차순 정렬 👉🏻 1️⃣
    max_genres = list(map(lambda x: x[0], sorted(max_d.items(), key=lambda x: -x[1])))
    
    for idx, [genre, play] in enumerate(zip(genres, plays)):
        if genre not in genre_play_d:
            genre_play_d[genre] = []
        
        genre_play_d[genre].append([idx, play])
        
    for genre in max_genres:
        # 재생수 -> 내림차순, 고유번호 -> 오름차순 👉🏻 2️⃣ & 3️⃣
        sorted_by_play_unique = sorted(genre_play_d[genre], key=lambda x: (-x[1], x[0]))
        # 장르별 최대 곡의 수는 2개 이므로 👉🏻 4️⃣
        max_two_plays = sorted_by_play_unique[:2]
        answer.extend(list(map(lambda x: x[0], max_two_plays)))

        
    return answer

실패코드와 코드는 동일하지만 1️⃣ 로직에 관한 코드가 수정되었다. 다른 사람 풀이를 보면서 느낀 건데 pop() 또는 popleft() 메서드를 써서 배열에 요소가 존재하지 않을 시 조건문을 추가하여 에러처리를 진행해 준 사람들이 많은데, 하나의 팁이면 팁인 slicing을 이용하면 에러가 나지 않고 빈 배열을 슬라이싱할 시 빈 배열을 return하므로 조건문을 추가하지 않아도 된다.

🛠️ python - 리팩터링

사실상 max_d와 genre_play_d를 하나의 for구문 안에서 돌릴 수 있으며 defaultdict인 builtin method를 이용해서 코드를 조금 수정해보았다.

from collections import defaultdict

def solution(genres, plays):
    answer = []
    max_played_genre_dict = defaultdict(lambda: 0)  # defaultdict(int)와 같다.
    genre_play_dict = defaultdict(lambda: [])  # defaultdict(list)와 같다.

    for unique_num, [genre, play] in enumerate(zip(genres, plays)):
        max_played_genre_dict[genre] += play
        genre_play_dict[genre].append([unique_num, play])

    for genre, _ in sorted(max_played_genre_dict.items(), key=lambda x: -x[1]):
        for unique_num, _ in sorted(genre_play_dict[genre], key=lambda x: (-x[1], x[0]))[:2]:
            answer.append(unique_num)


    return answer

위의 max_d -> max_played_genre_dict & genre_play_dict -> genre_play_dict으로 변수명을 바꾸었다. 그리고 위의 원래 코드를 보면 이 두가지의 dictionary를 구하기 위해서 for 구문을 두 번 돌아야 하는데 하나로 줄였다. 그리고 defaultdict builtin library를 사용하여 없는 key값을 확인하는 로직을 defaultdict에게 위임하여 코드를 줄여보았다.

default dict 초기값 설정

  • test_default_dict = defaultdict(int) 👉🏻 int형을 value값으로 설정할 것이다.

하지만, 초기값을 내가 원하는 값으로 설정하려면 어떻게 해야 할까? 🤔
👉🏻 int와 같은 인자로 넣어주면 기본 값이 0으로 설정이되는데, 인자를 받지 않는 익명함수의 return 값을 지정함으로써 해결할 수 있다.
Ex) test_default_dict = defaultdict(lambda: 100) 👉🏻 key가 없는 value값을 100으로 초기화

2️⃣ Javascript

function zip(...arrs){
    return [...arrs[0]].map((_, idx) => arrs.map((arr) => arr[idx]))
}

function solution(genres, plays) {
    const answer = [];
    const maxPlayedGenreObj = {};
    const genrePlayObj = {};
    
    zip(genres, plays).forEach(([genre, play], index) => {
        // 장르별 재생수 총합 구하기 👉🏻 1️⃣
        maxPlayedGenreObj[genre] = (maxPlayedGenreObj[genre] || 0) + play
        genrePlayObj[genre] = genrePlayObj[genre] ?? []
        genrePlayObj[genre].push([index, play])
    })
    
    // 재생수 총합 기준으로 내림차순 정렬 👉🏻 1️⃣
    const maxPlayedGenreArr = Object.entries(maxPlayedGenreObj).sort((a, b) => b[1] - a[1])
    
    for (const [genre, play] of maxPlayedGenreArr) {
        // 재생수 -> 내림차순, 고유번호 -> 오름차순 👉🏻 2️⃣ & 3️⃣
        const sortedByPlayUnique = genrePlayObj[genre].sort((a, b) => a[1] === b[1] ? a[0] - b[0] : b[1] - a[1])
        const extractUnique = sortedByPlayUnique.map(([uniqueNum, _]) => uniqueNum)
        // 장르별 최대 곡의 수는 2개 이므로 👉🏻 4️⃣
        const twoMusics = extractUnique.slice(0, 2)
        
        answer.push(...twoMusics)
    }
    
    
    return answer;
}

🔥 마치며

이 문제를 풀어보고 나서 알고리즘 종류를 봤는데 해시였다. 그냥 문제를 풀기위해 생각하다 보니 해시를 쓰게 됐다. 문제를 읽으면서 문제의 이해가 부족한건지 문제가 애매한 건지 사실상 문제를 이해하고 나서는 내가 잘못 이해했다는 생각이 든다. 문제를 더 읽고 이해를 확실하게 한 다음에 들어가 볼 필요가 있는 것 같다. 이와 같은 경우는 코드를 조금만 수정하면 됐지만 코테 시험시 이러면 멘붕.....🥲🥲🥲

profile
step by step
post-custom-banner

0개의 댓글