§1 HashSetとSortedSet

System.Collections.Generic.HashSetクラスSystem.Collections.Generic.SortedSetクラスは、数学における集合の概念を表すコレクションクラスです。 HashSetは.NET Framework 3.5 SP1、SortedSetは.NET Framework 4より提供されています。

HashSetクラス・SortedSetクラスを使うことで、一方のコレクションが他方のコレクションと重複する部分を持つか、あるいは完全に異なるか、他方を内包するか、といったコレクション同士の包含関係を調べることができます。 また、コレクション同士の和(OR)を求めて二つのコレクションをマージしたり、積(AND)を求めて共通する要素だけを残したりすることができます。

HashSet・SortedSetでは、要素は重複することなく格納されます。 Listなどとは異なり既に追加されている要素を追加しようとしても重複して格納されることなく、常に単一の要素として扱われます。 そのため、HashSet・SortedSetはキーだけを扱う一種のDictionaryのようなコレクションと見ることもできます。

またSortedSetクラスは、名前のとおりHashSetクラスに並べ替えの機能を追加したクラスで、要素を追加したり列挙したりする際には自動的にソートされます。

.NET Framework 4以降ではHashSet・SortedSetはともにISet<T>インターフェイスを実装しています。 .NET Framework 3.5ではISet<T>は存在せず、そのためHashSetもISet<T>を実装していないので注意してください。

System.Collections名前空間ではHashSetクラス・SortedSetクラスに相当するクラス、集合の概念を表すコレクションクラスは提供されていません。



§2 基本操作

HashSetクラス・SortedSetクラスともに、Listや他のコレクションクラスと共通するAdd, Remove, Contains, Clearなどのメソッドが用意されていて、単なるコレクションとして使う場合は他のコレクションクラスと何ら違いはありません。 また、SortedSetでは自動的に並べ替えが行われる点を除けば、動作と結果も同じです。

HashSet
HashSetへの要素の追加・削除・Containsによる要素の検索
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    HashSet<int> s = new HashSet<int>(new int[] {0, 1, 3, 4, 6});

    Print(s);

    // 要素を追加
    s.Add(2);

    Print(s);

    // 要素を削除
    s.Remove(6);

    Print(s);

    // 値5が含まれているか
    Console.WriteLine("Contains 5: {0}", s.Contains(5));
  }

  static void Print(HashSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 3, 4, 6, 
0, 1, 3, 4, 6, 2, 
0, 1, 3, 4, 2, 
Contains 5: False
SortedSet
SortedSetへの要素の追加・削除・Containsによる要素の検索
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<int> s = new SortedSet<int>(new int[] {0, 1, 3, 4, 6});

    Print(s);

    // 要素を追加
    s.Add(2);

    Print(s);

    // 要素を削除
    s.Remove(6);

    Print(s);

    // 値5が含まれているか
    Console.WriteLine("Contains 5: {0}", s.Contains(5));
  }

  static void Print(SortedSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 1, 3, 4, 6, 
0, 1, 2, 3, 4, 6, 
0, 1, 2, 3, 4, 
Contains 5: False

一方、HashSet・SortedSetでは同じ要素を複数回格納しようとしても重複して格納されることはなく、常に1つだけが格納されます。 この点はDictionaryのキーと似た動作と言えますが、重複していても例外がスローされることはありません。

HashSet・SortedSetともに同じ要素が重複した状態になることはありませんが、HashSetでは格納した時の順序は維持されます(最初に格納された順になります)。

HashSet
HashSetに重複する要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    HashSet<int> s = new HashSet<int>();

    // 同じ要素を複数回追加
    s.Add(3);
    s.Add(3);
    s.Add(1);
    s.Add(1);
    s.Add(2);
    s.Add(3);

    Print(s);
  }

  static void Print(HashSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
3, 1, 2, 
SortedSet
SortedSetに重複する要素を追加する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<int> s = new SortedSet<int>();

    // 同じ要素を複数回追加
    s.Add(3);
    s.Add(3);
    s.Add(1);
    s.Add(1);
    s.Add(2);
    s.Add(3);

    Print(s);
  }

  static void Print(SortedSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
1, 2, 3, 

§3 集合演算

HashSetクラス・SortedSetクラスには集合演算を行うメソッドとして次のメソッドが用意されています。 いずれも引数としてIEnumerable<T>を取り、戻り値はありません(void)。 したがって、これらのメソッドでは引数に指定された配列やコレクションなどとの演算結果をインスタンス自身に反映します。

集合演算を行うメソッド
メソッド 動作
HashSet.IntersectWith
SortedSet.IntersectWith
現在の集合と引数で指定されたIEnumerable<T>との積集合を求める
(引数の集合と共通する要素のみを残す)
HashSet.UnionWith
SortedSet.UnionWith
現在の集合と引数で指定されたIEnumerable<T>との和集合を求める
(引数の集合の要素をマージする)
HashSet.ExceptWith
SortedSet.ExceptWith
現在の集合と引数で指定されたIEnumerable<T>との差集合を求める
(引数の集合の要素を除外する)
HashSet.SymmetricExceptWith
SortedSet.SymmetricExceptWith
現在の集合と引数で指定されたIEnumerable<T>との対称差を求める
(引数の集合と共通する要素のみを除外する)

以下は、上記の集合演算を使った例です。

HashSet
HashSetで集合演算を行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    int[] set1 = new int[] {6, 2, 0, 4, 8};
    int[] set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める
    HashSet<int> s1 = new HashSet<int>(set1);

    s1.IntersectWith(set2);

    Print(s1);

    // 和集合を求める
    HashSet<int> s2 = new HashSet<int>(set1);

    s2.UnionWith(set2);

    Print(s2);

    // 差集合を求める
    HashSet<int> s3 = new HashSet<int>(set1);

    s3.ExceptWith(set2);

    Print(s3);

    // 対象差を求める
    HashSet<int> s4 = new HashSet<int>(set1);

    s4.SymmetricExceptWith(set2);

    Print(s4);
  }

  static void Print(HashSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
2, 0, 4, 
6, 2, 0, 4, 8, 3, 1, 
6, 8, 
6, 8, 3, 1, 
SortedSet
SortedSetで集合演算を行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    int[] set1 = new int[] {6, 2, 0, 4, 8};
    int[] set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める
    SortedSet<int> s1 = new SortedSet<int>(set1);

    s1.IntersectWith(set2);

    Print(s1);

    // 和集合を求める
    SortedSet<int> s2 = new SortedSet<int>(set1);

    s2.UnionWith(set2);

    Print(s2);

    // 差集合を求める
    SortedSet<int> s3 = new SortedSet<int>(set1);

    s3.ExceptWith(set2);

    Print(s3);

    // 対象差を求める
    SortedSet<int> s4 = new SortedSet<int>(set1);

    s4.SymmetricExceptWith(set2);

    Print(s4);
  }

  static void Print(SortedSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
0, 2, 4, 
0, 1, 2, 3, 4, 6, 8, 
6, 8, 
1, 3, 6, 8, 

SortedSetで並べ替えが行われる以外は、結果は同じです。 以下の図は、実行結果を図式化したものです。

IntersectWithの結果 UnionWithの結果 ExceptWithの結果 SymmetricExceptWithの結果

§3.1 LINQの拡張メソッドを使った集合演算

LINQの拡張メソッドを使うことでも集合演算を行うことはできます。 それぞれ対応するメソッドは次のとおりです。

対応するLINQの拡張メソッド
演算 HashSet/SortedSetのメソッド LINQの拡張メソッド
積集合 IntersectWith Intersect
和集合 UnionWith Union
差集合 ExceptWith Except
対象差 SymmetricExceptWith -

以下の例はこれらのメソッドを使ってHashSet・SortedSetと同様の集合演算を行う例です。 LINQでは、SymmetricExceptWithに相当するような対象差を求めるメソッドは直接提供されませんが、以下の例のように差集合同士の和集合を求めることで対象差を求めることができます。

LINQの拡張メソッドを使って集合演算を行う
using System;
using System.Collections.Generic;
using System.Linq;

class Sample {
  static void Main()
  {
    int[] set1 = new int[] {6, 2, 0, 4, 8};
    int[] set2 = new int[] {3, 1, 2, 0, 4};

    // 積集合を求める
    Print(set1.Intersect(set2));

    // 和集合を求める
    Print(set1.Union(set2));

    // 差集合を求める
    Print(set1.Except(set2));

    // 対象差を求める
    var diffset1 = set1.Except(set2);
    var diffset2 = set2.Except(set1);

    Print(diffset1.Union(diffset2));
  }

  static void Print(IEnumerable<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
2, 0, 4, 
6, 2, 0, 4, 8, 3, 1, 
6, 8, 
6, 8, 3, 1, 

§4 包含関係の検証

HashSetクラス・SortedSetクラスには包含関係の検証を行うメソッドとして次のメソッドが用意されています。 いずれも引数としてIEnumerable<T>を取り、戻り値は真偽値(boolean)です。 なお、要素の並びが異なっていても包含関係には影響しません。 これらのメソッドでは順序にかかわらずどのような要素が含まれているかどうかのみが検証されます。

包含関係の検証を行うメソッド
メソッド 動作
HashSet.SetEquals
SortedSet.SetEquals
現在の集合と引数で指定されたIEnumerable<T>が等しいかどうか調べる
(現在の集合と引数の集合が重複を無視して同じ要素で構成されている場合はtrue)
HashSet.Overlaps
SortedSet.Overlaps
現在の集合と引数で指定されたIEnumerable<T>が共通する要素を持つかどうか調べる
(現在の集合と引数の集合が一つでも同じ要素を持つ場合はtrue)
HashSet.IsSubsetOf
SortedSet.IsSubsetOf
現在の集合が引数で指定されたIEnumerable<T>の部分集合かどうか調べる
(現在の集合が引数の集合に含まれる場合はtrue)
HashSet.IsProperSubsetOf
SortedSet.IsProperSubsetOf
現在の集合が引数で指定されたIEnumerable<T>の真部分集合かどうか調べる
(現在の集合が引数の集合に含まれ、かつ両者が等しくない場合はtrue)
HashSet.IsSupersetOf
SortedSet.IsSupersetOf
現在の集合が引数で指定されたIEnumerable<T>の上位集合かどうか調べる
(現在の集合が引数の集合を含む場合はtrue)
HashSet.IsProperSupersetOf
SortedSet.IsProperSupersetOf
現在の集合が引数で指定されたIEnumerable<T>の真上位集合かどうか調べる
(現在の集合が引数の集合を含み、かつ両者が等しくない場合はtrue)

以下は、上記のメソッドを使った例です。

HashSet
HashSetを使って他の集合との包含関係の検証を行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    HashSet<int> s = new HashSet<int>(new int[] {3, 4, 1, 5, 2});

    Console.Write("Set: ");

    Print(s);

    Console.WriteLine();

    Console.WriteLine("SetEquals {{1, 2, 3, 4, 5}}: {0}",
                      s.SetEquals(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("SetEquals {{1, 2, 3, 4, 6}}: {0}",
                      s.SetEquals(new int[] {1, 2, 3, 4, 6}));
    Console.WriteLine("Overraps {{2, 3, 5, 7}}: {0}",
                      s.Overlaps(new int[] {2, 3, 5, 7}));
    Console.WriteLine("Overraps {{0, 6}}: {0}",
                      s.Overlaps(new int[] {0, 6}));

    Console.WriteLine();

    Console.WriteLine("IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
                      s.IsSubsetOf(new int[] {0, 1, 2, 3, 4, 5, 6}));
    Console.WriteLine("IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
                      s.IsProperSubsetOf(new int[] {0, 1, 2, 3, 4, 5, 6}));
    Console.WriteLine("IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
                      s.IsSubsetOf(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
                      s.IsProperSubsetOf(new int[] {1, 2, 3, 4, 5}));

    Console.WriteLine();

    Console.WriteLine("IsSupersetOf       {{2, 3, 4}}: {0}",
                      s.IsSupersetOf(new int[] {2, 3, 4}));
    Console.WriteLine("IsProperSupersetOf {{2, 3, 4}}: {0}",
                      s.IsProperSupersetOf(new int[] {2, 3, 4}));
    Console.WriteLine("IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
                      s.IsSupersetOf(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
                      s.IsProperSupersetOf(new int[] {1, 2, 3, 4, 5}));
  }

  static void Print(HashSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Set: 3, 4, 1, 5, 2, 

SetEquals {1, 2, 3, 4, 5}: True
SetEquals {1, 2, 3, 4, 6}: False
Overraps {2, 3, 5, 7}: True
Overraps {0, 6}: False

IsSubsetOf       {0, 1, 2, 3, 4, 5, 6}: True
IsProperSubsetOf {0, 1, 2, 3, 4, 5, 6}: True
IsSubsetOf       {1, 2, 3, 4, 5}: True
IsProperSubsetOf {1, 2, 3, 4, 5}: False

IsSupersetOf       {2, 3, 4}: True
IsProperSupersetOf {2, 3, 4}: True
IsSupersetOf       {1, 2, 3, 4, 5}: True
IsProperSupersetOf {1, 2, 3, 4, 5}: False
SortedSet
SortedSetを使って他の集合との包含関係の検証を行う
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<int> s = new SortedSet<int>(new int[] {3, 4, 1, 5, 2});

    Console.Write("Set: ");

    Print(s);

    Console.WriteLine();

    Console.WriteLine("SetEquals {{1, 2, 3, 4, 5}}: {0}",
                      s.SetEquals(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("SetEquals {{1, 2, 3, 4, 6}}: {0}",
                      s.SetEquals(new int[] {1, 2, 3, 4, 6}));
    Console.WriteLine("Overraps {{2, 3, 5, 7}}: {0}",
                      s.Overlaps(new int[] {2, 3, 5, 7}));
    Console.WriteLine("Overraps {{0, 6}}: {0}",
                      s.Overlaps(new int[] {0, 6}));

    Console.WriteLine();

    Console.WriteLine("IsSubsetOf       {{0, 1, 2, 3, 4, 5, 6}}: {0}",
                      s.IsSubsetOf(new int[] {0, 1, 2, 3, 4, 5, 6}));
    Console.WriteLine("IsProperSubsetOf {{0, 1, 2, 3, 4, 5, 6}}: {0}",
                      s.IsProperSubsetOf(new int[] {0, 1, 2, 3, 4, 5, 6}));
    Console.WriteLine("IsSubsetOf       {{1, 2, 3, 4, 5}}: {0}",
                      s.IsSubsetOf(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("IsProperSubsetOf {{1, 2, 3, 4, 5}}: {0}",
                      s.IsProperSubsetOf(new int[] {1, 2, 3, 4, 5}));

    Console.WriteLine();

    Console.WriteLine("IsSupersetOf       {{2, 3, 4}}: {0}",
                      s.IsSupersetOf(new int[] {2, 3, 4}));
    Console.WriteLine("IsProperSupersetOf {{2, 3, 4}}: {0}",
                      s.IsProperSupersetOf(new int[] {2, 3, 4}));
    Console.WriteLine("IsSupersetOf       {{1, 2, 3, 4, 5}}: {0}",
                      s.IsSupersetOf(new int[] {1, 2, 3, 4, 5}));
    Console.WriteLine("IsProperSupersetOf {{1, 2, 3, 4, 5}}: {0}",
                      s.IsProperSupersetOf(new int[] {1, 2, 3, 4, 5}));
  }

  static void Print(SortedSet<int> s)
  {
    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Set: 1, 2, 3, 4, 5, 

SetEquals {1, 2, 3, 4, 5}: True
SetEquals {1, 2, 3, 4, 6}: False
Overraps {2, 3, 5, 7}: True
Overraps {0, 6}: False

IsSubsetOf       {0, 1, 2, 3, 4, 5, 6}: True
IsProperSubsetOf {0, 1, 2, 3, 4, 5, 6}: True
IsSubsetOf       {1, 2, 3, 4, 5}: True
IsProperSubsetOf {1, 2, 3, 4, 5}: False

IsSupersetOf       {2, 3, 4}: True
IsProperSupersetOf {2, 3, 4}: True
IsSupersetOf       {1, 2, 3, 4, 5}: True
IsProperSupersetOf {1, 2, 3, 4, 5}: False

SortedSetで並べ替えが行われる以外は、結果は同じです。

§5 等価性比較のカスタマイズ

HashSetのコンストラクタで適切なIEqualityComparer<T>インターフェイスを指定することで、要素の等価性比較時の動作をカスタマイズ出来ます。 例えば、比較の際に大文字小文字の違いを無視するようにするといったことが出来ます。 以下は、StringComparerクラスを使って、大文字小文字の違いを無視するHashSetを作成する例です。

StringComparerを指定して大文字小文字の違いを意識する/無視するHashSetを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    string[] names = new string[] {"Alice", "Bob", "Charlie", "Dave", "Eve"};

    // 大文字小文字の違いを意識するHashSet
    HashSet<string> caseSensitiveSet = new HashSet<string>(names, StringComparer.CurrentCulture);
    // 大文字小文字の違いを無視するHashSet
    HashSet<string> caseInsensitiveSet = new HashSet<string>(names, StringComparer.CurrentCultureIgnoreCase);

    Console.WriteLine(caseSensitiveSet.IsSupersetOf(new string[] {"aLiCe", "BOB", "dave"}));
    Console.WriteLine(caseInsensitiveSet.IsSupersetOf(new string[] {"aLiCe", "BOB", "dave"}));
  }
}
実行結果
False
True

IEqualityComparer<T>インターフェイスや実装例については等価性の定義と比較、StringComparerについては文字列と比較オプション・カルチャの並べ替え規則 §.StringComparison列挙型とStringComparerクラスを参照してください。

SortedSetでは等価性比較だけではなく大小関係の比較も行うため、IEqualityComparer<T>インターフェイスではなくIComparer<T>インターフェイスを要求します。

§5.1 HashSetでKeyValuePairを扱う

等価性比較のカスタマイズ例として、HashSetでKeyValuePairを扱う例をみてみます。 次の例では、KeyValuePairからKeyの値を取得して比較を行うクラスKeyValuePairEqualityComparerを作成し、それをHashSetのコンストラクタに渡しています。 これによりKeyValuePair同士の比較ができるようになり、HashSetでKeyValuePairを扱うことができるようになります。

HashSetでKeyValuePairを扱う
using System;
using System.Collections.Generic;

// KeyValuePair<string, int>の等価性比較を行うIEqualityComparer<T>
class KeyValuePairEqualityComparer : EqualityComparer<KeyValuePair<string, int>> {
  public override bool Equals(KeyValuePair<string, int> x, KeyValuePair<string, int> y)
  {
    // KeyValuePairのKeyを比較する
    return string.Equals(x.Key, y.Key);
  }

  public override int GetHashCode(KeyValuePair<string, int> obj)
  {
    // KeyValuePairのKeyからハッシュ値を取得する
    return obj.Key.GetHashCode();
  }
}

class Sample {
  static void Main()
  {
    KeyValuePairEqualityComparer comparer = new KeyValuePairEqualityComparer();
    HashSet<KeyValuePair<string, int>> s1 = new HashSet<KeyValuePair<string, int>>(comparer);

    s1.Add(new KeyValuePair<string, int>("Dave", 1));
    s1.Add(new KeyValuePair<string, int>("Alice", 2));
    s1.Add(new KeyValuePair<string, int>("Alice", 99999));
    s1.Add(new KeyValuePair<string, int>("Bob", 3));
    s1.Add(new KeyValuePair<string, int>("Eve", 4));
    s1.Add(new KeyValuePair<string, int>("Charlie", 5));

    Print(s1);

    HashSet<KeyValuePair<string, int>> s2 = new HashSet<KeyValuePair<string, int>>(comparer);

    s2.Add(new KeyValuePair<string, int>("Alice", 3));
    s2.Add(new KeyValuePair<string, int>("Bob", 1));
    s2.Add(new KeyValuePair<string, int>("Charlie", 2));

    Print(s2);

    // 差集合を求める
    Console.WriteLine("[ExceptWith]");

    s1.ExceptWith(s2);

    Print(s1);
  }

  static void Print(HashSet<KeyValuePair<string, int>> s)
  {
    foreach (KeyValuePair<string, int> pair in s) {
      Console.Write("{0}:{1}, ", pair.Key, pair.Value);
    }

    Console.WriteLine();
  }
}
実行結果
Dave:1, Alice:2, Bob:3, Eve:4, Charlie:5, 
Alice:3, Bob:1, Charlie:2, 
[ExceptWith]
Dave:1, Eve:4, 

この例のKeyValuePairEqualityComparerではKeyの値のみを比較しているため、Valueにどのような値が格納されているかといったことは一切考慮されません。 Keyの値が同一であれば、Valueの値に関わらず同一の要素とみなされます。

§6 大小比較のカスタマイズ

SortedSetのコンストラクタで適切なIComparer<T>インターフェイスを指定することで、要素の大小関係比較時の動作をカスタマイズ出来ます。 例えば、比較の際に大文字小文字の違いを無視するようにするといったことが出来ます。

以下は、大文字小文字を無視し、アルファベット順とは逆順(Z-Aの順)になるようにソートするIComparer<string>を実装し、SortedSetでの並べ替え順をカスタマイズする例です。

大文字小文字の違いを無視し、アルファベット順とは逆順に並べ替えるSortedSetを作成する
using System;
using System.Collections.Generic;

// 大文字小文字の違いを無視し、アルファベット順とは逆順にソートするためのIComparer
class CaseInsensitiveReverseStringComparer : IComparer<string> {
  public int Compare(string x, string y)
  {
    // StringComparer.CurrentCultureIgnoreCase.Compareとは逆の結果を返すようにする
    return -1 * StringComparer.CurrentCultureIgnoreCase.Compare(x, y);
  }
}

class Sample {
  static void Main()
  {
    string[] names = new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"};

    SortedSet<string> caseSensitiveSet = new SortedSet<string>(names, StringComparer.CurrentCulture);
    SortedSet<string> reverseCaseInsensitiveSet = new SortedSet<string>(names, new CaseInsensitiveReverseStringComparer());

    Console.WriteLine("caseSensitiveSet");

    foreach (string e in caseSensitiveSet) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();

    Console.WriteLine(caseSensitiveSet.IsSupersetOf(new string[] {"aLiCe", "BOB", "dave"}));

    Console.WriteLine("reverseCaseInsensitiveSet");

    foreach (string e in reverseCaseInsensitiveSet) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();

    Console.WriteLine(reverseCaseInsensitiveSet.IsSupersetOf(new string[] {"aLiCe", "BOB", "dave"}));
  }
}
実行結果
caseSensitiveSet
Alice, Bob, Charlie, Dave, Eve, 
False
reverseCaseInsensitiveSet
Eve, Dave, Charlie, Bob, Alice, 
True

IComparer<T>インターフェイスや実装例については大小関係の定義と比較を参照してください。

§7 SortedSetのみで提供される操作

以下の操作はHashSetには用意されておらず、SortedSetのみで提供される操作です。

§7.1 最大値・最小値

SortedListクラスでは、MaxプロパティおよびMinプロパティで集合内での最大・最小の要素を取得することが出来ます。

Maxプロパティ・Minプロパティを参照してSortedList内の最大・最小の要素を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<string> s = new SortedSet<string>(new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"});

    Console.Write("Set: ");

    Print(s);

    // 最小値と最大値を表示
    Console.WriteLine("Mix: {0}", s.Min);
    Console.WriteLine("Max: {0}", s.Max);
  }

  static void Print(SortedSet<string> s)
  {
    foreach (string e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Set: Alice, Bob, Charlie, Dave, Eve, 
Mix: Alice
Max: Eve

HashSetで最大・最小の要素を取得したい場合には、LINQの拡張メソッドであるMaxメソッドMinメソッドを使うことができます。

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

class Sample {
  static void Main()
  {
    HashSet<string> s = new HashSet<string>(new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"});

    // 最小値と最大値を表示
    Console.WriteLine("Mix: {0}", s.Min());
    Console.WriteLine("Max: {0}", s.Max());
  }
}
実行結果
Mix: Adams
Max: Eve

SortedSetに対してもLINQのMaxメソッド・Minメソッドを用いることはできます。 ただし、LINQのMaxメソッド・Minメソッドでは全要素を操作した上で最大・最小の要素を返すため無駄が多く、SortedListではMaxプロパティ・Minプロパティの代わりにMaxメソッド・Minメソッドを用いる利点はありません。

§7.2 逆順での列挙

Reverseメソッドは、SortedSetを通常とは逆順に列挙する列挙子(IEnumerator<T>)を返します。 Array.ReverseメソッドList.Reverseメソッドとは異なり、コレクション内の要素の並びは変わりません。 Reverseメソッドを呼び出してもコレクション内の要素は逆順にはならず、あくまで逆順に列挙する列挙子を返すだけという点に注意してください。

Reverseメソッドを使ってSortedSet内の要素を逆順で列挙する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<int> s = new SortedSet<int>(new int[] {5, 1, 0, 3, 4, 2});

    foreach (int e in s.Reverse()) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();

    foreach (int e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
5, 4, 3, 2, 1, 0, 
0, 1, 2, 3, 4, 5, 

§7.3 部分集合(サブセット)の取得

SortedListクラスでは、GetViewBetweenメソッドを使うと、指定した範囲に該当する部分集合をSortedSet<T>で取得出来ます。

GetViewBetweenメソッドを使ってSortedSetの指定した範囲の部分集合を取得する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<string> s = new SortedSet<string>(new string[] {"Adams", "Cyndy", "Dave", "Bob", "Charlie", "Elliott", "Becky", "Alice", "Diana", "Eve"});

    Console.Write("Set: ");

    Print(s);

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(B, E): ");

    Print(s.GetViewBetween("B", "E"));

    // 最小で"Ae"、最大で"Bz"の範囲に該当する部分集合を取得する
    Console.Write("GetViewBetween(Ae, Bz): ");

    Print(s.GetViewBetween("Ae", "Bz"));
  }

  static void Print(SortedSet<string> s)
  {
    foreach (string e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
Set: Adams, Alice, Becky, Bob, Charlie, Cyndy, Dave, Diana, Elliott, Eve, 
GetViewBetween(B, E): Becky, Bob, Charlie, Cyndy, Dave, Diana, 
GetViewBetween(Ae, Bz): Alice, Becky, Bob, 

GetViewBetweenメソッドは、引数で指定した範囲に該当する部分のビューを返します。 SortedSetの一部をコピーしたものが返されるわけではないため、元になったSortedSetに変更を加えるとGetViewBetweenメソッドで取得したサブセットにも反映されます。

GetViewBetweenメソッドで部分集合を取得した後に元の集合に変更を加える
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<string> s = new SortedSet<string>(new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"});

    Console.Write("s: ");

    Print(s);

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    SortedSet<string> view = s.GetViewBetween("B", "E");

    Console.Write("view: ");

    Print(view);

    // 元の集合に変更を加える
    s.Add("Diana");

    Console.Write("view: ");

    Print(view);
  }

  static void Print(SortedSet<string> s)
  {
    foreach (string e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
s: Alice, Bob, Charlie, Dave, Eve, 
view: Bob, Charlie, Dave, 
view: Bob, Charlie, Dave, Diana, 

また逆に、GetViewBetweenメソッドで取得したサブセットに変更を加えることもでき、変更は元になったSortedSetに反映されます。

GetViewBetweenメソッドで取得した部分集合を通して元のSortedSetに変更を加える
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<string> s = new SortedSet<string>(new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"});

    Console.Write("s: ");

    Print(s);

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    SortedSet<string> view = s.GetViewBetween("B", "E");

    // 取得した部分集合に変更を加える
    view.Add("Cyndy");

    Console.Write("s: ");

    Print(s);
  }

  static void Print(SortedSet<string> s)
  {
    foreach (string e in s) {
      Console.Write("{0}, ", e);
    }

    Console.WriteLine();
  }
}
実行結果
s: Alice, Bob, Charlie, Dave, Eve, 
s: Alice, Bob, Charlie, Cyndy, Dave, Eve, 

GetViewBetweenメソッドで指定した範囲外の値をサブセットに対して追加しようとした場合にはArgumentOutOfRangeExceptionがスローされます。 GetViewBetweenメソッドで取得したサブセットからはその範囲内に対してのみ操作が行えます。

GetViewBetweenメソッドで指定した範囲外に変更を加える
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    SortedSet<string> s = new SortedSet<string>(new string[] {"Alice", "Eve", "Charlie", "Bob", "Dave"});

    // 最小で"B"、最大で"E"の範囲に該当する部分集合を取得する
    SortedSet<string> view = s.GetViewBetween("B", "E");

    // ビューの範囲外の値を追加しようとする
    view.Add("Adams");
  }
}
実行結果
System.ArgumentOutOfRangeException: 指定された引数は、有効な値の範囲内にありません。
パラメータ名: collection
   at System.Collections.Generic.SortedSet`1.TreeSubSet.AddIfNotPresent(T item)
   at Sample.Main(String[] args)