집합 (Set)
학습 목표
집합의 특성(순서 없음, 중복 불가)을 이해한다
합집합, 교집합, 차집합, 대칭차집합 연산을 수행할 수 있다
중복 제거와 멤버십 테스트에 집합을 활용할 수 있다
왜 중요한가
집합은 수학의 집합과 동일한 개념입니다. 중복을 허용하지 않고, 멤버십 테스트(in)가 O(1)로 매우 빠릅니다. 데이터 전처리에서 고유값 추출, 두 데이터셋 간 교차 분석, 중복 제거 등에 핵심적으로 사용됩니다.
집합 생성
# 중괄호로 생성
fruits = { "사과" , "바나나" , "체리" }
numbers = { 1 , 2 , 3 , 4 , 5 }
# set() 생성자
from_list = set ([ 1 , 2 , 2 , 3 , 3 , 3 ])
print (from_list) # {1, 2, 3} (중복 제거)
from_string = set ( "hello" )
print (from_string) # {'h', 'e', 'l', 'o'} (순서 보장 안 됨)
# 빈 집합 (주의: {}는 빈 딕셔너리)
empty_set = set () # 올바른 방법
empty_dict = {} # 이것은 빈 딕셔너리!
집합 연산
a = { 1 , 2 , 3 , 4 , 5 }
b = { 4 , 5 , 6 , 7 , 8 }
# 합집합 (Union)
print (a | b) # {1, 2, 3, 4, 5, 6, 7, 8}
print (a.union(b)) # 동일
# 교집합 (Intersection)
print (a & b) # {4, 5}
print (a.intersection(b)) # 동일
# 차집합 (Difference)
print (a - b) # {1, 2, 3}
print (a.difference(b)) # 동일
# 대칭 차집합 (Symmetric Difference) - 한쪽에만 있는 요소
print (a ^ b) # {1, 2, 3, 6, 7, 8}
print (a.symmetric_difference(b)) # 동일
요소 추가와 삭제
colors = { "빨강" , "파랑" }
# 추가
colors.add( "초록" )
print (colors) # {"빨강", "파랑", "초록"}
# 여러 요소 추가
colors.update([ "노랑" , "보라" ])
print (colors) # {"빨강", "파랑", "초록", "노랑", "보라"}
# 삭제
colors.remove( "빨강" ) # 없으면 KeyError
colors.discard( "검정" ) # 없어도 에러 없음
popped = colors.pop() # 임의의 요소 제거 및 반환
colors.clear() # 전체 삭제
부분집합과 상위집합
a = { 1 , 2 , 3 }
b = { 1 , 2 , 3 , 4 , 5 }
print (a.issubset(b)) # True (a ⊆ b)
print (a <= b) # True
print (b.issuperset(a)) # True (b ⊇ a)
print (b >= a) # True
print (a.isdisjoint({ 6 , 7 })) # True (교집합 없음)
중복 제거 활용
# 리스트에서 중복 제거
data = [ 1 , 2 , 2 , 3 , 3 , 3 , 4 , 4 , 4 , 4 ]
unique = list ( set (data))
print (unique) # [1, 2, 3, 4] (순서 보장 안 됨)
# 순서를 유지하면서 중복 제거
def deduplicate ( items ):
"""순서를 유지하면서 중복을 제거합니다."""
seen = set ()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
data = [ 3 , 1 , 4 , 1 , 5 , 9 , 2 , 6 , 5 , 3 ]
print (deduplicate(data)) # [3, 1, 4, 5, 9, 2, 6]
# Python 3.7+ dict로도 가능 (삽입 순서 보존)
unique_ordered = list ( dict .fromkeys(data))
print (unique_ordered) # [3, 1, 4, 5, 9, 2, 6]
멤버십 테스트 성능
# 리스트 vs 집합 멤버십 테스트
large_list = list ( range ( 100_000 ))
large_set = set ( range ( 100_000 ))
# 리스트: O(n) - 느림
99_999 in large_list # 최악의 경우 10만 번 비교
# 집합: O(1) - 빠름
99_999 in large_set # 해시로 즉시 확인
멤버십 테스트(in)를 자주 수행해야 할 때는 리스트 대신 집합을 사용하세요. 특히 데이터가 클수록 성능 차이가 극적으로 벌어집니다.
frozenset
frozenset은 불변(Immutable) 집합입니다. 딕셔너리의 키로 사용하거나, 집합의 집합을 만들 때 필요합니다.
fs = frozenset ([ 1 , 2 , 3 ])
# fs.add(4) # AttributeError - 수정 불가
# 딕셔너리 키로 사용
permissions = {
frozenset ([ "read" ]): "viewer" ,
frozenset ([ "read" , "write" ]): "editor" ,
frozenset ([ "read" , "write" , "admin" ]): "admin" ,
}
AI/ML에서의 활용
# 학습/검증/테스트 데이터 분리 검증
train_ids = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 }
val_ids = { 9 , 10 }
test_ids = { 11 , 12 }
# 데이터 누출(Data Leakage) 검증
assert train_ids.isdisjoint(val_ids), "학습-검증 데이터 겹침!"
assert train_ids.isdisjoint(test_ids), "학습-테스트 데이터 겹침!"
assert val_ids.isdisjoint(test_ids), "검증-테스트 데이터 겹침!"
# 어휘 사전(Vocabulary) 관리
vocab = set ()
for sentence in [ "I love Python" , "Python is great" ]:
words = sentence.lower().split()
vocab.update(words)
print (vocab) # {'i', 'love', 'python', 'is', 'great'}
# 고유 카테고리 추출
labels = [ "cat" , "dog" , "cat" , "bird" , "dog" , "cat" ]
unique_labels = sorted ( set (labels))
print (unique_labels) # ['bird', 'cat', 'dog']
label_to_id = {label: idx for idx, label in enumerate (unique_labels)}
print (label_to_id) # {'bird': 0, 'cat': 1, 'dog': 2}
아니요. 집합의 요소는 해시 가능한(Hashable) 불변 타입이어야 합니다. 리스트 대신 튜플을 사용하세요: {(1, 2), (3, 4)}는 가능하지만, {[1, 2], [3, 4]}는 TypeError가 발생합니다.
집합은 내부적으로 해시 테이블(Hash Table)을 사용합니다. 요소의 해시값에 따라 저장 위치가 결정되므로, 삽입 순서와 저장/출력 순서가 다를 수 있습니다.
체크리스트
다음 문서