본문 바로가기

collections 모듈

'collections' 모듈은 특수한 컨테이너 자료형을 제공합니다. 'deque'는 양방향 큐로 양쪽 끝에서 O(1)로 요소를 추가/제거하고, 'Counter'는 요소의 개수를 세어 딕셔너리로 반환합니다.

1. collections 모듈

Python의 collections 모듈은 다양한 형태의 컨테이너를 제공하여 데이터 조작을 쉽고 효율적으로 할 수 있게 합니다. 특히 deque와 Counter는 알고리즘 문제 풀이에서도 많이 사용됩니다. 여기서는 자주 사용하는 것들을 차례로 살펴보고, 이전에는 유용했으나 이제는 잘 쓰지 않는 OrderedDict도 함께 다루겠습니다.

1.1 deque

deque는 양쪽 끝에서 요소를 추가하거나 제거할 수 있는 스레드-안전한 양방향 큐입니다. 리스트와 비슷하지만 양쪽 끝에서의 데이터 처리가 훨씬 효율적입니다.

from collections import deque

d = deque()
d.append('a')  # 오른쪽 끝에 추가
d.appendleft('b')  # 왼쪽 끝에 추가
d.pop()  # 오른쪽 끝 요소 제거
d.popleft()  # 왼쪽 끝 요소 제거
  • Max Length: deque는 maxlen 매개변수를 설정하여 고정된 크기를 가질 수 있습니다. 이 크기를 초과하여 요소를 추가하면, 반대쪽 끝의 요소가 자동으로 제거됩니다.
    from collections import deque
    
    d = deque(maxlen=3)
    d.extend([1, 2, 3])  # deque([1, 2, 3])
    d.append(4)          # deque([2, 3, 4]), 1이 밀려납니다.
    print(d)
    
  • 고성능: deque는 양 끝에 요소를 추가하거나 제거할 때 리스트보다 훨씬 빠르며, 메모리 사용이 효율적입니다.
  • 스레드-안전(Thread-Safe): deque는 멀티스레딩 환경에서 안전하게 사용할 수 있습니다. 여러 스레드에서 동시에 deque에 접근하더라도 문제가 발생하지 않습니다.
  • 양방향 처리: 데이터를 양쪽 끝에서 추가하거나 제거할 수 있어, 큐나 스택과 같은 다양한 데이터 구조를 쉽게 구현할 수 있습니다.

1.2 Counter

Counter는 요소의 발생 횟수를 세는 데 사용되는 특수한 종류의 딕셔너리입니다. 이는 각 요소를 키로 하고 그 요소의 개수를 값으로 하는 컬렉션입니다.

from collections import Counter

c = Counter('hello world')
print(c)  # 각 요소의 개수를 세어 딕셔너리로 반환
  • 데이터 업데이트: update() 메서드를 사용하여 카운터에 새로운 데이터를 추가할 수 있습니다. 이는 기존의 카운트에 추가됩니다.
    c.update('hello')
    print(c)  # 'h'와 'e', 'l', 'o'의 카운트가 증가됨
    
  • 상위 N개 요소 얻기: most_common() 메서드를 사용하여 가장 흔한 요소들을 얻을 수 있습니다.
    print(c.most_common(3))  # 가장 흔한 세 요소 반환
    
  • 수학적 연산: Counter 객체는 덧셈, 뺄셈 등의 수학적 연산도 지원합니다.
    c1 = Counter('hello')
    c2 = Counter('world')
    print(c1 + c2)  # 두 카운터 합치기
    print(c1 - c2)  # c1에서 c2 제거
    

1.3 defaultdict

defaultdict는 없는 키에 접근했을 때 KeyError를 내는 대신 기본값을 자동으로 만들어주는 딕셔너리입니다. 생성할 때 기본값을 만들 함수를 넘겨줍니다.

from collections import defaultdict

# 값이 리스트인 딕셔너리
d = defaultdict(list)
d['fruits'].append('apple')  # 키가 없어도 빈 리스트를 만들고 시작합니다.
d['fruits'].append('banana')
print(d)

일반 딕셔너리로 같은 일을 하려면 매번 키의 존재를 확인해야 합니다.

d = {}
for word in ['apple', 'avocado', 'banana', 'blueberry']:
    key = word[0]
    if key not in d:      # 이 확인이 매번 필요합니다.
        d[key] = []
    d[key].append(word)
print(d)
from collections import defaultdict

d = defaultdict(list)
for word in ['apple', 'avocado', 'banana', 'blueberry']:
    d[word[0]].append(word)  # 확인 없이 바로 추가합니다.
print(dict(d))

개수를 셀 때는 defaultdict(int)를 사용합니다. int()는 0을 반환하기 때문입니다.

from collections import defaultdict

count = defaultdict(int)
for c in 'hello world':
    count[c] += 1
print(dict(count))

1.4 namedtuple

namedtuple은 인덱스뿐만 아니라 이름으로도 값을 꺼낼 수 있는 튜플을 만들어줍니다. x[0], x[1]처럼 숫자로 접근하면 그 값이 무엇인지 알기 어려운데, 이를 해결해줍니다.

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])

p = Point(10, 20)
print(p.x, p.y)  # 이름으로 접근
print(p[0])      # 튜플처럼 인덱스로도 접근
print(p)         # Point(x=10, y=20)

튜플이므로 값을 바꿀 수 없습니다. 값을 바꿔야 한다면 _replace로 새 객체를 만듭니다.

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p = Point(10, 20)

p2 = p._replace(x=100)  # 새 객체가 만들어집니다.
print(p, p2)

1.5 OrderedDict

OrderedDict는 키의 삽입 순서를 기억하는 딕셔너리입니다.

Python 3.7 버전부터는 딕셔너리가 삽입순서를 기억하게 되었습니다. 따라서 이제 이 모듈은 잘 사용되지 않습니다.

from collections import OrderedDict

od = OrderedDict()
od['a'] = 1
od['b'] = 2
print(od)  # 출력: OrderedDict([('a', 1), ('b', 2)])

1.6 ChainMap

ChainMap은 여러 딕셔너리를 하나로 묶어서 사용할 수 있게 해주는 클래스입니다. 여러 딕셔너리를 하나로 묶어서 사용할 때, 첫 번째 딕셔너리부터 차례대로 키를 찾아나가는 방식으로 동작합니다. 앞에 있는 값에 우선순위를 주고 싶다면 이 자료형을 사용하세요.

from collections import ChainMap

dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
cm = ChainMap(dict1, dict2)
print(cm['b'])  # dict1의 'b' 키를 찾아서 2 출력