Pythonの集合型(set)は、重複のないデータを扱い、和集合や積集合などの集合演算を高速に行える便利な型です。
本記事では、初心者の方にもわかりやすいように、setの基本からunion
(和集合)とintersection
(積集合)の使い方、演算子|
と&
、そして破壊的操作との違いまで順を追って解説します。
実用例や注意点も含めて、すぐに現場で役立つ知識を身に付けましょう。
Pythonのsetと集合演算の基本
setの作り方と空集合の定義
setは重複を持たないコレクションで、波かっこ{}
でリテラルを作れます。
空集合はset()
で作る点が重要です。
{}
は辞書(dict)になるため注意します。
# 基本的なsetの作成
a = {1, 2, 3, 3, 2} # 重複は自動的に取り除かれます
b = {"apple", "banana"}
# 空集合は set() を使います。{} は空の辞書(dict)です。
empty_set = set()
empty_dict = {}
print(type(a), a)
print(type(empty_set), empty_set)
print(type(empty_dict), empty_dict)
<class 'set'> {1, 2, 3}
<class 'set'> set()
<class 'dict'> {}
リテラルで作る際は順序が保証されないため、出力順は未定です。
結果の見た目を安定させたいときはsorted
で整列してから表示するとよいです。
和集合と積集合の意味をシンプルに確認
和集合は「どちらか一方でも含まれる要素の全体」です。
積集合は「両方に共通する要素の集まり」です。
最初に概念をコードで実感しておきます。
A = {1, 2, 3}
B = {3, 4}
U = A.union(B) # 和集合
I = A.intersection(B) # 積集合
# 表示の安定化のためにソートして出力します
print("和集合:", sorted(U))
print("積集合:", sorted(I))
和集合: [1, 2, 3, 4]
積集合: [3]
集合演算の記号と読み方
数学の記号とPythonでの書き方、読み方を対応させておきます。
記号に慣れるとコードが直感的に読めます。
概念 | 数学記号 | 読み方 | Pythonメソッド | Python演算子 |
---|---|---|---|---|
和集合 | A ∪ B | わしゅうごう | A.union(B) | A | B |
積集合 | A ∩ B | せきしゅうごう | A.intersection(B) | A & B |
差集合(参考) | A − B | さしゅうごう | A.difference(B) | A - B |
対称差(参考) | A △ B | たいしょうさ | A.symmetric_difference(B) | A ^ B |
演算子版は算術ではなく集合演算として定義されており、整数に対する|
や&
はビット演算なので混同しないようにしましょう。
和集合の作り方: union()
unionの使い方と戻り値
set.union
は新しい集合を返す非破壊的操作です。
元の集合は変更されません。
引数には他の集合だけでなく、リストやタプルなどの反復可能オブジェクトも渡せます。
A = {1, 2, 3}
B = {3, 4}
C = [4, 5] # リストもOK
U = A.union(B, C) # A ∪ B ∪ C
print("Aはそのまま:", sorted(A))
print("和集合(新しい集合):", sorted(U))
Aはそのまま: [1, 2, 3]
和集合(新しい集合): [1, 2, 3, 4, 5]
演算子パイプの使い方
和集合は演算子|
でも記述できます。
可読性が高く、数学の記法にも近いので覚えておくと良いです。
A = {1, 2}
B = {2, 3}
C = {3, 4}
U1 = A | B # 2つの和集合
U2 = A | B | C # 3つを連結して和集合
U3 = A.union(B).union(C) # メソッド連鎖でも同じ結果
print(sorted(U1), sorted(U2), sorted(U3))
[1, 2, 3] [1, 2, 3, 4] [1, 2, 3, 4]
両側がsetである必要があります。
片側がリストなどの場合は先にset()
で包みます。
A = {1, 2}
L = [2, 3]
# setに変換してから演算します
U = A | set(L)
print(sorted(U))
[1, 2, 3]
複数の集合で和集合を求める
union
は可変長引数を取るため、複数の集合を一度に渡せます。
演算子|
は連結で表現します。
A = {1, 2}
B = {2, 3}
C = {3, 4}
D = {0, 4}
U_method = A.union(B, C, D)
U_op = A | B | C | D
print(sorted(U_method))
print(sorted(U_op))
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
破壊的操作のupdateとの違い
update
は集合をその場で更新する破壊的操作です。
戻り値はNone
で、対象の集合自体が書き換わります。
A = {1, 2}
B = {2, 3}
U = A.union(B) # 非破壊: 新しい集合を返す
print("unionの戻り値:", sorted(U))
print("Aは変化なし:", sorted(A))
ret = A.update(B) # 破壊的: Aを書き換える、戻り値はNone
print("updateの戻り値:", ret)
print("Aは変化あり:", sorted(A))
unionの戻り値: [1, 2, 3]
Aは変化なし: [1, 2]
updateの戻り値: None
Aは変化あり: [1, 2, 3]
項目 | union | update |
---|---|---|
変更対象 | 新しい集合を作る | 自分自身を更新する |
戻り値 | 新しいset | None |
使いどころ | 元を残したい | 累積して集めたい |
累積処理ではupdate
が効率的です。
元を保ちたいときはunion
を選びます。
積集合の作り方: intersection()
intersectionの使い方と戻り値
set.intersection
は共通要素のみを取り出した新しい集合を返します。
こちらも非破壊です。
A = {1, 2, 3, 4}
B = {3, 4, 5}
I = A.intersection(B)
print(sorted(I))
print("Aは変化なし:", sorted(A))
[3, 4]
Aは変化なし: [1, 2, 3, 4]
演算子アンパサンドの使い方
積集合は演算子&
で書くのが簡潔です。
複数集合でも連結して書けます。
A = {1, 2, 3, 4}
B = {2, 4, 6}
C = {0, 2, 4, 8}
I1 = A & B
I2 = A & B & C # 3つの積集合
print(sorted(I1))
print(sorted(I2))
[2, 4]
[2, 4]
複数の集合で積集合を求める
可変長引数版を使うと、共通部分を一度に求められます。
A = {1, 2, 3, 4, 5}
B = {2, 3, 5, 7}
C = {0, 2, 3, 5, 8}
I_method = A.intersection(B, C)
I_chain = A.intersection(B).intersection(C)
print(sorted(I_method))
print(sorted(I_chain))
[2, 3, 5]
[2, 3, 5]
破壊的操作のintersection_updateとの違い
intersection_update
は自身を共通要素だけに絞る破壊的操作です。
戻り値はNone
で、元の集合が書き換わります。
A = {1, 2, 3, 4}
B = {2, 4, 6}
I = A.intersection(B) # 非破壊
print("intersection:", sorted(I))
print("Aは変化なし:", sorted(A))
ret = A.intersection_update(B) # 破壊的
print("intersection_updateの戻り値:", ret)
print("Aは共通要素だけに:", sorted(A))
intersection: [2, 4]
Aは変化なし: [1, 2, 3, 4]
intersection_updateの戻り値: None
Aは共通要素だけに: [2, 4]
例と注意点で身に付ける
タグやカテゴリで共通部分と全体を求める
ブログ記事のタグを例に、全記事で使われたタグ(積集合)と、どれかで使われたタグ(和集合)を求めます。
post1_tags = {"python", "set", "beginner"}
post2_tags = {"python", "tips", "set"}
post3_tags = {"python", "performance"}
# どれかで使われたタグ(和集合)
all_tags = post1_tags | post2_tags | post3_tags
# すべての記事で共通のタグ(積集合)
common_tags = post1_tags & post2_tags & post3_tags
print("和集合(全体):", sorted(all_tags))
print("積集合(共通):", sorted(common_tags))
和集合(全体): ['beginner', 'performance', 'python', 'set', 'tips']
積集合(共通): ['python']
複数の集合がリストに入っている場合は、functools.reduce
や反復でまとめる方法があります。
更新型のupdate
やintersection_update
を使うと効率よく累積できます。
from functools import reduce
tag_sets = [
{"python", "set", "beginner"},
{"python", "tips", "set"},
{"python", "performance"},
]
# reduceで和集合
all_tags = reduce(lambda acc, s: acc | s, tag_sets, set())
# 先頭を基準に積集合
common_tags = set(tag_sets[0])
for s in tag_sets[1:]:
common_tags.intersection_update(s) # 破壊的に絞り込み
print("和集合:", sorted(all_tags))
print("積集合:", sorted(common_tags))
和集合: ['beginner', 'performance', 'python', 'set', 'tips']
積集合: ['python']
リストをsetに変換して比較する
重複を含むリスト同士の共通要素や全体を調べたいとき、setに変換してから集合演算すると簡潔で高速です。
list_A = ["alice", "bob", "alice", "carol"]
list_B = ["bob", "dave", "bob", "erin"]
set_A = set(list_A) # {'alice', 'bob', 'carol'}
set_B = set(list_B) # {'bob', 'dave', 'erin'}
union_AB = set_A | set_B
intersect_AB = set_A & set_B
print("ユーザー全体(和集合):", sorted(union_AB))
print("共通ユーザー(積集合):", sorted(intersect_AB))
ユーザー全体(和集合): ['alice', 'bob', 'carol', 'dave', 'erin']
共通ユーザー(積集合): ['bob']
順序が必要な場合は、最後にsorted
やlist
で整形します。
set自体は順序を持ちません。
可読性とパフォーマンスのコツ
集合演算を読みやすく、かつ効率よく書くためのポイントをいくつか挙げます。
箇条書きに頼らず、それぞれの意図を説明します。
まず、単純な2〜3集合の演算は演算子|
や&
を使うと読みやすくなります。
数学の記法に近いため、ロジックがひと目で伝わります。
一方で、引数が可変であったり、別の型(リストやタプル)も同時に扱いたい場合は、union
やintersection
のメソッド版が柔軟です。
次に、ループで集合を累積していく場面では、非破壊で新しい集合を毎回生成するよりも、update
やintersection_update
を使ってその場で更新するほうがメモリ効率と速度の面で有利です。
特に大規模データでは差が出ます。
初期値の選び方も重要で、和集合の累積なら空集合set()
から、積集合の累積なら最初の集合を基準にintersection_update
で絞っていくのが定石です。
また、出力の安定性が必要なら、sorted
で並べ替えてから表示します。
テストコードでも順序の差分で失敗しないように、比較時にはset同士で比較するか、比較直前でソート済みリストに変換すると良いです。
最後に、型の混在で思わぬ例外を避けるため、演算子|
や&
を使う際は両オペランドがsetになっているかを確認し、必要に応じてset()
で明示的に変換します。
片方が空集合のときの結果を確認
空集合との演算は境界条件としてよく出ます。
結果を事前に知っておくと安全です。
A = {1, 2, 3}
E = set() # 空集合
print("A ∪ ∅:", sorted(A | E))
print("A ∩ ∅:", sorted(A & E))
print("破壊的updateでAに∅を足す:", sorted((A.copy()).update(E) or A)) # 戻り値Noneに注意
print("破壊的intersection_updateでAと∅の共通:", (A.copy()).intersection_update(E) or sorted(A.copy()))
A ∪ ∅: [1, 2, 3]
A ∩ ∅: []
破壊的updateでAに∅を足す: [1, 2, 3]
[]
上の例では、update
やintersection_update
の戻り値がNone
である点にも着目してください。
intersection_update
の直後は集合自体が空になります。
まとめ
Pythonのsetは、データの重複排除と高速な集合演算を同時に実現する強力な道具です。
和集合はunion
または|
、積集合はintersection
または&
で求められ、非破壊か破壊的かでunion
/intersection
とupdate
/intersection_update
を使い分けます。
複数集合の演算では、メソッドの可変長引数や演算子の連結が便利です。
実務では、タグの統合やユーザー集合の比較などに直結し、パフォーマンスの観点では累積には破壊的更新が有利です。
空集合や型の混在、表示順の安定化といった注意点も押さえつつ、読みやすいコードを心がけることで、集合演算を安全かつ効率的に活用できます。