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爆速講座

