좋아요! 해시 함수에 대해 잘 이해하고 있는지 확인해볼 수 있도록 다양한 스타일의 문제를 준비할게요.
문제 난이도는 정보처리기사 실기 시험 수준 + 실무 트랩 요소를 일부 섞어서 출제하겠습니다.
다음 중 해시 함수의 핵심 목적에 해당하지 않는 것은 무엇인가?
A. 입력값을 고정된 크기의 출력으로 변환하기
B. 데이터 검색 속도를 높이기 위한 인덱싱 구조를 제공하기
C. 데이터를 암호화하여 보안을 강화하기
D. 충돌(collision)이 절대로 발생하지 않도록 처리하기
| 선택지 | 설명 |
|---|---|
| A | 맞음. 해시 함수는 일반적으로 어떤 길이의 입력도 고정된 길이의 출력값(해시값)으로 변환함. 예: SHA-256 → 항상 256bit |
| B | 맞음. 해시 테이블, 해시 맵 등은 검색을 빠르게 하기 위한 자료구조로 해시 함수가 핵심 |
| C | 틀림. 절반 맞고 절반 틀림. 해시 함수는 비가역적이지만, 암호화 알고리즘과는 목적이 다름. 보안 목적의 암호화는 복호화가 전제됨. |
| D | 정답. 해시 함수는 수학적으로 충돌(collision)을 완전히 방지할 수 없음. 해시값은 유한하고 입력은 무한하므로 충돌은 이론적으로 필연적임. 다만 좋은 해시 함수는 충돌 확률을 최소화함. |
다음 코드에서 해시 함수의 동작 결과로 알맞은 출력은?
class Fruit:
def __init__(self, name):
self.name = name
def __hash__(self):
return hash(self.name)
def __eq__(self, other):
return self.name == other.name
a = Fruit("apple")
b = Fruit("apple")
c = Fruit("banana")
fruit_set = {a, b, c}
print(len(fruit_set))
A. 1
B. 2
C. 3
D. 에러 발생
a와 b는 서로 다른 객체지만 name이 같음 → 해시값도 같고, __eq__ 결과도 Trueset은 동일한 해시값 + __eq__ True → 중복으로 판단a와 b는 중복으로 처리되어 1개로 인식됨c는 완전히 다른 값이므로 별도로 추가됨최종적으로 fruit_set = {Fruit("apple"), Fruit("banana")} → 크기 2
다음 설명 중, 해시 충돌(Collision)에 대한 설명으로 옳은 것을 모두 고르시오. (복수 정답 가능)
A. 해시 충돌이 발생하면 데이터를 저장할 수 없다.
B. 해시 충돌은 다른 입력값이 같은 해시값을 가질 때 발생한다.
C. 체이닝(chaining)은 충돌을 해결하는 방법 중 하나이다.
D. 해시 충돌을 완전히 방지할 수 있는 해시 함수도 존재한다.
다음 코드에서 출력 결과는?
class CustomKey:
def __init__(self, id):
self.id = id
key1 = CustomKey(10)
key2 = CustomKey(10)
d = {}
d[key1] = "first"
d[key2] = "second"
print(len(d))
A. 1
B. 2
C. 에러 발생
D. 무작위 결과
__eq__와 __hash__를 오버라이딩하지 않았음key1 == key2는 False, hash(key1) != hash(key2)"first", "second" 두 개가 각각 다른 키로 들어감 → 길이 2| 개념 | 요약 |
|---|---|
| 해시 함수 | 입력 → 고정된 길이 출력, 빠른 검색용 |
| 충돌(Collision) | 서로 다른 입력 → 같은 해시값, 이론상 피할 수 없음 |
| 동일 객체로 인식 조건 | __hash__ 동일 + __eq__ True |
| 딕셔너리/셋의 키 조건 | 해시 가능 객체 (__hash__() 필수) |
| 충돌 해결 방식 | 체이닝, 오픈 어드레싱 등 |
좋아, 이번엔 조금 더 실전적인 문제를 줄게. 자료구조, 충돌, 해시테이블 구현, 해시 함수 보안 특성까지 다양하게 다루면서, 헷갈릴 수 있는 트랩들도 포함했어.
총 4문제로 구성하고, 각 문제는 실기 스타일 + 디버깅 해설을 포함할게.
다음 코드의 출력 결과를 예측하시오:
class Key:
def __init__(self, value):
self.value = value
def __hash__(self):
return 42
def __eq__(self, other):
return self.value == other.value
a = Key(1)
b = Key(1)
c = Key(2)
d = {}
d[a] = "apple"
d[b] = "banana"
d[c] = "carrot"
print(len(d))
A. 1
B. 2
C. 3
D. 에러 발생
__hash__()는 모든 객체에 대해 항상 42를 리턴 → 해시 충돌 발생a와 b는 해시값이 같고, __eq__도 True → b는 a를 덮어씀c는 해시는 같지만 __eq__는 False → 다른 키로 인정{
Key(1): "banana", # b가 덮어씀
Key(2): "carrot"
}
👉 결과적으로 길이 = 2
다음 중 보안 해시 함수(Cryptographic Hash Function)에 반드시 요구되는 특성이 아닌 것은?
A. 충돌 저항성(Collision Resistance)
B. 역상 저항성(Pre-image Resistance)
C. 빠른 계산 속도
D. 입력의 아주 작은 변화가 출력 전체를 바꾸는 성질
| 조건 | 설명 |
|---|---|
| A (필수) | 두 입력값이 같은 해시를 가지는 경우가 쉽게 발생하면 안 됨 |
| B (필수) | 출력값을 보고 입력값을 유추하기 어려워야 함 |
| C (틀림) | 보안 해시는 오히려 속도를 조절하여 느리게 만드는 경우도 있음 (예: bcrypt, scrypt) |
| D (필수) | avalanche effect (눈사태 효과)라고 불리며 보안 해시의 핵심 특성 |
Java나 Python에서 해시 테이블의 내부 구조 설명으로 옳지 않은 것은?
A. 해시 테이블은 내부적으로 배열을 사용하여 저장 위치를 정한다.
B. Java 8 이후에는 충돌이 심한 해시 버킷을 트리 구조로 전환할 수 있다.
C. Python의 딕셔너리는 오픈 어드레싱(open addressing) 방식을 사용한다.
D. 체이닝 방식은 충돌된 항목들을 연결리스트나 트리로 보관한다.
| 설명 | 정오 |
|---|---|
| A ✅ | Java, Python 모두 내부적으로 배열을 사용함 |
| B ✅ | Java 8 이후 HashMap은 충돌 많은 버킷을 Tree로 전환해 성능 향상 |
| C ❌ | Python dict는 체이닝 방식이 아니라, **Open Addressing (probe 방식)**도 아닌 “compact array + versioned hash” 방식 사용 |
| D ✅ | 체이닝은 LinkedList, Tree 구조로 충돌을 모아 보관하는 방식 |
다음 코드 실행 시 발생할 수 있는 문제를 고르시오.
class MyKey:
def __init__(self, id):
self.id = id
def __eq__(self, other):
return self.id == other.id
k1 = MyKey(1)
k2 = MyKey(1)
d = {}
d[k1] = 'hello'
print(d[k2])
A. KeyError 발생
B. 정상적으로 'hello' 출력
C. AttributeError 발생
D. TypeError 발생
__eq__만 오버라이딩했고 __hash__()는 안 함k1과 k2는 값은 같아도 해시값은 다름 → 딕셔너리에서는 서로 다른 키d[k2]는 존재하지 않음 → KeyError👉 딕셔너리 키로 쓰기 위해서는 반드시 __hash__와 __eq__를 둘 다 정의해야 함.
| 개념 | 기억할 점 |
|---|---|
| 해시 충돌 | __hash__가 같고, __eq__까지 같으면 중복으로 판단 |
| 해시테이블 구조 | Java는 체이닝 + 트리로 전환 (Java 8~), Python은 배열 기반 |
| 보안 해시 | 느릴 수도 있음! (암호 해시는 속도보다 안전성) |
| 사용자 정의 객체를 키로 쓸 때 | __hash__와 __eq__를 함께 오버라이드해야 동작함 |
아주 좋은 질문이야!
문제 4번에서 KeyError가 발생한 이유는 다음과 같아:
__eq__()는 정의되어 있어서 두 객체의 값을 비교할 수 있어__hash__()를 정의하지 않았기 때문에MyKey(1)과 MyKey(1)은 동일한 값이지만 서로 다른 해시값을 가짐__hash__()를 추가해주자아래처럼 코드를 수정하면 해결돼:
class MyKey:
def __init__(self, id):
self.id = id
def __eq__(self, other):
return self.id == other.id
def __hash__(self):
return hash(self.id)
k1 = MyKey(1)
k2 = MyKey(1)
d = {}
d[k1] = 'hello'
print(d[k2]) # ✅ 정상 출력: 'hello'
파이썬에서 딕셔너리의 키 비교는 이렇게 작동해:
__hash__() 값이 같은가?__eq__()로 비교하여 진짜 같은 값인가?# __hash__ 없음 → 두 객체는 hash 값이 다름
hash(k1) != hash(k2)
따라서 딕셔너리는 k2를 기존 키로 인식하지 못하고,
print(d[k2])는 KeyError 발생
def __hash__(self):
return hash(self.id)
self.id가 같으면 __hash__() 결과도 같음hash(1) → 1의 해시값은 항상 일정함→ 따라서 k1과 k2는 같은 해시 + 같은 값
→ 딕셔너리는 이 둘을 동일한 키로 판단
결과적으로 d[k2]는 d[k1]의 값을 가져옴
✅ 출력: hello
| 키 객체 | id 값 | hash 값 | eq |
|---|---|---|---|
| k1 | 1 | hash(1) | k1 == k2 → True |
| k2 | 1 | hash(1) | k2 == k1 → True |
→ 딕셔너리에서 동일한 키로 인식됨
사용자 정의 객체를 딕셔너리의 키로 쓰려면
반드시 __eq__()와 __hash__()를 쌍으로 정의해야
"동일 값 → 동일 키"로 제대로 작동해.
좋아! 이번엔 “해시 함수와 관련된 다양한 실전 트랩” 유형 문제를 출제해줄게.
특히 실무에서 많이 실수하는 것들, 정보처리기사 실기에서 함정으로 낼 법한 것들을 중심으로 준비했어.
다음 코드에서 출력 결과는?
class Box:
def __init__(self, items):
self.items = items
def __hash__(self):
return hash(tuple(self.items))
def __eq__(self, other):
return self.items == other.items
b1 = Box([1, 2])
b2 = Box([1, 2])
d = {b1: "first"}
b2.items.append(3)
print(d[b2])
A. "first" 출력됨
B. KeyError 발생
C. TypeError 발생
D. 에러 없이 "None" 출력
tuple(self.items)을 기반으로 함self.items는 mutable list임hash(tuple([1, 2])) → hash(tuple([1, 2, 3]))으로 변경)b1과 b2는 원래는 __hash__, __eq__ 모두 같았음b2.items.append(3) 하면서 → 해시값이 바뀜b2는 이제 딕셔너리가 찾을 수 없는 키가 되어버림KeyError 발생“mutable한 값을 해시 함수의 기준으로 삼으면 안 된다.”
💡 해시값은 딕셔너리 구조 내에서 **불변성(immutable)**을 가정하므로,
값이 바뀌면 참조가 깨짐.
다음 중 해시 가능한(= 딕셔너리 키로 사용 가능한) 자료형은 모두 고르시오. (복수 정답)
A. list
B. tuple
C. set
D. frozenset
E. int
| 자료형 | 해시 가능? | 이유 |
|---|---|---|
A. list | ❌ | mutable (값이 바뀌면 해시값도 바뀔 수 있어) |
B. tuple | ✅ | immutable, 내부 값이 모두 해시 가능해야 함 |
C. set | ❌ | mutable |
D. frozenset | ✅ | 불변형 set → 해시 가능 |
E. int | ✅ | 기본 자료형, 당연히 해시 가능 (hash(5) = 5) |
다음 중 해시 충돌(Collision)이 발생했을 때 사용되는 오픈 어드레싱(Open Addressing) 기법이 아닌 것은?
A. 선형 탐사 (Linear Probing)
B. 이차 탐사 (Quadratic Probing)
C. 체이닝 (Chaining)
D. 이중 해싱 (Double Hashing)
즉, 체이닝은 테이블 외부의 구조를 활용하는 방식이기 때문에
오픈 어드레싱에 속하지 않음
Java의 HashMap에서, 다음 중 hashCode()만 오버라이딩하고 equals()를 오버라이딩하지 않았을 때 발생할 수 있는 문제는?
A. 같은 값인데도 다른 객체로 인식된다
B. 충돌을 전혀 감지하지 못한다
C. 객체가 Set에서 중복 제거되지 않는다
D. 정상 동작한다
hashCode()만 오버라이딩하면 같은 해시값은 갖더라도 equals() 비교가 안 됨HashSet, HashMap에서 중복을 제거하려면 둘 다 있어야 함equals()와 hashCode()를 쌍으로 오버라이드해야 함| 문제 유형 | 핵심 포인트 |
|---|---|
| 값 변경 트랩 | mutable 객체를 hash 기준으로 쓰면 안 됨 |
| 해시 가능 타입 | list, set ❌ / frozenset, tuple ✅ |
| 충돌 처리 방식 | 체이닝 ≠ 오픈 어드레싱 |
| Java 해시 | hashCode만 있으면 객체 비교 불완전함 |
정확한 방향으로 가고 있어! 아주 잘 짚었고, 내가 조금 더 정확하고 예제로 디버깅하듯이 정리해줄게.
너가 질문한 내용을 정리하면 이렇게 요약돼:
❓ 딕셔너리 키에서 "mutable vs immutable"은 왜 중요해?
❓ 그리고 리스트(list)처럼 값이 바뀌는 걸 해시 함수에 쓰면 왜 안 돼?
딕셔너리(혹은 set)는 내부적으로 해시값을 기준으로 키를 저장하기 때문이야.
딕셔너리는 키를 저장할 때 다음을 수행해:
hash(key) → 메모리 주소 계산 → 해당 버킷 위치에 저장
💥 그런데, 그 키의 값이 바뀌면 해시값도 바뀜
→ 더 이상 딕셔너리가 그 키를 못 찾게 돼!
→ 결국 KeyError 발생하거나 값이 유실되는 일이 생김
class Box:
def __init__(self, items):
self.items = items
def __hash__(self):
return hash(tuple(self.items)) # 리스트 → 튜플로 바꿔 해시
def __eq__(self, other):
return self.items == other.items
b1 = Box([1, 2])
b2 = Box([1, 2])
d = {b1: "first"} # ✅ 여기에 b1을 키로 등록
b2.items.append(3) # ❌ b2 내부 리스트를 바꿔버림
print(d[b2]) # 💥 KeyError 발생!
b2는 원래 items = [1, 2]였기 때문에 hash(tuple([1, 2]))와 동일했음.append(3) 해버리면 items = [1, 2, 3] → hash 값이 바뀜b2의 해시가 바뀌어서 찾지 못함class SafeBox:
def __init__(self, items):
self.items = tuple(items) # 리스트를 튜플로 바꿔버림 (불변!)
def __hash__(self):
return hash(self.items)
def __eq__(self, other):
return self.items == other.items
a = SafeBox([1, 2])
b = SafeBox([1, 2])
d = {a: "hello"}
print(d[b]) # ✅ 'hello' 출력됨, 안전하게 작동함
items는 처음부터 tuple로 변환되었기 때문에 절대 바뀌지 않음| 구분 | 예시 | 해시 가능? | 이유 |
|---|---|---|---|
list | [1, 2, 3] | ❌ | mutable (값 변경 가능 → 해시값 깨짐) |
set | {1, 2} | ❌ | mutable |
tuple | (1, 2, 3) | ✅ | immutable |
frozenset | frozenset([1, 2]) | ✅ | immutable |
int, str | 5, 'abc' | ✅ | 기본 불변형 |
❗ mutable 객체를 딕셔너리 키로 쓰지 마라 — 값이 바뀌면 딕셔너리가 못 찾는다.
✅ 불변 객체만 키로 써라 (tuple, int, str, frozenset 등)