본문 바로가기
Python/기초

파이썬 셋(Set) 자료형, 이 글 하나로 정리

by 모두의 케빈 2023. 8. 22.

 

 

목차

 

파이썬 셋(Set) 자료형

 > 셋의 정의

 > 셋 선언하기

 > 셋 자료형의 중요한 특징

셋의 연산

 > 값 수정, 추가, 삭제

 > 집합 연산(교집합, 차집합, 합집합, 대칭차집합) 

셋과 관련된 기타 메서드

 > 자주 사용하지는 않지만 나름 유용한 메서드들

파이썬 셋 vs 파이썬 리스트 자료형

 > 셋의 속도가 빠른 이유

 > 셋과 리스트의 검색 속도 비교

 > 리스트가 셋 보다 빠른 경우

 

+ 기타 관련 글 추천(hashtable 구조)

 

 

 

 

파이선 셋(Set) 자료형


집합을 표현하는 셋(Set) 자료형

 

집합에 대해 기억하시나요? 집합은 앞에서 배운 컬렉션 자료형과 유사합니다. 숫자들이 집합 안에 모여 있습니다. 이 집합을 구현한 것이 파이썬의 셋(Set) 자료형입니다.

 

조금 다른 점이 있다면, 파이썬에서 셋(Set)은 숫자가 아닌 문자나 문자열도 포함할 수 있다는 점입니다. 셋의 선언은 중괄호로 합니다.

 

 

셋 자료형 선언하기

 

# 셋 선언
set_a = {1,2,3}
print(type(set_a))
 >>>>> 실행 결과: <class 'set'>

# 참고: 비어있는 셋 선언하기
set_a = set()

참고로 dict()라는 자료형도 중괄호를 사용하기 때문에 셋은 중괄호만으로는 선언할 수 없습니다.

 

 

셋 자료형의 첫 번째 특징: 중복을 허용하지 않는다.

 

셋 자료형에서 가장 중요한 특징은 원소들의 중복을 허용하지 않는다는 점입니다. 이러한 특성을 ‘unique 하다’라고 표현합니다. 예제를 보겠습니다.

# 셋은 중복을 허용하지 않는다.
set_a = {1,1,1,2,3}
set_b = {'abc','abc','de','d','a'}
print(set_a)
print(set_b)
 >>>>> 실행 결과
{1, 2, 3}
{'a', 'de', 'd', 'abc'}

 

 

 

 

셋 자료형의 두 번째 특징: 순서가 없다.

 

다른 중요한 특징은 셋에서는 순서의 개념이 없다는 점입니다. 셋에서 출력되는 값의 순서는 랜덤입니다. 우선 아래를 보시면 선언한 순서대로 값이 들어가 있지 않습니다.

 

set_a = {65,70,89,99,54,42,61,89,1,11,100}
set_a
 >>>>> 실행 결과: {1, 11, 42, 54, 61, 65, 70, 89, 99, 100}

 

이번에는 set_a의 원소를 for문을 사용하여 차례대로 출력해 보겠습니다. 출력 결과를 보면, 정의된 순서와 아무 관련이 없는 것을 볼 수 있습니다.

 

for i in set_a:
    print(i)
 >>>>> 실행 결과
65
1
99
100
..

 

 

셋 자료형의 세 번째 특징: 슬라이싱과 인덱싱이 불가능하다.

 

# 셋에서 인덱싱을 시도하면 아래와 같은 에러가 발생생
set_a = {65,70,89,99,54,42,61,89,1,11,100}
set_a[0]

 

 

셋 자료형의 개별 원소에 접근하기

 

그렇다면 셋의 개별 원소에 접근하려면 어떻게 해야 하느냐? 리스트로 형 변환하여 접근하는 것이 가장 일반적인 방법입니다.

# set_a의 인덱싱은 리스트로 형 변환해야 한다.
# set_a의 순서와 이를 list로 바꿨을 때 순서는 동일하다.
set_a = {65,70,89,99,54,42,61,89,1,11,100}
temp_list = list(set_a)

print(set_a)
print(temp_list)

 >>>>> 실행 결과
{65, 1, 99, 100, 70, 42, 11, 54, 89, 61}
[65, 1, 99, 100, 70, 42, 11, 54, 89, 61]

 

 

 

 

셋의 연산


 

추가, 삭제, 제거

 

셋은 중복을 허용하지 않기 때문에, 동일한 값이 이미 있다면, 새로운 값을 추가할 수 없습니다. 단, 기존에 동일한 값이 없다면 아래 방법으로 값을 추가할 수 있습니다.

 

 

셋 자료형에 개별 원소를 추가할 때는 add() 메서드를 활용합니다.

# add 메서드
set_a = {1,2,3,4,5}

set_a.add(7)
print(set_a)
 >>>>> 실행 결과: {1, 2, 3, 4, 5, 7}

 

여러 개의 값을 추가하고자 한다면, update() 메서드를 활용합니다. update 메서드의 인자는 컬렉션 자료형 형태로 전달해야 합니다.

# update 메서드
set_a = {1,2,3,4,5}

set_a.update([7,8,9])
print(set_a)
 >>>>> 실행 결과: {1, 2, 3, 4, 5, 7, 8, 9}

 

셋 자료형의 값을 제거하고 싶을 때는 remove 메서드를 활용합니다. 값이 있다면 제거하고, 값이 없다면 에러가 발생합니다.

# remove 메서드
set_a = {1,2,3,4,5}

set_a.remove(3)
set_a
 >>>>> 실행 결과: {1, 2, 4, 5}

 

 

 

 

없어도 에러가 안 나오게 하고 싶다면 discard() 메서드를 활용하세요.

 

# discard 메서드
set_a = {1,2,3,4,5}

set_a.remove(3)
set_a.discard(3) # 3이라는 값이 없지만, 에러가 발생하지 않음.

set_a
 >>>>> 실행 결과: {1, 2, 4, 5}

 

 

집합 연산

 

셋 자료형만의 독특한 연산이 있습니다. 셋은 집합의 개념과 유사하다고 말씀드렸는데요. 따라서 셋 자료형에서는 교집합, 합집합, 차집합에 대한 연산을 지원합니다.

 

# 교집합
set_a = {1,2,3,4,5}
set_b = {4,5,6,7,8}

set_a & set_b
 >>>>> 실행 결과: {4, 5}


# 합집합
set_a = {1,2,3,4,5}
set_b = {4,5,6,7,8}

set_a | set_b
 >>>>> 실행 결과: {1, 2, 3, 4, 5, 6, 7, 8}


# 차집합
set_a = {1,2,3,4,5}
set_b = {4,5,6,7,8}

set_a - set_b
 >>>>> 실행 결과: {1, 2, 3}


# 대칭 차집합
set_a = {1,2,3,4,5}
set_b = {4,5,6,7,8}

set_a ^ set_b
 >>>>> 실행 결과: {1, 2, 3, 6, 7, 8}

 

 

저는 집합 연산을 할 때는 기호를 사용하는데요. 아래처럼 명령어를 활용할 수 있으니, 참고해 주세요.

set_a = {1,2,3,4,5}
set_b = {4,5,6,7,8}

set_a.intersection(set_b)         # 교집합
set_a.difference(set_b)           # 차집합
set_a.union(set_b)                # 합집합
set_a.symmetric_difference(set_b) # 대칭 차집합

 

 

 

셋과 관련된 기타 메서드


 

자주 사용하지는 않지만, 셋과 관련된 기타 메서드들이 몇 개 있습니다. 아래 내용도 참고해서 공부하시면 도움이 될 듯하네요.

 

 

issubset() 메서드: 부분집합이면 True(참), 아니면 False(거짓) 반환

# issubset 메서드
set_a = {4,5,6}
set_b = {1,2,3,4,5,6}

set_a.issubset(set_b)
 >>>>> 실행 결과: True

 

 

issuperset () 메서드: 상위 집합이면 True(참), 아니면 False(거짓) 반환

# issuperset 메서드
set_a = {4,5,6}
set_b = {1,2,3,4,5,6}

set_a.issuperset(set_b)
 >>>>> 실행 결과: False

 

 

isdisjoint () 메서드: 교집합이 ‘있으면’ False(거짓), 없으면 True(참)

# isdisjoint 메서드
set_a = {4,5,6}
set_b = {1,2,3,4,5,6}

set_a.isdisjoint(set_b)
 >>>>> 실행 결과: False

 

 

 

 

파이썬 셋(Set) vs 파이썬 리스트(list) 자료형

셋의 검색 속도는 리스트보다 '압도적'으로 빠르다.

 

셋은 값을 저장할 때 hashtable 구조를 채택하고 있어서 값을 탐색하는 것이 빠릅니다. 셋은 해시 함수를 사용하여 임의의 원소를 숫자로 변환하고 “key : value” 쌍으로 맵핑하여 정보를 보관합니다. (hashtable의 구조에 대한 자세한 설명은 본문 아래 링크를 참고해 주세요.)

 

hashtable 구조의 장점은 값이 있는지, 없는지를 검색하는 등의 특정 연산에서 리스트 대비 속도가 월등하게 빠르다는 점입니다.

 

hashtable 구조에서는 값이 있는지 없는지를 확인하기 위해서는 key 값으로 한 번만 검색하면 됩니다. 그러나 리스트는 하나씩 값을 순회해야 하기 때문에 속도가 느릴 수밖에 없습니다.

 

 

리스트와 셋 자료형의 탐색 속도 비교

 

리스트와 셋의 검색 속도 차이를 비교해 보겠습니다. 우선 0부터 9,999,999까지 천 만개의 원소를 갖고 있는 list_a와 set_a를 선언합니다.

list_a = [i for i in range(10000000)]
set_a = set(list_a)

 

 

그리고 5,698,745 값이 있는지를 확인하는 in 연산을 하는 데 걸린 시간을 측정합니다. time.time()은 현재 시간을 기록합니다.

# set과 list의 in 연산자 처리 시간 비교

start_time = time.time()
print(5698745 in list_a)
print(f"list 연산 소요 시간: {time.time() - start_time}")

start_time = time.time()
print(5698745 in set_a)
print(f"set 연산 소요 시간: {time.time() - start_time}")

 >>>>> 실행 결과:
True
list 연산 소요 시간: 0.12418770790100098
True
set 연산 소요 시간: 0.0

 

 

소요 시간을 보면 list는 0.12초가 걸렸지만, set은 0.0초가 걸렸습니다. set이 놀랍도록 빠르네요. 물론, 리스트가 마냥 느린 것만은 아닙니다. 값을 순차적으로 조회해야만 하는 경우에는 리스트의 속도가 오히려 더 빠를 수 있습니다.

# 값을 순차적으로 '모두' 순회하는 경우에는 리스트가 더 빠를 수 있다.

start_time = time.time()
for i in list_a:
    pass
print(f"list 연산 소요 시간: {time.time() - start_time}")

start_time = time.time()
for i in set_a:
    pass
print(f"set 연산 소요 시간: {time.time() - start_time}")
 >>>>> 실행 결과:
list 연산 소요 시간: 0.45499634742736816
set 연산 소요 시간: 0.6410496234893799

 

 

 

아래 글도 읽어보세요.

 

 

파이썬 셋, 딕셔너리: hashtable 구조에 대하여

 

 

 

 

댓글