포스트

[Python 100일 챌린지] Day 13 - 세트 다루기 (set)

[Python 100일 챌린지] Day 13 - 세트 다루기 (set)

중복을 허용하지 않는 세트(집합)를 배워봅시다! 실무에서 중복 제거, 빠른 검색, 집합 연산(합집합, 교집합) 등에 활용됩니다. 수학의 집합 개념을 프로그래밍으로 구현할 수 있습니다. (25분 완독 ⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식


🎯 학습 목표 1: 세트가 무엇인지 이해하기

한 줄 설명

세트 = 중복을 허용하지 않고, 순서가 없는 자료구조

수학의 집합(Set)과 같은 개념입니다.

세트의 특징

  1. 중복 불가: 같은 값이 두 번 들어갈 수 없음
  2. 순서 없음: 인덱스로 접근 불가
  3. 빠른 검색: 값이 있는지 빠르게 확인 가능
  4. 변경 가능: 값 추가/삭제 가능

실생활 비유

1
2
3
4
5
📦 일반 상자 (리스트):
   [사과, 바나나, 사과, 포도]  # 중복 가능

🎒 특별한 주머니 (세트):
   {사과, 바나나, 포도}  # 중복 자동 제거!

왜 세트를 사용할까?

1
2
3
4
5
6
7
8
9
10
11
12
# 문제: 설문 응답에서 고유한 답변만 추출
responses = ["사과", "바나나", "사과", "포도", "바나나", "사과"]

# 리스트로 중복 제거 (복잡)
unique = []
for item in responses:
    if item not in unique:
        unique.append(item)

# 세트로 중복 제거 (간단!)
unique = set(responses)
print(unique)  # {'사과', '바나나', '포도'}

🎯 학습 목표 2: 세트 만들고 사용하기

기본 문법

1
2
3
4
5
6
7
8
9
10
11
# 빈 세트
empty_set = set()  # ⚠️ {}는 빈 딕셔너리!

# 데이터가 있는 세트
fruits = {"사과", "바나나", "포도"}
numbers = {1, 2, 3, 4, 5}

# 리스트에서 세트 만들기
my_list = [1, 2, 2, 3, 3, 3]
my_set = set(my_list)
print(my_set)  # {1, 2, 3} - 중복 제거됨!

중복 자동 제거

1
2
3
4
5
6
7
8
# 중복된 값은 하나만 남음
numbers = {1, 2, 3, 2, 1, 3}
print(numbers)  # {1, 2, 3}

# 문자열에서 고유한 문자만
text = "hello"
unique_chars = set(text)
print(unique_chars)  # {'h', 'e', 'l', 'o'}

길이 확인

1
2
fruits = {"사과", "바나나", "포도"}
print(len(fruits))  # 3

세트 컴프리헨션 (Set Comprehension)

리스트 컴프리헨션처럼 세트도 컴프리헨션으로 간결하게 생성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 1~10 중 짝수만 세트로
even_numbers = {x for x in range(1, 11) if x % 2 == 0}
print(even_numbers)  # {2, 4, 6, 8, 10}

# 문자열 리스트에서 길이 4 이상만
words = ["python", "is", "fun", "and", "easy"]
long_words = {word for word in words if len(word) >= 4}
print(long_words)  # {'python', 'easy'}

# 제곱수 세트 만들기
squares = {x**2 for x in range(1, 6)}
print(squares)  # {1, 4, 9, 16, 25}

# 문자열의 고유 문자 (대문자로 변환)
text = "hello world"
unique_chars = {char.upper() for char in text if char.isalpha()}
print(unique_chars)  # {'H', 'E', 'L', 'O', 'W', 'R', 'D'}

🎯 학습 목표 3: 세트 기본 연산 익히기

값 추가

1
2
3
4
5
6
7
8
9
fruits = {"사과", "바나나"}

# add() - 하나 추가
fruits.add("포도")
print(fruits)  # {'사과', '바나나', '포도'}

# 중복 추가 시도 (무시됨)
fruits.add("사과")
print(fruits)  # {'사과', '바나나', '포도'} - 변화 없음

여러 값 추가

1
2
3
4
5
6
7
8
9
10
fruits = {"사과", "바나나"}

# update() - 여러 개 추가
fruits.update(["포도", "딸기", "수박"])
print(fruits)  # {'사과', '바나나', '포도', '딸기', '수박'}

# 다른 세트와 병합
more_fruits = {"키위", "망고"}
fruits.update(more_fruits)
print(fruits)  # {'사과', '바나나', '포도', '딸기', '수박', '키위', '망고'}

값 삭제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fruits = {"사과", "바나나", "포도"}

# remove() - 값 삭제 (없으면 에러)
fruits.remove("바나나")
print(fruits)  # {'사과', '포도'}

# discard() - 값 삭제 (없어도 에러 안 남)
fruits.discard("딸기")  # 없지만 에러 안 남
print(fruits)  # {'사과', '포도'}

# pop() - 임의의 값 제거하고 반환
item = fruits.pop()
print(f"제거된 항목: {item}")

# clear() - 모두 제거
fruits.clear()
print(fruits)  # set()

in 연산자

1
2
3
4
5
6
fruits = {"사과", "바나나", "포도"}

# 포함 여부 확인 (매우 빠름!)
print("사과" in fruits)    # True
print("딸기" in fruits)    # False
print("수박" not in fruits)  # True

🎯 학습 목표 4: 세트 집합 연산 활용하기

합집합 (Union) - 모든 원소

1
2
3
4
5
6
7
8
9
10
set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 방법 1: | 연산자
union = set1 | set2
print(union)  # {1, 2, 3, 4, 5}

# 방법 2: union() 메서드
union = set1.union(set2)
print(union)  # {1, 2, 3, 4, 5}

교집합 (Intersection) - 공통 원소

1
2
3
4
5
6
7
8
9
10
set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 방법 1: & 연산자
intersection = set1 & set2
print(intersection)  # {3}

# 방법 2: intersection() 메서드
intersection = set1.intersection(set2)
print(intersection)  # {3}

차집합 (Difference) - 한쪽에만 있는 원소

1
2
3
4
5
6
7
8
9
10
11
12
13
14
set1 = {1, 2, 3}
set2 = {3, 4, 5}

# set1에만 있는 것
diff = set1 - set2
print(diff)  # {1, 2}

# set2에만 있는 것
diff = set2 - set1
print(diff)  # {4, 5}

# difference() 메서드
diff = set1.difference(set2)
print(diff)  # {1, 2}

대칭 차집합 (Symmetric Difference) - 교집합 제외

1
2
3
4
5
6
7
8
9
10
set1 = {1, 2, 3}
set2 = {3, 4, 5}

# 한 쪽에만 있는 것 (교집합 제외)
sym_diff = set1 ^ set2
print(sym_diff)  # {1, 2, 4, 5}

# symmetric_difference() 메서드
sym_diff = set1.symmetric_difference(set2)
print(sym_diff)  # {1, 2, 4, 5}

부분집합 및 포함 관계

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
set1 = {1, 2, 3}
set2 = {1, 2, 3, 4, 5}

# 부분집합 확인 (subset)
print(set1.issubset(set2))     # True (set1이 set2의 부분집합)
print(set1 <= set2)             # True (동일한 의미)

# 진부분집합 (자기 자신 제외)
print(set1 < set2)              # True (set1이 set2의 진부분집합)

# 상위집합 확인 (superset)
print(set2.issuperset(set1))   # True (set2가 set1의 상위집합)
print(set2 >= set1)             # True (동일한 의미)

# 진상위집합 (자기 자신 제외)
print(set2 > set1)              # True (set2가 set1의 진상위집합)

# 교집합 없음 확인 (disjoint)
set3 = {6, 7, 8}
print(set1.isdisjoint(set3))   # True (공통 요소 없음)
print(set1.isdisjoint(set2))   # False (공통 요소 있음)

실용 예제:

1
2
3
4
5
6
7
8
9
10
# 관리자 권한 확인
required_permissions = {"read", "write", "delete"}
user_permissions = {"read", "write", "delete", "admin"}

# 사용자가 필요한 권한을 모두 가졌는지 확인
if required_permissions.issubset(user_permissions):
    print("✅ 권한이 충분합니다.")
else:
    missing = required_permissions - user_permissions
    print(f"❌ 부족한 권한: {missing}")

🎯 학습 목표 5: 세트와 다른 자료구조 비교

세트 vs 리스트 vs 딕셔너리

특징 리스트 세트 딕셔너리
순서 ✅ 있음 ❌ 없음 ✅ 있음 (3.7+)
중복 ✅ 허용 ❌ 불허 키는 불허
인덱스 ✅ 가능 ❌ 불가 키로 접근
검색 속도 느림 ⚡ 빠름 ⚡ 빠름
용도 순서 중요 중복 제거 키-값 쌍
1
2
3
4
5
6
7
8
9
10
11
# 리스트
my_list = [1, 2, 2, 3]
print(my_list[0])  # 1

# 세트
my_set = {1, 2, 2, 3}
# print(my_set[0])  # 에러! 인덱스 없음

# 딕셔너리
my_dict = {"a": 1, "b": 2}
print(my_dict["a"])  # 1

반복문으로 순회

1
2
3
4
5
6
7
8
9
10
11
fruits = {"사과", "바나나", "포도"}

# for문으로 순회
for fruit in fruits:
    print(fruit)

# ⚠️ 순서는 보장되지 않음!

# 정렬이 필요하면 리스트로 변환
for fruit in sorted(fruits):
    print(fruit)

자료구조 변환

1
2
3
4
5
6
7
8
9
# 리스트 → 세트 (중복 제거)
numbers_list = [1, 2, 2, 3, 3, 4]
numbers_set = set(numbers_list)  # set() 생성자 사용
print(numbers_set)  # {1, 2, 3, 4}

# 문자열 → 세트 (고유 문자만)
text = "hello"
unique_chars = set(text)  # set() 생성자 사용
print(unique_chars)  # {'h', 'e', 'l', 'o'}

🎯 학습 목표 6: 실전에서 세트 활용하기

예제 1: 중복 제거

1
2
3
4
5
6
# 설문 응답에서 중복 제거
responses = ["사과", "바나나", "사과", "포도", "바나나", "사과"]

unique_responses = set(responses)
print(f"응답 종류: {unique_responses}")
print(f"고유 응답 수: {len(unique_responses)}")

출력:

1
2
응답 종류: {'사과', '바나나', '포도'}
고유 응답 수: 3개

예제 2: 공통 취미 찾기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
person1_hobbies = {"독서", "운동", "영화", "음악"}
person2_hobbies = {"게임", "운동", "영화", "요리"}

# 공통 취미
common = person1_hobbies & person2_hobbies
print(f"공통 취미: {common}")

# 모든 취미
all_hobbies = person1_hobbies | person2_hobbies
print(f"전체 취미: {all_hobbies}")

# person1만의 취미
only_person1 = person1_hobbies - person2_hobbies
print(f"person1만의 취미: {only_person1}")

출력:

1
2
3
공통 취미: {'운동', '영화'}
전체 취미: {'독서', '운동', '영화', '음악', '게임', '요리'}
person1만의 취미: {'독서', '음악'}

예제 3: 로또 번호 생성

1
2
3
4
5
6
7
8
9
10
import random

# 1~45 중 6개 추출 (중복 없이)
lotto = set()

while len(lotto) < 6:
    number = random.randint(1, 45)
    lotto.add(number)

print(f"로또 번호: {sorted(lotto)}")

예제 4: 수강 신청 시스템

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 각 과목 수강생
class_a = {"홍길동", "김철수", "이영희", "박민수"}
class_b = {"김철수", "이영희", "최영수", "정민지"}
class_c = {"홍길동", "박민수", "최영수", "강호동"}

# 1. 전체 수강생 (합집합)
all_students = class_a | class_b | class_c
print(f"전체 수강생: {len(all_students)}")
print(all_students)

# 2. 세 과목 모두 수강하는 학생 (교집합)
all_classes = class_a & class_b & class_c
print(f"세 과목 모두 수강: {all_classes}")

# 3. A만 수강하는 학생
only_a = class_a - class_b - class_c
print(f"A만 수강: {only_a}")

# 4. A와 B 둘 다 수강하는 학생
a_and_b = class_a & class_b
print(f"A와 B 둘 다 수강: {a_and_b}")

💡 실전 팁 & 주의사항

💡 Tip 1: 변경 불가능한 값만 저장

1
2
3
4
5
6
# ✅ 가능
good_set = {1, "hello", (1, 2)}

# ❌ 불가능 (리스트, 딕셔너리는 세트에 넣을 수 없음)
# bad_set = {[1, 2, 3]}  # TypeError!
# bad_set = {{"a": 1}}   # TypeError!

💡 Tip 2: 빈 세트 생성 주의

1
2
3
4
5
6
7
# ❌ 잘못된 방법 (빈 딕셔너리가 됨!)
empty = {}
print(type(empty))  # <class 'dict'>

# ✅ 올바른 방법
empty_set = set()
print(type(empty_set))  # <class 'set'>

💡 Tip 3: 순서 보장 안 됨

1
2
3
4
5
6
my_set = {3, 1, 2}
print(my_set)  # 매번 다른 순서로 출력될 수 있음

# 정렬이 필요하면 리스트로 변환
sorted_list = sorted(my_set)
print(sorted_list)  # [1, 2, 3]

💡 Tip 4: 빠른 검색에 활용

1
2
3
4
5
6
7
# 리스트에서 검색 (느림)
my_list = list(range(1000000))
print(999999 in my_list)  # 느림

# 세트에서 검색 (빠름!)
my_set = set(range(1000000))
print(999999 in my_set)  # 훨씬 빠름!

💡 Tip 5: remove vs discard

1
2
3
4
5
6
7
fruits = {"사과", "바나나"}

# remove() - 없으면 에러
# fruits.remove("포도")  # KeyError!

# discard() - 없어도 괜찮음
fruits.discard("포도")  # 에러 안 남

💡 Tip 6: 집합 연산 기호

1
2
3
4
5
6
7
8
9
10
11
12
13
14
set1 = {1, 2, 3}
set2 = {3, 4, 5}

# | : 합집합 (Union)
print(set1 | set2)  # {1, 2, 3, 4, 5}

# & : 교집합 (Intersection)
print(set1 & set2)  # {3}

# - : 차집합 (Difference)
print(set1 - set2)  # {1, 2}

# ^ : 대칭 차집합 (Symmetric Difference)
print(set1 ^ set2)  # {1, 2, 4, 5}

💡 Tip 7: 변경 불가능한 세트 (frozenset)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# frozenset - 한번 생성하면 변경 불가
immutable_set = frozenset([1, 2, 3])
print(immutable_set)  # frozenset({1, 2, 3})

# 추가/삭제 불가능
# immutable_set.add(4)  # AttributeError! 추가 불가

# 일반 세트는 딕셔너리 키로 사용 불가
# my_dict = {set([1, 2]): "value"}  # TypeError!

# frozenset은 딕셔너리 키로 사용 가능
my_dict = {frozenset([1, 2]): "value"}
print(my_dict)  # {frozenset({1, 2}): 'value'}

# frozenset 간 집합 연산 가능
fs1 = frozenset([1, 2, 3])
fs2 = frozenset([3, 4, 5])
print(fs1 | fs2)  # frozenset({1, 2, 3, 4, 5})

🧪 연습 문제

문제 1: 텍스트 데이터 중복 제거 및 분석

과제: 문장에서 중복 단어를 제거하고 다양한 통계를 출력하세요.

초기 데이터:

1
text = "python is fun python is easy python makes coding fun coding is powerful"

요구사항:

  1. 문장을 단어로 분리하여 리스트로 저장하세요
  2. 중복을 제거한 고유 단어들을 세트로 저장하세요
  3. 전체 단어 수(중복 포함)와 고유 단어 수를 출력하세요
  4. 중복된 단어만 추출하여 출력하세요 (2번 이상 등장한 단어)
  5. 고유 단어를 알파벳 순으로 정렬하여 출력하세요
💡 힌트
  • split()으로 단어 분리
  • set()으로 중복 제거
  • 리스트의 count() 메서드로 빈도 확인
  • sorted()로 정렬
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
text = "python is fun python is easy python makes coding fun coding is powerful"

# 1. 문장을 단어로 분리
words = text.split()
print(f"전체 단어: {words}")

# 2. 중복 제거
unique_words = set(words)
print(f"\n고유 단어: {unique_words}")

# 3. 통계 출력
total_count = len(words)
unique_count = len(unique_words)
duplicate_count = total_count - unique_count
print(f"\n=== 통계 ===")
print(f"전체 단어 수: {total_count}")
print(f"고유 단어 수: {unique_count}")
print(f"중복된 단어 수: {duplicate_count}")

# 4. 중복된 단어만 추출 (2번 이상 등장)
duplicates = set()
for word in unique_words:
    if words.count(word) >= 2:
        duplicates.add(word)

print(f"\n중복 단어: {duplicates}")
for word in duplicates:
    print(f"  - '{word}': {words.count(word)}")

# 5. 알파벳 순 정렬
sorted_words = sorted(unique_words)
print(f"\n알파벳 순 정렬: {sorted_words}")

출력:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
전체 단어: ['python', 'is', 'fun', 'python', 'is', 'easy', 'python', 'makes', 'coding', 'fun', 'coding', 'is', 'powerful']

고유 단어: {'python', 'is', 'fun', 'easy', 'makes', 'coding', 'powerful'}

=== 통계 ===
전체 단어 수: 13개
고유 단어 수: 7개
중복된 단어 수: 6개

중복 단어: {'python', 'is', 'fun', 'coding'}
  - 'python': 3번
  - 'is': 3번
  - 'fun': 2번
  - 'coding': 2번

알파벳 순 정렬: ['coding', 'easy', 'fun', 'is', 'makes', 'powerful', 'python']

문제 2: 친구 관계 분석 시스템

과제: 여러 사람의 친구 목록을 세트로 관리하고 집합 연산을 수행하세요.

초기 데이터:

1
2
3
alice_friends = {"철수", "영희", "민수", "지영"}
bob_friends = {"영희", "민수", "준호", "수진"}
charlie_friends = {"철수", "민수", "준호", "영수"}

요구사항:

  1. Alice와 Bob의 공통 친구를 찾아 출력하세요 (교집합)
  2. Alice만 아는 친구를 찾아 출력하세요 (차집합)
  3. Alice, Bob, Charlie 세 사람 모두의 친구를 합쳐 전체 친구 목록을 만드세요 (합집합)
  4. Alice와 Bob 중 한 명만 아는 친구를 찾아 출력하세요 (대칭 차집합)
  5. 세 사람 모두가 공통으로 아는 친구가 있는지 확인하세요
💡 힌트
  • & 연산자로 교집합
  • - 연산자로 차집합
  • | 연산자로 합집합
  • ^ 연산자로 대칭 차집합
  • 여러 세트의 교집합: set1 & set2 & set3
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
alice_friends = {"철수", "영희", "민수", "지영"}
bob_friends = {"영희", "민수", "준호", "수진"}
charlie_friends = {"철수", "민수", "준호", "영수"}

# 1. Alice와 Bob의 공통 친구 (교집합)
common_friends = alice_friends & bob_friends
print(f"Alice와 Bob의 공통 친구: {common_friends}")

# 2. Alice만 아는 친구 (차집합)
alice_only = alice_friends - bob_friends
print(f"Alice만 아는 친구: {alice_only}")

# 3. 전체 친구 목록 (합집합)
all_friends = alice_friends | bob_friends | charlie_friends
print(f"\n전체 친구 목록: {all_friends}")
print(f"총 친구 수: {len(all_friends)}")

# 4. Alice와 Bob 중 한 명만 아는 친구 (대칭 차집합)
exclusive_friends = alice_friends ^ bob_friends
print(f"\nAlice 또는 Bob만 아는 친구: {exclusive_friends}")

# 5. 세 사람 모두가 공통으로 아는 친구
common_all = alice_friends & bob_friends & charlie_friends
if common_all:
    print(f"\n세 사람 모두의 공통 친구: {common_all}")
else:
    print("\n세 사람 모두가 공통으로 아는 친구는 없습니다.")

# 추가: 각 친구별 인맥 수
print("\n=== 친구별 인맥 분석 ===")
for friend in sorted(all_friends):
    count = 0
    if friend in alice_friends:
        count += 1
    if friend in bob_friends:
        count += 1
    if friend in charlie_friends:
        count += 1
    print(f"{friend}: {count}명과 친구")

출력:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Alice와 Bob의 공통 친구: {'영희', '민수'}
Alice만 아는 친구: {'철수', '지영'}

전체 친구 목록: {'철수', '영희', '민수', '지영', '준호', '수진', '영수'}
총 친구 수: 7명

Alice 또는 Bob만 아는 친구: {'철수', '지영', '준호', '수진'}

세 사람 모두의 공통 친구: {'민수'}

=== 친구별 인맥 분석 ===
민수: 3명과 친구
수진: 1명과 친구
영수: 1명과 친구
영희: 2명과 친구
지영: 1명과 친구
준호: 2명과 친구
철수: 2명과 친구

🎯 연습 문제 핵심 포인트

  1. 중복 제거: set(list)로 리스트의 중복 요소 제거
  2. 집합 연산: 교집합(&), 합집합(|), 차집합(-), 대칭 차집합(^)
  3. 멤버십 검사: in 연산자로 요소 존재 확인 (O(1) 시간 복잡도)
  4. 세트 순회: 정렬이 필요하면 sorted(set)로 변환
  5. 집합 비교: issubset(), issuperset(), isdisjoint() 메서드
  6. 실전 활용: 데이터 정제, 중복 체크, 관계 분석에 세트 활용

📝 오늘 배운 내용 정리

  1. 세트: 중복 없고 순서 없는 자료구조
  2. 생성: set() 또는 {값1, 값2}
  3. 추가/삭제: add(), remove(), discard(), pop()
  4. 집합 연산: 합집합 |, 교집합 &, 차집합 -, 대칭 차집합 ^
  5. 용도: 중복 제거, 빠른 검색, 집합 연산
  6. 특징: 변경 불가능한 값만 저장, 순서 보장 안 됨

🔗 관련 자료


📚 이전 학습

Day 12: 딕셔너리 다루기 (dict)

어제는 딕셔너리(dict)로 키-값 쌍을 저장하는 방법을 배웠습니다!

📚 다음 학습

Day 14: 불린 (bool) ⭐⭐

내일은 참과 거짓을 다루는 불린 타입을 배웁니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 13/100 Phase 2: 자료형 마스터하기 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.