좋습니다. 이제 정보처리기사 실기가 3주도 남지 않았기 때문에, 해시(Hash)에 대한 개념부터 실전 문제 트랩까지 단단히 정리해보는 해시 집중 특강을 아래와 같이 진행하겠습니다.
"apple" → 238102데이터를 해시값으로 바꿔주는 함수
대표적 해시 함수 특징:
예:
hash("abc") % 10 = 3 → 테이블의 인덱스 3번 칸에 저장
dict, set은 내부적으로 해시 테이블을 사용함dict[key] = value 할 때 key를 해시함수로 바꿔서 배열처럼 저장서로 다른 key가 같은 해시값을 만들 수도 있음
해결 방법:
nums = [1, 2, 3, 2]
seen = set()
for num in nums:
if num in seen:
print("중복 발견!")
seen.add(num)
from collections import defaultdict
freq = defaultdict(int)
for word in ["apple", "banana", "apple"]:
freq[word] += 1
from collections import Counter
s1 = "listen"
s2 = "silent"
print(Counter(s1) == Counter(s2)) # True
다음 Python 코드에서 출력 결과는?
data = ["a", "b", "a", "c", "b", "d"]
unique = set(data)
print(len(unique))
👉 정답: 1번 (4)
→ set은 중복을 제거: {"a", "b", "c", "d"}
다음 중 해시 자료구조의 충돌(Collision) 처리 방식이 아닌 것은?
👉 정답: 2번
→ 이진 탐색 트리는 별개의 자료구조임 (자바에서는 Java 8 이후 충돌이 많으면 Tree로 바꾸긴 함, 하지만 일반적인 해시 충돌 해결법은 아님)
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 2);
System.out.println(map.get("apple"));
"apple"이라는 문자열은 내부적으로 **hashCode()**에 의해 해시값으로 바뀌어 저장됨| 트랩 | 설명 |
|---|---|
dict.get(key) | key 없을 경우 None 리턴 |
set.remove(x) vs set.discard(x) | remove는 없으면 에러, discard는 없으면 조용히 패스 |
defaultdict(int) | 초기값이 없을 때 자동 0 |
hash()는 값이 같으면 같은 해시 | 단, 충돌도 일어날 수 있음 |
| 해시 키는 변경 불가능한 타입만 가능 | 파이썬: list는 안 됨, tuple은 가능 |
dict, set / 자바에서는 HashMap, HashSet 기반좋아, 그럼 지금부터 정보처리기사 실기 스타일의 해시(Hash) 응용 문제 10문제를 줄게.
모두 실제 시험에 출제될 수 있는 트랩 포인트가 포함되어 있고,
각 문제마다 정답 + 상세 해설 + 실전 트랩 요점을 제공할게.
다음 코드의 출력값은?
data = ["a", "b", "a", "c", "b", "d"]
unique = set(data)
print(len(unique))
set()은 중복을 자동으로 제거한다."a", "b", "c", "d" → 총 4개🔍 트랩: set은 순서도 없고, 중복도 제거되므로
list처럼 생각하면 오답 가능
다음 중 해시의 키로 적절하지 않은 것은?
("a", 1)frozenset([1, 2])["b", 2]"apple"list는 mutable → 키로 쓸 수 없음🔍 트랩:
tuple,frozenset은 키로 가능하지만list,dict는 불가
data = {"a": 1, "b": 2}
print(data.get("c", 0))
출력값은?
KeyErrorNone0"c".get("key", default)는 key가 없을 경우 default값 반환"c"가 없기 때문에 0 반환🔍 트랩:
dict["c"]는 오류 발생하지만,.get()은 안전하게 default 반환
해시 충돌을 방지하거나 줄이기 위한 방법으로 적절하지 않은 것은?
🔍 트랩: 같은 인덱스에 저장하면 충돌을 늘리는 방식이다 (해결이 아니라 악화)
from collections import defaultdict
d = defaultdict(list)
d["a"].append(1)
print(d["b"])
출력 결과는?
KeyError[][0]Nonedefaultdict(list)는 새 키에 접근하면 자동으로 [] 생성🔍 트랩: 일반
dict였다면 KeyError 발생, 하지만defaultdict는 기본값 생성됨
다음 중 자바에서 HashMap의 해시 충돌 처리에 대한 설명으로 옳지 않은 것은?
put() 시 기존 값 교체됨🔍 트랩: 같은 해시값이 아닌 같은 key면 덮어씌워짐
s = set([1, 2, 3])
s.discard(4)
print(s)
{1, 2, 3}{1, 2, 3, 4}None.discard(x)는 값이 없으면 그냥 넘어감.remove(x)는 값이 없으면 에러🔍 트랩: discard와 remove의 차이 반드시 숙지
일반적으로 해시 테이블에서 탐색/삽입의 시간 복잡도는?
arr = ["dog", "god", "cat", "tac", "act"]
groups = {}
for word in arr:
key = "".join(sorted(word))
groups.setdefault(key, []).append(word)
print(len(groups))
"dgo": [dog, god], "act": [cat, tac, act]🔍 트랩:
sorted()후join()을 통해 아나그램 그룹핑 → 많이 나오는 실기 유형!
자바에서 HashMap의 키로 사용되는 객체에 대해 올바른 설명은?
🔍 트랩: hashCode() → 같은 버킷 찾음
→ equals() → 같은 key인지 판단
| 핵심 개념 | 기억할 점 |
|---|---|
| set | 중복 제거, 순서 없음 |
| dict.get() | 안전한 접근 (default 설정 가능) |
| defaultdict | 자동 기본값 생성 |
| 키 타입 | immutable만 가능 (tuple, str, frozenset 등) |
| remove vs discard | remove는 에러, discard는 무시 |
| Java의 HashMap | hashCode + equals 반드시 오버라이딩 |
| 충돌 처리 | 체이닝, 오픈 어드레싱, Java8 이후 Tree |
정확한 지적이야!
지금까지 제공한 문제들은 파이썬 기반 예제 중심이었어.
이유는 다음과 같아:
파이썬이 해시 응용을 간결하게 보여주기에 매우 적합
dict, set, defaultdict 등이 내장되어 있고, 문법이 직관적이야정보처리기사 실기 출제 유형 자체가 특정 언어보다 개념 위주
처음 개념을 잡을 때는 문법보다 흐름이 중요
하지만! 이제 너가 자바와 C언어를 같이 공부 중이라는 걸 알고 있으니까,
Java 버전의 HashMap, HashSet, 그리고 실전 트랩 중심으로 설명해줄게.
| 자료구조 | 설명 |
|---|---|
HashMap<K, V> | 키-값 쌍 저장 |
HashSet<E> | 값만 저장 (중복 불가) |
LinkedHashMap | 입력 순서 유지 |
TreeMap | 키 정렬 순서 유지 |
IdentityHashMap | == 비교 기준 (equals 무시) |
import java.util.*;
public class HashExample {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<>();
map.put("apple", 3);
map.put("banana", 2);
map.put("apple", 5); // 기존 값 덮어쓰기
System.out.println(map.get("apple")); // 출력: 5
}
}
동일한 키를 넣으면 값이 덮어짐
map.put("key", 1);
map.put("key", 2); // → key는 중복, 값은 2로 갱신됨
커스텀 객체를 키로 쓸 때는 hashCode()와 equals()를 모두 오버라이딩
class Person {
String name;
public Person(String name) { this.name = name; }
public boolean equals(Object o) { ... }
public int hashCode() { ... }
}
HashSet도 내부적으로 HashMap을 사용
Set<String> set = new HashSet<>();
set.add("apple");
set.add("apple"); // 중복 저장 안 됨
충돌이 많아진다면? → Java 8부터는 연결 리스트 → Tree 구조로 자동 전환
다음 코드 실행 결과는?
Map<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
map.put("a", 3);
System.out.println(map.get("a"));
✅ 정답: 3번
→ "a"라는 키는 덮어써짐 → 마지막 값 3 저장됨
class Fruit {
String name;
public Fruit(String name) {
this.name = name;
}
}
public class Test {
public static void main(String[] args) {
Map<Fruit, Integer> map = new HashMap<>();
map.put(new Fruit("apple"), 1);
System.out.println(map.get(new Fruit("apple"))); // null?
}
}
✅ 출력: null
→ 이유: equals()와 hashCode()를 오버라이딩하지 않았기 때문
너가 원한다면, 지금부터는 해시 문제를 자바로 낼게.
예를 들어:
HashMap과 equals/hashCode 트랩HashSet 중복 제거 원리LinkedHashMap 순서 유지매우 중요한 질문이야!
자바에서 equals()와 hashCode()를 왜 오버라이딩해야 하는지는, 해시 구조(HashMap, HashSet)를 정확히 이해하기 위한 핵심 중의 핵심이야.
자바에서 HashMap, HashSet 같은 해시 기반 자료구조는 내부적으로 다음 두 가지를 사용해서 key를 저장하고 찾는다:
hashCode() → 버킷 위치(배열 인덱스) 를 정함equals() → 진짜 같은 객체인지를 비교함생각해봐:
💡 즉:
hashCode()는 어디에 저장할지를 찾고,
equals()는 진짜 같은 key인지 확인하는 거야.
map.put(key, value) 를 하면:
hashCode()로 **버킷(배열 인덱스)**를 결정하고equals()로 비교map.get(key) 할 때도 똑같이:
hashCode()로 버킷 찾고equals() 비교해서 값 꺼냄class Person {
String name;
public Person(String name) {
this.name = name;
}
}
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
equals()는 false고,hashCode()도 다르게 나옴 (Object 기본 동작: 주소 기반)그래서 HashMap은 이렇게 오작동함:
Map<Person, String> map = new HashMap<>();
map.put(p1, "개발자");
System.out.println(map.get(p2)); // ❌ null 나옴
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person other = (Person) o;
return this.name.equals(other.name);
}
@Override
public int hashCode() {
return name.hashCode(); // 문자열의 해시 사용
}
}
이제 다음은 정상 작동함:
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
Map<Person, String> map = new HashMap<>();
map.put(p1, "개발자");
System.out.println(map.get(p2)); // ✅ "개발자"
| 함수 | 역할 | 왜 필요? |
|---|---|---|
hashCode() | 버킷 위치 계산 | 빠른 위치 탐색을 위해 |
equals() | 값 비교 | 같은 객체인지 판단 위해 |
| 둘 다 재정의 | 필수! | HashMap, HashSet에서 key로 쓰려면 둘 다 override 해야 함 |
"같은 객체"면
→equals()는true
→hashCode()는 같아야 한다
반대로,
hashCode()가 같다고 해서equals()도 무조건 true는 아님
→ 이건 충돌 상황일 수 있음
원한다면 아래 중 하나 골라줘:
이 중 골라줘도 되고, 직접 원하는 유형 말해줘도 돼.
지금이 진짜 자바 해시 구조의 본질을 잡을 황금 타이밍이야.