Wie implementiere ich GetHashCode für eine Struktur mit zwei Zeichenfolgen, wenn beide Zeichenfolgen austauschbar sind?

70

Ich habe eine Struktur in C #:

public struct UserInfo
{
   public string str1
   {
     get;
     set;
   }

   public string str2
   {
     get;
     set;
   }   
}

Die einzige Regel ist das UserInfo(str1="AA", str2="BB").Equals(UserInfo(str1="BB", str2="AA"))

Wie überschreibe ich die GetHashCode-Funktion für diese Struktur?

Graviton
quelle
3
@nawfal, sollte es nicht umgekehrt sein? Meine Frage wurde am 16. September 2008 veröffentlicht, aber die von Ihnen vorgeschlagene wurde am 22. September 2008 veröffentlicht.
Graviton

Antworten:

69

MSDN :

Eine Hash-Funktion muss die folgenden Eigenschaften haben:

  • Wenn zwei Objekte als gleich verglichen werden, muss die GetHashCodeMethode für jedes Objekt denselben Wert zurückgeben. Wenn jedoch zwei Objekte nicht als gleich verglichen werden, müssen die GetHashCodeMethoden für die beiden Objekte keine unterschiedlichen Werte zurückgeben.
  • Die GetHashCodeMethode für ein Objekt muss konsistent denselben Hashcode zurückgeben, solange der Objektstatus, der den Rückgabewert des Objekts bestimmt, nicht geändert wirdEquals geändert wird. Beachten Sie, dass dies nur für die aktuelle Ausführung einer Anwendung gilt und dass ein anderer Hash-Code zurückgegeben werden kann, wenn die Anwendung erneut ausgeführt wird.
  • Für die beste Leistung muss eine Hash-Funktion eine zufällige Verteilung für alle Eingaben generieren.

Die richtige Berücksichtigung ist:

return str1.GetHashCode() ^ str2.GetHashCode() 

^ kann durch eine andere kommutative Operation ersetzt werden

aku
quelle
Sollte das nicht return str1.GetHashCode () ^ str2.GetHashCode () sein?
roomaroo
3
Berücksichtigt auch keine Nullwerte.
Omer van Kloeten
15
Omer van Kloeten sollte für jeden .net-Entwickler offensichtlich sein. schnelles Beispiel, um allgemeine Idee zu zeigen, nicht vollständige Lösung
aku
2
Wenn Sie erwarten, dass str1, str2 und str2, str1 in Ihrem Hash sehr häufig vorkommen, ist die Suchgeschwindigkeit möglicherweise etwas langsamer als erwartet. Die Suchgeschwindigkeit kann auch durch Zwischenspeichern des Hashcodes erhöht werden. Offensichtlich können dies vorzeitige Optimierungen sein.
Brian
+1 für den Hinweis auf die Wichtigkeit einer kommutativen Operation
Pandincus
27

Siehe Jon Skeets Antwort - binäre Operationen wie ^sind nicht gut, sie erzeugen oft kollidierenden Hash!

Tomáš Kafka
quelle
7
aber jon sagt, es ist schlecht, weil es genau das tut, was OP will. F(a,b) == F(b,a)...
Noctis
15
public override int GetHashCode()
{
    unchecked
    {
        return (str1 ?? String.Empty).GetHashCode() +
            (str2 ?? String.Empty).GetHashCode();
    }
}

Die Verwendung des Operators '+' ist möglicherweise besser als die Verwendung von '^', da Sie zwar explizit möchten, dass ('AA', 'BB') und ('BB', 'AA') explizit identisch sind, dies aber möglicherweise nicht möchten ( 'AA', 'AA') und ('BB', 'BB') müssen gleich sein (oder alle gleichen Paare).

Die Regel 'so schnell wie möglich' wird in dieser Lösung nicht vollständig eingehalten, da bei Nullen ein 'GetHashCode ()' für die leere Zeichenfolge ausgeführt wird, anstatt sofort eine bekannte Konstante zurückzugeben, aber auch ohne explizite Messung bin ich bereit eine Vermutung zu riskieren, dass der Unterschied nicht groß genug wäre, um sich Sorgen zu machen, wenn Sie nicht viele Nullen erwarten.


quelle
5
  1. In der Regel besteht eine einfache Möglichkeit zum Generieren eines Hashcodes für eine Klasse darin, alle Datenfelder, die an der Generierung des Hashcodes beteiligt sein können, zu XOREN (wobei darauf zu achten ist, dass andere auf Null prüfen). Dies erfüllt auch die (künstliche?) Anforderung, dass die Hashcodes für UserInfo ("AA", "BB") und UserInfo ("BB", "AA") identisch sind.

  2. Wenn Sie Annahmen über die Verwendung Ihrer Klasse treffen können, können Sie möglicherweise Ihre Hash-Funktion verbessern. Wenn beispielsweise str1 und str2 häufig gleich sind, ist XOR möglicherweise keine gute Wahl. Wenn jedoch str1 und str2 beispielsweise Vor- und Nachnamen darstellen, ist XOR wahrscheinlich eine gute Wahl.

Obwohl dies eindeutig kein reales Beispiel sein soll, kann darauf hingewiesen werden, dass: - dies wahrscheinlich ein schlechtes Beispiel für die Verwendung einer Struktur ist: Eine Struktur sollte normalerweise eine Wertesemantik haben, was nicht der Fall zu sein scheint der Fall hier. - Die Verwendung von Eigenschaften mit Setzern zum Generieren eines Hash-Codes ist ebenfalls problematisch.

Joe
quelle
Hmm, warum hat seine Struktur Ihrer Meinung nach keine Wertesemantik? Und könnten Sie Ihren letzten Satz erweitern?
Stefan Monov
4

Ein einfacher allgemeiner Weg ist dies:

return string.Format("{0}/{1}", str1, str2).GetHashCode();

Wenn Sie keine strengen Leistungsanforderungen haben, ist dies die einfachste, die ich mir vorstellen kann, und ich verwende diese Methode häufig, wenn ich einen zusammengesetzten Schlüssel benötige. Es behandelt die nullFälle einwandfrei und verursacht (m) keine Hash-Kollisionen (im Allgemeinen). Wenn Sie '/' in Ihren Zeichenfolgen erwarten, wählen Sie einfach ein anderes Trennzeichen, das Sie nicht erwarten.

Daniel Lidström
quelle
Sehr einfach. Dies kann in C # 6.0 auf einfach vereinfacht werden return $"{str1}/{str2}".GetHashCode();. Siehe String Interpolation
styfle
Nicht sicher, was ist, wenn str1 = "a / b" und str2 = ""? Dies hätte den gleichen Hash wie str1 = "a" und str2 = "b /".
Erwin Mayer
1
@ErwinMayer verwendet ein Trennzeichen, von dem Sie wissen, dass es nicht in Ihren Zeichenfolgen enthalten ist. Außerdem muss GetHashCode nicht immer eindeutige Werte zurückgeben. Es wird als Optimierung verwendet, um zu vermeiden, dass Equalszu oft angerufen wird (ein genauer Vergleich ist häufig teurer).
Daniel Lidström
Wie stellt dies sicher, dass der gleiche Hashcode für str1 = "a", str2 = "b" und für str1 = "b" str2 = "a" angezeigt wird? Gibt es etwas Magie, so dass "a / b" und "b / a" zu demselben Hash führen?
Kasper van den Berg
@KaspervandenBerg Nein, diese beiden müssen unterschiedliche Hashs haben, da sie nicht gleich sind, oder?
Daniel Lidström
3
public override int GetHashCode()   
{       
    unchecked      
    {           
        return(str1 != null ? str1.GetHashCode() : 0) ^ (str2 != null ? str2.GetHashCode() : 0);       
    }   
}
user11556
quelle
7
Warum deaktiviert? xor kann nicht überlaufen.
Konrad Rudolph
3

In diesem Sinne schlägt ReSharper Folgendes vor:

public int GetHashCode()
{
    unchecked
    {
        int hashCode;

        // String properties
        hashCode = (hashCode * 397) ^ (str1!= null ? str1.GetHashCode() : 0);
        hashCode = (hashCode * 397) ^ (str2!= null ? str1.GetHashCode() : 0);

        // int properties
        hashCode = (hashCode * 397) ^ intProperty;
        return hashCode;
    }
}

397 ist eine Primzahl von ausreichender Größe, um zu bewirken, dass die Ergebnisvariable überläuft und die Bits des Hash etwas mischt, wodurch eine bessere Verteilung der Hash-Codes bereitgestellt wird. Ansonsten gibt es in 397 nichts Besonderes, das es von anderen Primzahlen gleicher Größe unterscheidet.

Jani Hyytiäinen
quelle
Dieser Hash-Code erfüllt nicht die Anforderungen von OP: Die einzige Regel ist, dass UserInfo (str1 = "AA", str2 = "BB"). Gleich (UserInfo (str1 = "BB", str2 = "AA"))
Kasper van den Berg
2

Ach ja, wie Gary Shutler betonte:

return str1.GetHashCode() + str2.GetHashCode();

Kann überlaufen. Sie können versuchen, das Casting so lange durchzuführen, wie Artem es vorgeschlagen hat, oder Sie können die Anweisung mit dem nicht aktivierten Schlüsselwort umgeben:

return unchecked(str1.GetHashCode() + str2.GetHashCode());
Grokys
quelle
1

Probieren Sie dieses aus:

(((long)str1.GetHashCode()) + ((long)str2.GetHashCode())).GetHashCode()
Artem Tikhomirov
quelle
0

Viele Möglichkeiten. Z.B

return str1.GetHashCode() ^ str1.GetHashCode()

VolkerK
quelle
0

Vielleicht so etwas wie str1.GetHashCode () + str2.GetHashCode ()? oder (str1.GetHashCode () + str2.GetHashCode ()) / 2? Auf diese Weise wäre es das gleiche, unabhängig davon, ob str1 und str2 vertauscht werden ....

Mike Stone
quelle
0

Sortieren Sie sie und verketten Sie sie dann:

return ((str1.CompareTo (str2) <1)? str1 + str2: str2 + str1)
    .GetHashCode ();
Steve Morgan
quelle
2
Dies führt dazu, dass Ihre GetHashCode-Methode ziemlich viel Arbeit leistet. Hash-Codes sollen schnell sein. Aus MSDN: "Mit einer Hash-Funktion wird schnell eine Zahl (Hash-Code) generiert, die dem Wert eines Objekts entspricht." Das Zuweisen einer neuen Zeichenfolge scheint eine schlechte Idee innerhalb einer Hash-Funktion zu sein.
Wilka
0

Das Ergebnis von GetHashCode soll sein:

  1. So schnell wie möglich.
  2. So einzigartig wie möglich.

Wenn ich das bedenke, würde ich so etwas machen:

if (str1 == null)
    if (str2 == null)
        return 0;
    else
       return str2.GetHashCode();
else
    if (str2 == null)
        return str1.GetHashCode();
    else
       return ((ulong)str1.GetHashCode() | ((ulong)str2.GetHashCode() << 32)).GetHashCode();

Bearbeiten: Die Nullen vergessen. Code behoben.

Omer van Kloeten
quelle
1
Die einzige Regel ist, dass UserInfo (str1 = "AA", str2 = "BB"). Gleich (UserInfo (str1 = "BB", str2 = "AA"))
Alfred Barthand
-1

Zu kompliziert und vergisst Nullen usw. Dies wird für Dinge wie Bucketing verwendet, damit Sie mit so etwas davonkommen können

if (null != str1) {
    return str1.GetHashCode();
}
if (null != str2) {
    return str2.GetHashCode();
}
//Not sure what you would put here, some constant value will do
return 0;

Dies wird durch die Annahme verzerrt, dass str1 in einem ungewöhnlich großen Anteil von Fällen wahrscheinlich nicht häufig vorkommt.

Roger Willcocks
quelle
Dies erfüllt nicht die Bedingung, dass die Reihenfolge von str1 und str2 keine Rolle spielt. ("A", "B") und ("B", "A") erzeugen unterschiedliche Hashcodes.
Sebastian Negraszus
6,5 Jahre später? Und auf welchen Zustand beziehen Sie sich? Dies ist die Diskussion über die Generierung eines Hashcodes für eine Struktur mit 2 Zeichenfolgen, nicht für das, was beim Vergleich von 2 Zeichenfolgen passiert.
Roger Willcocks
Die Strukturen ("A", "B") und ("B", "A") sollten als gleich angesehen werden. Daher müssen ihre Hash-Codes gleich sein. Aber ("A", "B") erzeugt den Hash-Code von "A" und ("B", "A") erzeugt den Hash-Code von "B" - was nicht gleich ist.
Sebastian Negraszus
Da diese Frage mindestens in den letzten 6 Monaten bearbeitet wurde, bin ich mir nicht sicher, ob sie ursprünglich in dieser Frage enthalten war.
Roger Willcocks