概要(set の差集合は「片方だけにある要素」を取り出す基本操作)
差集合(difference)は、集合 A から集合 B に含まれる要素を取り除いた結果です。順序はなく、重複は自動的に排除されます。Python では演算子「-」またはメソッド set.difference(…) を使います。最重要ポイントは「差集合は順序つき(非対称)」ということ。A – B と B – A は別物です。
A = {"apple", "banana", "orange"}
B = {"banana", "grape", "orange"}
print(A - B) # {'apple'} (A にだけある)
print(B - A) # {'grape'} (B にだけある)
print(A.difference(B)) # 同じ結果
Python基本の使い方(ここが重要)
二集合の差(A – B)と複数集合の差(A – B – C)
差集合は「最初の集合から、後ろの集合に含まれるものをすべて除く」操作です。複数集合を一度に処理したいなら difference の可変引数が読みやすいです。
A = {1, 2, 3, 4}
B = {3, 4, 5}
C = {4, 6}
print(A - B) # {1, 2}
print(A.difference(B, C)) # {1, 2}(B と C をまとめて除く)
Python破壊的更新なら difference_update(メモリ節約)
結果だけを使うなら「元集合を直接“差集合”にする」更新が便利です。状態を持つ処理や大きな集合で有効です。
A = {"a", "b", "c", "d"}
A.difference_update({"b", "d"})
print(A) # {'a', 'c'}
Pythonリストやタプルは「set に変換」してから差集合
集合演算は set 同士が基本です。配列データは set(…) に変換し、必要に応じて並べ替えや型へ戻しましょう。
xs = ["coffee", "tea", "sugar", "tea"]
ys = ["tea", "milk", "coffee"]
only_xs = set(xs) - set(ys)
print(sorted(only_xs)) # ['sugar']
Python重要ポイントの深掘り(方向性・性能・型と境界挙動)
差集合は「方向がある」非対称演算
- 結論: A – B は「A にだけある要素」。B – A は「B にだけある要素」。
- 設計のコツ: 何を“基準”に見たいか(どちら側の片方だけを取りたいか)を明確にして、式の左右を間違えない。
大量データでは「小さい集合から先に差し引く」と速い
- 理由: 差し引きで候補が減るほど、後続の membership チェックが軽くなる。
- 実践: サイズ昇順に並べ、difference の可変引数でまとめて差し引く。
sets = [set(range(0, 100000, 2)), set(range(0, 100000, 3)), set(range(0, 100000, 5))]
sets.sort(key=len)
base, rest = sets[0], sets[1:]
res = base.difference(*rest)
Python値はハッシュ可能(不変)である必要がある
- OK: int, str, tuple, frozenset など。
- NG: list, dict のような可変オブジェクト。
A = {("x", 1), ("y", 2)}
B = {("y", 2)}
print(A - B) # {('x', 1)}
Python境界挙動(空集合・自身との差)
- 空と差: A – ∅ は A、A – A は ∅。
- 読みにくい分岐は不要: 仕様として覚えておけば、特別扱いせずに書ける。
print({1,2}.difference(set())) # {1, 2}
print({1,2}.difference({1,2})) # set()
Python実務での使いどころ(差分検出・棚卸し・否定条件)
データ差分の抽出(「新しく追加された/削除された」)
- 追加分: new_ids – old_ids
- 削除分: old_ids – new_ids
old = {101, 102, 103}
new = {102, 103, 104}
added = new - old # {104}
removed = old - new # {101}
Pythonフォルダの棚卸し(片方にしかないファイル名)
- 目的: どちらか一方にしかないファイルを特定して同期・削除の判断に使う。
from pathlib import Path
def names(root): return {p.name for p in Path(root).iterdir() if p.is_file()}
left_only = names("dirA") - names("dirB")
right_only = names("dirB") - names("dirA")
Python検索の否定条件(「この条件に当てはまらないもの」)
- 設計: universe – excluded の形で書くと読みやすい。対象集合(universe)を先に定義しておく。
all_ids = {101, 102, 103, 104}
excluded = {102, 104}
result = all_ids - excluded # {101, 103}
Pythonよくある落とし穴の回避(文字列・空集合・辞書ビュー)
文字列は「文字集合」になる。単語差を取りたいならトークン化
- 注意: set(“coffee”) は文字の集合。単語で扱うには split。
s1 = "coffee tea sugar"
s2 = "tea milk"
print(set(s1.split()) - set(s2.split())) # {'coffee', 'sugar'}
Python空集合は set()。{} は空辞書
- 初期化ミス: {} は dict。差集合の初期値は set() を使う。
empty = set()
Python辞書のキー・値の差集合は「view」を使う
- キー: dict.keys() は集合演算に対応。
- 値: dict.values() も多くは可能だが、重複に注意(集合化で重複は消える)。
A = {"id":1, "name":"hanako", "age":30}
B = {"id":1, "name":"taro", "city":"Tokyo"}
print(A.keys() - B.keys()) # {'age'}
Python例題で身につける(定番から一歩先まで)
例題1:CSV の列から「片方にしかない商品名」を抽出
import csv
def only_in_first(path, colA, colB):
with open(path, "r", encoding="utf-8", newline="") as f:
rows = list(csv.reader(f))
A = {r[colA] for r in rows if len(r) > colA}
B = {r[colB] for r in rows if len(r) > colB}
return A - B
# print(only_in_first("sales.csv", 0, 1))
Python例題2:拡張子の差(画像集合から“未対応拡張子”を除外)
images = {".png", ".jpg", ".gif"}
unsupported = {".tiff", ".bmp", ".gif"}
safe = images - unsupported # {'.png', '.jpg'}
Python例題3:多集合の差(順に差し引くより可変引数で明快に)
base = {"A","B","C","D"}
ban1 = {"B","X"}
ban2 = {"C"}
result = base.difference(ban1, ban2) # {'A','D'}
Python例題4:リストから差集合を作って出力整形
def diff_lists(xs, ys):
return sorted(set(xs) - set(ys))
print(diff_lists([1,2,2,3,4], [2,4,5])) # [1, 3]
Pythonまとめ
set の差集合は「片方(基準側)にだけある要素」を取り出す最短手段です。二集合なら「-」、複数なら difference、元集合の更新は difference_update が便利。差集合は非対称である点が最重要で、左右を間違えると結果が逆になります。配列は set へ変換、文字列はトークン化、辞書は keys()/values() のビューで扱う。大量データは小さい集合から先に差し引くと効率的。差集合を“差分検出と否定条件の基本”として習慣化すれば、同期、棚卸し、変更追跡、フィルタリングまで、短く正確で実務品質のコードが書けます。
