2009-07-02 16 views
5

Zasadniczo mam następujące pory:Jak mam wdrożyć Object.GetHashCode() dla złożonej równości?

class Foo { 
    public override bool Equals(object obj) 
    { 
     Foo d = obj as Foo ; 
     if (d == null) 
      return false; 

     return this.Equals(d); 
    } 

    #region IEquatable<Foo> Members 

    public bool Equals(Foo other) 
    { 
     if (this.Guid != String.Empty && this.Guid == other.Guid) 
      return true; 
     else if (this.Guid != String.Empty || other.Guid != String.Empty) 
      return false; 

     if (this.Title == other.Title && 
      this.PublishDate == other.PublishDate && 
      this.Description == other.Description) 
      return true; 

     return false; 
    } 
} 

Tak, problem jest taki: mam non-wymagane pole Guid, który jest unikatowy identyfikator. Jeśli nie jest to ustawione, muszę spróbować określić równość w oparciu o mniej dokładne metryki jako próbę określenia, czy dwa obiekty są równe. Działa to dobrze, ale sprawia, że ​​GetHashCode() jest brudny ... Jak powinienem to zrobić? Naiwna implementacja byłaby podobna do:

public override int GetHashCode() { 
    if (this.Guid != String.Empty) 
     return this.Guid.GetHashCode(); 

    int hash = 37; 
    hash = hash * 23 + this.Title.GetHashCode(); 
    hash = hash * 23 + this.PublishDate.GetHashCode(); 
    hash = hash * 23 + this.Description.GetHashCode(); 
    return hash; 
} 

Ale jakie są szanse na zderzenie dwóch typów hasha? Oczywiście, nie oczekiwałbym, że będzie to 1 in 2 ** 32. Czy to zły pomysł, a jeśli tak, to w jaki sposób powinienem to robić?

+0

Ważniejsze jest to, że twój algorytm skrótu zgadza się z twoim algorytmem równości, niż rozkład jest jednolity. Pamiętaj, że celem skrótu jest uzyskanie czystej dystrybucji w tabeli mieszania; Dopóki nie jesteś masywnie wypaczony do jednego konkretnego kubła, szanse są dobre, nic ci nie będzie. Jeśli jesteś zaniepokojony, wybierz rozsądny scenariusz, który może spotkać konsumenta twojego obiektu - powiedzmy, umieszczając kilkaset z nich w słowniku, jeśli to rozsądne - i wykonaj kilka testów, aby sprawdzić, czy jesteś akceptowalny wyniki. –

+0

Najbardziej kiedykolwiek widziałem w rzeczywistym użyciu było ~ 200, ale typowe użycie jest <30, więc prawdopodobnie masz rację. –

+1

Heck, z mniej niż 30 pozycji, wyszukiwanie liniowe na liście połączonej jest prawdopodobnie wykonalne. Możesz zawsze zwracać kod skrótu, mieć 100% szansy na kolizję, a mimo to uzyskać akceptowalną wydajność. Istotą dobrej dystrybucji kodów skrótu jest uzyskanie skali wydajności, gdy rozmiar słownika staje się duży. Możesz mieć kiepską dystrybucję i nadal uzyskiwać dobre wyniki, jeśli zamierzasz umieścić tylko niewielką liczbę przedmiotów w tabeli. –

Odpowiedz

4

Nie sądzę, że jest jakiś problem z podejściem, które wybrałeś. Niepokojące "zbyt dużo" o kolizjach hash jest prawie zawsze oznaką nadmiernego myślenia o problemie; tak długo, jak mieszanie jest bardzo prawdopodobne, powinno być w porządku.

Ostatecznie możesz nawet rozważyć pominięcie wartości Description ze swojego skrótu, jeśli uzasadnione jest oczekiwanie, że w większości przypadków obiekty można odróżnić na podstawie tytułu i daty publikacji (książki?).

Można nawet rozważyć lekceważenie identyfikatora GUID w funkcji haszowania i używać go tylko w implementacji Equals, aby ujednoznacznić mało prawdopodobny (?) Przypadek konfliktów hash.

+0

Altho, oczywiście, identyfikator GUID, jeśli jest obecny, prawdopodobnie będzie mieszał dużo szybciej niż arbitralny ciąg znaków ... więc może to być możliwa optymalizacja wydajności. – jerryjvl

+0

Opis musi być zawarty w równości (a co za tym idzie w kodzie skrótu) –

+0

Och, i dla rekordu, pozycje RSS. –

7

Bardzo łatwy hash code method for custom classes jest bitowe XOR każdego z kodów hash pól razem. To może być tak proste, jak to:

int hash = 0; 
hash ^= this.Title.GetHashCode(); 
hash ^= this.PublishDate.GetHashCode(); 
hash ^= this.Description.GetHashCode(); 
return hash; 

Z link above:

XOR ma następujące właściwości ładne:

  • to nie zależy od kolejności obliczeń.
  • Nie "marnuje" bitów. Jeśli zmienisz nawet jeden bit w jednym ze składników, ostateczna wartość zmieni się.
  • Jest szybki, pojedynczy cykl na nawet najbardziej prymitywnym komputerze.
  • Umożliwia zachowanie jednolitego rozkładu. Jeśli oba połączone elementy są równomiernie rozmieszczone, tak będzie połączenie. Innymi słowy, nie ma tendencji do zwijania zakresu trawienia do węższego pasma.

XOR nie działa dobrze, jeśli można oczekiwać, aby mieć zduplikowane wartości w polach jak zduplikowane wartości będą znoszą się nawzajem, kiedy XORed. Ponieważ mieszamy razem trzy niepowiązane pola, które nie powinny w tym przypadku stanowić problemu.

+7

XOR nie zależy od kolejności obliczeń jest mieczem obosiecznym ... jeśli masz obiekty z wieloma polami tego samego typu (na przykład dwie daty), wtedy gdy są one zamieniane wokół obiektów, będą wyglądać tak samo "do skrótu. – jerryjvl

Powiązane problemy