HashSet<T>と集合演算
HashSet<T>は、重複しない要素を高速に管理(追加、削除、検索)できるC#のコレクションです。
HashSet<T>の真の強力さは、AddやContainsといった基本的な操作だけでなく、数学的な「集合演算」を非常に効率的に実行できる点にあります。
- 和集合 (Union): 2つのセットのすべての要素(重複なし)
- 積集合 (Intersection): 2つのセットの両方に存在する共通の要素
- 差集合 (Difference): 片方のセットにだけ存在する要素
この記事では、HashSet<T>が提供する主要な集合演算メソッド(UnionWith, IntersectWith, ExceptWith)の使い方を解説します。
準備:サンプルHashSet<T>
string型のHashSet<T>を使用して、2つのグループ(developersとmanagers)が持つ権限(string)を管理するシナリオを例にします。
using System;
using System.Collections.Generic;
public class HashSetSetOpsExample
{
// セットの内容を分かりやすく出力するためのヘルパーメソッド
private static void PrintSet(string title, HashSet<string> set)
{
Console.WriteLine($"{title} ({set.Count}件): {{ {string.Join(", ", set)} }}");
}
public static void Main()
{
// 開発者チームが持つ権限
var developerPermissions = new HashSet<string>
{
"CodeAccess",
"TestEnvironments",
"DeployToStaging"
};
// マネージャーチームが持つ権限
var managerPermissions = new HashSet<string>
{
"DeployToStaging", // 開発者と共通
"ViewReports",
"ApproveChanges"
};
Console.WriteLine("--- 元の権限セット ---");
PrintSet("Developers", developerPermissions);
PrintSet("Managers ", managerPermissions);
}
}
出力結果(元のセット):
--- 元の権限セット ---
Developers (3件): { CodeAccess, TestEnvironments, DeployToStaging }
Managers (3件): { DeployToStaging, ViewReports, ApproveChanges }
重要な注意点:インスタンスの直接変更(インプレース)
これから紹介するUnionWith, IntersectWith, ExceptWithメソッドは、新しいHashSet<T>を返しません。
これらのメソッドは、呼び出し元のHashSet<T>インスタンス自体を直接変更(破壊)します。
そのため、元のセット(developerPermissions)を変更したくない場合は、以下のコード例のように、操作用のコピーをnew HashSet<T>(...)で作成してから、そのコピーに対してメソッドを実行する必要があります。
1. UnionWith (和集合)
setA.UnionWith(setB)は、setAにsetBのすべての要素を追加します。HashSet<T>の特性により、重複する要素は自動的に無視されます。
これは「A または B」(A ∪ B)に相当します。
UnionWith のコード例
// --- 1. UnionWith (和集合) ---
// 開発者とマネージャーの「どちらかが」持つ権限のすべて
// 元のセットを変更しないよう、コピーを作成
var allPermissions = new HashSet<string>(developerPermissions);
// allPermissions (コピー) に managerPermissions の要素を追加
allPermissions.UnionWith(managerPermissions);
Console.WriteLine("\n--- UnionWith (和集合) ---");
PrintSet("All Permissions", allPermissions);
出力結果:
--- UnionWith (和集合) ---
All Permissions (5件): { CodeAccess, TestEnvironments, DeployToStaging, ViewReports, ApproveChanges }
2. IntersectWith (積集合)
setA.IntersectWith(setB)は、setAから、setBには存在しない要素をすべて取り除きます。結果として、setAとsetBの両方に共通する要素だけが残ります。
これは「A かつ B」(A ∩ B)に相当します。
IntersectWith のコード例
// --- 2. IntersectWith (積集合) ---
// 開発者とマネージャーの「両方が」持つ共通の権限
var commonPermissions = new HashSet<string>(developerPermissions); // コピーを作成
// commonPermissions から、managerPermissions にも含まれる要素だけを残す
commonPermissions.IntersectWith(managerPermissions);
Console.WriteLine("\n--- IntersectWith (積集合) ---");
PrintSet("Common Permissions", commonPermissions);
出力結果:
--- IntersectWith (積集合) ---
Common Permissions (1件): { DeployToStaging }
3. ExceptWith (差集合)
setA.ExceptWith(setB)は、setAから、setBにも存在する要素をすべて取り除きます。
これは「A には存在するが B には存在しない」(A - B)に相当します。AとBの順序が非常に重要です。
ExceptWith のコード例
// --- 3. ExceptWith (差集合) ---
// (A - B) 開発者「だけが」持つ権限
var developerOnly = new HashSet<string>(developerPermissions); // コピーを作成
developerOnly.ExceptWith(managerPermissions); // マネージャーの権限を取り除く
Console.WriteLine("\n--- ExceptWith (差集合 A - B) ---");
PrintSet("Developer-Only", developerOnly);
// (B - A) マネージャー「だけが」持つ権限
var managerOnly = new HashSet<string>(managerPermissions); // コピーを作成
managerOnly.ExceptWith(developerPermissions); // 開発者の権限を取り除く
Console.WriteLine("\n--- ExceptWith (差集合 B - A) ---");
PrintSet("Manager-Only", managerOnly);
出力結果:
--- ExceptWith (差集合 A - B) ---
Developer-Only (2件): { CodeAccess, TestEnvironments }
--- ExceptWith (差集合 B - A) ---
Manager-Only (2件): { ViewReports, ApproveChanges }
まとめ
HashSet<T>が提供する集合演算メソッドは、2つのコレクション間の関係性を扱うロジックを非常に高速かつ簡潔に記述できます。
UnionWith(other): 和集合(両方の全要素)。setにotherを追加します。IntersectWith(other): 積集合(共通要素)。setからotherに無いものを削除します。ExceptWith(other): 差集合(setのみの要素)。setからotherと共通するものを削除します。
これらのメソッドはすべて元のHashSet<T>を直接変更するため、元のデータを保持したい場合は、必ずコピーを作成してから操作を行う必要があります。
