Python複数辞書のキーに対する集合演算完全ガイド:共通・和・差・対称差の効率的な処理方法

 

Python辞書を扱う際、複数の辞書のキーを比較して「共通するキーは何か?」「片方にだけあるキーは?」といった集合演算が必要な場面は頻繁にあります。本記事では、辞書のキーに対する共通(積)・和・差・対称差などの集合演算を効率的に行う方法を、実践的なサンプルコードとともに詳しく解説します。

集合演算の基本概念

Python辞書のキーは集合(set)として扱うことができ、数学的な集合演算を適用できます。主な演算は以下の4種類です。

dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'b': 20, 'c': 30, 'd': 40}

# キーを集合として取得
keys1 = set(dict1.keys())
keys2 = set(dict2.keys())

print(f"辞書1のキー: {keys1}")  # {'a', 'b', 'c'}
print(f"辞書2のキー: {keys2}")  # {'b', 'c', 'd'}

共通部分(積集合)の取得

基本的な共通キーの取得

user_data = {'name': '太郎', 'age': 25, 'city': '東京'}
profile_data = {'name': '太郎', 'email': 'taro@example.com', 'age': 25}

# 共通するキーを取得
common_keys = set(user_data.keys()) & set(profile_data.keys())
print(f"共通キー: {common_keys}")  # {'name', 'age'}

# または intersection() メソッド
common_keys2 = set(user_data.keys()).intersection(profile_data.keys())
print(f"共通キー: {common_keys2}")  # {'name', 'age'}

共通キーの値を比較

dict1 = {'name': '太郎', 'age': 25, 'city': '東京'}
dict2 = {'name': '太郎', 'age': 30, 'city': '大阪'}

# 共通キーで値が同じものを検索
common_keys = set(dict1.keys()) & set(dict2.keys())
matching_values = {}
different_values = {}

for key in common_keys:
    if dict1[key] == dict2[key]:
        matching_values[key] = dict1[key]
    else:
        different_values[key] = (dict1[key], dict2[key])

print(f"値が同じ: {matching_values}")      # {'name': '太郎'}
print(f"値が異なる: {different_values}")    # {'age': (25, 30), 'city': ('東京', '大阪')}

複数辞書の共通キー

dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'b': 20, 'c': 30, 'd': 40}
dict3 = {'c': 300, 'd': 400, 'e': 500}

# 3つの辞書の共通キー
all_common = set(dict1.keys()) & set(dict2.keys()) & set(dict3.keys())
print(f"全て共通: {all_common}")  # {'c'}

# 関数化
def find_common_keys(*dicts):
    """複数辞書の共通キーを取得"""
    if not dicts:
        return set()
    
    common = set(dicts[0].keys())
    for d in dicts[1:]:
        common &= set(d.keys())
    return common

result = find_common_keys(dict1, dict2, dict3)
print(f"共通キー: {result}")  # {'c'}

和集合(全キーの統合)

基本的な和集合

config_default = {'host': 'localhost', 'port': 8080, 'debug': False}
config_user = {'port': 9000, 'timeout': 30}

# 全キーの取得
all_keys = set(config_default.keys()) | set(config_user.keys())
print(f"全キー: {all_keys}")  # {'host', 'port', 'debug', 'timeout'}

# または union() メソッド
all_keys2 = set(config_default.keys()).union(config_user.keys())
print(f"全キー: {all_keys2}")  # {'host', 'port', 'debug', 'timeout'}

辞書のマージ処理

def merge_with_priority(primary_dict, secondary_dict):
    """優先度付きで辞書をマージ"""
    # 全キーを取得
    all_keys = set(primary_dict.keys()) | set(secondary_dict.keys())
    
    merged = {}
    for key in all_keys:
        if key in primary_dict:
            merged[key] = primary_dict[key]  # 第一辞書を優先
        else:
            merged[key] = secondary_dict[key]
    
    return merged

base_config = {'host': 'localhost', 'port': 8080}
user_config = {'port': 9000, 'ssl': True, 'timeout': 30}

result = merge_with_priority(user_config, base_config)  # user_configを優先
print(result)  # {'port': 9000, 'ssl': True, 'timeout': 30, 'host': 'localhost'}

複数辞書の和集合

sales_q1 = {'太郎': 100, '花子': 120}
sales_q2 = {'花子': 130, '次郎': 110}
sales_q3 = {'次郎': 115, '美香': 140}

# 全営業担当者を取得
def get_all_keys(*dicts):
    """複数辞書の全キーを取得"""
    all_keys = set()
    for d in dicts:
        all_keys |= set(d.keys())
    return all_keys

all_salespeople = get_all_keys(sales_q1, sales_q2, sales_q3)
print(f"全営業担当者: {all_salespeople}")  # {'太郎', '花子', '次郎', '美香'}

差集合(一方にのみ存在するキー)

基本的な差集合

dict1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
dict2 = {'b': 20, 'c': 30, 'e': 50}

# dict1にだけあるキー
only_in_dict1 = set(dict1.keys()) - set(dict2.keys())
print(f"dict1のみ: {only_in_dict1}")  # {'a', 'd'}

# dict2にだけあるキー
only_in_dict2 = set(dict2.keys()) - set(dict1.keys())
print(f"dict2のみ: {only_in_dict2}")  # {'e'}

# または difference() メソッド
diff1 = set(dict1.keys()).difference(dict2.keys())
diff2 = set(dict2.keys()).difference(dict1.keys())

実用的な差分分析

last_month_users = {'user001', 'user002', 'user003', 'user004'}
this_month_users = {'user002', 'user003', 'user005', 'user006'}

# 先月から今月での変化分析
def analyze_user_changes(last_month, this_month):
    last_set = set(last_month)
    this_set = set(this_month)
    
    return {
        'left_users': last_set - this_set,      # 退会ユーザー
        'new_users': this_set - last_set,       # 新規ユーザー
        'retained_users': last_set & this_set   # 継続ユーザー
    }

changes = analyze_user_changes(last_month_users, this_month_users)
print(f"退会: {changes['left_users']}")      # {'user001', 'user004'}
print(f"新規: {changes['new_users']}")       # {'user005', 'user006'}
print(f"継続: {changes['retained_users']}")  # {'user002', 'user003'}

設定の差分チェック

default_config = {
    'host': 'localhost',
    'port': 8080,
    'debug': False,
    'ssl': False,
    'timeout': 30
}

current_config = {
    'host': 'production.example.com',
    'port': 443,
    'ssl': True,
    'cache': True
}

def config_diff_analysis(default, current):
    """設定の差分分析"""
    default_keys = set(default.keys())
    current_keys = set(current.keys())
    
    return {
        'missing_keys': default_keys - current_keys,    # 設定不足
        'extra_keys': current_keys - default_keys,      # 追加設定
        'common_keys': default_keys & current_keys      # 共通設定
    }

diff = config_diff_analysis(default_config, current_config)
print(f"不足設定: {diff['missing_keys']}")    # {'debug', 'timeout'}
print(f"追加設定: {diff['extra_keys']}")     # {'cache'}
print(f"共通設定: {diff['common_keys']}")    # {'host', 'port', 'ssl'}

対称差集合(排他的論理和)

基本的な対称差

dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'b': 20, 'c': 30, 'd': 40}

# どちらか一方にだけあるキー
symmetric_diff = set(dict1.keys()) ^ set(dict2.keys())
print(f"対称差: {symmetric_diff}")  # {'a', 'd'}

# または symmetric_difference() メソッド
sym_diff2 = set(dict1.keys()).symmetric_difference(dict2.keys())
print(f"対称差: {sym_diff2}")  # {'a', 'd'}

データ同期での活用

local_files = {'file1.txt', 'file2.txt', 'file3.txt', 'file5.txt'}
remote_files = {'file2.txt', 'file3.txt', 'file4.txt', 'file6.txt'}

def sync_analysis(local, remote):
    """ファイル同期分析"""
    local_set = set(local)
    remote_set = set(remote)
    
    return {
        'sync_needed': local_set ^ remote_set,      # 同期が必要なファイル
        'common': local_set & remote_set,           # 既に同期済み
        'local_only': local_set - remote_set,       # ローカルのみ
        'remote_only': remote_set - local_set       # リモートのみ
    }

sync_info = sync_analysis(local_files, remote_files)
print(f"同期必要: {sync_info['sync_needed']}")     # {'file1.txt', 'file4.txt', 'file5.txt', 'file6.txt'}
print(f"同期済み: {sync_info['common']}")          # {'file2.txt', 'file3.txt'}

バージョン比較での活用

version_1_features = {'login', 'profile', 'search', 'cart'}
version_2_features = {'login', 'profile', 'advanced_search', 'wishlist', 'cart'}

def feature_comparison(v1_features, v2_features):
    """機能比較分析"""
    v1_set = set(v1_features)
    v2_set = set(v2_features)
    
    return {
        'changed_features': v1_set ^ v2_set,        # 変更された機能
        'removed_features': v1_set - v2_set,       # 削除された機能
        'added_features': v2_set - v1_set,         # 追加された機能
        'unchanged_features': v1_set & v2_set      # 変更なし
    }

comparison = feature_comparison(version_1_features, version_2_features)
print(f"追加機能: {comparison['added_features']}")    # {'advanced_search', 'wishlist'}
print(f"削除機能: {comparison['removed_features']}")  # {'search'}
print(f"変更なし: {comparison['unchanged_features']}")# {'login', 'profile', 'cart'}

実践的な応用例

データベーステーブル比較

table_schema_old = {
    'id': 'INTEGER',
    'name': 'VARCHAR(100)',
    'email': 'VARCHAR(255)',
    'created_at': 'TIMESTAMP'
}

table_schema_new = {
    'id': 'INTEGER',
    'name': 'VARCHAR(150)',
    'email': 'VARCHAR(255)', 
    'phone': 'VARCHAR(20)',
    'updated_at': 'TIMESTAMP'
}

def schema_diff_analysis(old_schema, new_schema):
    """スキーマ差分分析"""
    old_keys = set(old_schema.keys())
    new_keys = set(new_schema.keys())
    
    common_keys = old_keys & new_keys
    
    # データ型の変更チェック
    type_changes = {}
    for key in common_keys:
        if old_schema[key] != new_schema[key]:
            type_changes[key] = (old_schema[key], new_schema[key])
    
    return {
        'added_columns': new_keys - old_keys,
        'dropped_columns': old_keys - new_keys,
        'type_changes': type_changes,
        'unchanged_columns': {k for k in common_keys if old_schema[k] == new_schema[k]}
    }

schema_diff = schema_diff_analysis(table_schema_old, table_schema_new)
print(f"追加カラム: {schema_diff['added_columns']}")       # {'phone', 'updated_at'}
print(f"削除カラム: {schema_diff['dropped_columns']}")     # {'created_at'}
print(f"型変更: {schema_diff['type_changes']}")           # {'name': ('VARCHAR(100)', 'VARCHAR(150)')}

API仕様比較

api_v1_endpoints = {
    '/users': 'GET',
    '/users/{id}': 'GET',
    '/users': 'POST',
    '/login': 'POST'
}

api_v2_endpoints = {
    '/users': 'GET',
    '/users/{id}': 'GET',
    '/users/{id}': 'PUT',
    '/users/{id}': 'DELETE',
    '/auth/login': 'POST',
    '/auth/logout': 'POST'
}

def api_version_comparison(v1_endpoints, v2_endpoints):
    """API仕様バージョン比較"""
    v1_keys = set(v1_endpoints.keys())
    v2_keys = set(v2_endpoints.keys())
    
    return {
        'deprecated_endpoints': v1_keys - v2_keys,      # 廃止されたエンドポイント
        'new_endpoints': v2_keys - v1_keys,             # 新しいエンドポイント
        'common_endpoints': v1_keys & v2_keys,          # 共通エンドポイント
        'breaking_changes': v1_keys ^ v2_keys           # 破壊的変更
    }

api_comparison = api_version_comparison(api_v1_endpoints, api_v2_endpoints)
print(f"廃止: {api_comparison['deprecated_endpoints']}")
print(f"新規: {api_comparison['new_endpoints']}")

権限管理システム

admin_permissions = {'read', 'write', 'delete', 'manage_users', 'system_config'}
editor_permissions = {'read', 'write', 'edit_content'}
viewer_permissions = {'read'}

def permission_analysis(*permission_sets):
    """権限分析"""
    if len(permission_sets) < 2:
        return {}
    
    # 全権限の取得
    all_permissions = set()
    for perm_set in permission_sets:
        all_permissions |= perm_set
    
    # 共通権限
    common_permissions = set(permission_sets[0])
    for perm_set in permission_sets[1:]:
        common_permissions &= perm_set
    
    return {
        'all_permissions': all_permissions,
        'common_permissions': common_permissions,
        'role_specific': {
            f'role_{i}': perm_set - common_permissions 
            for i, perm_set in enumerate(permission_sets)
        }
    }

perm_analysis = permission_analysis(admin_permissions, editor_permissions, viewer_permissions)
print(f"全権限: {perm_analysis['all_permissions']}")
print(f"共通権限: {perm_analysis['common_permissions']}")  # {'read'}
print(f"役割固有: {perm_analysis['role_specific']}")

パフォーマンス最適化

大量データでの効率的な集合演算

import timeit

# 大量データの準備
large_dict1 = {f'key_{i}': i for i in range(50000)}
large_dict2 = {f'key_{i}': i * 2 for i in range(25000, 75000)}

# 方法1: set()を使用
def method1_sets(d1, d2):
    return set(d1.keys()) & set(d2.keys())

# 方法2: keys()のビューオブジェクトを直接使用
def method2_keys(d1, d2):
    return d1.keys() & d2.keys()

# 方法3: 手動ループ(参考用)
def method3_manual(d1, d2):
    common = []
    for key in d1:
        if key in d2:
            common.append(key)
    return set(common)

# パフォーマンステスト(小規模データで実行)
small_dict1 = {f'key_{i}': i for i in range(1000)}
small_dict2 = {f'key_{i}': i * 2 for i in range(500, 1500)}

time1 = timeit.timeit(lambda: method1_sets(small_dict1, small_dict2), number=1000)
time2 = timeit.timeit(lambda: method2_keys(small_dict1, small_dict2), number=1000)

print(f"set()変換: {time1:.4f}秒")
print(f"keys()直接: {time2:.4f}秒")  # 通常はこちらが高速

メモリ効率的な処理

def memory_efficient_set_ops(dict1, dict2, operation='intersection'):
    """メモリ効率的な集合演算"""
    # ジェネレータを使用してメモリ使用量を抑制
    if operation == 'intersection':
        return (key for key in dict1 if key in dict2)
    elif operation == 'union':
        seen = set()
        for key in dict1:
            if key not in seen:
                seen.add(key)
                yield key
        for key in dict2:
            if key not in seen:
                seen.add(key)
                yield key
    elif operation == 'difference':
        return (key for key in dict1 if key not in dict2)

# 使用例
d1 = {'a': 1, 'b': 2, 'c': 3}
d2 = {'b': 20, 'c': 30, 'd': 40}

# ジェネレータとして取得(メモリ効率的)
common_gen = memory_efficient_set_ops(d1, d2, 'intersection')
common_list = list(common_gen)
print(f"共通キー: {common_list}")  # ['b', 'c']

エラーハンドリングと型安全性

安全な集合演算関数

from typing import Dict, Set, Any, Optional

def safe_key_intersection(*dicts: Dict[Any, Any]) -> Optional[Set[Any]]:
    """安全な共通キー取得"""
    if not dicts:
        return None
    
    try:
        # 最初の辞書のキーから開始
        result = set(dicts[0].keys()) if dicts[0] else set()
        
        # 他の辞書との積集合を計算
        for d in dicts[1:]:
            if not isinstance(d, dict):
                print(f"警告: 辞書ではないオブジェクトをスキップ: {type(d)}")
                continue
            result &= set(d.keys())
        
        return result
        
    except Exception as e:
        print(f"エラー: {e}")
        return None

# 使用例
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 20, 'c': 30}
invalid_input = "not a dict"

result = safe_key_intersection(dict1, dict2, invalid_input)
print(f"安全な結果: {result}")  # {'b'}

汎用的な集合演算クラス

class DictKeySetOperations:
    """辞書キー集合演算クラス"""
    
    @staticmethod
    def intersection(*dicts):
        """積集合(共通キー)"""
        if not dicts:
            return set()
        
        result = set(dicts[0].keys())
        for d in dicts[1:]:
            result &= set(d.keys())
        return result
    
    @staticmethod
    def union(*dicts):
        """和集合(全キー)"""
        result = set()
        for d in dicts:
            result |= set(d.keys())
        return result
    
    @staticmethod
    def difference(dict1, dict2):
        """差集合(dict1にのみ存在)"""
        return set(dict1.keys()) - set(dict2.keys())
    
    @staticmethod
    def symmetric_difference(dict1, dict2):
        """対称差集合(どちらか一方のみ)"""
        return set(dict1.keys()) ^ set(dict2.keys())
    
    @classmethod
    def analyze_all(cls, dict1, dict2):
        """全ての集合演算を実行"""
        return {
            'intersection': cls.intersection(dict1, dict2),
            'union': cls.union(dict1, dict2),
            'difference_1_2': cls.difference(dict1, dict2),
            'difference_2_1': cls.difference(dict2, dict1),
            'symmetric_difference': cls.symmetric_difference(dict1, dict2)
        }

# 使用例
ops = DictKeySetOperations()
d1 = {'a': 1, 'b': 2, 'c': 3}
d2 = {'b': 20, 'c': 30, 'd': 40}

analysis = ops.analyze_all(d1, d2)
for operation, result in analysis.items():
    print(f"{operation}: {result}")

まとめ

Python辞書のキーに対する集合演算は、データ比較や分析において非常に強力な機能です。適切に活用することで、効率的で読みやすいコードが書けます。

基本的な演算子

  • &(積集合): 共通するキー
  • |(和集合): すべてのキー
  • -(差集合): 一方にのみ存在するキー
  • ^(対称差集合): どちらか一方にのみ存在するキー

実用的な活用場面

  • データ同期とバージョン管理
  • 設定ファイルの差分分析
  • API仕様の変更管理
  • 権限システムの設計

パフォーマンスのポイント

  • dict.keys()のビューオブジェクトは直接集合演算可能
  • 大量データではメモリ効率を考慮
  • エラーハンドリングで安全性を確保

これらの技術を組み合わせることで、様々なデータ処理タスクを効率的かつ安全に実行できるでしょう。

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座