모든 강의를 무료로 볼 수 있어요. 회원가입 없이도 학습 가능합니다.

28강 / 전체 32강

딕셔너리(dict) — 해시 테이블 원리와 실전 활용

9분 읽기 조회 4

해시 테이블 원리로 딕셔너리의 O(1) 조회를 이해하고 빈도 카운팅·그루핑·디스패치 패턴을 마스터한다.

🎯 이 강의에서 배우는 것

딕셔너리는 파이썬에서 가장 중요한 자료구조입니다. 내부적으로 해시 테이블(hash table)로 구현되어 키 조회가 평균 O(1)입니다. 파이썬의 이름공간(namespace), 클래스의 속성, 함수의 지역 변수 저장에도 딕셔너리가 사용됩니다. 딕셔너리를 완전히 이해하면 파이썬 자체를 더 깊이 이해하게 됩니다.

🔑 해시 테이블의 원리 — O(1) 접근의 비밀

# 해시 테이블 동작 원리 (단순화):
# 1. hash("name") → 정수값 (예: 1234567890)
# 2. 버킷 인덱스 = 1234567890 % capacity
# 3. 해당 버킷에 ("name", "홍길동") 저장
# 4. 조회 시: 같은 hash() → 같은 버킷 → 즉시 찾음 (O(1)!)

print(hash("name"))     # 정수값 (실행마다 다름 — 파이썬 해시 랜덤화)
print(hash(42))         # 42 (int는 자기 자신)
print(hash(3.14))       # 322818021289163912 (근사값 기반)
print(hash((1, 2, 3)))  # 2528502973977326415 (불변 튜플 해시 가능)

# 같은 값 == 같은 해시 (해시 함수의 필수 속성)
print(hash("hello") == hash("hello"))   # True (항상)
# 같은 해시 ≠ 같은 값 (해시 충돌)
# 파이썬은 개방 주소법으로 충돌 해결

# 딕셔너리 생성 방법
d1 = {"name": "홍길동", "age": 30}
d2 = dict(name="홍길동", age=30)
d3 = dict([("name", "홍길동"), ("age", 30)])
d4 = dict.fromkeys(["a", "b", "c"], 0)   # {'a': 0, 'b': 0, 'c': 0}
print(d1 == d2 == d3)   # True

# Python 3.7+: 삽입 순서 보장
d = {}
d["c"] = 3
d["a"] = 1
d["b"] = 2
print(list(d.keys()))   # ['c', 'a', 'b'] — 삽입 순서 유지

🔍 딕셔너리 조회 메서드 — KeyError 방지

person = {"name": "홍길동", "age": 30, "city": "서울"}

# d[key]: 없으면 KeyError
print(person["name"])   # 홍길동
try:
    print(person["email"])   # KeyError!
except KeyError as e:
    print(f"없는 키: {e}")

# d.get(key, default): 없으면 default 반환 (기본 None)
print(person.get("email"))          # None
print(person.get("email", "미등록"))  # 미등록

# d.setdefault(key, default): 없으면 default를 설정하고 반환
person.setdefault("email", "noreply@example.com")
print(person["email"])   # noreply@example.com (새로 추가됨)
person.setdefault("name", "이름없음")
print(person["name"])    # 홍길동 (이미 있으면 변경 안 함)

# 딕셔너리 뷰 객체 (view objects)
keys = person.keys()    # dict_keys (동적 뷰)
values = person.values()
items = person.items()

person["score"] = 100   # 딕셔너리 수정
print(list(keys))   # ['name', 'age', 'city', 'email', 'score'] ← 자동 반영!

# 순회
for key in person:
    print(key)   # 키 순회

for key, value in person.items():
    print(f"{key}: {value}")

✏️ 딕셔너리 수정 메서드

d = {"a": 1, "b": 2, "c": 3}

# 추가/수정
d["d"] = 4          # 새 키 추가
d["a"] = 10         # 기존 키 값 변경

# update: 딕셔너리 또는 키-값 쌍으로 일괄 수정
d.update({"e": 5, "f": 6})    # 딕셔너리로
d.update(g=7, h=8)            # 키워드 인수로
d.update([("i", 9)])          # (키, 값) 쌍 리스트로

# 삭제
del d["a"]                    # KeyError 가능
removed = d.pop("b")          # 삭제 후 값 반환
removed_safe = d.pop("z", 0)  # 없으면 0 반환 (KeyError 없음)
last_key, last_val = d.popitem()   # 마지막 삽입 항목 삭제 반환
d.clear()                     # 전체 삭제

# 딕셔너리 합치기
d1 = {"a": 1, "b": 2}
d2 = {"b": 20, "c": 3}

# 방법 1: {**d1, **d2} (Python 3.5+) — d2가 우선
merged1 = {**d1, **d2}
print(merged1)   # {'a': 1, 'b': 20, 'c': 3}

# 방법 2: | 연산자 (Python 3.9+)
merged2 = d1 | d2
print(merged2)   # {'a': 1, 'b': 20, 'c': 3}

# 방법 3: |= 인-플레이스 업데이트
d1 |= d2
print(d1)   # {'a': 1, 'b': 20, 'c': 3}

🏭 딕셔너리 실전 패턴

from collections import defaultdict

# 패턴 1: 빈도 카운팅
text = "the quick brown fox jumps over the lazy dog"
word_count = {}
for word in text.split():
    word_count[word] = word_count.get(word, 0) + 1
print(word_count)

# defaultdict를 사용하면 더 간결
word_count2 = defaultdict(int)
for word in text.split():
    word_count2[word] += 1

# 패턴 2: 그루핑
students = [
    {"name": "홍길동", "grade": "A"},
    {"name": "이순신", "grade": "B"},
    {"name": "강감찬", "grade": "A"},
    {"name": "유관순", "grade": "B"},
]

groups = defaultdict(list)
for s in students:
    groups[s["grade"]].append(s["name"])
print(dict(groups))   # {'A': ['홍길동', '강감찬'], 'B': ['이순신', '유관순']}

# 패턴 3: 디스패치 테이블 (if-elif 대체)
def handle_add(a, b): return a + b
def handle_sub(a, b): return a - b
def handle_mul(a, b): return a * b
def handle_div(a, b): return a / b

operations = {
    "+": handle_add,
    "-": handle_sub,
    "*": handle_mul,
    "/": handle_div,
}

def calculate(a, op, b):
    if op not in operations:
        raise ValueError(f"알 수 없는 연산자: {op}")
    return operations[op](a, b)

print(calculate(10, "+", 3))   # 13
print(calculate(10, "*", 3))   # 30

# 패턴 4: 캐싱
_cache = {}
def get_user(user_id):
    if user_id not in _cache:
        # 실제로는 DB 쿼리
        _cache[user_id] = {"id": user_id, "name": f"User {user_id}"}
    return _cache[user_id]

⚠️ 자주 하는 실수

  • 순회 중 딕셔너리 수정: for k in d: del d[k]는 RuntimeError: dictionary changed size during iteration. 수정이 필요하면 for k in list(d.keys()): ...처럼 뷰를 리스트로 변환 후 순회하세요.
  • d.keys(), d.values()를 리스트로 착각: 이들은 뷰 객체로 원본 딕셔너리와 연동됩니다. 독립적인 리스트가 필요하면 list(d.keys())로 변환하세요.
  • 같은 해시 != 같은 키: 해시 충돌 가능성 때문에 딕셔너리는 hash(k1) == hash(k2)이더라도 k1 == k2를 다시 확인합니다. 커스텀 클래스를 키로 쓸 때 __hash__와 __eq__를 일관되게 구현해야 합니다.

📝 정리 및 다음 강의 예고

  • 해시 테이블: hash(key) → 버킷 인덱스 → 평균 O(1) 조회. 해시 가능 객체만 키로 사용.
  • d.get(key, default): KeyError 없이 안전 조회. d.setdefault(k, v): 없을 때만 설정.
  • Python 3.7+부터 삽입 순서 보장. Python 3.9+에서 |와 |=로 병합.
  • 빈도 카운팅, 그루핑, 디스패치 테이블 — 딕셔너리가 활약하는 핵심 패턴.

다음 강의: 셋(set)과 frozenset — 해시 기반 집합의 수학적 연산과 실전 활용, 중복 제거의 올바른 방법을 배웁니다.

관련 주제

  • 해시 테이블 원리
  • 해시 충돌과 개방주소법
  • get·setdefault 메서드
  • 딕셔너리 병합(|)
  • 삽입순서 보장
  • 빈도카운팅·디스패치 패턴
  • 개발·프로그래밍
  • 개발·프로그래밍 강의
  • 파이썬 기초 40강 — 처음 배우는 프로그래밍
  • 무료강의
  • 무료 온라인 강의
  • NUGUNA
  • 누구나

📚 시리즈 전체 공유

파이썬 기초 40강 — 처음 배우는 프로그래밍

이 강의가 속한 시리즈는 총 32강, 모두 무료입니다. 처음부터 배우려는 동료에게 시리즈 전체를 알려 주세요.

댓글

0/1000

불러오는 중...