Di solito una semplice funzione hash funziona prendendo le "parti componenti" dell'input (caratteri nel caso di una stringa) e moltiplicandole per i poteri di una costante e sommandole in un tipo intero. Quindi ad esempio un tipico hash (anche se non particolarmente buono) di una stringa potrebbe essere:
(first char) + k * (second char) + k^2 * (third char) + ...
Quindi, se viene inserito un gruppo di stringhe che hanno tutte lo stesso primo carattere, i risultati saranno tutti lo stesso modulo k, almeno fino a quando il tipo intero non trabocca.
[Ad esempio, la stringa hashCode di Java è stranamente simile a questa - fa in modo che i caratteri si invertano, con k = 31. In questo modo si ottengono relazioni sorprendenti modulo 31 tra stringhe che terminano allo stesso modo e relazioni sorprendenti modulo 2 ^ 32 tra stringhe uguali tranne vicino alla fine. Questo non confonde seriamente il comportamento hashtable.]
Una tabella hash funziona prendendo il modulo dell'hash sul numero di bucket.
In una tabella hash è importante non produrre collisioni per casi probabili, poiché le collisioni riducono l'efficienza della tabella.
Supponiamo ora che qualcuno inserisca un intero gruppo di valori in una tabella hash che abbia qualche relazione tra gli oggetti, come se tutti avessero lo stesso primo carattere. Questo è un modello di utilizzo abbastanza prevedibile, direi, quindi non vogliamo che produca troppe collisioni.
Si scopre che "a causa della natura della matematica", se la costante utilizzata nell'hash e il numero di bucket sono coprimi , le collisioni sono minimizzate in alcuni casi comuni. Se non sono coprimi, quindi ci sono alcune relazioni abbastanza semplici tra input per i quali le collisioni non sono minimizzate. Tutti gli hash escono allo stesso modo del fattore comune, il che significa che cadranno tutti nell'1 / n dei secchi che hanno quel valore modulo il fattore comune. Ottieni n volte più collisioni, dove n è il fattore comune. Poiché n è almeno 2, direi che è inaccettabile che un caso d'uso abbastanza semplice generi almeno il doppio delle collisioni rispetto al normale. Se un utente sta per dividere la nostra distribuzione in secchi, vogliamo che sia un incidente strano, non un semplice utilizzo prevedibile.
Ora, le implementazioni di hashtable ovviamente non hanno alcun controllo sugli oggetti messi in esse. Non possono impedire che siano collegati. Quindi la cosa da fare è assicurarsi che i conteggi della costante e del bucket siano coprimi. In questo modo non si fa affidamento sul solo "ultimo" componente per determinare il modulo del bucket rispetto ad alcuni piccoli fattori comuni. Per quanto ne so non devono essere primi per raggiungere questo obiettivo, solo coprimi.
Ma se la funzione hash e la tabella hash sono scritte in modo indipendente, allora la tabella hash non sa come funziona la funzione hash. Potrebbe usare una costante con piccoli fattori. Se sei fortunato, potrebbe funzionare in modo completamente diverso ed essere non lineare. Se l'hash è abbastanza buono, qualsiasi conteggio dei bucket va bene. Ma un hashtable paranoico non può assumere una buona funzione hash, quindi dovrebbe usare un numero primo di bucket. Allo stesso modo una funzione hash paranoica dovrebbe usare una costante primaria di grandi dimensioni, per ridurre la possibilità che qualcuno usi un numero di bucket che hanno un fattore comune con la costante.
In pratica, penso che sia abbastanza normale usare una potenza di 2 come numero di secchi. Questo è conveniente e consente di evitare di cercare o preselezionare un numero primo della giusta grandezza. Quindi fai affidamento sulla funzione hash per non usare nemmeno i moltiplicatori, che è generalmente un presupposto sicuro. Ma puoi ancora ottenere comportamenti di hashing occasionali basati su funzioni di hash come quella sopra e il conteggio dei bucket principali potrebbe aiutare ulteriormente.
Mettere sul principio che "tutto deve essere primo" è per quanto ne so una condizione sufficiente ma non necessaria per una buona distribuzione su hashtable. Permette a tutti di interagire senza la necessità di presumere che gli altri abbiano seguito la stessa regola.
[Modifica: c'è un altro motivo più specializzato per utilizzare un numero primo di bucket, ovvero se si gestiscono le collisioni con sondaggi lineari. Quindi calcoli una falcata dall'hashcode e se quella falcata risulta essere un fattore del conteggio dei bucket, puoi fare solo (bucket_count / stride) sonde prima di tornare da dove hai iniziato. Il caso che vuoi evitare di più è stride = 0, ovviamente, che deve essere con maiuscole / minuscole, ma per evitare anche maiuscole / minuscole con maiuscole / minuscole pari a un numero intero piccolo, puoi semplicemente rendere bucket_count primo e non preoccuparti di cosa passo è fornito non è 0.]