Während die in Jon Skeets Antwort beschriebene Vorlage im Allgemeinen als Hash-Funktionsfamilie gut funktioniert, ist die Auswahl der Konstanten wichtig, und der Keim 17
und der Faktor von, 31
wie in der Antwort angegeben, funktionieren für allgemeine Anwendungsfälle überhaupt nicht gut. In den meisten Anwendungsfällen liegen die Hash-Werte viel näher bei Null als int.MaxValue
und die Anzahl der gemeinsam gehashten Elemente beträgt einige Dutzend oder weniger.
Für das Hashing eines ganzzahligen Tupels {x, y}
mit -1000 <= x <= 1000
und -1000 <= y <= 1000
hat es eine abgrundtiefe Kollisionsrate von fast 98,5%. Zum Beispiel {1, 0} -> {0, 31}
, {1, 1} -> {0, 32}
usw. Wenn wir die Abdeckung erweitern , um auch n-Tupel umfasst , wo 3 <= n <= 25
, tut es weniger schrecklich mit einer Kollisionsgeschwindigkeit von etwa 38%. Aber wir können es viel besser machen.
public static int CustomHash(int seed, int factor, params int[] vals)
{
int hash = seed;
foreach (int i in vals)
{
hash = (hash * factor) + i;
}
return hash;
}
Ich habe eine Monte-Carlo-Stichproben-Suchschleife geschrieben, die die obige Methode mit verschiedenen Werten für Startwert und Faktor über verschiedene zufällige n-Tupel zufälliger Ganzzahlen getestet hat i
. Zulässige Bereiche waren 2 <= n <= 25
(wo n
zufällig, aber zum unteren Ende des Bereichs hin voreingenommen) und -1000 <= i <= 1000
. Für jedes Samen- und Faktorpaar wurden mindestens 12 Millionen einzigartige Kollisionstests durchgeführt.
Nach ca. 7 Stunden Laufzeit , das beste Paar gefunden (wo der Samen und Faktor sowohl auf 4 Stellen begrenzt waren oder weniger) war: seed = 1009
, factor = 9176
, mit einer Kollisionsgeschwindigkeit von 0,1131%. In den 5- und 6-stelligen Bereichen gibt es noch bessere Optionen. Aber ich habe der Kürze halber den besten 4-stelligen Darsteller ausgewählt, und er zeigt sich in allen gängigen int
und char
Hashing-Szenarien recht gut . Es scheint auch gut mit ganzen Zahlen von viel größeren Größen zu funktionieren.
Es ist erwähnenswert, dass "Prime sein" keine allgemeine Voraussetzung für eine gute Leistung als Keim und / oder Faktor zu sein schien, obwohl dies wahrscheinlich hilfreich ist. 1009
oben erwähnt ist in der Tat Prime, ist es aber 9176
nicht. Ich habe explizit Variationen davon getestet, bei denen ich factor
zu verschiedenen Primzahlen in der Nähe 9176
(während des Verlassens seed = 1009
) gewechselt habe und alle schlechter abschnitten als die obige Lösung.
Zuletzt habe ich auch mit der generischen ReSharper-Empfehlungsfunktionsfamilie von verglichen hash = (hash * factor) ^ i;
und das Original, CustomHash()
wie oben erwähnt, übertrifft es ernsthaft. Der ReSharper XOR-Stil scheint Kollisionsraten im Bereich von 20 bis 30% für allgemeine Anwendungsfallannahmen zu haben und sollte meiner Meinung nach nicht verwendet werden.