【C#】HashSetの基本:重複しない要素を高速に管理するコレクション

目次

HashSet<T>とは何か?

C#のSystem.Collections.Generic名前空間には、List<T>Dictionary<TKey, TValue>といった便利なコレクションが用意されています。その中でもHashSet<T>は、ユニークな(重複しない)要素を管理することに特化したコレクションです。

HashSet<T>の主な特徴は以下の2点です。

  1. 重複の自動的な排除: 同じ要素を2回Add(追加)しようとしても、無視されます(エラーにはなりません)。HashSet<T>内には、常に一意な要素だけが格納されます。
  2. 非常に高速な検索: Dictionaryがキーを高速に検索するのと同様に、HashSet<T>Containsメソッド(要素が含まれているか)の実行が非常に高速です。List<T>.Containsがリストの先頭から順に探す(要素数に比例して遅くなる)のに対し、HashSet<T>.Containsは要素数に関わらずほぼ一定の速度で動作します。

一方で、List<T>とは異なり、HashSet<T>は要素の順序を保証しません(追加した順序で保持されません)。


HashSet<T>の基本的な使い方

HashSet<T>は、タグの管理、重複のないIDリストの作成、既知のアイテムの高速なチェックなどに最適です。

1. 初期化と Add (重複の無視)

new HashSet<T>()でインスタンス化します。コレクション初期化子({ ... })で初期値を設定することもできます。

Addメソッドは、要素の追加を試み、bool値(追加に成功したか=まだ存在しなかったか)を返します。

using System;
using System.Collections.Generic; // HashSet<T> を使うために必要

public class HashSetAddExample
{
    public static void Main()
    {
        // 1. HashSet<string> の初期化
        var uniqueTags = new HashSet<string>
        {
            "C#", "Programming", ".NET"
        };

        Console.WriteLine($"--- 初期状態 ({uniqueTags.Count}件) ---");
        PrintSet(uniqueTags);

        // 2. 新しい要素の追加 (成功する)
        bool addedNew = uniqueTags.Add("LINQ");
        Console.WriteLine($"\n\"LINQ\" の追加成功: {addedNew} (True)");

        // 3. 重複した要素の追加 (無視される)
        bool addedDuplicate = uniqueTags.Add("C#");
        Console.WriteLine($"\"C#\" の追加成功: {addedDuplicate} (False)");

        Console.WriteLine($"\n--- 追加後の状態 ({uniqueTags.Count}件) ---");
        PrintSet(uniqueTags); // "C#" は1つしか存在しない
    }
    
    // HashSet の内容を出力するヘルパーメソッド
    private static void PrintSet(HashSet<string> set)
    {
        Console.WriteLine(string.Join(", ", set));
    }
}

出力結果(順序は保証されません):

--- 初期状態 (3件) ---
C#, Programming, .NET

"LINQ" の追加成功: True
"C#" の追加成功: False

--- 追加後の状態 (4件) ---
C#, Programming, .NET, LINQ

2. Contains (高速な存在確認)

HashSet<T>の最大の強みは、Containsメソッドによる高速な存在確認です。

bool hasDotNet = uniqueTags.Contains(".NET"); // True
bool hasJava = uniqueTags.Contains("Java");   // False

Console.WriteLine($"'.NET' は存在するか: {hasDotNet}");
Console.WriteLine($"'Java' は存在するか: {hasJava}");

3. Remove (要素の削除)

Removeメソッドは、指定した要素がHashSet<T>内に存在すれば削除し、true(削除成功)を返します。存在しなければ何もせずfalseを返します。

// "Programming" を削除
bool removed = uniqueTags.Remove("Programming");
Console.WriteLine($"\n\"Programming\" の削除成功: {removed} (True)");

Console.WriteLine($"\n--- 削除後の状態 ({uniqueTags.Count}件) ---");
PrintSet(uniqueTags); // "Programming" が消えている

4. foreach による反復処理

HashSet<T>foreachループで全要素を処理できますが、List<T>とは異なり、要素が取り出される順序は保証されない(追加した順序とは限らない)ことに注意が必要です。


C#コード全体(まとめ)

using System;
using System.Collections.Generic;

public class HashSetUsageExample
{
    public static void Main()
    {
        // 1. HashSet の初期化 (重複は自動的に無視される)
        var uniqueTags = new HashSet<string>
        {
            "C#", "Programming", ".NET", "C#" // "C#" は1つだけ格納される
        };

        Console.WriteLine("--- 初期状態 ---");
        PrintSet(uniqueTags); // 3件

        // 2. Add: 重複の試行
        bool addedDuplicate = uniqueTags.Add(".NET");
        Console.WriteLine($"\n'.NET' の追加試行: {addedDuplicate} (False)");

        // 3. Count: 要素数の取得
        Console.WriteLine($"現在の要素数: {uniqueTags.Count}");

        // 4. Contains: 高速な存在確認
        bool hasCSharp = uniqueTags.Contains("C#");
        Console.WriteLine($"'C#' は存在するか: {hasCSharp} (True)");

        // 5. Remove: 要素の削除
        uniqueTags.Remove("Programming");
        Console.WriteLine($"\n'Programming' を削除しました。");

        // 6. foreach: 反復処理 (順序は保証されない)
        Console.WriteLine("\n--- 最終的な要素一覧 ---");
        foreach (var tag in uniqueTags)
        {
            Console.WriteLine(tag);
        }
    }

    // HashSet の内容を出力するヘルパーメソッド
    private static void PrintSet(HashSet<string> set)
    {
        Console.WriteLine(string.Join(", ", set));
    }
}

出力結果(順序は異なる場合があります):

--- 初期状態 ---
C#, Programming, .NET

'.NET' の追加試行: False
現在の要素数: 3
'C#' は存在するか: True (True)

'Programming' を削除しました。

--- 最終的な要素一覧 ---
C#
.NET

まとめ

HashSet<T>は、List<T>や配列とは異なる目的を持つコレクションです。

  • List<T>: 順序が重要で、重複を許可する場合。(例: 履歴、スコアのリスト)
  • HashSet<T>: 順序は重要ではなく、重複を排除したい場合、または「その要素が存在するかどうか」を高速に判定したい場合。(例: タグ、固有IDの管理)
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

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

目次