Skip to main content

collections 모듈

학습 목표

  • Counter로 빈도수를 효율적으로 계산할 수 있다
  • defaultdict로 키 부재 시 기본값을 자동 생성할 수 있다
  • deque로 양방향 큐 연산을 수행할 수 있다
  • 적절한 컨테이너 타입을 선택할 수 있다

왜 중요한가

collections 모듈은 Python 내장 자료구조의 확장판입니다. 기본 dict, list로 구현하면 코드가 복잡해지는 패턴들을 간결하고 효율적으로 처리합니다. 데이터 분석, 텍스트 처리, 큐 구현 등 실무에서 필수적인 도구입니다.

Counter

요소의 빈도수를 세는 딕셔너리의 하위 클래스입니다.
from collections import Counter

# 기본 사용
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
print(count)  # Counter({"apple": 3, "banana": 2, "cherry": 1})

# 문자열의 문자 빈도
char_count = Counter("mississippi")
print(char_count)  # Counter({"s": 4, "i": 4, "p": 2, "m": 1})

# 가장 흔한 요소
print(count.most_common(2))   # [("apple", 3), ("banana", 2)]

# 산술 연산
a = Counter({"a": 3, "b": 1})
b = Counter({"a": 1, "b": 2})
print(a + b)    # Counter({"a": 4, "b": 3})
print(a - b)    # Counter({"a": 2})
print(a & b)    # Counter({"a": 1, "b": 1}) - 교집합 (최솟값)
print(a | b)    # Counter({"a": 3, "b": 2}) - 합집합 (최댓값)

# 요소 반복
print(list(count.elements()))
# ["apple", "apple", "apple", "banana", "banana", "cherry"]

# 업데이트
count.update(["apple", "date"])
print(count["apple"])  # 4
print(count["date"])   # 1

defaultdict

존재하지 않는 키에 접근할 때 자동으로 기본값을 생성합니다.
from collections import defaultdict

# int 기본값 (0)
word_count = defaultdict(int)
for word in ["apple", "banana", "apple", "cherry"]:
    word_count[word] += 1
print(dict(word_count))  # {"apple": 2, "banana": 1, "cherry": 1}

# list 기본값 ([])
grouped = defaultdict(list)
students = [("A반", "김철수"), ("B반", "이영희"), ("A반", "박민수")]
for class_name, student in students:
    grouped[class_name].append(student)
print(dict(grouped))  # {"A반": ["김철수", "박민수"], "B반": ["이영희"]}

# set 기본값 (set())
tags = defaultdict(set)
data = [("doc1", "python"), ("doc1", "ml"), ("doc2", "python")]
for doc, tag in data:
    tags[doc].add(tag)
print(dict(tags))  # {"doc1": {"python", "ml"}, "doc2": {"python"}}

# lambda로 커스텀 기본값
config = defaultdict(lambda: "unknown")
config["name"] = "test"
print(config["name"])      # "test"
print(config["version"])   # "unknown"

일반 dict와의 비교

# 일반 dict - KeyError 처리 필요
word_count = {}
for word in words:
    if word in word_count:
        word_count[word] += 1
    else:
        word_count[word] = 1

# 또는 setdefault 사용
word_count = {}
for word in words:
    word_count.setdefault(word, 0)
    word_count[word] += 1

# defaultdict - 깔끔
word_count = defaultdict(int)
for word in words:
    word_count[word] += 1

deque (Double-Ended Queue)

양쪽 끝에서 O(1)로 추가/삭제가 가능한 이중 연결 리스트 기반 자료구조입니다.
from collections import deque

# 생성
d = deque([1, 2, 3])
d = deque(maxlen=5)  # 최대 크기 제한

# 양쪽 추가/삭제
d = deque([2, 3, 4])
d.appendleft(1)      # [1, 2, 3, 4]
d.append(5)           # [1, 2, 3, 4, 5]
d.popleft()           # [2, 3, 4, 5] - 반환값: 1
d.pop()               # [2, 3, 4]    - 반환값: 5

# 회전
d = deque([1, 2, 3, 4, 5])
d.rotate(2)           # [4, 5, 1, 2, 3] - 오른쪽으로 2칸
d.rotate(-2)          # [1, 2, 3, 4, 5] - 왼쪽으로 2칸

# maxlen을 이용한 슬라이딩 윈도우
window = deque(maxlen=3)
for i in range(5):
    window.append(i)
    print(list(window))
# [0]
# [0, 1]
# [0, 1, 2]
# [1, 2, 3]     <- 0이 자동 제거
# [2, 3, 4]     <- 1이 자동 제거

list vs deque 성능 비교

연산listdeque
끝에 추가 appendO(1)O(1)
앞에 추가 appendleftO(n)O(1)
끝에서 삭제 popO(1)O(1)
앞에서 삭제 popleftO(n)O(1)
인덱스 접근 [i]O(1)O(n)

OrderedDict

Python 3.7 이후 일반 dict도 삽입 순서를 보장하지만, OrderedDict는 순서 관련 추가 기능을 제공합니다.
from collections import OrderedDict

od = OrderedDict()
od["c"] = 3
od["a"] = 1
od["b"] = 2

# 마지막으로 이동
od.move_to_end("c")
print(list(od.keys()))  # ["a", "b", "c"]

# 처음으로 이동
od.move_to_end("c", last=False)
print(list(od.keys()))  # ["c", "a", "b"]

# 순서를 고려한 비교
od1 = OrderedDict([("a", 1), ("b", 2)])
od2 = OrderedDict([("b", 2), ("a", 1)])
print(od1 == od2)  # False (순서가 다르므로)

# 일반 dict는 순서 무시 비교
d1 = {"a": 1, "b": 2}
d2 = {"b": 2, "a": 1}
print(d1 == d2)  # True

AI/ML에서의 활용

from collections import Counter, defaultdict, deque

# 단어 빈도 분석 (NLP)
corpus = "the cat sat on the mat the cat ate the food"
word_freq = Counter(corpus.split())
vocab = [word for word, _ in word_freq.most_common(100)]
print(f"상위 3개: {word_freq.most_common(3)}")
# [("the", 4), ("cat", 2), ("sat", 1)]

# 클래스별 데이터 그룹핑
dataset = [
    {"label": "cat", "path": "img1.jpg"},
    {"label": "dog", "path": "img2.jpg"},
    {"label": "cat", "path": "img3.jpg"},
]
by_label = defaultdict(list)
for item in dataset:
    by_label[item["label"]].append(item["path"])
print(dict(by_label))
# {"cat": ["img1.jpg", "img3.jpg"], "dog": ["img2.jpg"]}

# 이동 평균 (Moving Average) - 학습 손실 스무딩
loss_window = deque(maxlen=10)
for epoch in range(20):
    loss = 1.0 / (epoch + 1)  # 예시 손실값
    loss_window.append(loss)
    avg_loss = sum(loss_window) / len(loss_window)
    if epoch % 5 == 0:
        print(f"Epoch {epoch}: loss={loss:.4f}, avg={avg_loss:.4f}")
여러 딕셔너리를 하나로 합쳐서 검색할 때 사용합니다. 설정 파일에서 기본값 + 환경변수 + 커맨드라인 인자를 계층적으로 관리할 때 유용합니다.
반복적으로 기본값이 필요하면 defaultdict가 깔끔합니다. 한두 번만 필요하면 setdefault()로 충분합니다.

체크리스트

  • Counter로 빈도수를 계산하고 most_common()을 사용할 수 있다
  • defaultdict로 키 부재 시 자동 기본값을 생성할 수 있다
  • deque로 양방향 큐 연산과 슬라이딩 윈도우를 구현할 수 있다

다음 문서