Python, set型で集合演算(和集合、積集合や部分集合の判定など)
Pythonでは、集合を表すset型が組み込みのデータ型として提供されている。
集合setは重複しない要素(同じ値ではない要素、ユニークな要素)のコレクションで、和集合・差集合・積集合などの集合演算を行うことができる。
- setオブジェクトを生成: {}, 集合内包表記
- setとlistやtupleを相互に変換: set(), list(), tuple()
- setに要素を追加: add()
- setから要素を削除: discard(), remove(), pop(), clear()
- 和集合: |演算子, union()
- 積集合(共通部分): &演算子, intersection()
- 差集合: -演算子, difference()
- 対称差集合: ^演算子, symmetric_difference()
- 部分集合か判定: <=演算子, issubset()
- 上位集合か判定: >=演算子, issuperset()
- 互いに素か判定: isdisjoint()
- イミュータブルな集合: frozenset
setはミュータブルで要素の追加・削除などが可能。最後に説明するように、setと同じく集合演算などのメソッドを持つがイミュータブルなfrozensetもある。
setやfrozensetに対しても、リストやタプルなどと同様に、組み込み関数len()で要素数を取得したり、in演算子で要素の存在確認をしたりできる。
setオブジェクトを生成: {}, 集合内包表記
set型のオブジェクトは波括弧{}で生成できる。カンマ区切りで要素を書く。
重複する値は無視されて、一意な値のみが要素として残る。また、setは順序をもたないので、生成時の順序は保持されない。
s = {3, 1, 2, 2, 3, 1, 4}
print(s)
# {1, 2, 3, 4}
print(type(s))
# <class 'set'>
異なる型を要素として持つこともできる。ただし、リストのようなミュータブル(更新可能)なオブジェクトは登録できない。タプルはイミュータブルなので可。
s = {1.23, 'abc', (0, 1, 2)}
print(s)
# {(0, 1, 2), 1.23, 'abc'}
# s = {[0, 1, 2]}
# TypeError: unhashable type: 'list'
異なる型でも値が等価であれば重複していると見なされる。例えば、真偽値boolは整数intのサブクラスであり、Trueは1、Falseは0と等価。
s = {1, 1.0, True}
print(s)
# {1}
空の波括弧{}は辞書dictと見なされるため、空のset(空集合)を生成するにはset()を使う。
s = set()
print(s)
# set()
print(type(s))
# <class 'set'>
リスト内包表記と同様に集合内包表記もある。角括弧[]ではなく波括弧{}を使う。
- 関連記事: Pythonリスト内包表記の使い方
s = {i**2 for i in range(5)}
print(s)
# {0, 1, 4, 9, 16}
setとlistやtupleを相互に変換: set(), list(), tuple()
set型のオブジェクトはコンストラクタset()でも生成できる。
引数にリストやタプルなどのイテラブルオブジェクトを指定すると、重複する要素が除外されて一意な値のみを要素とする集合setが生成される。
l = [2, 2, 3, 1, 3, 4]
print(set(l))
# {1, 2, 3, 4}
t = (2, 2, 3, 1, 3, 4)
print(set(t))
# {1, 2, 3, 4}
上の例のようにset()を利用してリストやタプルから重複した要素を取り除くことができるが、元の順序は保持されない。順序を保持したまま重複した要素を削除する方法などについては以下の記事を参照。
集合setをリストやタプルに変換するにはlist(), tuple()を使う。
s = {1, 2, 3}
print(list(s))
# [1, 2, 3]
print(tuple(s))
# (1, 2, 3)
setに要素を追加: add()
集合setに要素を追加するには、add()メソッドを使う。
s = {0, 1, 2}
s.add(3)
print(s)
# {0, 1, 2, 3}
setに別のsetを結合して要素を追加するには、後述の|演算子やunion()メソッドによる和集合を用いる。
setから要素を削除: discard(), remove(), pop(), clear()
集合setから要素を削除するには、discard(), remove(), pop(), clear()メソッドを使う。
discard()メソッドは引数に指定した要素を削除する。存在しない値を指定すると何もしない。
s = {0, 1, 2}
s.discard(1)
print(s)
# {0, 2}
s.discard(10)
print(s)
# {0, 2}
remove()メソッドも引数に指定した要素を削除するが、存在しない値を指定するとエラーKeyError。
s = {0, 1, 2}
s.remove(1)
print(s)
# {0, 2}
# s.remove(10)
# KeyError: 10
pop()メソッドは要素を削除し、その値を返す。どの要素を削除するかは選択できない。空集合だとエラーKeyError。
s = {0, 1, 2}
v = s.pop()
print(v)
# 0
print(s)
# {1, 2}
s = set()
# v = s.pop()
# KeyError: 'pop from an empty set'
clear()メソッドはすべての要素を削除し、空集合とする。
s = {0, 1, 2}
s.clear()
print(s)
# set()
和集合: |演算子, union()
和集合(合併、ユニオン)は|演算子またはunion()メソッドで取得できる。
s1 = {0, 1, 2}
s2 = {1, 2, 3}
s3 = {2, 3, 4}
print(s1 | s2)
# {0, 1, 2, 3}
print(s1.union(s2))
# {0, 1, 2, 3}
union()メソッドには複数の引数を指定できる。また、集合setだけでなく、set()でset型に変換できるリストやタプルなども引数に指定可能。以降のメソッドも同様。
print(s1.union(s2, s3))
# {0, 1, 2, 3, 4}
print(s1.union(s2, [5, 6, 5, 7, 5]))
# {0, 1, 2, 3, 5, 6, 7}
結果を左辺のオブジェクトに代入・更新する累算代入演算子|=およびupdate()メソッドもある。
s1 |= s2
print(s1)
# {0, 1, 2, 3}
s2.update(s3)
print(s2)
# {1, 2, 3, 4}
積集合(共通部分): &演算子, intersection()
積集合(共通部分、交差、インターセクション)は&演算子またはintersection()メソッドで取得できる。
s1 = {0, 1, 2}
s2 = {1, 2, 3}
s3 = {2, 3, 4}
print(s1 & s2)
# {1, 2}
print(s1.intersection(s2))
# {1, 2}
print(s1.intersection(s2, s3))
# {2}
累算代入は&=演算子およびintersection_update()メソッド。
s1 &= s2
print(s1)
# {1, 2}
s2.intersection_update(s3)
print(s2)
# {2, 3}
差集合: -演算子, difference()
差集合は-演算子またはdifference()メソッドで取得できる。
s1 = {0, 1, 2}
s2 = {1, 2, 3}
s3 = {2, 3, 4}
print(s1 - s2)
# {0}
print(s1.difference(s2))
# {0}
print(s1.difference(s2, s3))
# {0}
累算代入は-=演算子およびdifference_update()メソッド。
s1 -= s2
print(s1)
# {0}
s2.difference_update(s3)
print(s2)
# {1}
対称差集合: ^演算子, symmetric_difference()
対称差集合(どちらか一方にだけ含まれる要素の集合)は^演算子またはsymmetric_difference()メソッドで取得できる。論理演算における排他的論理和(XOR)に相当する。
これまでのメソッドと異なり、symmetric_difference()メソッドに指定できる引数は1つのみ。
s1 = {0, 1, 2}
s2 = {1, 2, 3}
s3 = {2, 3, 4}
print(s1 ^ s2)
# {0, 3}
print(s1.symmetric_difference(s2))
# {0, 3}
累算代入は^=演算子およびsymmetric_difference_update()メソッド。
s1 ^= s2
print(s1)
# {0, 3}
s2.symmetric_difference_update(s3)
print(s2)
# {1, 4}
部分集合か判定: <=演算子, issubset()
ある集合が別の集合の部分集合かを判定するには、<=演算子またはissubset()メソッドを使う。
s1 = {0, 1}
s2 = {0, 1, 2, 3}
print(s1 <= s2)
# True
print(s1.issubset(s2))
# True
<=演算子もissubset()メソッドも等価な集合に対してTrueを返す。真部分集合であるかを判定するには、等価な集合に対してFalseを返す<演算子を使う。
print(s1 <= s1)
# True
print(s1.issubset(s1))
# True
print(s1 < s1)
# False
上位集合か判定: >=演算子, issuperset()
ある集合が別の集合の上位集合かを判定するには、>=演算子またはissuperset()を使う。
s1 = {0, 1}
s2 = {0, 1, 2, 3}
print(s2 >= s1)
# True
print(s2.issuperset(s1))
# True
>=演算子もissuperset()メソッドも等価な集合に対してTrueを返す。真上位集合であるかを判定するには、等価な集合に対してFalseを返す>演算子を使う。
print(s1 >= s1)
# True
print(s1.issuperset(s1))
# True
print(s1 > s1)
# False
互いに素か判定: isdisjoint()
二つの集合が互いに素か(共通する要素がないか)を判定するには、isdisjoint()メソッドを使う。
s1 = {0, 1}
s2 = {1, 2}
s3 = {2, 3}
print(s1.isdisjoint(s2))
# False
print(s1.isdisjoint(s3))
# True
イミュータブルな集合: frozenset
これまで説明してきた通り、setはミュータブルで要素の追加・削除などが可能。イミュータブルな集合型であるfrozensetも提供されている。
コンストラクタfrozenset()にリストなどのイテラブルオブジェクトを指定して生成する。
fs = frozenset([2, 2, 3, 1, 3, 4])
print(fs)
# frozenset({1, 2, 3, 4})
print(type(fs))
# <class 'frozenset'>
要素を追加するadd()や削除するdiscard()メソッドは使えない。
# fs.add(5)
# AttributeError: 'frozenset' object has no attribute 'add'
# fs.discard(1)
# AttributeError: 'frozenset' object has no attribute 'discard'
和集合などの集合演算を行う演算子やメソッドはsetと同様なものが使用可能。
fs1 = frozenset([0, 1, 2])
fs2 = frozenset([1, 2, 3])
print(fs1 | fs2)
# frozenset({0, 1, 2, 3})
print(fs1.difference(fs2))
# frozenset({0})
print(fs1.isdisjoint(fs2))
# False
ミュータブルなsetは辞書のキーや他のsetの要素として使えないが、イミュータブルなfrozensetは使える。
s = {0, 1, 2}
fs = frozenset([0, 1, 2])
# d = {s: 100}
# TypeError: unhashable type: 'set'
d = {fs: 100}
print(d)
# {frozenset({0, 1, 2}): 100}
# ss = {s}
# TypeError: unhashable type: 'set'
ss = {fs}
print(ss)
# {frozenset({0, 1, 2})}