Python | データ構造強化:set の差集合

Python Python
スポンサーリンク
  1. 概要(set の差集合は「片方だけにある要素」を取り出す基本操作)
  2. 基本の使い方(ここが重要)
    1. 二集合の差(A – B)と複数集合の差(A – B – C)
    2. 破壊的更新なら difference_update(メモリ節約)
    3. リストやタプルは「set に変換」してから差集合
  3. 重要ポイントの深掘り(方向性・性能・型と境界挙動)
    1. 差集合は「方向がある」非対称演算
    2. 大量データでは「小さい集合から先に差し引く」と速い
    3. 値はハッシュ可能(不変)である必要がある
    4. 境界挙動(空集合・自身との差)
  4. 実務での使いどころ(差分検出・棚卸し・否定条件)
    1. データ差分の抽出(「新しく追加された/削除された」)
    2. フォルダの棚卸し(片方にしかないファイル名)
    3. 検索の否定条件(「この条件に当てはまらないもの」)
  5. よくある落とし穴の回避(文字列・空集合・辞書ビュー)
    1. 文字列は「文字集合」になる。単語差を取りたいならトークン化
    2. 空集合は set()。{} は空辞書
    3. 辞書のキー・値の差集合は「view」を使う
  6. 例題で身につける(定番から一歩先まで)
    1. 例題1:CSV の列から「片方にしかない商品名」を抽出
    2. 例題2:拡張子の差(画像集合から“未対応拡張子”を除外)
    3. 例題3:多集合の差(順に差し引くより可変引数で明快に)
    4. 例題4:リストから差集合を作って出力整形
  7. まとめ

概要(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() のビューで扱う。大量データは小さい集合から先に差し引くと効率的。差集合を“差分検出と否定条件の基本”として習慣化すれば、同期、棚卸し、変更追跡、フィルタリングまで、短く正確で実務品質のコードが書けます。

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