dictionary를 정렬하는 방법?

1. 문제

 

개발자가 사용하는 언어와 언어 선호도를 입력하면 그에 맞는 직업군을 추천해주는 알고리즘을 개발하려고 한다

 

아래 표는 5개 직업군 별로 많이 사용하는 5개 언어에 직업군 언어 점수를 부여한 표이다.

 

그림1. 직업군에 선호 언어 점수 부여한 예시 표

 

예를 들면 SQL의 SI 직업군 언어 점수는 3점이지만 CONTENTS 직업군 언어 점수는 2점입니다.

 

SQL의 HARDWARE, PORTAL, GAME 직업군 언어 점수는 0점입니다.

 

직업군 언어 점수를 정리한 문자열 배열 table, 개발자가 사용하는 언어를 담은 문자열 배열 languages, 언어 선호도를 담은 정수 배열 preference가 매개변수로 주어집니다.

 

개발자가 사용하는 언어의 언어선호도 * 직업군 언어 점수의 총합이 가장 높은 직업군을 return하도록 solution 함수를 완성해주세요.

 

총합이 같은 직업군이 여러 개일 경우, 이름이 사전 순으로 가장 빠른 직업군을 return해주세요.

 

 

2. 제한사항

 

table의 길이=5

 

table의 원소는 “직업군 5점언어 4점언어 3점언어 2점언어 1점언어”형식의 문자열

 

table은 모든 테스트케이스에서 동일

 

직업군, 5점언어, 4점언어, 3점언어, 2점언어, 1점언어는 공백으로 구분

 

$1 \leq languages의 길이 \leq 9$

 

languages의 원소는 “java”, “javascript”, “c”, “c++”, “c#”, “sql”, “python”, “kotlin”, “php”중 1개 이상으로 이루어짐

 

languages의 원소는 중복되지 않습니다

 

preference의 길이 = languages의 길이

 

$1 \leq preference의 원소 \leq 10$

 

preference의 i번째 원소는 languages의 i번째 원소의 언어 선호도입니다.

 

return할 문자열은 “si”, “contents”, “hardware”, “portal”, “game” 중 하나

 

 

3. 입출력 예시

 

그림2. 입출력 예시

 

그림3. 입출력 예시1 설명
그림4. 입출력 예시2 설명

4. 나의 풀이

 

 

dictionary를 이용해서 언어 별로 5,4,3,2,1점을 뽑아낼 수 있도록 만든다

 

table에서 문자열이 순서가 정해져있으니 split을 이용해서 5,4,3,2,1 배분

 

def solution(table, languages, preference):
    
    answer = ''
    
    total_dict = {}
    
    for score_str in table:
        
        score_dict = {}
        
        job,five,four,three,two,one = score_str.split()
        
        score_dict[five] = 5
        
        score_dict[four] = 4
        
        score_dict[three] = 3
        
        score_dict[two] = 2
        
        score_dict[one] = 1

 

다음 현재 job의 total 점수를 계산해야하는데

 

total_score = 0

for languages,prefer in zip(languages,preference):
    
    try:
        total_score += prefer * score_dict[language]
        
    except:
        pass

total_dict[job] = total_score

 

languages와 preference는 길이가 같고 대응하는 원소가 해당 언어의 점수니까 zip을 이용해서 한번의 for문으로 돌아

 

score_dict에 없는 언어는 해당 job에서 0점이니까 try~except문을 이용

 

language를 score_dict에 넣어봐서 점수가 존재한다면 prefer랑 곱해서 total_score에 더해줌

 

만약 score_dict에 없으면 0을 곱해야하므로 total_score에 0점을 더함

 

이는 결국 score_dict에 존재하지 않으면 except 문으로 옮겨가는데 0점을 더하는 것은 굳이 더하지 않아도 되니까 pass로 처리

 

total_score를 계산하면 total_dict에 해당 job의 total_score를 넣어줘

 

max_score = 0

for job,score in total_dict.items():
    
    if max_score < score:
        
        max_score = score
        
        answer = job
    
    elif max_score == score:
        
        if answer > job:
            
            answer = job
    
    else:
        
        pass

return answer

 

이제 max score인 job을 찾아야하는데 score 값이 동일하면 사전순으로 빠른 job이 답이 되어야하므로

 

먼저 max_score=0으로 둔 다음 total_dict에서 job과 score를 하나씩 뽑아

 

total_dict.items()를 이용하면 key,value로 하나씩 뽑을 수 있다

 

현재 max_score와 비교해서 score가 더 크면 그 순간 score는 최댓값이므로 max_score에 score를 넣어주고 answer에 job을 넣어

 

근데 현재 max_score와 score가 동일하다면…

 

사전 순으로 빠른 단어가 answer에 들어와야하므로

 

이전 max_score에서 넣은 answer와 현재 max_score의 job을 비교해서

 

answer>job이면 answer에 job을 넣는다

 

그 외의 경우는 pass로 넘겨준다. for문을 전부 돌면 answer를 return

 

 

5. 다른 풀이

 

가장 좋아요를 많이 받은 풀이를 살펴보면

 

def solution(table, languages, preference):
    
    score = {}
    
    for t in table:
        
        for lang,pref in zip(languages,preference):
            
            if lang in t.split():
                
                score[t.split()[0]] = score.get(t.split()[0],0)+(6-t.split().index(lang))*pref
                
    return sorted(score.items(),key=lambda item:[-item[1],item[0]])[0][0]

 

table에서 하나씩 뽑고 zip을 이용해서 languages랑 preference를 같이 뽑은건 나도 했으니까… 너도 잘했어

 

나보다 더 깔끔하다면… score_dict를 굳이 안만들고 바로 total_score를 계산했네

 

t에는 '직업군 5점언어 4점언어 3점언어 2점언어 1점언어' 이렇게 문자열로 되어 있으니까

 

t.split() = [직업군,5점언어,4점언어,3점언어,2점언어,1점언어] 이렇게 되어서 lang가 t.split()안에 존재하면 점수를 계산할거고 존재하지 않으면 0점이니까 점수를 계산할 필요가 없어

 

---------------------------------------------------------------------------------------------------------------------------

 

score.get(t.split()[0],0)을 사용했는데

 

score라는 dict에 t.split()[0](==직업군)이라는 key를 넣어서 그 직업군에 맞는 score인 value를 내준다

 

그런데 t.split()[0] (==직업군)이라는 key가 존재하지않으면 점수 계산이 안된거니까 0을 내줌

 

get함수를 이용하면 나처럼 try~except를 안써도 됨

 

t = "SI JAVA JAVASCRIPT SQL PYTHON C#"

 

언어 선호도 점수가 PYTHON,C+,SQL = 7,5,5라고 해보면

 

(6-t.split().index(lang)를 하면 lang라는 언어의 t.split()에 존재하는 index를 구해서…

 

나는 사전을 따로 만들어서 점수를 추출했는데 이 사람은 규칙을 이용했네. 이런 규칙을 알아내는것도 중요하다

 

t.split()이 [job,5점,4점,3점,2점,1점]으로 구성되어가지고

 

만약 lang이 4점언어라면 index가 2여서 6에서 빼면 실제로 4점이 나오거든…

 

예를 들어서 t.split() = [SI,JAVA,JAVASCRIPT,SQL,PYTHON,C#]

 

현재 lang = PYTHON이고 pref = 7인데 현재 SCORE[SI]=0일거임

 

PYTHON의 index는 4이고 (6-t.split().index(lang)=2로 실제로 2점언어인데 계산한 점수도 2점으로 일치하고

 

이 점수에 prefer를 곱하면 해당 job의 해당 언어에 대한 score가 나오는거임

 

SI의 PYTHON에 대한 score는 2*7=14임

 

이걸 다시 score[t.split()[0]]에 저장을 해두면 SCORE[SI]=14가 저장되어 있음

 

이제 다른 언어 C+이 나오면 c+은 t.split() = [SI,JAVA,JAVASCRIPT,SQL,PYTHON,C#]에 존재하지 않으니까 pass

 

다시 다른 언어 SQL이 나오면  t.split() = [SI,JAVA,JAVASCRIPT,SQL,PYTHON,C#]에 존재하니까 점수를 계산하는데

 

score.get(t.split()[0],0) = score.get(SI,0) = 14

 

(6-t.split().index(lang)=6-3=3으로 실제 3점언어이고 pref=5여서 score=15이고 total_score는

 

14+15=29가 score[t.split()[0]]=29로 저장이 된다

 

---------------------------------------------------------------------------------------------------------------------------

 

마지막 score에서 최댓값 뽑을때 sorted(score.items(), key = lambda item:[-item[1],item[0]])[0][0]을 썼는데

 

이거에 대해서 자세히 파헤쳐보면

 

먼저 total_dict.items()하면 (key,value) 쌍의 tuple을 가지는 dict_items라는 list 비슷한 친구를 반환함

 

print(total_dict.items())

dict_items([('SI',29),('CONTENTS',36),('HARDWARE',41),('PORTAL',21),('GAME',25)])

 

 

 

기본적으로 sorted함수는 iterable한 데이터를 오름차순으로 정렬해서 리스트로 반환함

 

print(sorted(total_dict.items()))

[('CONTENTS',36),('GAME',25),('HARDWARE',41),('PORTAL',21),('SI',29)]

 

tuple은 앞에 있는 원소를 기준으로 크기 비교하니까 job이 사전에서 빠른 순서로 오름차순 정렬된 모습

 

sorted에 key인자에 lambda 함수를 이용해서 key를 기준으로 정렬할지 value를 기준으로 정렬할지 결정할 수 있음

 

print(sorted(total_dict.items(),key=lambda item:item[0]))
print(sorted(total_dict.items(),key=lambda item:item[1]))

[('CONTENTS',36),('GAME',25),('HARDWARE',41),('PORTAL',21),('SI',29)]
[('PORTAL',21),('GAME',25),('SI',29),('CONTENTS',36),('HARDWARE',41)]

 

sorted(total_dict.items(), key=lambda item:item[0])을 보면

 

total_dict.items()의 원소 하나가 item으로 지정되고

 

여기서 total_dict.items()에는 (key,value)를 원소로 가지는 리스트이므로 item[0]은 job인 key를 의미함

 

그래서 job을 기준으로 오름차순 정렬해주고

 

sorted(total_dict.items(),key=lambda item:item[1])은 value를 기준으로 정렬함

 

key함수에 key=lambda item:-item[1]로 -를 붙이면 내림차순 정렬함

 

print(sorted(total_dict.items(),key=lambda item:item[1]))
print(sorted(total_dict.items(),key=lambda item:-item[1]))

[('PORTAL',21),('GAME',25),('SI',29),('CONTENTS',36),('HARDWARE',41)]
[('HARDWARE',41),('CONTENTS',36),('SI',29),('GAME',25),('PORTAL',21)]

 

lambda 함수 안에 리스트를 넣어서 다중조건으로 정렬할수도 있음

 

key=lambda item:[-item[1],item[0]]하면 value를 기준으로 내림차순 정렬을 함

 

그런데 서로 같은 value가 있으면 key를 기준으로 오름차순 정렬을 함

 

print(sorted(total_dict.items(),key=lambda item:-item[1]))
print(sorted(total_dict.items(),key=lambda item:[-item[1],item[0]]))

[('SI',55),('PORTAL',55),('CONTENTS',53),('GAME',22),('HARDWARE',19)]
[('PORTAL',55),('SI',55),('CONTENTS',53),('GAME',22),('HARDWARE',19)]

 

위 그림을 보면 -item[1]로 정렬한것과 [-item[1],item[0]]로 정렬한 것이 차이가 있음

 

-item[1],item[0]로 정렬하는 것은 item[1]이 동일하다면 item[0]를 비교해서 item[0]를 오름차순으로 정렬함

 

-item[1]로 정렬한것과는 다르게

 

portal과 si는 55로 동일하니까 portal 다음이 si이므로 portal,55 >> si,55로 정렬된 모습

 

그러면 이제 sorted에 의해 리스트로 반환되어 있는데 원하는 값은 0번째 원소의 tuple에서 0번째 원소인 job을 추출해야하므로

 

sorted(total_dict.items(),key=lambda item:[-item[1],item[0])[0][0]을 하면 나같이 for문으로 비교 안해도 한줄로 가능함

 

-------------------------------------------------------------------------------------------------------------------------------

 

6. 되돌아보기

 

sorted함수를 이용해서 dictionary를 정렬하는 방법 완벽하게 정리했으니 이제 할 수 있겠지?

사실 for문을 이용해서 max값을 추출하는 나의 방법도 비효율적일수 있지만 고전적이면서 기본이라 중요하다

 

다음 dictionary의 get함수를 이용하면 value를 바로 추출할 수 있다

 

물론 try~except로 key-value 다룰수 있지만..

 

dict.get([key],default_value)를 하면 dict에 key를 넣어서 value를 추출하는데 dict에 key가 존재하지 않으면 default_value로 지정한 값을 내준다

 

마지막으로 사전을 만들어서 추출한 나와 규칙을 이용해서 한줄로 점수를 추출한 (6-t.split().index(lang)) 이것도 주목한다면 좋을듯

 

 

 

TAGS.

Comments