HashSet, eşitlik için öğeleri nasıl karşılaştırır?


128

Bir sınıfım var 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);
    }
}

Bir hash setine bu sınıfın bir nesnesinin listesini eklediğimde:

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);

Her şey yolunda ve ha.countgüzel 2, ama:

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));

Şimdi ha.countise 3.

  1. Neden gelmez HashSetsaygı a'ın CompareTometodunu.
  2. HashSetBenzersiz nesnelerin bir listesine sahip olmanın en iyi yolu mudur ?

Yapıcıya bir uygulama ekleyin IEqualityComparer<T>veya sınıfta uygulayın a. msdn.microsoft.com/en-us/library/bb301504(v=vs.110).aspx
Jaider

Yanıtlar:


138

Bir kullanır IEqualityComparer<T>( EqualityComparer<T>.Defaultinşaatta farklı bir tane belirtmediğiniz sürece).

Kümeye bir eleman eklediğinizde, karma kodunu kullanarak bulacak IEqualityComparer<T>.GetHashCodeve hem karma kodunu hem de elemanı depolayacaktır (elbette elemanın zaten kümede olup olmadığını kontrol ettikten sonra).

Bir öğeyi yukarı aramak için, önce IEqualityComparer<T>.GetHashCodekarma kodunu bulmak için, ardından aynı karma koduna sahip tüm öğeler IEqualityComparer<T>.Equalsiçin gerçek eşitliği karşılaştırmak için kullanacaktır .

Bu, iki seçeneğiniz olduğu anlamına gelir:

  • Yapıcıya bir gelenek IEqualityComparer<T>geçirin. TKendini değiştiremiyorsanız veya varsayılan olmayan bir eşitlik ilişkisi istiyorsanız bu en iyi seçenektir (örneğin, "negatif kullanıcı kimliğine sahip tüm kullanıcılar eşit kabul edilir"). Bu, neredeyse hiçbir zaman türün kendisine uygulanmaz (yani Foouygulanmaz IEqualityComparer<Foo>), ancak yalnızca karşılaştırmalar için kullanılan ayrı bir türdedir.
  • Geçersiz kılarak, tip kendisinde eşitliğini uygulamak GetHashCodeve Equals(object). İdeal IEquatable<T>olarak, özellikle bir değer türüyse, tür içinde de uygulayın . Bu yöntemler, varsayılan eşitlik karşılaştırıcısı tarafından çağrılacaktır.

Bunların hiçbirinin sıralı bir karşılaştırma açısından nasıl olmadığına dikkat edin - bu mantıklıdır, çünkü kesinlikle eşitliği kolayca belirleyebileceğiniz ancak toplam bir sıralamayı belirtemeyeceğiniz durumlar vardır. Dictionary<TKey, TValue>Temelde bunların hepsi aynı .

Yalnızca eşitlik karşılaştırmaları yerine sıralama kullanan bir küme istiyorsanız SortedSet<T>, .NET 4'ten kullanmalısınız - bu IComparer<T>, bir IEqualityComparer<T>. Bu, kullanacak IComparer<T>.Compare- hangisine IComparable<T>.CompareToya IComparable.CompareToda kullanıyorsanız , yetki verecektir Comparer<T>.Default.


7
1 Ayrıca not @ kaldıraç en basit yolu söyledi işaret (IMO bir yorum burada olması gerektiğini) tyriker cevabı IEqualityComparer<T>.GetHashCode/Equals()uygulamaktır Equalsve GetHashCodeüzerine Tkendisi (ve bunu yaparken, aynı zamanda kesinlikle yazılı muadili uygulamak istiyorum : - bool IEquatable<T>.Equals(T other))
Ruben Bartelink

5
Açıkça en basit durumda geçersiz kılma o devlet değil yaptığı gibi çok hassas olmasına rağmen bu cevap biraz özellikle yeni kullanıcılar için kafa karıştırıcı olabilir Equalsve GetHashCode@ tyriker yanıtında belirtildiği gibi - yeterlidir.
BartoszKP

Imo bir kez uyguladıktan sonra IComparable(veya IComparerbu konuda) eşitliği ayrı olarak (ama sadece GetHashCode) uygulamanız istenmemelidir . Bir anlamda karşılaştırılabilirlik arayüzleri eşitlik arayüzlerinden miras almalıdır. İki ayrı işleve sahip olmanın (bir şeyin eşit olup olmadığını söyleyerek eşitliği ayrı ayrı optimize edebileceğiniz) performans faydalarını anlıyorum ama yine de .. Örneklerin CompareToişlev açısından eşit olduğunu ve çerçevenin dikkate alınmayacağını belirlediğinizde aksi takdirde çok kafa karıştırıcı söyledi.
nawfal

@ nawfal her şeyin mantıksal bir düzeni yoktur. Eğer bir bool özelliğini içeren iki şey karşılaştırıyorsanız gibi bir şey yazmak zorunda kalmak korkunç sadece düz olduğu a.boolProp == b.boolProp ? 1 : 0ya da olması gerektiği a.boolProp == b.boolProp ? 0 : -1ya a.boolProp == b.boolProp ? 1 : -1. Yuk!
Simon_Weaver

1
@Simon_Weaver öyle. Önerdiğim varsayımsal özelliğimde bundan bir şekilde kaçınmak istiyorum.
nawfal

77

Söylenmemiş sol olmuş cevabın bir kısmını İşte en açıklama: aramalarınızdan nesne türü HashSet<T>uygulamak zorunda değildir IEqualityComparer<T>ama bunun yerine sadece geçersiz kılmak için vardır Object.GetHashCode()ve Object.Equals(Object obj).

Bunun yerine:

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

Bunu yap:

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

İnce, ancak bu, HashSet'in amaçlandığı şekilde çalışmasını sağlamaya çalışırken bir günün daha iyi bir parçası için beni heyecanlandırdı. Ve diğerlerinin de söylediği gibi , setle çalışırken ve gerektiğinde HashSet<a>aramayı sonlandıracak .a.GetHashCode()a.Equals(obj)


2
İyi bir nokta. @ JonSkeet'in cevabıyla ilgili yorumumda belirtildiği gibi BTW, ayrıca bool IEquatable<T>.Equals(T other)hafif bir verimlilik kazancı, ancak daha da önemlisi netlik avantajı için uygulamalısınız. Uygulama gereği ek olarak obv nedenlerle, GetHashCodeyanında IEquatable<T>, IEquatable <T> için doc tutarlılık amacıyla da geçersiz gerektiğini bahseder object.Equalstutarlılık için
Ruben Bartelink

Bunu uygulamayı denedim. ovveride getHashcodeÇalışır, ancak override bool equalshatayı alır: hiçbir yöntem geçersiz kılma bulundu. Herhangi bir fikir?
Stefanvds

Sonunda aradığım bilgi. Teşekkür ederim.
Mauro Sampietro

Yukarıdaki cevapla ilgili yorumlarımdan - "Yerine" durumunuzda, olabilir public class a : IEqualityComparer<a> {ve sonra new HashSet<a>(a).
HankCa

Ancak yukarıdaki Jon Skeets yorumlarına bakın.
HankCa

9

HashSetkullanır Equalsve GetHashCode().

CompareTo sıralı setler içindir.

Benzersiz nesneler istiyorsanız, ancak yineleme sıralarını önemsemiyorsanız HashSet<T>, genellikle en iyi seçimdir.


5

yapıcı HashSet, yeni nesne eklemek için IEqualityComparer uygulayan nesneyi alır. HashSet'te yöntemi kullanmak isterseniz, Equals, GetHashCode'u geçersiz kılmanız gerekir.

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");
        }
    }
}

Ya İsim boşsa? null'un karma değeri nedir?
joe
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.