
https://www.acmicpc.net/problem/11509
*성능 기준 상위권 풀이는 아니다.
import sys
input = sys.stdin.readline
n = int(input())
arrow = {}
ans = 0
for h in map(int, input().split()):
if arrow.get(h, 0):
arrow[h] -= 1
else:
ans += 1
arrow[h-1] = arrow.get(h-1, 0) + 1
print(ans)
arrow라는 이름으로 딕셔너리를 선언해 높이별 화살의 상태를 관리하고, 풍선의 높이 h에 대한 화살의 존재유무에 따라 카운팅을 변경한다. 화살의 총 개수는 ans 변수로 관리한다.
h 높이인 화살이 있을 때 ( if절 )dict.get(x, y) 메소드는 딕셔너리에 키 x가 있는 경우에는 해당 값을 반환하고, 없는 경우에는 default value y를 반환한다. 따라서 arrow[h]가 0이 아닌 경우(=화살이 존재하는 경우) 해당 값을 감산한다.
h 높이인 화살이 없을 때 ( else절 )새로운 화살을 쏘고 ans 를 증가시킨다.
화살를 새로 쏘았는지 여부를 떠나 현재 높이의 풍선을 터뜨렸기 때문에 화살의 높이는 h-1이 된다. 따라서 다음 문장을 통해 arrow[h-1]을 증가시킨다.
arrow[h-1] = arrow.get(h-1, 0) + 1
arrow.get(h-1, 0)을 호출해 대입하는 이유는, arrow[h-1]의 값이 없는 경우 키 에러가 발생하기 때문이다.
defaultdict를 사용한 풀이위 코드를 dict.get() 대신 defaultdict로 작성한 풀이이다.
import sys
from collections import defaultdict
input = sys.stdin.readline
n = int(input())
arrow = defaultdict(int)
ans = 0
for h in map(int, input().split()):
if arrow[h]:
arrow[h] -= 1
else:
ans += 1
arrow[h-1] += 1
print(ans)
두 코드의 채점 결과를 비교하면 dict.get()을 사용한 코드가 미세하게 성능이 더 좋다.
| 방식 | 메모리 | 시간 |
|---|---|---|
dict.get() 사용 | 86320 KB | 508 ms |
defaultdict 사용 | 87332 KB | 520 ms |
defaultdict 인스턴스 선언시에는 기본 팩토리 메소드를 생성하는데, 조회하는 데이터의 성격에 따라 여기서 오버헤드로 작용할 수 있다.
일반 딕셔너리에서 dict.get()을 사용해 없는 키를 조회하는 경우에는 y를 반환하고 딕셔너리의 아이템이 변경되지 않지만, defaultdict에서 없는 키가 조회되는 경우에는 해당 키가 새로 추가되기 때문이다.
일반 딕셔너리 dict_a와 defaultdict로 선언한 dict_b의 동작을 비교해보자.
>>> dict_a = {'A':1, 'B':2, 'C':3}
#dict_a에서 키 'D'를 조회한 후 해당 값이 없으면 0 반환
>>> dict_a.get('D', 0)
0
>>> dict_a
{'A': 1, 'B': 2, 'C': 3}
다음은 defaultdict 객체 dict_b이다.
>>> from collections import defaultdict
# 선언 생략
>>> dict_b
defaultdict(<class 'int'>, {'A': 1, 'B': 2, 'C': 3})
>>> dict_b['D']
0
>>> dict_b
defaultdict(<class 'int'>, {'A': 1, 'B': 2, 'C': 3, 'D': 0})
자료들을 찾아보니 defaultdict의 이점은 높은 가독성과 간결성에 있고, 이를 위해 약간의 성능 저하를 감수하는 것이 주류 의견인 듯 하다. 물론 작업과 자료의 특성에 따라 성능은 달라질 수 있다.
참고 : OrderedDict vs defaultdict vs dict
*python3.6부터는 기본 딕셔너리가collections.OrderedDict와 동일하게 동작한다.
defaultdict 사용은 매우 편리하고 다양하게 활용할 수 있지만, (코딩테스트 채점 결과 등에서는) 사용하지 않은 경우에 비해 메모리 사용량이 높거나 시간이 더 걸릴 때가 많았다.
위의 동작 비교를 바탕으로, 다음과 같은 상황에서는 defaultdict의 오버헤드가 좀 더 클 수 있다.
키의 존재 여부에 따라 값 조작 빈도가 달라지거나, 실제로는 다른 키의 값을 조작하는 경우
defaultdict에서 존재하지 않는 키를 조회하는 경우 조회와 동시에 아이템을 생성하기 때문에 조작 또는 사용하지 않을 값에 대한 키를 조회했을 경우 불필요한 메모리를 차지하게 된다.a를 조회하고 존재 여부에 따라 키 b의 값을 조작한다고 할 때, 키 a를 조회함과 동시에 값이 생성되므로 부적절하다.키의 조건에 따라 다양한 default value 지정이 필요한 경우
dict의 setdefault()를 사용하는 것이 간결할 것 같다.반면, defaultdict는 이럴 때 편리하거나 효율적이다.