집합

집합 에서 Python

16개의 연습 문제

집합 소개

set은 _변경 가능_하고 순서가 없는 해시 가능한 객체의 모음이에요. 집합의 구성원은 서로 달라야 해요. 중복된 항목은 허용되지 않아요. 여러 가지 서로 다른 데이터 타입은 물론, tuples의 tuple처럼 중첩된 구조까지 담을 수 있어요. 모든 원소가 _해시 가능_하기만 하면 돼요. 집합에는 변경할 수 없는 frozenset 버전도 있어요.

집합은 다른 자료 구조나 항목 묶음에서 중복을 빠르게 제거할 때 가장 흔히 사용해요. 순서를 정하거나 중복을 추적할 필요가 없을 때 효율적으로 비교하는 데에도 써요.

다른 컬렉션 타입(딕셔너리, 배열, 튜플)처럼 sets도 다음을 지원해요:

  • for item in <set>으로 반복하기,
  • in과 not in으로 소속 여부 확인하기,
  • len()으로 길이 계산하기,
  • copy()로 얕은 복사하기.

sets은 다음을 지원하지 않아요:

  • 어떤 종류의 인덱싱도
  • 정렬이나 삽입을 통한 순서 지정
  • 슬라이싱
  • +를 통한 연결

set에서 소속 여부를 확인하는 것은 (평균적으로) 상수 시간 복잡도를 가져요. 반면 list나 string에서 소속 여부를 확인할 때는 데이터의 길이가 늘어날수록 시간 복잡도도 커져요. <set>.union(), <set>.intersection(), <set>.difference() 같은 메서드도 (평균적으로) 상수 시간 복잡도를 가져요.

집합 만들기

집합을 만드는 방법은 여러 가지지만, 가장 간단한 방법은 _집합 리터럴_을 선언하거나, set 클래스 생성자(set())를 사용하거나, _집합 컴프리헨션_을 사용하는 거예요.

집합 리터럴

set은 중괄호 {}와 원소 사이의 쉼표를 사용해 _집합 리터럴_로 바로 입력할 수 있어요. 중복은 조용히 무시돼요:

>>> one_element = {'➕'}
{'➕'}

>>> multiple_elements = {'➕', '🔻', '🔹', '🔆'}
{'➕', '🔻', '🔹', '🔆'}

>>> multiple_duplicates =  {'Hello!', 'Hello!', 'Hello!', 
                            '¡Hola!','Привіт!', 'こんにちは!', 
                            '¡Hola!','Привіт!', 'こんにちは!'}
{'こんにちは!', '¡Hola!', 'Hello!', 'Привіт!'}

집합 리터럴은 dict 리터럴과 같은 중괄호를 사용해요. 그래서 빈 set을 만들려면 set()을 사용해야 해요.

집합 생성자

set()(set 클래스의 생성자)은 어떤 iterable이든 인자로 전달해서 사용할 수 있어요. iterable의 원소는 하나씩 순회하면서 set에 개별적으로 추가돼요. 원소의 순서는 보존되지 않고, 중복은 조용히 무시돼요:

# To create an empty set, the constructor must be used.
>>> no_elements = set()
set()

# The tuple is unpacked & each element is added.  
# Duplicates are removed.
>>> elements_from_tuple = set(("Parrot", "Bird", 
                               334782, "Bird", "Parrot"))
{334782, 'Bird', 'Parrot'}

# The list is unpacked & each element is added.
# Duplicates are removed.
>>> elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 
                              7, 11, 7, 11, 13, 13])
{2, 3, 5, 7, 11, 13}

집합 컴프리헨션

lists와 dicts처럼, 집합도 _컴프리헨션_으로 만들 수 있어요:

# First, a list with duplicates
>>> numbers = [1,2,3,4,5,6,6,5,4,8,9,9,9,2,3,12,18]

# This set comprehension squares the numbers divisible by 3
# Duplicates are removed.
>>> calculated = {item**2 for item in numbers if item % 3 == 0}
{9, 36, 81, 144, 324}

집합을 만들 때 주의할 점

set 생성자는 "언패킹" 방식으로 동작하기 때문에, 문자열과 함께 사용하면 의외의 결과가 나올 수 있어요:

# String elements (Unicode code points) are 
# iterated through and added *individually*.
>>> elements_string = set("Timbuktu")
{'T', 'b', 'i', 'k', 'm', 't', 'u'}

# Unicode separators and positioning code points 
# are also added *individually*.
>>> multiple_code_points_string = set('अभ्यास')
{'अ', 'भ', 'य', 'स', 'ा', '्'}

기억해 두세요. 집합은 서로 다른 데이터 타입과 중첩된 데이터 타입을 담을 수 있지만, 모든 set 원소가 _해시 가능_해야 해요:

# Attempting to use a list for a set member throws a TypeError
>>> lists_as_elements = {['🌈','💦'], 
                        ['☁️','⭐️','🌍'], 
                        ['⛵️', '🚲', '🚀']}

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'


# Standard sets are mutable, so they cannot be hashed.
>>> sets_as_elements = {{'🌈','💦'}, 
                        {'☁️','⭐️','🌍'}, 
                        {'⛵️', '🚲', '🚀'}}

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'set'

하지만 frozenset() 타입을 사용하면 sets의 set을 만들 수 있어요:

# Frozensets don't have a literal form.
>>> set_1 = frozenset({'🌈','💦'})
>>> set_2 = frozenset({'☁️','⭐️','🌍'})
>>> set_3 = frozenset({'⛵️', '🚲', '🚀'})

>>> frozen_sets_as_elements = {set_1, set_2, set_3}
>>> frozen_sets_as_elements
{frozenset({'⛵️', '🚀', '🚲'}),
 frozenset({'🌈', '💦'}),
 frozenset({'☁️', '⭐️', '🌍'})}

집합 원소 추가하고 제거하기

원소는 <set>.add(<item>)과 <set>.remove(<item>) 메서드를 사용해 set에 추가하거나 제거할 수 있어요. .remove(<item>) 메서드는 항목이 set에 없으면 KeyError를 발생시켜요:

>>> creatures = {'crow', 'duck', 'fish', 'monkey', 'elephant'}
>>> creatures.add('beaver')
>>> creatures.remove('duck')
>>> creatures
{'beaver', 'crow', 'elephant', 'fish', 'monkey'}

# Trying to remove an item that is not present raises a KeyError
>>> creatures.remove('bear')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  KeyError: 'bear'

집합 원소를 제거하는 다른 방법들

  • <set>.discard(<item>)은 set에서 항목을 제거하지만, 항목이 없어도 KeyError를 발생시키지 않아요.
  • <set>.clear()은 집합의 모든 항목을 제거해요.
  • <set>.pop()은 임의의 항목을 제거하고 _반환_하며, set이 비어 있으면 KeyError를 발생시켜요.

집합 연산

집합에는 일반적으로 수학의 집합 연산을 흉내 내는 메서드가 있어요. 이런 메서드 중 대부분(전부는 아니에요)에는 대응하는 연산자가 있어요. 메서드는 보통 어떤 iterable이든 인자로 받지만, 연산자는 연산의 양쪽이 모두 sets 또는 frozensets이어야 해요.

집합 간 소속 확인

<set>.isdisjoint(<other_collection>) 메서드는 어떤 set의 원소가 다른 집합의 원소와 겹치는지 확인할 때 사용해요. 이 메서드는 어떤 iterable이나 set이든 인자로 받아요. 두 집합에 공통 원소가 없으면 True 값을, 원소가 공유되면 False 값을 반환해요.

# Both mammals and additional_animals are lists.
>>> mammals = ['squirrel','dog','cat','cow', 'tiger', 'elephant']
>>> additional_animals = ['pangolin', 'panda', 'parrot', 
                          'lemur', 'tiger', 'pangolin']

# Animals is a dict.
>>> animals = {'chicken': 'white',
               'sparrow': 'grey',
               'eagle': 'brown and white',
               'albatross': 'grey and white',
               'crow': 'black',
               'elephant': 'grey', 
               'dog': 'rust',
               'cow': 'black and white',
               'tiger': 'orange and black',
               'cat': 'grey',
               'squirrel': 'black'}
               
# Birds is a set.
>>> birds = {'crow','sparrow','eagle','chicken', 'albatross'}

# Mammals and birds don't share any elements.
>>> birds.isdisjoint(mammals)
True

# There are also no shared elements between 
# additional_animals and birds.
>>> birds.isdisjoint(additional_animals)
True

# Animals and mammals have shared elements.
# **Note** The first object needs to be a set or converted to a set
# since .isdisjoint() is a set method.
>>> set(animals).isdisjoint(mammals)
False

부분집합과 상위집합 확인

<set>.issubset(<other_collection>)은 <set>의 모든 원소가 <other_collection>에도 있는지 확인할 때 사용해요. 연산자 형태는 <set> <= <other_set>이에요:

# Set methods will take any iterable as an argument.
# All members of birds are also members of animals.
>>> birds.issubset(animals)
True

# All members of mammals also appear in animals.
# **Note** The first object needs to be a set or converted to a set
# since .issubset() is a set method.
>>> set(mammals).issubset(animals)
True

# Both objects need to be sets to use a set operator
>>> birds <= set(mammals)
False

# A set is always a loose subset of itself.
>>> set(additional_animals) <= set(additional_animals)
True

<set>.issuperset(<other_collection>)는 .issubset()의 반대예요. <other_collection>의 모든 원소가 <set>에도 있는지 확인할 때 사용해요. 연산자 형태는 <set> >= <other_set>이에요:

# All members of mammals also appear in animals.
# **Note** The first object needs to be a set or converted to a set
# since .issuperset() is a set method.
>>> set(animals).issuperset(mammals)
True

# All members of animals do not show up as members of birds.
>>> birds.issuperset(animals)
False

# Both objects need to be sets to use a set operator
>>> birds >= set(mammals)
False

# A set is always a loose superset of itself.
>>> set(animals) >= set(animals)
True

'진부분집합'과 '진상위집합'

<set> < <other_set>과 <set> > <other_set>은 _진부분집합_인지 확인할 때 사용해요. set이 진부분집합이려면 < 연산자에 대해 (<set> <= <other_set>) 그리고 (<set> != <other_set>)여야 해요.

set이 진상위집합이려면 > 연산자에 대해 (<set> >= <other_set>) 그리고 (<set> != <other_set>)여야 해요. 이 연산자들에는 대응하는 메서드가 없어요:

>>> animal_names = {'albatross','cat','chicken','cow','crow','dog',
                   'eagle','elephant','sparrow','squirrel','tiger'}

>>> animals_also = {'albatross','cat','chicken','cow','crow','dog',
                   'eagle','elephant','sparrow','squirrel','tiger'}

>>> mammals = {'squirrel','dog','cat','cow', 'tiger', 'elephant'}
>>> birds   = {'crow','sparrow','eagle','chicken', 'albatross'}

>>> mammals < animal_names
True

>>> animal_names > birds
True

# A set is not a *proper subset* if set == other set.
>>> animals_also < animal_names
False

# A set is never a *proper subset* of itself
>>> animals_also < animals_also
False

집합 합집합

<set>.union(*<other iterables>)는 <set>과 모든 <other iterables>의 원소를 담은 새 set을 반환해요. 이 메서드의 연산자 형태는 <set> | <other set 1> | <other set 2> | ... | <other set n>이에요.

>>> perennials = {'Asparagus', 'Broccoli', 'Sweet Potato', 'Kale'}
>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Summer Squash'}
>>> more_perennials = ['Radicchio', 'Rhubarb', 
                      'Spinach', 'Watercress']

# Methods will take any iterable as an argument.
>>> perennials.union(more_perennials)
{'Asparagus','Broccoli','Kale','Radicchio','Rhubarb',
'Spinach','Sweet Potato','Watercress'}

# Operators require sets.
>>> set(more_perennials) | perennials
{'Asparagus',
 'Broccoli',
 'Kale',
 'Radicchio',
 'Rhubarb',
 'Spinach',
 'Sweet Potato',
 'Watercress'}

집합 차집합

<set>.difference(*<other iterables>)는 원래 <set>의 원소 중 <others>에 없는 원소를 담은 새 set을 반환해요. 이 메서드의 연산자 버전은 <set> - <other set 1> - <other set 2> - ... - <other set n>이에요.

>>> berries_and_veggies = {'Asparagus', 
                          'Broccoli', 
                          'Watercress', 
                          'Goji Berries', 
                          'Goose Berries', 
                          'Ramps', 
                          'Walking Onions', 
                          'Blackberries', 
                          'Strawberries', 
                          'Rhubarb', 
                          'Kale', 
                          'Artichokes', 
                          'Currants'}

>>> veggies = ('Asparagus', 'Broccoli', 'Watercress', 'Ramps',
               'Walking Onions', 'Rhubarb', 'Kale', 'Artichokes')

# Methods will take any iterable as an argument.
>>> berries = berries_and_veggies.difference(veggies)
{'Blackberries','Currants','Goji Berries',
 'Goose Berries', 'Strawberries'}

# Operators require sets.
>>> berries_and_veggies - berries
{'Artichokes','Asparagus','Broccoli','Kale',
'Ramps','Rhubarb','Walking Onions','Watercress'}

집합 교집합

<set>.intersection(*<other iterables>)는 원래 set과 모든 <others>에 공통으로 있는 원소를 담은 새 set을 반환해요(다시 말해, 모든 것이 교차하는 set이에요). 이 메서드의 연산자 버전은 <set> & <other set> & <other set 2> & ... & <other set n>이에요

>>> perennials = {'Annatto','Asafetida','Asparagus','Azalea',
                 'Winter Savory', 'Broccoli','Curry Leaf','Fennel', 
                 'Kaffir Lime','Kale','Lavender','Mint','Oranges',
                 'Oregano', 'Tarragon', 'Wild Bergamot'}

>>> annuals = {'Corn', 'Zucchini', 'Sweet Peas', 'Marjoram', 
              'Summer Squash', 'Okra','Shallots', 'Basil', 
              'Cilantro', 'Cumin', 'Sunflower', 'Chervil', 
              'Summer Savory'}

>>> herbs = ['Annatto','Asafetida','Basil','Chervil','Cilantro',
            'Curry Leaf','Fennel','Kaffir Lime','Lavender',
            'Marjoram','Mint','Oregano','Summer Savory', 
            'Tarragon','Wild Bergamot','Wild Celery',
            'Winter Savory']


# Methods will take any iterable as an argument.
>>> perennial_herbs = perennials.intersection(herbs)
{'Annatto', 'Asafetida', 'Curry Leaf', 'Fennel', 'Kaffir Lime',
 'Lavender', 'Mint', 'Oregano', 'Wild Bergamot','Winter Savory'}

# Operators require both groups be sets.
>>> annuals & set(herbs)
 {'Basil', 'Chervil', 'Marjoram', 'Cilantro'}

집합 대칭 차집합

<set>.symmetric_difference(<other iterable>)는 <set> 또는 <other>에 있지만 둘 다에는 없는 원소를 담은 새 set을 반환해요. 이 메서드의 연산자 버전은 <set> ^ <other set>이에요.

>>> plants_1 = {'🌲','🍈','🌵', '🥑','🌴', '🥭'}
>>> plants_2 = ('🌸','🌴', '🌺', '🌲', '🌻', '🌵')


# Methods will take any iterable as an argument.
>>> fruit_and_flowers = plants_1.symmetric_difference(plants_2)
>>> fruit_and_flowers
{'🌸', '🌺', '🍈', '🥑', '🥭','🌻' }


# Operators require both groups be sets.
>>> fruit_and_flowers ^ plants_1
{'🌲',  '🌸', '🌴', '🌵','🌺', '🌻'}

>>> fruit_and_flowers ^ set(plants_2)
{'🥭', '🌴', '🌵', '🍈', '🌲', '🥑'}
Note

집합이 세 개 이상일 때 대칭 차집합을 구하면, 각 set에만 있는 원소와 함께 여러 집합이 공유하는 원소도 결과 set에 포함돼요(자세한 내용은 위키백과의 대칭 차집합 문서를 참고하세요).

여러 집합 각각에만 있는 항목만 얻으려면, 모든 2-집합 조합 사이의 교집합을 별도의 단계에서 모아서 제거해야 해요:

>>> one = {'black pepper','breadcrumbs','celeriac','chickpea flour',
           'flour','lemon','parsley','salt','soy sauce',
           'sunflower oil','water'}

>>> two = {'black pepper','cornstarch','garlic','ginger',
           'lemon juice','lemon zest','salt','soy sauce','sugar',
           'tofu','vegetable oil','vegetable stock','water'}

>>> three = {'black pepper','garlic','lemon juice','mixed herbs',
             'nutritional yeast', 'olive oil','salt','silken tofu',
             'smoked tofu','soy sauce','spaghetti','turmeric'}

>>> four = {'barley malt','bell pepper','cashews','flour',
            'fresh basil','garlic','garlic powder', 'honey',
            'mushrooms','nutritional yeast','olive oil','oregano',
            'red onion', 'red pepper flakes','rosemary','salt',
            'sugar','tomatoes','water','yeast'}

>>> intersections = (one & two | one & three | one & four | 
                     two & three | two & four | three & four)
...
{'black pepper','flour','garlic','lemon juice','nutritional yeast', 
'olive oil','salt','soy sauce', 'sugar','water'}

# The ^ operation will include some of the items in intersections, 
# which means it is not a "clean" symmetric difference - there
# are overlapping members.
>>> (one ^ two ^ three ^ four) & intersections
{'black pepper', 'garlic', 'soy sauce', 'water'}

# Overlapping members need to be removed in a separate step
# when there are more than two sets that need symmetric difference.
>>> (one ^ two ^ three ^ four) - intersections
...
{'barley malt','bell pepper','breadcrumbs', 'cashews','celeriac',
  'chickpea flour','cornstarch','fresh basil', 'garlic powder',
  'ginger','honey','lemon','lemon zest','mixed herbs','mushrooms',
  'oregano','parsley','red onion','red pepper flakes','rosemary',
  'silken tofu','smoked tofu','spaghetti','sunflower oil', 'tofu', 
  'tomatoes','turmeric','vegetable oil','vegetable stock','yeast'}
GitHub에서 편집 링크가 새 창이나 탭에서 열려요

집합 배우기

연습이 잠겨 있어요

집합 개념을 연습하려면 연습 문제 4개를 더 잠금 해제해요