C# Tips | コレクション・LINQ:積集合

C# C#
スポンサーリンク

はじめに:「積集合」は“共通しているものだけを抜き出すフィルター”

業務でデータを扱っていると、こういう問いがよく出てきます。

「A システムと B システムの両方に登録されているユーザーは?」
「マスタにもファイルにも存在するコードだけを対象にしたい」
「複数条件を満たすものだけを抽出したい」

こういう「両方に共通して存在するもの」だけを取り出すのが、集合でいう「積集合」です。
C# / LINQ では Intersect を使うことで、積集合をシンプルに書けます。

ここから、初心者向けに

積集合のイメージ
Intersect の基本(値型・string)
オブジェクトでの積集合
実務での積集合の使いどころ

を、例題付きでかみ砕いて説明していきます。


積集合のイメージをつかむ

A ∩ B とは「A と B の両方にあるもの」

集合の世界で A ∩ B(A キャップ B)は、

「A にも B にも両方存在するもの全部」

という意味です。

例えば、

A = {1, 2, 3, 4}
B = {3, 4, 5}

なら、A ∩ B = {3, 4} です。
1 と 2 は A にしかない、5 は B にしかないので除外され、「両方に共通している 3 と 4」だけが残ります。

LINQ の Intersect は、この「A ∩ B」をそのままやってくれるメソッドです。


基本:数値・文字列の積集合を Intersect で求める

int の積集合

まずは一番シンプルな例から。

using System;
using System.Collections.Generic;
using System.Linq;

var a = new List<int> { 1, 2, 3, 4 };
var b = new List<int> { 3, 4, 5 };

var common = a.Intersect(b).ToList();

Console.WriteLine(string.Join(", ", common)); // 3, 4
C#

ここでの重要ポイントは、「a.Intersect(b) は“a と b の両方にある要素”を返す」ということです。
Intersect は「集合」として扱うので、重複は1つにまとめられます。

var x = new[] { 1, 1, 2, 3 };
var y = new[] { 1, 3, 3 };

var common2 = x.Intersect(y).ToList(); // 1, 3
C#

1 が何回出てきても、「共通しているかどうか」だけを見ます。

string の積集合(コードの共通部分)

商品コードの共通部分を取るイメージで見てみます。

var masterCodes = new[] { "A001", "A002", "A003" };
var fileCodes   = new[] { "A002", "A003", "A004" };

var commonCodes = masterCodes.Intersect(fileCodes).ToList();

Console.WriteLine(string.Join(", ", commonCodes)); // A002, A003
C#

「マスタにもファイルにも存在するコード」だけを取り出しています。
業務でよくある「両方にあるものだけを対象にしたい」場面そのものです。

ここでの重要ポイントは、「Intersect は“共通部分だけを残すフィルター”」だということです。
差集合(Except)が「片方にだけあるもの」を出すのに対し、積集合(Intersect)は「両方にあるもの」だけを出します。


オブジェクトの積集合:何をもって「同じ」とみなすか

そのまま Intersect すると「参照が同じかどうか」になる

クラスのリストに対して Intersect を使うときは注意が必要です。

public class User
{
    public string Id { get; set; } = "";
    public string Name { get; set; } = "";
}
C#
var list1 = new List<User>
{
    new User { Id = "U001", Name = "Sato" },
    new User { Id = "U002", Name = "Suzuki" },
};

var list2 = new List<User>
{
    new User { Id = "U002", Name = "Suzuki" },
    new User { Id = "U003", Name = "Tanaka" },
};
C#

見た目は同じ U002 ですが、別インスタンスです。
このまま Intersect すると、「参照が同じかどうか」で比較されるため、共通要素なしと判定されてしまいます。

var common = list1.Intersect(list2).ToList(); // 実は空になる
C#

ここでの重要ポイントは、「クラスの Intersect はデフォルトでは“参照比較”」だということです。
業務的には「Id が同じなら同じユーザー」とみなしたいので、そのままでは使えません。

プロジェクションしてから Intersect する

初心者に一番分かりやすいのは、「比較したいキーだけに変換してから積集合を取る」方法です。

var ids1 = list1.Select(x => x.Id);
var ids2 = list2.Select(x => x.Id);

var commonIds = ids1.Intersect(ids2).ToList(); // U002
C#

これで、「両方に存在するユーザー Id 一覧」が取れます。
必要なら、そこから元のリストに戻すこともできます。

var commonUsers = list1
    .Where(u => commonIds.Contains(u.Id))
    .ToList();
C#

ここでの重要ポイントは、「積集合を取りたいときは、“何をもって同一とみなすか”を先に決める」ことです。
Id なのか、コードなのか、複数項目の組み合わせなのか――それを Select で取り出してから Intersect する、という流れです。

複数項目の組み合わせで積集合を取りたいとき

例えば、「顧客コード+日付の組み合わせが共通しているものだけを取りたい」場合は、匿名型を使うと楽です。

var keys1 = list1.Select(x => new { x.CustomerCode, x.Date });
var keys2 = list2.Select(x => new { x.CustomerCode, x.Date });

var commonKeys = keys1.Intersect(keys2).ToList();
C#

匿名型は「全プロパティが同じなら同一」とみなされるので、
複数項目の組み合わせで積集合を取りたいときにとても便利です。


実務でよくある積集合のパターン

「両方に存在するコードだけを対象にする」

マスタとファイルの両方に存在するコードだけを処理したい、というパターンです。

var masterCodes = new[] { "A001", "A002", "A003" };
var fileCodes   = new[] { "A002", "A003", "A004" };

var targetCodes = masterCodes.Intersect(fileCodes).ToList(); // A002, A003
C#

この targetCodes を使って、後続の処理を絞り込めます。

var targetMaster = master
    .Where(m => targetCodes.Contains(m.Code))
    .ToList();
C#

ここでの重要ポイントは、「積集合で“対象範囲”を先に決めてから、詳細処理に進む」という設計です。
「そもそも両方に存在するものだけを扱う」と決めてしまうと、後のコードがかなりシンプルになります。

「複数条件を満たすユーザーだけを抽出する」イメージで使う

積集合は、「条件ごとの集合を作ってから、共通部分を取る」という考え方にも使えます。

例えば、

営業部に所属しているユーザー集合
権限“管理者”を持っているユーザー集合

の両方に属するユーザーだけを取りたい、という場合。

var salesUsers   = users.Where(u => u.Department == "Sales").Select(u => u.Id);
var adminUsers   = users.Where(u => u.Role == "Admin").Select(u => u.Id);

var targetUserIds = salesUsers.Intersect(adminUsers).ToList();
C#

「営業部かつ管理者」という条件を、
「営業部ユーザー集合 ∩ 管理者ユーザー集合」として表現しているイメージです。


積集合を使うときに意識してほしいこと

「どっちにもあるものだけでいいのか?」を先に決める

積集合を使う前に、必ず自分に問いかけてください。

両方に存在するものだけを対象にしてよいのか
片方にしかないものも、何らかの形で扱う必要があるのか

両方にあるものだけでよいなら、Intersect でスッキリ書けます。
片方にしかないものも気にするなら、差集合(Except)やマージ(Join)と組み合わせて設計する必要があります。

「何をもって同じとみなすか」を明文化する

積集合で一番大事なのは、ここです。

Id が同じなら同じとみなすのか
コードが同じなら同じとみなすのか
コード+日付の組み合わせが同じなら同じとみなすのか

これを日本語で言えるようにしてから、Select や匿名型でキーを作り、Intersect を呼ぶ。
この順番を守ると、「業務のルールがそのままコードに落ちている」状態になります。


まとめ:「積集合ユーティリティ」は“共通部分だけをクリアに見せるレンズ”

積集合の本質は、

「2つの世界のうち、“両方に共通して存在するもの”だけを浮かび上がらせる」

ことです。

押さえておきたいポイントは、

a.Intersect(b) は「a と b の両方にあるもの」
重複は1つにまとめられる(集合として扱う)
オブジェクトでは「何をもって同じとみなすか」を決めてから Select して Intersect
マスタ vs ファイル、システム A vs システム B の共通部分抽出にとても相性がいい

ここまで腹落ちしていれば、
「for で2重ループを回して if で共通要素を探す」コードから卒業して、
“意図がそのまま読める LINQ ベースの積集合処理”を、落ち着いて書けるようになります。

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