여느 때와 다름없이 알고리즘 문제를 풀고 있었다. (문제는 [프로그래머스 - 여행경로] 참고)
문제 초반부에서 만약 경로가 [a, b], [b, c], [c, a] 처럼 주어졌다고 하면, a -> b라는 경로들의 방향을 담고 싶다.
하지만 a가 str형이라 인덱스(정수 고정) - 값처럼 사용할 수 없어 리스트를 못 사용했다.
그래서 키(문자 가능) - 값(문자 가능)처럼 dictionary를 사용해서 문제를 풀었다.
나는 아래 코드처럼 사용했다.
tickets = [["ICN", "SFO"], ["ICN", "ATL"], ["SFO", "ATL"], ["ATL", "ICN"], ["ATL","SFO"]]
t_dict = dict()
for i in range(len(tickets)):
if tickets[i][0] not in t_dict:
t_dict[tickets[i][0]] = [tickets[i][1]]
else:
t_dict[tickets[i][0]] += [tickets[i][1]]
print(t_dict)
# 출력 결과
# {'ICN': ['SFO', 'ATL'], 'SFO': ['ATL'], 'ATL': ['ICN', 'SFO']}
다 풀고나서 다른 사람들 코드를 보고있는데 대부분 defaultdict를 사용하는 것 아니겠나.
그래서 사용법도 쉽기도하고 알아두면 좋을 것 같아서 defaultdict에 대해 정리해보려고 한다.
우선 거두절미하고 바로 위 코드를 아래 코드로 바꿀 수 있다.
from collections import defaultdict
tickets = [["ICN", "SFO"], ["ICN", "ATL"], ["SFO", "ATL"], ["ATL", "ICN"], ["ATL","SFO"]]
t_dict = defaultdict(list)
for start, end in tickets:
t_dict[start].append(end)
print(t_dict)
# 출력 결과
# defaultdict(<class 'list'>, {'ICN': ['SFO', 'ATL'], 'SFO': ['ATL'], 'ATL': ['ICN', 'SFO']})
import하고 for문이 획기적으로 줄어버렸는데, 출력 결과는 형식빼고 똑같다.
파이썬 공식 문서에 따르면 defaultdict는 아래처럼 사용할 수 있다고 한다.
class collections.defaultdict(default_factory=None, /[, ...])
그리고 설명에 따르면 다음과 같은 말들이 적혀있다.
1. 새 사전과 유사한 개체를 반환한다. ( = 유사 딕셔너리)
2. defaultdict는 기본 제공 dict 클래스의 하위 클래스이다.( = dict()랑 결은 같다.)
3. 하나의 메서드를 재정의하고 쓰기 가능한 인스턴스 변수를 하나 추가한다.(위 식의 default_factory의 역할)
4. 나머지 기능은 dict 클래스와 동일하다.( = 나는 2번이랑 같은 말이라고 느꼈다.)
요약하자면 딕셔너리랑 유사한데 3번을 중요하게 보면 된다.
default_factory 함수로 받는 인자를 설정(int, list, set 등)하여 간단하게 dict 형태로 만들 수 있다는 것.(그룹화, 카운트 등)
여기서 제일 중요한 작동 원리가 default_factory()에 대한 건데 설명은 아래와 같다.
생성자의 첫 번째 인자가 있으면 인자로, 없으면 None으로 초기화된다.
밑줄을 예시로 들면 defaultdict에 키 A를 넣었는데 키 A가 defaultdict에 없으면 값을 None으로 초기화시켜 준다는 것
결론은 우리가 if문쓰고 하는 짓을 알아서 해준다는 것..!!(대박)
이제 아래 예제를 보면서 확인해보자
1. defaultdict(int)의 경우
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1), ('yellow', 1)]
s_int = defaultdict(int) # default_factory() -> int() 호출
for k, v in s:
s_int[k] += 1 # Counting
# 참고) sorted(XXX.items()) -> 출력 보기 편하라고 해놓은 것
print(sorted(s_int.items()))
# 출력 결과
[('blue', 2), ('red', 1), ('yellow', 3)]
보다시피 default_factory를 int로 설정하였을 때, defaultdict를 Counting 용도로 유용하게 사용할 수 있다.
(참고 - 위에서는 (k, v)에서 v는 사용하고 있지않다. 아래에서 list, set도 같이 보여줄려고 같은 s로 사용한 것)
만약 defaultdict를 사용하지 않았다면 없으면 정의하고 있으면 추가하는 if문이 있어야 할 것이다.
2. defaultdict(list)의 경우
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1), ('yellow', 1)]
s_list = defaultdict(list) # default_factory() -> list() 호출
for k, v in s:
s_list[k].append(v) # Grouping List // list는 append() 사용
print(sorted(s_list.items()))
# 출력 결과
[('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3, 1])]
list를 사용할 경우, Key-Value 쌍을 리스트의 딕셔너리로 그룹화 할 수 있다.
보면 'yellow' : 1 쌍을 일부로 중복시켜 놨는데, 구분되어 잘 들어간 것을 확인할 수 있다.
3. defaultdict(set)의 경우
from collections import defaultdict
s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1), ('yellow', 1)]
s_set = defaultdict(set) # default_factory() -> set() 호출
for k, v in s:
s_set[k].add(v) # Grouping Set // set은 add() 사용
print(sorted(s_set.items()))
# 출력 결과
[('blue', {2, 4}), ('red', {1}), ('yellow', {1, 3})]
set의 경우, Key-Value 쌍을 집합 형식의 딕셔너리로 그룹화 할 수 있다.
리스트와는 다르게 여기선 'yellow' : 1 쌍이 집합 형식이라서 중복 처리가 된 것도 확인할 수 있다.
이렇게 defaultdict를 살펴볼 수 있었는데 사실 공식 문서나 이론이 길었지 사용법은 쉽다.
결론은 쉽게 dict 형태로 만들 수 있어서 좋다는 것(심지어 collections에서 가져와서 더 익숙)