【C#】LINQのToHashSetを活用したコレクションの重複排除と集合演算

C#において、配列やリストなどのコレクションから重複を取り除いたり、特定の集合演算を行ったりする際、HashSet<T>は非常に強力なクラスです。

特に.NET Framework 4.7.2および.NET Core 2.0以降で利用可能なLINQの拡張メソッドToHashSetを使用することで、既存のコレクションを効率的にHashSet<T>へ変換できます。ここでは、商品管理システムの在庫データを題材に、ToHashSetによる重複排除と、IntersectWithメソッドを用いた共通項目の抽出について解説します。


目次

ToHashSetメソッドの概要

ToHashSetは、IEnumerable<T>インターフェースを実装するコレクション(配列、Listなど)を、重複のない一意な要素のみを持つHashSet<T>に変換するメソッドです。

通常のToListToArrayと比較した際の主な利点は以下の通りです。

  1. 自動的な重複排除: 変換の過程で重複する要素が自動的に取り除かれます。
  2. 検索の高速化: ハッシュベースのルックアップにより、要素の検索(Containsなど)が高速に行えます。

実践的なコード例:在庫リストの照合

以下のコードは、複数の倉庫から取得した商品IDリストをもとに、重複を整理し、両方の倉庫に存在する共通の商品IDを抽出する例です。

ここでは、重複を含む配列からHashSet<T>を生成し、さらに別のリストとの積集合(共通部分)を求めています。

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

namespace InventoryManagement
{
    class Program
    {
        static void Main()
        {
            // シナリオ: 
            // 倉庫Aと倉庫Bの在庫チェックを行い、両方の倉庫に存在する商品IDを特定する。
            // データ入力ミスにより、リストには重複が含まれている可能性がある。

            // 倉庫Aの在庫データ(重複あり)
            var warehouseAInventory = new[] { 1001, 1002, 1005, 1001, 1003 };

            // 倉庫Bの在庫データ(重複なし)
            var warehouseBInventory = new[] { 1002, 1003, 1004, 1006 };

            // 1. ToHashSetを使用して、倉庫AのデータをHashSet<int>に変換
            // この時点で、重複していた "1001" は1つにまとめられます。
            HashSet<int> uniqueInventoryA = warehouseAInventory.ToHashSet();

            // 2. 比較対象として倉庫BのデータもHashSetに変換(またはそのまま利用)
            // ここでは比較用にToHashSetを使用します。
            HashSet<int> uniqueInventoryB = warehouseBInventory.ToHashSet();

            // 3. IntersectWithを使用して積集合を求める
            // uniqueInventoryAの内容が書き換わり、「両方に含まれる要素」のみが残ります。
            uniqueInventoryA.IntersectWith(uniqueInventoryB);

            // 結果の出力
            Console.WriteLine("--- 両方の倉庫に存在する商品ID ---");
            foreach (var productId in uniqueInventoryA)
            {
                Console.WriteLine($"Product ID: {productId}");
            }
        }
    }
}

実行結果

--- 両方の倉庫に存在する商品ID ---
Product ID: 1002
Product ID: 1003

技術的なポイントと注意点

1. LINQのIntersectとHashSetのIntersectWithの違い

C#で集合の共通部分(積集合)を求める方法は主に2つありますが、挙動が異なります。

  • IEnumerable.Intersect (LINQ):
    • 新しいシーケンス(IEnumerable<T>)を返します。
    • 元のコレクションは変更されません。
  • HashSet.IntersectWith:
    • 戻り値はvoidです。
    • メソッドを呼び出したインスタンス自身を書き換えます
    • メモリ割り当ての観点では、新しいコレクションを生成しないため効率的な場合がありますが、元のデータを保持したい場合は注意が必要です。

2. 参照型の扱い

上記の例ではint型(値型)を使用しましたが、自作クラスなどの参照型を扱う場合、ToHashSetや集合演算が正しく動作するためには、そのクラスでGetHashCodeEqualsメソッドを適切にオーバーライドするか、ToHashSetの引数にIEqualityComparer<T>を渡す必要があります。これを行わない場合、オブジェクトの参照(メモリアドレス)に基づいて比較が行われるため、プロパティの値が同じでも「別のオブジェクト」として扱われる可能性があります。

まとめ

ToHashSetメソッドを使用することで、簡潔な記述でコレクションの重複排除が可能となります。また、変換後のHashSet<T>が持つIntersectWithなどのメソッドを活用することで、複数のデータソース間の照合処理を効率的に実装できます。データの整合性チェックやフィルタリング処理において、積極的に活用すべき機能です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次