Por lo general, una función hash simple funciona tomando las "partes componentes" de la entrada (caracteres en el caso de una cadena) y multiplicándolas por las potencias de alguna constante, y sumándolas juntas en algún tipo entero. Entonces, por ejemplo, un hash típico (aunque no especialmente bueno) de una cadena podría ser:
(first char) + k * (second char) + k^2 * (third char) + ...
Luego, si se alimentan un conjunto de cadenas que tienen el mismo primer carácter, los resultados serán todos del mismo módulo k, al menos hasta que se desborde el tipo entero.
[Como ejemplo, el string hashCode de Java es inquietantemente similar a esto: hace el orden inverso de los caracteres, con k = 31. Entonces obtienes relaciones sorprendentes módulo 31 entre cadenas que terminan de la misma manera, y relaciones sorprendentes módulo 2 ^ 32 entre cadenas que son iguales excepto cerca del final. Esto no estropea seriamente el comportamiento de tabla hash.]
Una tabla hash funciona tomando el módulo del hash sobre el número de cubos.
Es importante en una tabla hash no producir colisiones para casos probables, ya que las colisiones reducen la eficiencia de la tabla hash.
Ahora, supongamos que alguien pone un montón de valores en una tabla hash que tiene alguna relación entre los elementos, como que todos tienen el mismo primer carácter. Este es un patrón de uso bastante predecible, diría, por lo que no queremos que produzca demasiadas colisiones.
Resulta que "debido a la naturaleza de las matemáticas", si la constante utilizada en el hash y el número de cubos son coprimos , las colisiones se minimizan en algunos casos comunes. Si no son coprimos, entonces hay algunas relaciones bastante simples entre las entradas para las cuales no se minimizan las colisiones. Todos los hashes salen igual al módulo del factor común, lo que significa que todos caerán en la 1ª parte de los cubos que tienen ese valor del módulo del factor común. Obtienes n veces más colisiones, donde n es el factor común. Como n es al menos 2, diría que es inaceptable que un caso de uso bastante simple genere al menos el doble de colisiones de lo normal. Si algún usuario va a dividir nuestra distribución en cubos, queremos que sea un accidente extraño, no un simple uso predecible.
Ahora, las implementaciones de tabla hash obviamente no tienen control sobre los elementos puestos en ellas. No pueden evitar que estén relacionados. Entonces, lo que hay que hacer es asegurarse de que la cuenta constante y el conteo sean coprimos. De esa manera, no dependerá únicamente del "último" componente para determinar el módulo de la cubeta con respecto a algún factor común pequeño. Por lo que sé, no tienen que ser primos para lograr esto, solo coprime.
Pero si la función hash y la tabla hash se escriben de forma independiente, entonces la tabla hash no sabe cómo funciona la función hash. Podría estar usando una constante con pequeños factores. Si tienes suerte, podría funcionar de manera completamente diferente y no ser lineal. Si el hash es lo suficientemente bueno, entonces cualquier conteo de cubos está bien. Pero una tabla hash paranoica no puede asumir una buena función hash, por lo que debe usar un número primo de cubos. Del mismo modo, una función hash paranoica debería usar una constante principal grande, para reducir la posibilidad de que alguien use varios cubos que tienen un factor común con la constante.
En la práctica, creo que es bastante normal usar una potencia de 2 como número de cubos. Esto es conveniente y ahorra tener que buscar o preseleccionar un número primo de la magnitud correcta. Por lo tanto, confía en la función hash para no usar incluso multiplicadores, lo que generalmente es una suposición segura. Pero aún puede obtener comportamientos de hash malos ocasionales basados en funciones hash como la anterior, y el recuento de cubos principales podría ayudar aún más.
Poner sobre el principio de que "todo tiene que ser primo" es, hasta donde yo sé, una condición suficiente pero no necesaria para una buena distribución sobre tablas hash. Permite a todos interactuar sin necesidad de asumir que los demás han seguido la misma regla.
[Editar: hay otra razón más especializada para usar un número primo de cubos, que es si manejas colisiones con sondeo lineal. Luego calcula un paso a partir del código hash, y si ese paso resulta ser un factor del conteo de cubos, entonces solo puede hacer sondas (bucket_count / stride) antes de volver a donde comenzó. El caso que más desea evitar es stride = 0, por supuesto, que debe estar en mayúsculas especiales, pero para evitar también una mayúscula especial bucket_count / stride igual a un número entero pequeño, puede hacer que bucket_count sea primo y no le importe lo que se proporciona zancada no es 0.]