Python | データ構造強化:set の排他的論理和

Python Python
スポンサーリンク
  1. 概要(set の排他的論理和は「片方にしかない要素だけ」をまとめる)
  2. 基本の使い方(ここが重要)
    1. 二集合の排他的論理和(^)を最短で書く
    2. 複数集合の排他的論理和は symmetric_difference の連鎖か reduce
    3. 元集合を更新するなら symmetric_difference_update(破壊的)
  3. 重要ポイントの深掘り(式変形・性質・型と境界挙動)
    1. 式の意味と変形(和集合−積集合)
    2. 可換・結合が成り立つ(順序に依存しない)
    3. 値はハッシュ可能である必要がある
    4. 境界挙動(空集合・同一集合)
  4. 実務での使いどころ(両側差分・同期・検証)
    1. 追加と削除を“一度に”知りたいときの差分検出
    2. フォルダ同期で「片側にしかないファイル」を一括特定
    3. 検索の XOR 条件(どちらか一方に合致したもの)
  5. よくある落とし穴の回避(文字列・空集合・辞書ビュー)
    1. 文字列は「文字の集合」になる。単語で扱うならトークン化
    2. 空集合は set()、{} は空辞書
    3. 辞書のキー・値の排他的論理和はビューで扱う
  6. 例題で身につける(定番から一歩先まで)
    1. 例題1:基本の排他的論理和と式変形の確認
    2. 例題2:CSV の二列から「片方にしか出ない商品名」を抽出
    3. 例題3:ディレクトリ群で「どこかにしかない拡張子」を抽出
    4. 例題4:複数集合の排他的論理和で“奇数回出た要素”を抽出
  7. まとめ

概要(set の排他的論理和は「片方にしかない要素だけ」をまとめる)

排他的論理和(symmetric difference)は、集合 A と集合 B のうち「どちらか一方にしか含まれない要素」だけを取り出します。共通している要素は除外されます。Python では演算子「^」またはメソッド set.symmetric_difference(…) を使います。差集合と違い、順序(どちらを左に置くか)に依存しない点が重要です。

A = {"apple", "banana", "orange"}
B = {"banana", "grape", "orange"}
print(A ^ B)                          # {'apple', 'grape'}
print(A.symmetric_difference(B))      # 同じ結果
Python

基本の使い方(ここが重要)

二集合の排他的論理和(^)を最短で書く

「^」は見やすく、二集合の排他的論理和を一行で書けます。共通要素を除いた“両側の差分の合体”と覚えてください。

A = {1, 2, 3}
B = {3, 4}
print(A ^ B)                          # {1, 2, 4}
Python

複数集合の排他的論理和は symmetric_difference の連鎖か reduce

3つ以上をまとめたい場合は、連鎖して適用します(「^」は結合的なので順に畳み込めます)。

A = {1, 2}
B = {2, 3}
C = {3, 4}
res = A ^ B ^ C                       # {1, 4}
# あるいは
res2 = A.symmetric_difference(B).symmetric_difference(C)
Python

元集合を更新するなら symmetric_difference_update(破壊的)

結果だけ使いたい場面で便利です。状態を保つコードやメモリ削減に向きます。

A = {"a", "b", "c"}
A.symmetric_difference_update({"b", "d"})
print(A)                              # {'a', 'c', 'd'}
Python

重要ポイントの深掘り(式変形・性質・型と境界挙動)

式の意味と変形(和集合−積集合)

排他的論理和は次の関係式で理解すると応用が効きます。
A ^ B = (A | B) – (A & B)
先に「全部まとめる(和集合)」→「共通を引く(差集合)」という分解で考えられます。

A = {1,2,3}
B = {3,4}
print((A | B) - (A & B))              # {1,2,4}(A ^ B と同じ)
Python

可換・結合が成り立つ(順序に依存しない)

A ^ B = B ^ A(可換)、(A ^ B) ^ C = A ^ (B ^ C)(結合)が成り立ちます。差集合(A – B)が非対称なのと対照的です。

値はハッシュ可能である必要がある

集合の要素は不変・ハッシュ可能(int、str、tuple、frozenset など)。list や dict は入れられません。

A = {("x", 1), ("y", 2)}
B = {("y", 2), ("z", 3)}
print(A ^ B)                          # {('x', 1), ('z', 3)}
Python

境界挙動(空集合・同一集合)

A ^ ∅ は A、A ^ A は ∅。特別な分岐を書かずにそのまま使えます。

print({1,2}.symmetric_difference(set()))  # {1, 2}
print({1,2}.symmetric_difference({1,2}))  # set()
Python

実務での使いどころ(両側差分・同期・検証)

追加と削除を“一度に”知りたいときの差分検出

old と new の集合で、片方にしかない ID を一発で取得できます。追加と削除の両方の候補が同時にわかります。

old = {101, 102, 103}
new = {102, 103, 104}
diff = old ^ new                        # {101, 104}
Python

フォルダ同期で「片側にしかないファイル」を一括特定

左右どちらか片側だけに存在するファイル名を素早く抽出できます。

from pathlib import Path

def names(root): return {p.name for p in Path(root).iterdir() if p.is_file()}
only_one_side = names("dirA") ^ names("dirB")
Python

検索の XOR 条件(どちらか一方に合致したもの)

OR 条件では重複を含みますが、XOR は「二つの条件のうち片方にだけ合致」を集合演算で表現できます。

cheap   = {101, 102, 105}
popular = {102, 104, 105}
xor_ids = cheap ^ popular               # {101, 104}
Python

よくある落とし穴の回避(文字列・空集合・辞書ビュー)

文字列は「文字の集合」になる。単語で扱うならトークン化

文字列をそのまま set にすると各文字の集合になります。単語で排他的論理和を取りたいなら split などで分割しましょう。

s1 = "coffee tea sugar"
s2 = "tea milk coffee"
print(set(s1.split()) ^ set(s2.split()))   # {'sugar', 'milk'}
Python

空集合は set()、{} は空辞書

初期化ミスを避けるため、空集合は必ず set() を使います。

empty = set()
Python

辞書のキー・値の排他的論理和はビューで扱う

辞書同士のキー集合で「片側にしかないキー」を取るなら .keys() を使います。

A = {"id":1, "name":"hanako", "age":30}
B = {"id":1, "name":"taro",   "city":"Tokyo"}
print(A.keys() ^ B.keys())                # {'age', 'city'}
Python

例題で身につける(定番から一歩先まで)

例題1:基本の排他的論理和と式変形の確認

A = {"coffee", "tea", "sugar"}
B = {"tea", "milk"}
print(A ^ B)                         # {'coffee', 'sugar', 'milk'}
print((A | B) - (A & B))             # 同じ結果
Python

例題2:CSV の二列から「片方にしか出ない商品名」を抽出

import csv

def xor_items(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(xor_items("sales.csv", 0, 1))
Python

例題3:ディレクトリ群で「どこかにしかない拡張子」を抽出

from pathlib import Path

def ext_set(root: str) -> set[str]:
    return {p.suffix.lower() for p in Path(root).rglob("*") if p.is_file()}

xor_exts = ext_set("dirA") ^ ext_set("dirB")
print(sorted(xor_exts))
Python

例題4:複数集合の排他的論理和で“奇数回出た要素”を抽出

sets = [{1,2,3}, {2,3,4}, {3,4,5}]
res = set()
for s in sets:
    res ^= s
print(res)                             # すべての集合に対して奇数回現れた要素
Python

まとめ

set の排他的論理和は「片方にしかない要素」を一瞬で抽出するための基本操作です。二集合なら「^」、複数なら連鎖や symmetric_difference で扱えます。A ^ B = (A | B) – (A & B) の式変形を覚えると応用が利き、可換・結合が成り立つため順序に依存しません。配列は set に変換、文字列はトークン化、辞書は keys()/values() のビューで扱う。差分検出、フォルダ同期、XOR 条件の検索まで、排他的論理和を“両側差分の標準手段”として習慣化すれば、短く読みやすく、実務品質のコードが書けます。

タイトルとURLをコピーしました