컬렉션 자료구조는 시퀀스 자료구조와 달리 데이터를 서로 연관시키지 않고 모아두는 컨테이너와 같습니다. 컬렉션 자료구조는 시퀀스 자료구조에서 사용할 수 있었던 속성 중 세 가지 속성을 지닙니다.
파이썬 내장 컬렉션 타입의 속성 |
1. 멤버십 연산 in |
2. 크기 함수 len(col) |
3. 반복성(iterable) |
즉, 시퀀스 자료구조의 인덱싱/슬라이싱을 제외한 나머지 속성을 사용할 수 있습니다.
파이썬 내장 컬렉션 자료형에는 셋(집합, set)과 딕셔너리(dictionary)가 있습니다. 이번 글에서는 파이썬의 셋에 대한 기본적인 운용을 다뤄보도록 하겠습니다.
목차
1. 셋 선언
2. 자료형 변환 set( )
3. 컬렉션 데이터 타입 속성-셋
(1). 멤버십 연산 in
(2). 크기 함수 len(seq)
(3). 반복성(iterable)
4. 셋 메서드와 연산자
(1) add( )
(2) update( )
(3) union( )
(4) intersection( )
(5) difference( )
(6) symmetric_difference( )
(7) clear( )
(8) discard( ), remove( ), pop( )
(9) issubset( )
셋
파이썬의 셋은 반복 가능(iterable)하고 가변(mutable)이며 중복 요소가 없고 정렬되지 않은 컬렉션 자료형입니다. 앞서 언급했듯 인덱스 연산은 할 수 없으며 멤버십 테스트 및 중복 항목 제거에 주로 사용되는 자료구조입니다.
셋의 운용과 관련해서 삽입 시간복잡도는 (정렬되지 않았으므로) O(1)이고 수학에서 익히 다뤘듯 합집합과 교집합 연산이 가능합니다. 합잡합의 시간 복잡도는 O(m+n), 교집합의 경우 두 셋 중 더 작은 셋에 대해서만 계산하므로 시간 복잡도는 O(n)입니다.
1. 셋 선언
셋은 일반정으로 중괄호 { }를 이용하여 선언합니다.
even_number = {0, 2, 4, 6, 8} print(even_number) print(type(even_number)) |
결과)
{0, 2, 4, 6, 8} <class 'set'> |
주의해야될 부분은 비어 있는 셋을 선언할 때는 { }로 하면 안됩니다. { }은 비어있는 딕셔너리(dictionary)로 다음 글에서 다룰 자료형이 됩니다. 비어 있는 셋을 선언할 때는 set( ) 함수를 이용해야 합니다.
empty_dictionary = { } empty_set = set( ) print(empty_dictionary) print(type(empty_dictionary)) print(empty_set) print(type(empty_set)) |
결과)
{} <class 'dict'> set() <class 'set'> |
2. 자료형 변환 set( )
반복 가능한 자료형은 셋 자료형으로 변환할 수 있습니다. 문자열, 튜플, 리스트, 심지어 다음 글에서 다룰 딕셔너리까지 셋 자료형으로 변환할 수 있으며, 이때 중복된 값은 버려집니다.
lett = 'letters' lett_set = set(lett) print(lett_set) |
결과)
{'r', 'e', 's', 't', 'l'} |
위의 결과에서 중복된 문자인 e와 t는 하나만 들어있다는 것에 주목하시면 됩니다.
name_list = ['전영근', '이명현', '김태영', '윤영', '윤영', '강정규', '홍진우', '전현우'] name_set = set(name_list) print(name_set) |
결과)
{'이명현', '김태영', '윤영', '강정규', '전현우', '홍진우', '전영근'} |
다음 글에서 다룰 딕셔너리도 셋으로 변환 가능합니다. 이때 딕셔너리의 키만 수집됩니다.
name_dictionary = {'icp': '윤영', '302': '김현수', '302': '명경규'} name_set = set(name_dictionary) print(name_set) |
결과)
{'302', 'icp'} |
3. 컬렉션 데이터 타입 속성-셋
(1). 멤버십 연산 in
객체 in 셋은 앞의 해당 객체가 셋 내에서 검색되는지를 확인합니다. 검색이 된다면 True를, 검색이 되지 않는다면 False를 반환합니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} print('윤영' in icp) |
결과)
True |
멤버십 연산은 셋을 이용하는 가장 일반적인 사용처입니다.
drinks = { 'martini' : {'vodka', 'vermouth'}, 'black russian' : {'vodka', 'kahlua'}, 'white russian' : {'cream', 'kahlua', 'vodka'}, 'manhattan' : {'rye', 'vermouth', 'bitters'}, 'screwdriver' : {'orange juice', 'vodka'} } for name, contents in drinks.items( ): if 'vodka' in contents: print(name) |
결과)
martini black russian white russian screwdriver |
drinks는 key와 value로 구성되어 있는 딕셔너리 객체입니다. key에는 칵테일 이름이, value에는 해당 칵테일에 사용되는 재료들이 셋의 자료형으로 들어와 있는 상태입니다.
for name, contents in drinks.items( ):
이 부분은 name과 contents를 drinks 딕셔너리의 key와 value에 각각 할당하는 언패킹입니다. 즉, 첫 번째 순서로
name = 'martini', contents = {'vodka', 'vermouth'}
가 할당됩니다. 다음으로 조건문
if 'vodka' in contents:
를 판단하며 이때 문자열 'vodka'가 contents에서 검색되는 지를 확인합니다. 확인되면
print(name)
name인 'martini'를 출력합니다. for 문이 가장 앞에 있으므로 그 다음 반복을 시행합니다. 이와 같이 딕셔너리와 셋을 이용하면 특정 값을 갖고 있는 객체가 어떤 것인지를 검색할 수 있습니다.
(2). 크기 함수 len(seq)
크기 함수 len(col)는 컬렉션의 길이를 정수로 반환합니다.
name_set = {'전영근', '이명현', '김태영', '윤영', '윤영', '강정규', '홍진우', '전현우'} print(len(name_set)) |
결과)
7 |
(3). 반복성(iterable)
셋을 이용하여 for이나 while과 같은 구문을 이용한 반복 처리가 가능합니다.
name_set = {'전영근', '이명현', '김태영', '윤영', '윤영', '강정규', '홍진우', '전현우'} for name in name_set: print(name) |
결과)
전영근 홍진우 전현우 김태영 강정규 이명현 윤영 |
셋은 위치가 지정되어 있지 않은 배열이기 때문에 위의 소스코드를 시행할 때마다 다른 결과를 얻게 됩니다.
4. 셋 메서드와 연산자
add( ), update( ), union( ), intersection( ), difference( ), clear( ), discard( ), remove( ), pop( )
(1) add( )
A.add(x): 셋 A에 객체 x가 없을 경우 추가합니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우'} print(id(icp)) icp.add('전현우') print(icp) print(id(icp)) |
결과)
1870707821256 {'홍진우', '강정규', '윤영', '전현우', '김태영', '이명현'} 1870707821256 |
셋은 가변 자료형이기 때문에 add( ) 메서드를 이용한 자료 삽입 이후에도 id( ) 함수의 결과가 같은 것을 확인할 수 있습니다. 즉, add( ) 메서드는 사용되는 셋 그 자체에 자료를 추가합니다.
(2) update( )
A.update(B): B를 A에 추가합니다. 합집합 연산으로 A |= B와같이 |= 연산자를 이용할 수 있습니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우'} mipro = {'전영근', '김현수', '명경규', '김태영'} print(id(icp)) icp.update(mipro) print(icp) print(id(icp)) |
결과)
2186256583368 {'홍진우', '윤영', '김현수', '이명현', '김태영', '명경규', '전영근', '강정규'} 2186256583368 |
icp에 mipro를 추가하여 icp의 원소가 달라졌어도 icp의 id( ) 값은 변하지 않았음에 주목해야 합니다. 즉, icp 셋 그 자체에 mipro의 원소들을 추가하는 것입니다. 또한 중복된 값은 자동적으로 제거됨을 확인할 수 있습니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우'} mipro = {'전영근', '김현수', '명경규', '김태영'} icp |= mipro print(icp) |
결과)
{'홍진우', '김현수', '김태영', '강정규', '윤영', '전영근', '이명현', '명경규'} |
(3) union( )
A.union(B): update( ) 메서드와 같지만 연산 결과를 복사본으로 반환합니다. A | B와 동일합니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우'} mipro = {'전영근', '김현수', '명경규', '김태영'} print(id(icp)) new_group = icp.union(mipro) print(icp) print(id(icp)) print(new_group) print(id(new_group)) |
결과)
2108623489736 {'홍진우', '윤영', '이명현', '김태영', '강정규'} 2108623489736 {'홍진우', '명경규', '김현수', '윤영', '이명현', '김태영', '강정규', '전영근'} 2108625178184 |
icp.union(mipro) 메서드를 실행하더라도 icp 셋에는 변화가 없으며, 새로 할당된 변수인 new_group이 합집합 결과임을 알 수 있습니다. 또한 icp와 new_group의 id( ) 값이 다르다는 것에 주목해야 합니다.
(4) intersection( )
A.intersection(B): A와 B의 교집합의 복사본을 반환합니다. A & B와 동일합니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} print(id(icp)) cross_group = icp.intersection(group302) print(icp) print(id(icp)) print(cross_group) print(id(cross_group)) |
결과)
1190209325768 {'전영근', '김태영', '홍진우', '윤영', '전현우', '이명현', '강정규'} 1190209325768 {'전영근', '김태영', '윤영'} 1190211014216 |
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} cross_group = icp & group302 print(cross_group) |
결과)
{'전영근', '윤영', '김태영'} |
(5) difference( )
A.difference(B): A와 B의 차집합의 복사본을 반환합니다. A - B와 동일합니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} print(id(icp)) diff_group = icp.difference(group302) print(icp) print(id(icp)) print(diff_group) print(id(diff_group)) |
결과)
1779400313544 {'윤영', '강정규', '홍진우', '전현우', '전영근', '김태영', '이명현'} 1779400313544 {'이명현', '전현우', '강정규', '홍진우'} 1779402001992 |
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} diff_group = icp - group302 print(diff_group) |
결과)
{'홍진우', '전현우', '강정규', '이명현'} |
(6) symmetric_difference( )
A.symmetric_difference(B): A와 B의 각 원소 중 중복(교집합)을 제외한 집합의 복사본을 반환합니다. A ^ B와 동일합니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} print(id(icp)) clear_group = icp.symmetric_difference(group302) print(icp) print(id(icp)) print(clear_group) print(id(clear_group)) |
결과)
1771451321032 {'전영근', '강정규', '이명현', '윤영', '김태영', '전현우', '홍진우'} 1771451321032 {'이명현', '강정규', '명경규', '전현우', '홍진우', '김현수'} 1771453009480 |
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} clear_group = icp ^ group302 print(clear_group) |
결과)
{'강정규', '이명현', '홍진우', '명경규', '김현수', '전현우'} |
(7) clear( )
A.clear( ): 셋 A의 모든 항목을 제거합니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} print(id(icp)) icp.clear( ) print(icp) print(id(icp)) |
결과)
1864805059272 set() 1864805059272 |
clear( ) 메서드는 셋 자체에서 삭제 작업을 하므로 위의 소스코드에서 icp 내 모든 값이 삭제되며, id( ) 값도 변하지 않습니다.
(8) discard( ), remove( ), pop( )
A.discard(x): A의 항목 x를 제거하며 반환값은 없습니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} print(id(icp)) order = icp.discard('윤영') print(order) print(icp) print(id(icp)) icp.discard('김현수') print(icp) |
결과)
1639175555784 None {'이명현', '김태영', '전현우', '강정규', '홍진우', '전영근'} 1639175555784 {'이명현', '김태영', '전현우', '강정규', '홍진우', '전영근'} |
discard( ) 메서드는 대상이 되는 셋 자체에서 시행되는 메서드이므로 icp.discard('윤영')이 실행된 결과를 order에 할당한 뒤 그 결과를 보기 위해 order를 출력하면 None을 얻을 수 있습니다. 반환값이 없다는 것은 이를 두고 한 말입니다. 앞에서 셋 자체에 시행되는 메서드들은 모두 이와 같습니다. 연산 결과를 복사본으로 반환하는 메서드들은 결과 그 자체가 새로운 변수에 할당됩니다. 또한 셋 내에 해당 항목이 없어도(icp.discard('김현수')) 시행 가능한 메서드임에 주목해야 합니다.
A.remove(x): A.discard(x)와 같지만 항목 x가 없을 경우 KeyError 예외를 발생시킵니다.
icp = {'전영근', '이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} print(id(icp)) order = icp.remove('윤영') print(order) print(icp) print(id(icp)) icp.remove('김현수') print(icp) |
결과)
icp.remove('김현수') KeyError: '김현수' 2940453259976 None {'전영근', '김태영', '홍진우', '전현우', '이명현', '강정규'} 2940453259976 |
A.pop( ): A에서 한 항목을 무작위로 제거하고 그 항목을 반환합니다. 셋이 비어있으면 KeyError 예외를 발생시킵니다.
name_set = {'윤영', '김현수', '명경규'} name_set.pop( ) print(name_set) name_set.pop( ) print(name_set) name_set.pop( ) print(name_set) name_set.pop( ) print(name_set) |
결과)
{'김현수', '명경규'} {'명경규'} set() |
(9) issubset( )
A.issubset(B): A가 B의 부분집합(subset)인지를 판별합니다. 맞으면 True, 아니면 False를 반환합니다. A <= B와 동일합니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} both_group = {'김태영', '윤영'} print(group302.issubset(icp)) print(both_group.issubset(icp)) |
결과)
False True |
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} group302 = {'전영근', '김태영', '윤영', '김현수', '명경규'} both_group = {'김태영', '윤영'} print(group302 <= icp) print(both_group <= icp) |
결과)
False True |
관련 연산자로 A < B, A >= B, A > B가 있습니다.
A < B: A가 B의 진부분집합임을 판별합니다. 진부분집합이란, 부분집합이되 동일하지 않은 집합을 말합니다.
icp = {'이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} some_mipro = {'이명현', '김태영', '윤영', '강정규', '홍진우', '전현우'} both_group = {'김태영', '윤영'} print(some_mipro < icp) print(both_group < icp) |
결과)
False True |
A >= B: A <= B의 반대입니다. B가 A의 부분집합임을 판별합니다.
A > B: A < B의 반대입니다. B가 A의 진부분집합임을 판별합니다.
'Python-기본 > Python-데이터 타입 및 자료 구조' 카테고리의 다른 글
Python-컬렉션 데이터 타입(2. 딕셔너리) (0) | 2021.01.12 |
---|---|
Python-3. 시퀀스 데이터 타입(3. 리스트) (0) | 2021.01.04 |
Python-3. 시퀀스 데이터 타입(2. 튜플) (0) | 2020.12.30 |
Python-3. 시퀀스 데이터 타입(1. 문자열) (0) | 2020.12.20 |
Python-2. 숫자 데이터 타입 다루기 (0) | 2020.12.14 |