파이썬은 수학의 집합을 표현하는 세트(set)라는 자료형을 제공합니다. {} 중괄호 안에 값을 저장하며, 각 값은 ,(콤마)로 구분합니다.
fruits = {"apple", "banana", "cherry"}
print(fruits) # {'cherry', 'apple', 'banana'} (순서가 다를 수 있음)
세트에는 세 가지 핵심 특성이 있습니다.
| 특성 | 설명 |
|---|---|
| 순서 없음 | 출력할 때마다 요소의 순서가 달라질 수 있음 |
| 중복 불가 | 같은 값은 하나만 저장됨 |
| 인덱싱 불가 | [] 대괄호로 특정 요소에 접근할 수 없음 |
# 중복된 값은 자동으로 제거됨
s = {1, 2, 2, 3, 3, 3}
print(s) # {1, 2, 3}
# 인덱싱 불가
# print(s[0]) # TypeError!
# 중괄호로 생성
s1 = {1, 2, 3}
# set()으로 반복 가능한 객체를 넣어 생성
s2 = set([1, 2, 3]) # 리스트로부터
s3 = set("hello") # 문자열로부터 → {'h', 'e', 'l', 'o'}
s4 = set(range(5)) # range로부터
# ⚠️ 빈 세트는 반드시 set()을 사용
empty_set = set() # ✅ 빈 세트
empty_dict = {} # ❌ 이것은 빈 딕셔너리!
⚠️ 세트 안에 세트는 들어갈 수 없습니다. 세트의 요소는 해시 가능(hashable)한 불변 객체만 가능하기 때문입니다. 세트를 요소로 넣고 싶다면
frozenset을 사용해야 합니다.
세트는 인덱싱이 불가능하므로, 특정 값이 있는지 확인하려면 in 연산자를 사용합니다.
s = {1, 2, 3, 4, 5}
print(3 in s) # True
print(10 not in s) # True
세트의 가장 강력한 기능은 집합 연산입니다. 연산자와 메서드 두 가지 방식을 모두 지원합니다.
| 연산 | 연산자 | 메서드 | 설명 |
|---|---|---|---|
| 합집합 | \| | union() | 두 세트의 모든 요소 |
| 교집합 | & | intersection() | 두 세트에 공통으로 있는 요소 |
| 차집합 | - | difference() | 한쪽에만 있는 요소 |
| 대칭 차집합 | ^ | symmetric_difference() | 한쪽에만 있는 요소 (양방향) |
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
# 합집합: 두 세트의 모든 요소
print(a | b) # {1, 2, 3, 4, 5, 6}
# 교집합: 공통 요소
print(a & b) # {3, 4}
# 차집합: a에만 있는 요소
print(a - b) # {1, 2}
# 대칭 차집합: 한쪽에만 있는 요소
print(a ^ b) # {1, 2, 5, 6}
집합 연산 결과를 바로 할당하는 할당 연산자도 사용할 수 있습니다.
| 할당 연산자 | 동일한 메서드 |
|---|---|
\|= | update() |
&= | intersection_update() |
-= | difference_update() |
^= | symmetric_difference_update() |
a = {1, 2, 3}
a |= {4, 5} # a.update({4, 5})와 동일
print(a) # {1, 2, 3, 4, 5}
a &= {1, 2, 6} # a.intersection_update({1, 2, 6})와 동일
print(a) # {1, 2}
세트 a의 모든 요소가 세트 b에 포함되면, a는 b의 부분집합입니다.
a = {1, 2}
b = {1, 2, 3, 4}
# 부분집합 확인
print(a <= b) # True
print(a.issubset(b)) # True
# 진부분집합: 부분집합이면서 같지 않을 때
print(a < b) # True (a는 b의 진부분집합)
print(a < a) # False (자기 자신은 진부분집합이 아님)
세트 b가 세트 a의 모든 요소를 포함하면, b는 a의 상위집합입니다.
a = {1, 2}
b = {1, 2, 3, 4}
# 상위집합 확인
print(b >= a) # True
print(b.issuperset(a)) # True
# 진상위집합: 상위집합이면서 같지 않을 때
print(b > a) # True
print(b > b) # False
a = {1, 2, 3}
b = {1, 2, 3}
c = {4, 5, 6}
# 세트가 같은지 확인
print(a == b) # True
print(a != c) # True
# disjoint: 두 세트가 겹치지 않는지 확인
print(a.isdisjoint(c)) # True (겹치는 요소 없음)
print(a.isdisjoint(b)) # False (겹치는 요소 있음)
| 메서드 | 기능 | 요소가 없을 때 |
|---|---|---|
add(요소) | 요소 하나 추가 | — |
remove(요소) | 특정 요소 삭제 | KeyError 발생 |
discard(요소) | 특정 요소 삭제 | 그냥 넘어감 |
pop() | 임의의 요소 삭제 후 반환 | KeyError 발생 (빈 세트) |
clear() | 모든 요소 삭제 | — |
s = {1, 2, 3}
# add: 요소 추가
s.add(4)
print(s) # {1, 2, 3, 4}
# remove: 삭제 (없으면 에러)
s.remove(4)
print(s) # {1, 2, 3}
# s.remove(99) # KeyError!
# discard: 삭제 (없어도 에러 없음)
s.discard(99) # 에러 없이 넘어감
print(s) # {1, 2, 3}
# pop: 임의의 요소 삭제 후 반환
value = s.pop()
print(value) # 1, 2, 3 중 하나 (예측 불가)
# clear: 모든 요소 삭제
s.clear()
print(s) # set()
💡 요소가 존재하는지 확실하지 않다면
remove대신discard를 사용하는 것이 안전합니다.
s = {1, 2, 3, 4, 5}
print(len(s)) # 5
이전 포스트에서 다룬 리스트, 딕셔너리와 마찬가지로, 세트도 할당과 복사에 차이가 있습니다.
# 할당: 같은 세트를 공유
a = {1, 2, 3}
b = a
b.add(4)
print(a) # {1, 2, 3, 4} ← a도 변경됨!
# 복사: 독립된 세트
a = {1, 2, 3}
b = a.copy()
b.add(4)
print(a) # {1, 2, 3} ← a는 변경되지 않음
세트는 순서가 없으므로, 반복문으로 출력할 때 순서가 매번 달라질 수 있습니다.
s = {"apple", "banana", "cherry"}
for item in s:
print(item)
# 출력 순서는 매번 다를 수 있음
for 반복문과 if 조건문을 사용하여 세트를 생성할 수 있습니다. 리스트 컴프리헨션과 문법이 동일하지만, [] 대신 {}를 사용합니다.
# 기본 세트 컴프리헨션
squares = {x ** 2 for x in range(5)}
print(squares) # {0, 1, 4, 9, 16}
# if 조건 추가
even_squares = {x ** 2 for x in range(10) if x % 2 == 0}
print(even_squares) # {0, 4, 16, 36, 64}
💡 세트 컴프리헨션의 결과는 중복이 자동으로 제거됩니다. 이 특성을 활용하면 리스트의 중복 제거에도 유용합니다.
# 리스트의 중복 제거에 활용
numbers = [1, 2, 2, 3, 3, 3, 4]
unique = {x for x in numbers}
print(unique) # {1, 2, 3, 4}