- 문제
스트리밍 사이트에서 장르 별로 가장 많이 재생된 노래를 두 개씩 모아 베스트 앨범을 출시하려 합니다. 노래는 고유 번호로 구분하며, 노래를 수록하는 기준은 다음과 같습니다.
- 속한 노래가 많이 재생된 장르를 먼저 수록합니다.
- 장르 내에서 많이 재생된 노래를 먼저 수록합니다.
- 장르 내에서 재생 횟수가 같은 노래 중에서는 고유 번호가 낮은 노래를 먼저 수록합니다
- 입출력
노래의 장르를 나타내는 문자열 배열 genres와 노래별 재생 횟수를 나타내는 정수 배열 plays가 주어질 때, 베스트 앨범에 들어갈 노래의 고유 번호를 순서대로 return 하도록 solution 함수를 완성하세요.
제한사항
- genres[i]는 고유번호가 i인 노래의 장르입니다.
- plays[i]는 고유번호가 i인 노래가 재생된 횟수입니다.
- genres와 plays의 길이는 같으며, 이는 1 이상 10,000 이하입니다.
- 장르 종류는 100개 미만입니다.
- 장르에 속한 곡이 하나라면, 하나의 곡만 선택합니다.
- 모든 장르는 재생된 횟수가 다릅니다.
- 코드
def solution(genres, plays):
answer = []
dic = {x:[] for x in set(genres)}
for x in zip(genres, plays, range(len(genres))):
dic[x[0]].append([x[1], x[2]])
sort_genre = sorted(list(dic.keys()), key=lambda x: sum(map(lambda y: y[0], dic[x])), reverse=True)
for i in sort_genre:
temp = [x[1] for x in sorted(dic[i], key=lambda x: (x[0], -x[1]), reverse=True)]
answer.extend(temp[:min(len(temp), 2)])
return answer
python의 중고급 skill의 함수들이 많이 들어가있어서 한줄한줄 설명해보겠습니다.
genres와 plays의 list는 다음과 같다고 가정하고 설명하겠습니다.
genres = ['classic', 'pop', 'classic', 'classic', 'pop']
plays = [500, 600, 150, 800, 2500]
dic = {x:[] for x in set(genres)}
print(dic)
# {'classic': [], 'pop': []}
set함수를 통해 중복된 장르를 제거하고 unique한 장르만 남긴다.
먼저 list comprehension에 대한 이해가 필요하다.
위는 set(genres)에 대한 값(['classic', 'pop'])을 x로 받고(여기서 말하는 x는 for x in set(genres) 부분)
최종 {} (dictionary를 의미)안에 담기는 것은 앞에서 받은 x를 key로하고 value에 [](빈 리스트)를 만드는 것이다.
for x in zip(genres, plays, range(len(genres))):
dic[x[0]].append([x[1], x[2]])
print(dic)
# {'pop': [[600, 1], [2500, 4]], 'classic': [[500, 0], [150, 2], [800, 3]]}
zip함수는 말그대로 묶어주는 함수이다. zip함수를 모르면 for문을 반복적으로 쓰거나 index로 접근하거나 좀 더 코드가 복잡해질수 있는 것을 간단하게 만들어준다.
genres 리스트, plays 리스트, range(len(genres))에 대한 값을 index 순차적으로 3개의 list를 한꺼번에 접근이 가능하다.
즉 x로받는 첫번째 값은 ['classic', 500, 0] 두번쨰 값은 ['pop', 600, 1]이다.
마지막 range(len(genres))를 한 이유는 해당 노래에대한 고유번호를 만들기 위함이다.
dic는 현재 key가 장르이기때문에 x[0](장르)로 접근해서 value인 빈리스트에 [x[1](재생수),x[2](고유번호)]를 묶어서 append시켜준다.
sort_genre = sorted(list(dic.keys()), key=lambda x: sum(map(lambda y: y[0], dic[x])), reverse=True)
print(sort_genre)
# ['pop', 'classic']
여기서는 장르별 우선으로 한다고했기 때문에 재생수가 더 높은 장르 순서로 정렬하는 것이다.
sorted(정렬하고 싶은 값들, key= lambda x: x는 앞에 정렬하고 싶은 값들이 오는데 원하는 기준을 여기에 지정해주면 된다.)
현재 dic안의 key값들이 장르이기 때문에 해당 값을 맨 처음에 적어주고(list(dic.keys())
현재 x값은 장르이기 때문에 dic[x]를 접근해서 재생수를 불러와야하기 때문에 map 함수를 다시 이용해준다.
map(lambda y : y[0], dic[x]) : x는 장르이기때문에 해당장르의 value값 [재생수, 고유번호]에서 y[0](즉 재생수)를 map함수를 통해 각각 불러온다. 예를들어서 'pop': [[600, 1], [2500, 4]]의 경우 600, 2500을 map함수를 통해 불러오는 것이다.
불러온 재생수들을 sum함수를 통해 합을 구해주면 된다.
마지막 reverse=True는 내림차순 정렬을 위해 사용해준다.
for i in sort_genre:
temp = [x[1] for x in sorted(dic[i], key=lambda x: (x[0], -x[1]), reverse=True)]
print(temp)
# [4, 1] -> 'pop'
# [3, 0, 2] -> 'classic'
answer.extend(temp[:min(len(temp), 2)])
이제 재생수가 높은 장르를 찾았기 때문에 이번엔 각 장르에서 재생수가 높은 순서 재생수가 같다면 고유번호가 낮은 순서를 찾으면 된다.
위에서 찾은 장르를 통해 각 장르를 불러오고(sorted(dic[i]) 재생수가 높은 순서 (x[0]) 왜 0인지는 위에서 보면 dic의 key값을 장르로 접근하면 value중에서 첫번째 값이 재생수이기 때문이다. 그 다음에 lambda x: 뒤에 (1,2)는 1을 기준으로 정렬하고 1의 값이 같으면 2의 기준으로 정렬하라는 뜻이다. x[1]은 고유번호이기 때문에 고유번호 기준으로로 정렬하는데 고유번호는 낮은순서로 뽑아야하기 때문에 앞에 -를 붙여준다. reverse=True를 뺀다면 (-x[0], x[1])로 해주면 된다.
이제 2개의 값만 뽑아야 하는데 장르별로 1개의 곡이 존재할수도 있기 때문에 min함수를 통해 현재길이와 2를 비교하여 낮은 값을 찾아서 index slicing을 해주면 된다.
마지막으로 append를 하게되면 [[4,1], [0,3]]이렇게 되버린다. 즉 list에 list를 append했기 때문이다. 하나의 list처럼 보이게하려면 extend함수를 쓰면된다.
- 출처
programmers.co.kr/learn/courses/30/parts/12077
'알고리즘' 카테고리의 다른 글
백준 2751번: 수 정렬2 (python, 파이썬) (0) | 2020.10.27 |
---|---|
백준 1806번: 부분합 (python, 파이썬) (0) | 2020.10.24 |
백준 11655번: ROT13 (python, 파이썬) (0) | 2020.10.20 |
백준 1012번: 유기농 배추 (python, 파이썬) (0) | 2020.10.20 |
백준 14500번: 테트로미노 (python, 파이썬) (0) | 2020.10.19 |
댓글