HashSetは要素が等しいかどうかをどのように比較しますか?


127

私はクラスがありIComparableます:

public class a : IComparable
{
    public int Id { get; set; }
    public string Name { get; set; }

    public a(int id)
    {
        this.Id = id;
    }

    public int CompareTo(object obj)
    {
        return this.Id.CompareTo(((a)obj).Id);
    }
}

このクラスのオブジェクトのリストをハッシュセットに追加すると、次のようになります。

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(a1);

すべてが問題なく、ha.countですが2、:

a a1 = new a(1);
a a2 = new a(2);
HashSet<a> ha = new HashSet<a>();
ha.add(a1);
ha.add(a2);
ha.add(new a(1));

ha.countです3

  1. の方法をHashSet尊重しないのはなぜですか。aCompareTo
  2. HashSet一意のオブジェクトのリストを作成する最良の方法はありますか?

の実装をIEqualityComparer<T>コンストラクタに追加するか、クラスに実装しaます。msdn.microsoft.com/en-us/library/bb301504(v=vs.110).aspx
Jaider

回答:


137

IEqualityComparer<T>EqualityComparer<T>.Default構築時に別のものを指定しない限り)を使用します。

要素をセットに追加すると、それはを使用してハッシュコードを検索しIEqualityComparer<T>.GetHashCode、ハッシュコードと要素の両方を保存します(もちろん、要素が既にセットにあるかどうかを確認した後)。

要素を検索するには、まずIEqualityComparer<T>.GetHashCodeハッシュコードを見つけるためにを使用し、次に同じハッシュコードを持つすべての要素IEqualityComparer<T>.Equalsについて、実際の等価性を比較するために使用します。

つまり、2つのオプションがあります。

  • カスタムIEqualityComparer<T>をコンストラクターに渡します。T自分自身を変更できない場合、またはデフォルト以外の等価関係が必要な場合(たとえば、「負のユーザーIDを持つすべてのユーザーは等しいと見なされる」)、これは最良のオプションです。これは型自体にはほとんど実装されてFooいません(つまり、は実装されていませんIEqualityComparer<Foo>)が、比較にのみ使用される別の型には実装されていません。
  • GetHashCodeand をオーバーライドして、型自体に等価を実装しますEquals(object)IEquatable<T>特に値型の場合は、型にも実装するのが理想的です。これらのメソッドは、デフォルトの等値比較子によって呼び出されます。

順序付けられた比較の観点からこれがどれにも当てはまらないことに注意してください。これは、完全な順序付けではなく、等価性を簡単に指定できる状況が確かにあるためです。これはDictionary<TKey, TValue>基本的にと同じです。

等価比較ではなく順序付けを使用するセットが必要な場合はSortedSet<T>、.NET 4から使用する必要があります。これにより、のIComparer<T>代わりにを指定できますIEqualityComparer<T>。これは使用しますIComparer<T>.Compare-を使用するIComparable<T>.CompareToIComparable.CompareTo、委任しますComparer<T>.Default


7
tyrikerの答え@ +1またノート(IMOここでコメントでなければならないこと)を活用するための最も簡単な方法が言ったことを指摘してIEqualityComparer<T>.GetHashCode/Equals()実装することですEqualsし、GetHashCodeT(自身とあなたがそれをやっている間、あなたはまた、強く型付けされた対応を実施したいです:- bool IEquatable<T>.Equals(T other)
Ruben Bartelink 2013年

5
非常に正確なものの、それは最も単純なケースのためにオーバーライドすることを明記していないので、この答えは、特に新規ユーザーのために、多少混乱するかもしれEqualsおよびGetHashCode@のtyrikerの答えで述べたように-十分です。
BartoszKP 2013年

Imo一度実装するとIComparable(またはIComparerその点については)、同等性を個別に実装するように求められるべきではありません(ただしだけGetHashCode)。ある意味では、比較可能性インターフェースは等価インターフェースから継承する必要があります。私は2つの別々の関数(何かが等しいかどうかを言うだけで別々に等式を最適化できる)でのパフォーマンスの利点を理解していますが、それでもなお、インスタンスがCompareTo関数で等しく、フレームワークが考慮しない場合を指定すると、非常に混乱します。それ。
nawfal 2015年

@nawfalはすべてに論理的な順序があるわけではありません。あなたはブールプロパティを含む2つのものを比較している場合、次のように記述する必要がためにひどい単なるであるa.boolProp == b.boolProp ? 1 : 0か、それがあるべきa.boolProp == b.boolProp ? 0 : -1a.boolProp == b.boolProp ? 1 : -1。ユック!
Simon_Weaver 2017

1
@Simon_Weaverです。私が提案していた私の架空の機能では、どういうわけかそれを避けたいです。
nawfal 2017

77

答えられてHashSet<T>いないままになっている回答の一部を以下に示します:オブジェクトタイプは実装する必要はありませんがIEqualityComparer<T>、代わりにとをオーバーライドする必要がObject.GetHashCode()ありObject.Equals(Object obj)ます。

これの代わりに:

public class a : IEqualityComparer<a>
{
  public int GetHashCode(a obj) { /* Implementation */ }
  public bool Equals(a obj1, a obj2) { /* Implementation */ }
}

これをして:

public class a
{
  public override int GetHashCode() { /* Implementation */ }
  public override bool Equals(object obj) { /* Implementation */ }
}

微妙ですが、これは私がHashSetを意図したとおりに機能させるための1日の大部分につまずきました。そして、他の人が言ったように、セットで作業するときに、必要に応じHashSet<a>て電話をかけることにa.GetHashCode()なりa.Equals(obj)ます。


2
いい視点ね。ところで、@ JonSkeetの回答に関する私のコメントで述べたように、bool IEquatable<T>.Equals(T other)わずかな効率向上のために実装する必要がありますが、より重要なのは、明快さの利点です。OBVの理由から、実装する必要性に加えてGetHashCode並んでIEquatable<T>、IEquatable <T>のためのドキュメントは、一貫性のためにあなたもオーバーライドする必要があることを言及してobject.Equals一貫性を保つために
ルーベンBartelink

これを実装してみました。ovveride getHashcode動作しますが、override bool equalsエラーを取得していない:オーバーライドに見つかりませ方法を。何か案が?
Stefanvds 14

最後に私が探していた情報。ありがとうございました。
Mauro Sampietro 2017年

上記の回答に対する私のコメントから-あなたの「代わりに」の場合、あなたはpublic class a : IEqualityComparer<a> {、そしてnew HashSet<a>(a)
HankCa、2018

ただし、上記のJon Skeetsのコメントを参照してください。
HankCa、2018

9

HashSetとを使用EqualsGetHashCode()ます。

CompareTo 注文セット用です。

一意のオブジェクトが必要だが、その反復順序を気にしない場合HashSet<T>は、通常、これが最良の選択です。


5

コンストラクターHashSetは、新しいオブジェクトを追加するためにIEqualityComparerを実装するオブジェクトを受け取ります。HashSetでメソッドを使用する場合は、Equals、GetHashCodeをオーバーライドする必要があります。

namespace HashSet
{
    public class Employe
    {
        public Employe() {
        }

        public string Name { get; set; }

        public override string ToString()  {
            return Name;
        }

        public override bool Equals(object obj) {
            return this.Name.Equals(((Employe)obj).Name);
        }

        public override int GetHashCode() {
            return this.Name.GetHashCode();
        }
    }

    class EmployeComparer : IEqualityComparer<Employe>
    {
        public bool Equals(Employe x, Employe y)
        {
            return x.Name.Trim().ToLower().Equals(y.Name.Trim().ToLower());
        }

        public int GetHashCode(Employe obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            HashSet<Employe> hashSet = new HashSet<Employe>(new EmployeComparer());
            hashSet.Add(new Employe() { Name = "Nik" });
            hashSet.Add(new Employe() { Name = "Rob" });
            hashSet.Add(new Employe() { Name = "Joe" });
            Display(hashSet);
            hashSet.Add(new Employe() { Name = "Rob" });
            Display(hashSet);

            HashSet<Employe> hashSetB = new HashSet<Employe>(new EmployeComparer());
            hashSetB.Add(new Employe() { Name = "Max" });
            hashSetB.Add(new Employe() { Name = "Solomon" });
            hashSetB.Add(new Employe() { Name = "Werter" });
            hashSetB.Add(new Employe() { Name = "Rob" });
            Display(hashSetB);

            var union = hashSet.Union<Employe>(hashSetB).ToList();
            Display(union);
            var inter = hashSet.Intersect<Employe>(hashSetB).ToList();
            Display(inter);
            var except = hashSet.Except<Employe>(hashSetB).ToList();
            Display(except);

            Console.ReadKey();
        }

        static void Display(HashSet<Employe> hashSet)
        {
            if (hashSet.Count == 0)
            {
                Console.Write("Collection is Empty");
                return;
            }
            foreach (var item in hashSet)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }

        static void Display(List<Employe> list)
        {
            if (list.Count == 0)
            {
                Console.WriteLine("Collection is Empty");
                return;
            }
            foreach (var item in list)
            {
                Console.Write("{0}, ", item);
            }
            Console.Write("\n");
        }
    }
}

名前がnullの場合はどうなりますか?nullのハッシュ値は何ですか?
ジョー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.