Normalerweise funktioniert eine einfache Hash-Funktion, indem die "Komponenten" der Eingabe (Zeichen im Fall einer Zeichenfolge) mit den Potenzen einer Konstanten multipliziert und zu einem ganzzahligen Typ addiert werden. So könnte beispielsweise ein typischer (wenn auch nicht besonders guter) Hash eines Strings sein:
(first char) + k * (second char) + k^2 * (third char) + ...
Wenn dann eine Reihe von Zeichenfolgen eingespeist werden, die alle das gleiche erste Zeichen haben, sind die Ergebnisse alle das gleiche Modulo k, zumindest bis der ganzzahlige Typ überläuft.
[Als Beispiel ist Javas String hashCode diesem unheimlich ähnlich - er führt die Zeichen in umgekehrter Reihenfolge mit k = 31 aus. Sie erhalten also auffällige Beziehungen Modulo 31 zwischen Zeichenfolgen, die auf die gleiche Weise enden, und auffällige Beziehungen Modulo 2 ^ 32 zwischen Zeichenfolgen, die bis auf das Ende gleich sind. Dies bringt das Hashtable-Verhalten nicht ernsthaft durcheinander.]
Eine Hashtabelle berechnet den Modul des Hash über die Anzahl der Buckets.
In einer Hashtabelle ist es wichtig, in wahrscheinlichen Fällen keine Kollisionen zu erzeugen, da Kollisionen die Effizienz der Hashtabelle verringern.
Angenommen, jemand fügt eine ganze Reihe von Werten in eine Hashtabelle ein, die eine Beziehung zwischen den Elementen haben, wie alle, die das gleiche erste Zeichen haben. Ich würde sagen, dies ist ein ziemlich vorhersehbares Nutzungsmuster, daher möchten wir nicht, dass es zu viele Kollisionen erzeugt.
Es stellt sich heraus, dass "aufgrund der Natur der Mathematik", wenn die im Hash verwendete Konstante und die Anzahl der Buckets Koprime sind , Kollisionen in einigen häufigen Fällen minimiert werden. Wenn sie nicht koprime sindDann gibt es einige ziemlich einfache Beziehungen zwischen Eingaben, für die Kollisionen nicht minimiert werden. Alle Hashes sind gleich modulo dem gemeinsamen Faktor, was bedeutet, dass sie alle in das 1 / n-te der Buckets fallen, deren Wert modulo der gemeinsame Faktor ist. Sie erhalten n-mal so viele Kollisionen, wobei n der gemeinsame Faktor ist. Da n mindestens 2 ist, würde ich sagen, dass es für einen ziemlich einfachen Anwendungsfall nicht akzeptabel ist, mindestens doppelt so viele Kollisionen wie normal zu erzeugen. Wenn ein Benutzer unsere Verteilung in Eimer aufteilt, möchten wir, dass es sich um einen Freak-Unfall handelt und nicht um eine einfache vorhersehbare Verwendung.
Jetzt haben Hashtable-Implementierungen offensichtlich keine Kontrolle über die darin enthaltenen Elemente. Sie können nicht verhindern, dass sie verwandt sind. Sie müssen also sicherstellen, dass die Anzahl der Konstanten und der Bucket gleichzeitig erfolgt. Auf diese Weise verlassen Sie sich nicht nur auf die "letzte" Komponente, um den Modul des Eimers in Bezug auf einen kleinen gemeinsamen Faktor zu bestimmen. Soweit ich weiß, müssen sie nicht erstklassig sein, um dies zu erreichen, sondern nur Koprime.
Wenn die Hash-Funktion und die Hashtabelle jedoch unabhängig voneinander geschrieben werden, weiß die Hashtabelle nicht, wie die Hash-Funktion funktioniert. Möglicherweise wird eine Konstante mit kleinen Faktoren verwendet. Wenn Sie Glück haben, funktioniert es möglicherweise ganz anders und ist nichtlinear. Wenn der Hash gut genug ist, ist jede Bucket-Anzahl in Ordnung. Eine paranoide Hashtabelle kann jedoch keine gute Hash-Funktion annehmen und sollte daher eine Primzahl von Buckets verwenden. In ähnlicher Weise sollte eine paranoide Hash-Funktion eine größere Primkonstante verwenden, um die Wahrscheinlichkeit zu verringern, dass jemand eine Anzahl von Buckets verwendet, die zufällig einen gemeinsamen Faktor mit der Konstante haben.
In der Praxis halte ich es für ziemlich normal, eine Potenz von 2 als Anzahl der Eimer zu verwenden. Dies ist praktisch und erspart das Durchsuchen oder Vorauswählen einer Primzahl der richtigen Größe. Sie verlassen sich also auf die Hash-Funktion, um nicht einmal Multiplikatoren zu verwenden, was im Allgemeinen eine sichere Annahme ist. Aber Sie können immer noch gelegentlich schlechte Hashing-Verhaltensweisen erhalten, die auf Hash-Funktionen wie der oben beschriebenen basieren, und die Anzahl der Haupt-Buckets könnte weiter helfen.
Das Prinzip, dass "alles Primzahl sein muss", ist meines Wissens eine ausreichende, aber nicht notwendige Voraussetzung für eine gute Verteilung über Hashtabellen. Es ermöglicht jedem, zusammenzuarbeiten, ohne davon ausgehen zu müssen, dass die anderen die gleiche Regel befolgt haben.
[Bearbeiten: Es gibt einen anderen, spezielleren Grund, eine Primzahl von Buckets zu verwenden, wenn Sie Kollisionen mit linearer Abtastung behandeln. Dann berechnen Sie einen Schritt aus dem Hashcode, und wenn sich herausstellt, dass dieser Schritt ein Faktor für die Bucket-Anzahl ist, können Sie nur (Bucket_Count / Stride) -Sonden durchführen, bevor Sie wieder dort sind, wo Sie begonnen haben. Der Fall, den Sie am meisten vermeiden möchten, ist natürlich stride = 0, was ein Sonderfall sein muss. Um jedoch auch zu vermeiden, dass Bucket_count / Stride mit Sondergehäusen gleich einer kleinen Ganzzahl ist, können Sie einfach den Bucket_count prim machen und sich nicht darum kümmern, was der ist Schritt ist vorausgesetzt, es ist nicht 0.]