해시 문제 분석(파이썬 버전 시작)

류한선·2025년 7월 2일

실기연습-2

목록 보기
72/95

문제 코드 다시 (풀이할 코드):

class Key:
    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return self.val == other.val

    def __hash__(self):
        return hash(self.val)

k1 = Key(10)
k2 = Key(10)
k3 = Key(20)

d = {}
d[k1] = 'apple'
d[k2] = 'banana'
d[k3] = 'carrot'

print(len(d))
print(d[k1])

코드 한 줄씩 해부 및 실행 과정


1. class Key:

  • 역할: Key라는 사용자 정의 클래스 선언
  • 왜 이 위치? 클래스 선언은 객체를 만들기 전에 반드시 먼저 있어야 함
  • 실행 시: 이 줄은 클래스 타입 Key를 메모리에 등록한다.
  • 예시: 이 시점에 Key라는 이름으로 새로운 자료형이 만들어진 상태

2~4. def __init__(self, val): ... self.val = val

  • 역할: 객체 초기화 메서드. 생성 시 val 값을 멤버변수 self.val에 저장
  • 왜 이 위치? 클래스 내부에서 객체 초기화를 정의해야 생성 시 값 할당 가능
  • 실행 시: Key(10) 하면 이 메서드가 호출되고 val=10self.val에 저장됨
  • 예시: k1 = Key(10)k1.val == 10

5~7. def __eq__(self, other): return self.val == other.val

  • 역할: 두 Key 객체가 == 비교될 때 멤버 val 값으로 비교
  • 왜? 딕셔너리는 해시 충돌 뒤 키 비교를 하는데, ==가 기본 객체 ID 비교가 아니라 이 val 값 비교가 되도록 해야 동일 키로 인식
  • 실행 시: k1 == k2 호출하면 k1.val == k2.val 값 비교 결과 반환
  • 예시: k1.val=10, k2.val=10k1 == k2True

8~10. def __hash__(self): return hash(self.val)

  • 역할: Key 객체의 해시값을 멤버변수 val의 해시값으로 반환
  • 왜? 딕셔너리 키는 hash()로 위치 탐색 → val 값이 같으면 같은 해시값을 가지도록 해야 키 충돌 가능
  • 실행 시: hash(k1)hash(10)과 동일한 값을 반환
  • 예시: hash(k1) == hash(k2)True (둘 다 10으로부터 해시 계산)

11~13. k1 = Key(10), k2 = Key(10), k3 = Key(20)

  • 역할: Key 객체 세 개 생성, 각각 val 10, 10, 20

  • 왜? 예제를 통해 같은 값이지만 다른 객체인 경우와 다른 값을 가진 객체를 비교

  • 실행 시: k1.val=10, k2.val=10, k3.val=20

  • 시각화:

    k1 -> Key(val=10)
    k2 -> Key(val=10)
    k3 -> Key(val=20)

14. d = {}

  • 역할: 빈 딕셔너리 생성
  • 왜? 해시 맵 테스트용 컨테이너
  • 실행 시: 메모리에 빈 해시 테이블 구조 준비
  • 상태: d = {} (빈 상태)

15. d[k1] = 'apple'

  • 역할: 딕셔너리에 k1을 키로, 'apple'을 값으로 저장

  • 동작 원리:

    1. hash(k1) 호출 → hash(10)
    2. 해시 테이블에서 hash(10) 위치 찾음
    3. 해당 위치에 k1 키와 'apple' 값 저장
  • 상태:

    d = {
      k1: 'apple'
    }

16. d[k2] = 'banana'

  • 역할: 딕셔너리에 k2를 키로, 'banana'를 값으로 저장

  • 핵심 동작:

    1. hash(k2) 호출 → hash(10) (k1과 동일)
    2. 해시 테이블 위치 탐색 시 이미 k1이 있음 → 키가 같은지 비교
    3. k2 == k1 비교 → k2.val == k1.val (10 == 10) → True
    4. 키가 동일하므로, 기존 키 k1의 값 'apple''banana'로 덮어씀
  • 상태:

    d = {
      k1: 'banana'  # 기존 apple 덮어쓰기됨
    }
  • 중요한 점: 키는 k1k2가 같은 것으로 간주돼 하나만 존재


17. d[k3] = 'carrot'

  • 역할: 딕셔너리에 k3 키, 'carrot' 값 저장

  • 동작:

    1. hash(k3) 호출 → hash(20)
    2. 이 해시 위치는 비어있으므로 새 엔트리 생성
  • 상태:

    d = {
      k1: 'banana',
      k3: 'carrot'
    }

18. print(len(d))

  • 역할: 딕셔너리 d의 요소 개수 출력
  • 현재 요소는 k1k3 두 개
  • 출력값: 2

19. print(d[k1])

  • 역할: 키 k1에 대응하는 값을 출력
  • 현재 k1 키의 값은 'banana' (15번 줄에서 덮어쓰기됨)
  • 출력값: 'banana'

최종 답안

  • len(d)2
  • d[k1]'banana'

즉, 정답은 B번


왜 이렇게 동작하는가? (핵심 요약)

  • k1k2는 별개 객체지만, __hash__()__eq__() 오버라이드 덕분에 동일한 키로 취급됨
  • 그래서 d[k2] = 'banana'd[k1]의 값을 덮어쓰는 것과 같음
  • 결과적으로 딕셔너리는 두 개의 키(k1/k2 하나 묶임, k3 하나)만 가짐

좋아, 이번에도 풀이 없이 문제만 주는 건 없다!
이제부터 위 코드 한 줄 한 줄을 완전히 디버깅하듯 해설해줄게. 이 문제의 핵심은 **mutable 객체(리스트)를 dict의 키로 쓰는 것이 가능한가?**에 대한 트랩이야.


🔁 문제 다시 보기

a = [1, 2, 3]
b = [1, 2, 3]

d = {}
d[a] = "alpha"
d[b] = "beta"

print(len(d))

🧩 코드 흐름 및 해설


1. a = [1, 2, 3]

  • 역할: 리스트 [1, 2, 3]를 생성하고 변수 a에 할당

  • 현재 상태:

    a → [1, 2, 3]

2. b = [1, 2, 3]

  • 역할: 동일한 값을 가지는 새로운 리스트를 만들고 변수 b에 할당

  • ab는 값은 같지만, 서로 다른 객체

  • 현재 상태:

    b → [1, 2, 3]
    a is b → False
    a == b → True

==는 내부 값 비교
is는 객체 주소(동일 객체) 비교


3. d = {}

  • 역할: 빈 딕셔너리 생성

  • 이 딕셔너리는 key-value 쌍을 저장하는 해시 테이블

  • 상태:

    d = {}

4. d[a] = "alpha"

  • 역할: 리스트 a를 key로 사용해 "alpha" 저장 시도
  • 문제 발생 포인트: 리스트는 mutable(가변) 객체라서 __hash__()가 정의되어 있지 않음
>>> hash([1,2,3])
TypeError: unhashable type: 'list'
  • 딕셔너리의 key는 반드시 hashable해야 한다!
  • 리스트는 가변 객체 → 해시값이 고정될 수 없으므로 dict의 키로 사용 불가

💥 여기서 에러 발생!

d[a] = "alpha"

에서 **TypeError: unhashable type: 'list'**가 발생하면서 프로그램 종료됨


❌ 그 다음 줄 d[b] = "beta"는 실행되지 않음

print(len(d))도 도달하지 않음


✅ 정답: D. 에러 발생


📌 핵심 정리: dict의 key 조건

조건설명
불변(immutable)값을 변경할 수 없어야 함 (예: int, str, tuple)
해시 가능 (hashable)__hash__()가 정의돼 있고, __eq__() 비교와 일관성이 있어야 함
리스트(list), 딕셔너리(dict), 집합(set) 등은 mutable → 해시 불가능

🔁 참고: 바꾸면 동작하는 코드

아래처럼 tuple을 key로 쓰면 동작함 (immutable이라서):

a = (1, 2, 3)
b = (1, 2, 3)

d = {}
d[a] = "alpha"
d[b] = "beta"

print(len(d))  # 출력: 1

→ 이 경우 a == b이고 hash(a) == hash(b)이므로 같은 키로 간주되어 덮어쓰기됨


아주 좋아! 이 코드는 해시와 불변(immutable) 객체의 핵심 동작 원리를 묻는 대표적인 예제야.
지금부터 각 줄을 디버깅 수준으로 해부해서 왜 len(d)가 1이 되는지 완벽하게 설명해줄게.


🔁 코드 전체 다시 보기

a = (1, 2, 3)
b = (1, 2, 3)

d = {}
d[a] = "alpha"
d[b] = "beta"

print(len(d))  # 출력: 1

🔍 전체 요점부터 요약

  • a == b → 값 비교 결과 True
  • hash(a) == hash(b) → 튜플의 값이 같으면 해시도 같음
  • 그러므로 b를 key로 넣으면 a 키와 동일하다고 판단되어 덮어쓰기 발생
  • 결국 딕셔너리에는 키 하나만 존재

🧠 한 줄씩 흐름 해설


🔹 a = (1, 2, 3)

  • 역할: 튜플 (1, 2, 3)을 변수 a에 저장

  • 이유: 딕셔너리 키로 쓸 수 있도록 불변(immutable) 객체 사용

  • 상태:

    a → (1, 2, 3)

🔹 b = (1, 2, 3)

  • 역할: 같은 값을 가진 튜플을 새로 하나 더 생성해서 b에 저장

  • 주의: ab서로 다른 객체지만 같은 값을 가짐

  • 확인:

    a == b → True      # 값 비교 (튜플은 요소 하나씩 비교함)
    a is b → False     # 객체 주소 비교 (다름)
  • 시각화:

    메모리
    ┌──────┐      ┌──────┐
    │  a   │      │  b   │
    │(1,2,3)│      │(1,2,3)│
    └──────┘      └──────┘

🔹 d = {}

  • 역할: 빈 딕셔너리 생성

  • 상태:

    d = {}

🔹 d[a] = "alpha"

  • 동작:

    1. hash(a) 호출 → hash((1, 2, 3))
    2. 특정 해시값 위치에 키 a와 값 "alpha" 저장
  • 상태:

    d = {
      (1, 2, 3): "alpha"
    }

🔹 d[b] = "beta"

이 부분이 핵심이다. 지금부터 이 줄에서 정확히 어떤 일이 벌어지는가를 디버깅처럼 따라가 보자.

👣 1단계: 해시값 비교

  • 먼저 b를 딕셔너리 키로 넣으려면 해시값을 구함
  • hash(b) = hash((1, 2, 3))
    → 이 값은 a와 동일

👣 2단계: 해시 충돌 후 키 비교

  • 파이썬 딕셔너리는 같은 해시값이 있으면 == 비교로 같은 키인지 판단
  • b == aTrue
    → 키가 같다고 판단됨

✅ 결론

  • 파이썬은 이미 a라는 키가 존재하고,

  • b동일한 키로 인식되기 때문에

  • "alpha""beta"덮어쓰기한다.

  • 최종 상태:

    d = {
      (1, 2, 3): "beta"
    }

🔹 print(len(d))

  • 딕셔너리에 존재하는 키 개수 출력
  • 덮어쓰기가 발생했기 때문에 키는 1개
  • 출력 결과: 1

✅ 최종 정답: 1


💡 핵심 요약 (암기 팁 포함)

개념설명
불변 자료형 (tuple, str, int)hash() 가능, 딕셔너리 key로 사용 가능
값이 같으면==hash() 모두 같으면 key로 덮어쓰기됨
파이썬 딕셔너리 key 비교 방식1️⃣ hash(key) → 같으면 → 2️⃣ key1 == key2 비교까지 수행

✨ 보너스 테스트 코드

a = (1, 2, 3)
b = (1, 2, 3)

print(hash(a))  # ex) 2528502973977326415
print(hash(b))  # 동일한 값 나옴
print(a == b)   # True
print(a is b)   # False (다른 객체)

d = {}
d[a] = "apple"
d[b] = "banana"

print(d)        # {(1, 2, 3): 'banana'}
print(len(d))   # 1

0개의 댓글