코딩테스트 고득점 Kit 해시 마지막문제 베스트앨범
- 문제 이해
- 스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려고 함
- 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같다
- 속한 노래가 많이 재생된 장르를 먼저 수록
- 장르 내에서 가장 많이 재생된 노래를 먼저 수록
- 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록
- 노래의 장르를 나타내는 문자열 배열 genres와 노래 별로 재생 횟수를 나타내는 정수 배열 plays가 주어질 때,
-
배스트 앨범에 들어갈 노래의 고유 번호를 순서대로 return 하도록 하는 함수 작성
- 제한사항
- genres[i]는 고유번호가 i인 노래의 장르
- plays[i]는 고유 번호 길이가 i인 노래가 재생된 횟수
- genres와 plays의 길이는 같으며 , 1이상 만 이하
- 장르 종류는 100개 미만
-
장류에 속한 곡이 하나라면, 하나의 곡만 선택
- 입출력 예시
- genres / plays / return
- [“classic”,”pop”,”classic”,”classic”,”pop”] / [500,600,150,800,2500], [4,1,3,0]
- classic 장르는 1,450회 재생되었으며, classic 노래는 다음과 같다.
- 고유번호 3: 800회 재생
- 고유번호 0: 500회 재생
-
고유번호 2: 150회 재생
- pop장르는 3100회 재생되었으며, pop 노래는 다음과 같다
- 고유번호 4: 2500회 재생
-
고유번호 1: 600회 재생
- 따라서 pop 장르의 [4,1]번 노래를 먼저,classic장르의 [3,0]번 노래를 그 다음에 수록한다.
문제해결전략
- zip 함수로 장르별, plays를 key,value로 묶은 뒤
- 재생된 장르 순서로 정렬한다.(정렬 하지 않고, for문을 한번씩 돌아서, 가장 큰거, 그 다음 큰거를 찾아줄 수 도 있다.)
- 이 때, 초기 genres의 인덱스(고유번호)를 기억하고 있어야 하므로, value안에 튜플로 인덱스와 plays를 함께 저장한다.
- 문제를 풀어본다.
해보니 안되었던 것
d = {genres: plays for genres, plays in zip(genres, plays)}
바로 해시를 작성하면, 키가 중복되므로 문제가 발생한다.- 딕셔너리 안에 딕셔너리를 저장하는건 어떨까 ?, 반복문을 돌면서 넣기가 쉽지가 않더라.
- 그래서 key의 value로 리스트를 넣은뒤 + 연산을 통해 반복적으로 리스트에 idx와 plays[i]를 저장해주었다.
- 파이썬 딕셔너리는 순서를 관리하지 않는다. -> 컬렉션 모듈 OrderedDict를 쓴다고 하는데, 이런 방법은 문제랑 벗어날 방법일 것이다.
- 파이썬 for in을 쓸 때, in 다음에 나오는 tuple이 2개라면, x,y와 같은 원소 2개로 해당 값을 받아올 수 있다.
- 코드를 짤 순 있겠으나, 너무 비효율적인 방법을 고집하고 있어서 다른 분들 코드를 참고했다.
- 정리하자면 나는
for x, y in zip(genres, plays):
d[x] = d.get(x, arr) + [(idx, y)]
idx += 1
print(d)
- 이런 식으로 dictionary를 구성하였다.
{'classic': [(0, 500), (2, 150), (3, 800)], 'pop': [(1, 600), (4, 2500)]}
풀이 참조
- 다른 분은 이와 같은 구조로 딕셔너리를 구성하셨다.
{
'장르이름': {
't': 전체 재생 시간
'l': [노래정보리스트, {'v': 재생시간, 'i': 고유번호}]
}
}
- key와 value에 대해서 주어진 값으로 연결하기 보다, 이런 식으로 딕셔너리 안에서 이중 딕셔너리를 정의해줄 때,
- 임의로 key를 내가 부여한다면 value를 가져오기 편하겠다.
{
'classic':
{'t': 1450,
'l': [{'v': 500, 'i': 0}, {'v': 150, 'i': 2}, {'v': 800, 'i': 3}]
},
'pop':
{'t': 3100,
'l': [{'v': 600, 'i': 1}, {'v': 2500, 'i': 4}]
}
}
- 이후 또 배울 점은 딕셔너리의 정렬을 위해 리스트의 형태로 바꿔줬다는 점이다.
- 이후 sorted 함수를 통해서 정렬을 해준다. t -> 시간이 가장 큰 것을 기준으로 오름차순으로
- 이후 각 장르별로 재생 횟수를 정렬하고, answer를 구한다.
def solution(genres, plays):
'''
각 노래의 grouping을 진행할 변수를 선언해준다.
다음과 같은 구조를 가지게 된다.
{
'장르이름': {
't': 전체 재생 시간
'l': [노래정보리스트, {'v': 재생시간, 'i': 고유번호}]
}
}
'''
d = {}
# 노래 갯수 만큼 loop를 돌며 grouping을 진행한다.
for i in range(len(genres)):
# 장르에 해당하는 key가 없으면 새로 만들어준다.
if not genres[i] in d:
d[genres[i]] = {'t': 0, 'l': []}
d[genres[i]]['l'].append({'v': plays[i], 'i': i})
d[genres[i]]['t'] += plays[i]
print(d)
# 정렬을 위해 list 형태로 바꿔준다.
# 앞으로의 연산이나, answer에는 장르 이름이 필요없기에 문제없다.
d = list(d.values())
d = sorted(d, key=lambda k: k['t'], reverse=True)
answer = []
# 각 장르별 재생 횟수를 정렬하고, answer를 구한다.
for i in range(len(d)):
v = d[i]['l']
v = sorted(v, key=lambda k: k['v'], reverse=True)
for j in range(2 if len(v) >= 2 else len(v)):
answer.append(v[j]['i'])
return answer
- 내가 원하던 방향의 깔끔한 코드다.
- 이 코드를 노트에 적고 정리하자.
참고자료 [프로그래머스]https://programmers.co.kr/learn/challenges [참고 블로그]https://blog.rajephon.dev/2018/10/14/programmers-solution-hash-best-album/