1. 프로그래머스 완주하지 못한 선수 문제
마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.
조건
- 마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
- completion의 길이는 participant의 길이보다 1 작습니다.
- 참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
- 참가자 중에는 동명이인이 있을 수 있습니다.
participant | completion | return |
---|---|---|
[“leo”, “kiki”, “eden”] | [“eden”, “kiki”] | “leo” |
[“marina”, “josipa”, “nikola”, “vinko”, “filipa”] | [“josipa”, “filipa”, “marina”, “nikola”] | “vinko” |
[“mislav”, “stanko”, “mislav”, “ana”] | [“stanko”, “ana”, “mislav”] | “mislav” |
모든 테스트를 통과한 풀이답안
def solution(participant, completion):
d = {}
for x in participant:
d[x] = d.get(x,0) + 1
for y in completion:
d[y] = d[y] - 1
answer = [a for a,b in d.items() if b > 0]
answer = answer[0]
return answer
이름에 중복이 있는 문제이므로 for문을 돌리는 방법도 있겠지만 시간도 많이 걸리고 효율성이 떨어진다.
이와 같은 상황에 사용할 수 있는 자료구조는 해시이다.
해시 : key(키)와 value(값)를 저장하는 데이터 구조
- 장점
- 데이터 저장 또는 읽는 속도가 빠름
- key에 대한 데이터가 중복확인이 쉬움
- 단점
- 저장공간이 많이 필요
- 주소가 동일할 경우 충돌가능성 있음
여기서 시간 복잡도가 가장 적은 해시라는 자료구조를 이용하는게 Point였다.
우선 딕셔너리가 해시 구조중에 하나이므로 d={}를 생성했다.
그리고 participant(마라톤 참여자)의 이름을 키로 두고 value값을 get합수를 사용해 이름이 나오면 1씩 더하게끔 했다.
그럼 같은 이름이 중복되어 나와도 value값은 2이상으로 표현되어 구별할 수 있다.
그리고 completion(완주자)의 이름이 키로 나오면 그 키(완주자 이름)의 value값을 1을 빼면 보통은 값이 0이 된다.
여기서 상쇄되지 않은 이름이 완주하지 못한 사람으로 나옴을 알 수 있다.
collections 모듈 사용한 예시
def solution(participant, completion):
answer = collections.Counter(participant) - collections.Counter(completion)
return list(answer.keys())[0]
다른 사람의 코드 예시를 보다가 모듈을 적절히 활용하면 더 쉽게 접근이 가능하다고 느껴 들고왔다.
흥미로웠던 점은 Counter 즉 딕셔너리끼리 뺄셈이 되어 상쇄가 된다는 점이었다.
같은 키에 값이 2와 2라면 빼면 0이니 딕셔너리에서 없어지고 2와 1이라면 1이 남아 딕셔너리에도 키와 값이 존재했다.
collections 모듈 : 파이썬의 범용 내장 컨테이너 dict, list, set 및 tuple에 대한 대안을 제공하는 특수 컨테이너 데이터형을 구현
여러가지 객체가 있지만 특히 Counter가 매우 유용하게 쓰일 것 같다.
Counter 객체 : 편리하게 빠르게 개수를 세어주는 도구
# 목록에 있는 단어의 빈도를 집계합니다
cnt = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
cnt[word] += 1
cnt
Counter는 word가 나오면 value값으로 1을 더해주고 같은 word가 나오면 반복해 1을 더해준다.
Counter({'blue': 3, 'red': 2, 'green': 1})
다음과 같이 결과가 나온다.
딕셔너리의 형태라는 것을 알 수 있다.
※ 특히 자연어처리시 빈도수를 체크할 때 유용하게 사용가능하다.
import re
words = re.findall(r'\w+', open('hamlet.txt').read().lower())
Counter(words).most_common(10)
[('the', 1143), ('and', 966), ('to', 762), ('of', 669), ('i', 631),
('you', 554), ('a', 546), ('my', 514), ('hamlet', 471), ('in', 451)]
여기서 가장 흔한 단어 10개를 Counter를 사용하면 쉽게 찾아줍니다.