동빈나 채널의 파이썬 문법 부수기 유튜브 강의를 참고하여 정리한 내용이다.
사전(dict)과 집합(set)은 둘 다 해시 테이블 기반이다.
조회, 삽입, 삭제가 평균 O(1)이라는 게 가장 중요한 특징이다.
코딩테스트에서 특정 값의 존재 여부를 빠르게 확인하거나 빈도수를 셀 때 필수적으로 사용한다.
사전 (dict)
해시 테이블 기반 O(1) 조회
사전은 키-값 쌍으로 데이터를 저장한다. 내부적으로 해시 테이블을 사용하기 때문에 리스트에서 O(N)이 걸리던 탐색이 사전에서는 O(1)에 가능하다.
# 1만 개의 데이터에서 특정 값 탐색
import time
data_list = list(range(1000000))
data_dict = {i: True for i in range(1000000)}
# 리스트 탐색 - O(N)
start = time.time()
999999 in data_list
print(f"리스트: {time.time() - start:.6f}초") # 약 0.012초
# 사전 탐색 - O(1)
start = time.time()
999999 in data_dict
print(f"사전: {time.time() - start:.6f}초") # 약 0.000001초
사전 초기화
# 중괄호 {}
a = {"key1": "value1", "key2": "value2"}
# dict() 함수
b = dict(key1="value1", key2="value2")
# 빈 사전
c = {}
d = dict()
print(a) # {'key1': 'value1', 'key2': 'value2'}
print(type(c)) # <class 'dict'>
print(type({})) # <class 'dict'> - 빈 {}는 사전
# 주의: {1, 2, 3}은 집합, {}은 사전
# 리스트로 사전 만들기
keys = ["a", "b", "c"]
values = [1, 2, 3]
d = dict(zip(keys, values))
print(d) # {'a': 1, 'b': 2, 'c': 3}
# 컴프리헨션
squares = {i: i**2 for i in range(1, 6)}
print(squares) # {1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
사전 접근 및 수정
값 읽기
d = {"name": "Alice", "age": 25, "score": 95}
# 대괄호 접근
print(d["name"]) # Alice
print(d["age"]) # 25
# 없는 키 접근 - KeyError 발생
print(d["gender"])
# KeyError: 'gender'
# get() 사용 - 없으면 None 반환
print(d.get("name")) # Alice
print(d.get("gender")) # None
# 기본값 지정
print(d.get("gender", "Unknown")) # Unknown
print(d.get("age", 0)) # 25 (있으면 원래 값)
코딩테스트에서 키 존재 여부가 불확실할 때는 d.get(key, default) 패턴을 많이 쓴다.
값 추가 및 수정
d = {"a": 1}
# 새 키-값 추가
d["b"] = 2
print(d) # {'a': 1, 'b': 2}
# 기존 값 수정
d["a"] = 100
print(d) # {'a': 100, 'b': 2}
# update() 로 여러 개 한 번에
d.update({"c": 3, "d": 4})
print(d) # {'a': 100, 'b': 2, 'c': 3, 'd': 4}
삭제
d = {"a": 1, "b": 2, "c": 3}
# del
del d["a"]
print(d) # {'b': 2, 'c': 3}
# del - 없는 키 삭제 시 에러
del d["z"] # KeyError: 'z'
# pop() - 삭제 후 값 반환
val = d.pop("b")
print(val) # 2
print(d) # {'c': 3}
# pop() - 없는 키에 기본값 설정
val = d.pop("z", "없음")
print(val) # 없음 (에러 없음)
keys(), values(), items()
d = {"apple": 3, "banana": 5, "cherry": 2}
# 키만
print(d.keys()) # dict_keys(['apple', 'banana', 'cherry'])
print(list(d.keys())) # ['apple', 'banana', 'cherry']
# 값만
print(d.values()) # dict_values([3, 5, 2])
print(list(d.values())) # [3, 5, 2]
# 키-값 쌍
print(d.items()) # dict_items([('apple', 3), ('banana', 5), ('cherry', 2)])
# 반복
for key in d:
print(key, d[key])
for key, value in d.items():
print(f"{key}: {value}")
사전 정렬
d = {"banana": 2, "apple": 5, "cherry": 3}
# 키 기준 정렬
sorted_by_key = dict(sorted(d.items()))
print(sorted_by_key) # {'apple': 5, 'banana': 2, 'cherry': 3}
# 값 기준 정렬
sorted_by_val = dict(sorted(d.items(), key=lambda x: x[1]))
print(sorted_by_val) # {'banana': 2, 'cherry': 3, 'apple': 5}
# 값 기준 내림차순
sorted_desc = dict(sorted(d.items(), key=lambda x: x[1], reverse=True))
print(sorted_desc) # {'apple': 5, 'cherry': 3, 'banana': 2}
빈도수 세기 패턴
코딩테스트에서 사전을 가장 많이 쓰는 상황이다.
# 기본 패턴
text = "abracadabra"
freq = {}
for char in text:
if char in freq:
freq[char] += 1
else:
freq[char] = 1
print(freq) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
# get() 활용 - 더 간결
freq = {}
for char in text:
freq[char] = freq.get(char, 0) + 1
print(freq) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
# collections.Counter - 가장 편함
from collections import Counter
freq = Counter(text)
print(freq) # Counter({'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1})
print(freq["a"]) # 5
print(freq["z"]) # 0 (없는 키도 0 반환)
# 가장 많이 등장한 것
print(freq.most_common(2)) # [('a', 5), ('b', 2)]
defaultdict 패턴
from collections import defaultdict
# 기본값이 있는 사전
d = defaultdict(int) # 기본값 0
d["a"] += 1
d["b"] += 5
d["a"] += 2
print(dict(d)) # {'a': 3, 'b': 5}
print(d["c"]) # 0 - 없는 키도 에러 없이 기본값 반환
# 리스트 기본값
graph = defaultdict(list)
graph[1].append(2)
graph[1].append(3)
graph[2].append(4)
print(dict(graph)) # {1: [2, 3], 2: [4]}
그래프를 인접 리스트로 표현할 때 defaultdict(list)를 자주 쓴다.
집합 (set)
두 가지 특징
- 중복 불허: 같은 값이 두 번 이상 저장되지 않음
- 순서 없음: 인덱싱이 안 되고, 출력 순서가 보장되지 않음
a = {1, 2, 3, 4, 5}
print(a) # {1, 2, 3, 4, 5}
print(type(a)) # <class 'set'>
# 중복 자동 제거
b = {1, 2, 2, 3, 3, 3}
print(b) # {1, 2, 3}
# 리스트에서 중복 제거
nums = [1, 2, 2, 3, 3, 3, 4]
unique = list(set(nums))
print(unique) # [1, 2, 3, 4] (순서는 보장 안 됨)
집합 초기화
# 중괄호 - 원소가 있을 때
a = {1, 2, 3}
# set() 함수 - 빈 집합은 반드시 set() 사용
b = set() # 빈 집합
c = {} # 이건 빈 사전!
print(type(b)) # <class 'set'>
print(type(c)) # <class 'dict'>
# 이터러블로 초기화
d = set([1, 2, 3, 2, 1]) # 리스트에서
print(d) # {1, 2, 3}
e = set("hello") # 문자열에서
print(e) # {'h', 'e', 'l', 'o'} - 중복 l 하나 제거
빈 집합은 반드시
set()으로 만든다.{}은 빈 사전이다.
집합 연산
a = {1, 2, 3, 4, 5}
b = {3, 4, 5, 6, 7}
# 합집합 (|)
print(a | b) # {1, 2, 3, 4, 5, 6, 7}
print(a.union(b)) # 동일
# 교집합 (&)
print(a & b) # {3, 4, 5}
print(a.intersection(b)) # 동일
# 차집합 (-)
print(a - b) # {1, 2} - a에만 있는 것
print(a.difference(b)) # 동일
# 대칭차집합 (^) - 한쪽에만 있는 것
print(a ^ b) # {1, 2, 6, 7}
print(a.symmetric_difference(b)) # 동일
부분집합, 초집합 확인
a = {1, 2, 3}
b = {1, 2, 3, 4, 5}
c = {1, 2, 3}
print(a.issubset(b)) # True - a ⊆ b
print(a <= b) # True - 동일
print(b.issuperset(a)) # True - b ⊇ a
print(b >= a) # True - 동일
print(a == c) # True - 같은 집합
print(a.isdisjoint({6, 7, 8})) # True - 교집합 없음
집합 메서드
s = {1, 2, 3}
# 추가
s.add(4)
print(s) # {1, 2, 3, 4}
# 이미 있는 값 추가 - 무시됨 (에러 아님)
s.add(3)
print(s) # {1, 2, 3, 4}
# 여러 개 추가
s.update({5, 6, 7})
print(s) # {1, 2, 3, 4, 5, 6, 7}
# 제거
s.remove(7)
print(s) # {1, 2, 3, 4, 5, 6}
# 없는 값 remove - 에러
s.remove(99) # KeyError: 99
# discard - 없어도 에러 없음
s.discard(99) # 조용히 무시
s.discard(6)
print(s) # {1, 2, 3, 4, 5}
# 임의의 원소 제거 후 반환
val = s.pop()
print(val) # 임의의 값 (순서 없으므로 예측 불가)
| 메서드 | 기능 | 없을 때 |
|---|---|---|
add(x) |
x 추가 | 무시 |
remove(x) |
x 제거 | KeyError |
discard(x) |
x 제거 | 무시 |
update(s) |
s의 모든 원소 추가 | - |
pop() |
임의 원소 제거 후 반환 | KeyError (빈 집합) |
clear() |
전체 삭제 | - |
집합의 in 연산 - O(1)
# 리스트에서 in - O(N)
a_list = list(range(1000000))
999999 in a_list # 느림
# 집합에서 in - O(1)
a_set = set(range(1000000))
999999 in a_set # 빠름
값의 존재 여부만 확인하면 되고 순서나 중복이 필요 없다면 리스트보다 집합을 쓴다.
사전과 집합의 공통점
| 특징 | 사전 (dict) | 집합 (set) |
|---|---|---|
| 내부 구조 | 해시 테이블 | 해시 테이블 |
| 탐색 시간 | O(1) 평균 | O(1) 평균 |
| 순서 | Python 3.7+에서 삽입 순서 유지 | 순서 없음 |
| 중복 | 키 중복 불가 | 원소 중복 불가 |
| 인덱싱 | 키로 접근 | 불가 |
Python 3.7부터 사전은 삽입 순서를 보장한다. 하지만 집합은 여전히 순서가 없다.
실전 패턴 모음
두 리스트의 공통 원소 찾기
a = [1, 2, 3, 4, 5]
b = [3, 4, 5, 6, 7]
# set 교집합 활용 - O(min(len(a), len(b)))
common = set(a) & set(b)
print(common) # {3, 4, 5}
print(list(common)) # [3, 4, 5]
중복 없이 방문 체크
visited = set()
def dfs(node):
if node in visited:
return
visited.add(node)
# 탐색 로직
아나그램 확인
def is_anagram(s1, s2):
from collections import Counter
return Counter(s1) == Counter(s2)
print(is_anagram("listen", "silent")) # True
print(is_anagram("hello", "world")) # False
# 집합만으로는 부족 (빈도수 무시)
print(set("aab") == set("ab")) # True - 틀린 결과
키 존재 여부 체크 후 처리
graph = {"A": ["B", "C"], "B": ["D"]}
# 잘못된 방법 - KeyError 가능
neighbors = graph["E"] # KeyError
# 올바른 방법들
neighbors = graph.get("E", []) # 빈 리스트 기본값
print(neighbors) # []
if "E" in graph:
neighbors = graph["E"]
else:
neighbors = []
자주 하는 실수 모음
# 1. 빈 집합을 {}로 만들려다 사전이 됨
s = {}
print(type(s)) # <class 'dict'> - 집합 아님!
s = set() # 이게 올바른 빈 집합
# 2. 집합은 인덱싱 불가
s = {1, 2, 3}
print(s[0]) # TypeError: 'set' object is not subscriptable
# 3. 없는 키에 [] 접근
d = {"a": 1}
print(d["b"]) # KeyError: 'b'
print(d.get("b", 0)) # 0 - 안전한 방법
# 4. remove vs discard 혼동
s = {1, 2, 3}
s.remove(5) # KeyError: 5
s.discard(5) # 에러 없음
# 5. 집합 원소로 리스트 사용 시도 (unhashable)
s = set()
s.add([1, 2]) # TypeError: unhashable type: 'list'
s.add((1, 2)) # 튜플은 가능
# 6. 사전 반복 중 크기 변경
d = {"a": 1, "b": 2, "c": 3}
for key in d:
if key == "b":
del d[key]
# RuntimeError: dictionary changed size during iteration
# 올바른 방법
keys_to_delete = [key for key in d if key == "b"]
for key in keys_to_delete:
del d[key]
정리
사전과 집합은 해시 테이블 덕분에 O(1) 탐색이 가능하다. 코딩테스트에서:
- 빈도수 세기:
Counter또는d.get(key, 0) + 1패턴 - 방문 체크: 집합으로 O(1) in 연산
- 빠른 조회 필요: 리스트 대신 사전/집합
- 공통 원소 찾기:
set(a) & set(b) - 그래프 표현:
defaultdict(list)
빈 집합은 set(), 빈 사전은 {} 또는 dict()로 만든다는 것만 헷갈리지 않으면 된다.
'Study > Python' 카테고리의 다른 글
| [Python] 반복문 (0) | 2026.03.07 |
|---|---|
| [Python] 조건문 (0) | 2026.03.07 |
| [Python] 리스트(List) (0) | 2026.03.07 |
| [Python] Boolean 자료형 (0) | 2026.03.07 |
| [Python] 문자열(String) 자료형과 튜플 (1) | 2026.03.07 |
