web-dev-qa-db-fra.com

Pourquoi les fonctions de hachage devraient-elles utiliser un module de nombre premier?

Il y a longtemps, j'ai acheté un livre sur les structures de données à un prix avantageux de 1,25 $. L'explication d'une fonction de hachage indiquait qu'elle devrait finalement être modifiée par un nombre premier en raison de "la nature des mathématiques".

Qu'attendez-vous d'un livre à 1,25 $?

Quoi qu'il en soit, j'ai eu des années pour réfléchir à la nature des mathématiques et je n'arrive toujours pas à comprendre.

La distribution des nombres est-elle vraiment plus uniforme même lorsqu'il existe un nombre premier de compartiments? Ou est-ce un vieux conte de programmeur que tout le monde accepte parce que tout le monde sinon l'accepte?

314
theschmitzer

Habituellement, une simple fonction de hachage fonctionne en prenant les "composants" de l'entrée (les caractères dans le cas d'une chaîne), en les multipliant par les puissances d'une constante et en les additionnant dans un type entier. Ainsi, par exemple, un hachage typique (mais pas particulièrement bon) d'une chaîne pourrait être:

(first char) + k * (second char) + k^2 * (third char) + ...

Ensuite, si un groupe de chaînes ayant toutes le même premier caractère sont insérées, les résultats seront tous identiques, modulo k, au moins jusqu’à ce que le type entier déborde.

[Par exemple, la chaîne de caractères hashCode de Java ressemble étrangement à ceci: elle effectue l'ordre inverse des caractères, avec k = 31. Ainsi, vous obtenez des relations frappantes modulo 31 entre des chaînes qui se terminent de la même façon, et des relations frappantes modulo 2 ^ 32 entre des chaînes identiques, sauf vers la fin. Cela ne gâche pas sérieusement le comportement de hashtable.]

Une table de hachage fonctionne en prenant le module du hachage sur le nombre de seaux.

Dans une table de hachage, il est important de ne pas produire de collision pour les cas probables, car elle réduit l'efficacité de la table de hachage.

Supposons maintenant que quelqu'un place dans une table de hachage toute une série de valeurs ayant une relation entre les éléments, comme si tous avaient le même premier caractère. Je dirais que c'est un modèle d'utilisation assez prévisible, nous ne voulons donc pas qu'il génère trop de collisions.

Il s'avère que "en raison de la nature des mathématiques", si la constante utilisée dans le hachage et le nombre de compartiments sont coprime , les collisions sont alors minimisées. S'ils ne sont pas coprime , il existe des relations assez simples entre les entrées pour lesquelles les collisions ne sont pas minimisées. Tous les hashs sont égaux modulo au facteur commun, ce qui signifie qu'ils tomberont tous dans le 1/nième des compartiments qui ont cette valeur modulo le facteur commun. Vous obtenez n fois plus de collisions, où n est le facteur commun. Puisque n est au moins 2, je dirais qu'il est inacceptable pour un cas d'utilisation assez simple de générer au moins deux fois plus de collisions que la normale. Si certains utilisateurs veulent diviser notre distribution en plusieurs catégories, nous voulons que ce soit un accident accidentel, pas un usage simple et prévisible.

Maintenant, les implémentations de hashtable n'ont évidemment aucun contrôle sur les éléments qui y sont placés. Ils ne peuvent pas les empêcher d'être liés. La chose à faire est donc de s’assurer que les comptages des constantes et des seaux sont identiques. De cette façon, vous ne vous fiez pas uniquement au "dernier" composant pour déterminer le module du godet par rapport à un petit facteur commun. Autant que je sache, ils n'ont pas besoin d'être les meilleurs pour atteindre cet objectif, juste du coprime.

Mais si la fonction de hachage et la table de hachage sont écrites indépendamment, la table de hachage ne sait pas comment la fonction de hachage fonctionne. Il peut s'agir d'une constante avec de petits facteurs. Si vous avez de la chance, cela pourrait fonctionner complètement différemment et être non linéaire. Si le hachage est suffisant, le nombre de seaux est correct. Mais une table de hachage paranoïaque ne peut assumer une bonne fonction de hachage, elle doit donc utiliser un nombre premier de compartiments. De même, une fonction de hachage paranoïaque devrait utiliser une constante principale plus grande, afin de réduire le risque que quelqu'un utilise un nombre de seaux multiple ayant un facteur commun avec la constante.

En pratique, je pense qu'il est assez normal d'utiliser une puissance de 2 en tant que nombre de compartiments. Ceci est pratique et évite de chercher ou de présélectionner un nombre premier de la bonne magnitude. Donc, vous vous fiez à la fonction de hachage pour ne pas utiliser même des multiplicateurs, ce qui est généralement une hypothèse sûre. Mais vous pouvez toujours avoir de mauvais comportements de hachage occasionnels basés sur des fonctions de hachage telles que celle décrite ci-dessus, et le nombre de compartiments principaux pourrait aider davantage.

Envisager le principe selon lequel "tout doit être primordial" est, à ma connaissance, une condition suffisante mais non nécessaire pour une bonne distribution sur des tables de hachage. Cela permet à tout le monde d'interagir sans avoir à supposer que les autres ont suivi la même règle.

[Edit: il existe une autre raison, plus spécialisée, d'utiliser un nombre premier de compartiments, à savoir si vous gérez des collisions avec un sondage linéaire. Ensuite, vous calculez une foulée à partir du hashcode, et si cette foulée s'avère être un facteur du nombre de seaux, vous pouvez uniquement effectuer des sondes (nombre de seaux/foulée) avant de revenir à votre point de départ. Le cas que vous voulez surtout éviter est stride = 0, bien sûr, ce qui doit être une casse spéciale, mais pour éviter aussi une casse spéciale bucket_count/stride égal à un petit entier, vous pouvez simplement définir bucket_count premier stride est à condition que ce ne soit pas 0.]

228
Steve Jessop

La première chose que vous faites lorsque vous insérez/récupérez à partir de hash table est de calculer le hashCode pour la clé donnée, puis de trouver le compartiment approprié en ajustant hashCode à la taille de hashTable en faisant hashCode% table_length. Voici 2 "déclarations" que vous avez probablement déjà lues quelque part

  1. Si vous utilisez une puissance de 2 pour table_length, la recherche (hashCode (clé)% 2 ^ n) est aussi simple et rapide que (hashCode (clé) & (2 ^ n -1)). Mais si votre fonction de calcul de hashCode pour une clé donnée n'est pas bonne, vous allez certainement souffrir de la mise en cluster de nombreuses clés dans quelques compartiments de hachage.
  2. Mais si vous utilisez des nombres premiers pour table_length, les codes hashCodes calculés peuvent être mappés dans les différents compartiments de hachage, même si vous utilisez une fonction hashCode légèrement stupide.

Et voici la preuve.

Si supposons que votre fonction hashCode donne les hashCodes suivants, entre autres {x, 2x, 3x, 4x, 5x, 6x ...}, ils seront tous regroupés dans un nombre m de compartiments, où m = table_length/GreatestCommonFactor (longueur_table, x). (Il est trivial de vérifier/dériver cela). Maintenant, vous pouvez faire l’une des choses suivantes pour éviter le clustering

Assurez-vous de ne pas générer trop de hashCodes qui sont des multiples d'un autre hashCode comme dans {x, 2x, 3x, 4x, 5x, 6x ...}. Mais cela peut être un peu difficile si votre hashTable est supposé avoir des millions d'entrées ..__ Ou vous pouvez simplement rendre m égal à longueur_table en rendant GreatestCommonFactor (longueur_table, x) égal à 1, c'est-à-dire en faisant un nombre maximal de tables par longueur avec x. Et si x peut être n'importe quel nombre, assurez-vous que table_length est un nombre premier.

De - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html

28
user177612

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

Explication assez claire, avec des images aussi.

Éditer: En résumé, les nombres premiers sont utilisés car vous avez les meilleures chances d'obtenir une valeur unique lorsque vous multipliez les valeurs par le nombre premier choisi et que vous les additionnez. Par exemple, en donnant une chaîne, multiplier chaque lettre par le nombre premier, puis les additionner vous donnera sa valeur de hachage.

Une meilleure question serait, pourquoi exactement le nombre 31? 

9
AlbertoPL

tl; dr

index[hash(input)%2] entraînerait une collision pour la moitié de tous les hachages possibles et une plage de valeurs. index[hash(input)%prime] entraîne une collision de <2 de tous les hachages possibles. Fixer le diviseur à la taille de la table garantit également que le nombre ne peut pas être supérieur à la table.

9
Indolering

Les primes sont utilisées parce que vous avez de bonnes chances d'obtenir une valeur unique pour une fonction de hachage typique qui utilise les polynômes modulo P. Par exemple, vous utilisez une telle fonction de hachage pour des chaînes de longueur <= N, et vous avez une collision. Cela signifie que 2 polynômes différents produisent la même valeur modulo P. La différence de ces polynômes est encore un polynôme de même degré N (ou moins). Il n’a pas plus que N racines (c’est là que se présente la nature des mathématiques, puisque cette affirmation n’est vraie que pour un polynôme sur un corps => nombre premier). Donc, si N est très inférieur à P, vous ne risquez probablement pas de collision. Après cela, l'expérience peut probablement montrer que 37 est assez grand pour éviter les collisions pour une table de hachage de chaînes d'une longueur de 5 à 10, et suffisamment petit pour être utilisé pour des calculs. 

8
TT_

Juste pour fournir un autre point de vue, il y a ce site: 

http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth

Ce qui soutient que vous devez utiliser le plus grand nombre possible de compartiments au lieu d’arrondir à un nombre premier de compartiments. Cela semble être une possibilité raisonnable. Intuitivement, je peux certainement voir comment un plus grand nombre de seaux serait préférable, mais je ne peux pas en dire un argument mathématique.

5
Falaina

Cela dépend du choix de la fonction de hachage.

De nombreuses fonctions de hachage combinent les différents éléments des données en les multipliant par quelques facteurs modulo la puissance de deux correspondant à la taille de mot de la machine (ce module est libre en laissant simplement déborder le calcul).

Vous ne voulez pas de facteur commun entre un multiplicateur pour un élément de données et la taille de la table de hachage, car il peut arriver que la modification de l'élément de données ne répartisse pas les données sur l'ensemble de la table. Si vous choisissez une prime pour la taille de la table, un tel facteur commun est hautement improbable.

D'autre part, ces facteurs sont généralement composés de nombres premiers impairs. Vous devez donc également utiliser des puissances de deux pour votre table de hachage (par exemple, Eclipse utilise 31 lorsqu'il génère la méthode Java hashCode ()).

3
starblue

Les primes sont des nombres uniques. Elles sont unique en cela, le produit d'un prime avec tout autre nombre a le meilleur chance d'être unique (pas aussi unique que le premier lui-même bien sûr) en raison de le fait qu'un prime est utilisé pour composez-le. Cette propriété est utilisée dans fonctions de hachage.

Étant donné une chaîne "Samuel", vous pouvez générer un hachage unique par multiplier chacun des chiffres constitutifs ou lettres avec un nombre premier et en ajoutant leur place. C'est pourquoi les nombres premiers sont utilisés.

Cependant, utiliser des nombres premiers est un vieux technique. La clé ici pour comprendre que tant que vous pouvez générer un clé suffisamment unique que vous pouvez déplacer à d'autres techniques de hachage aussi. Aller ici pour plus sur ce sujet à propos de http://www.azillionmonkeys.com/qed/hash.html

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

3
user105033

Supposons que votre taille de table (ou le nombre pour modulo) soit T = (B * C). Maintenant, si le hachage pour votre entrée est comme (N * A * B) où N peut être n'importe quel entier, alors votre sortie ne sera pas bien distribuée. Parce que chaque fois que n devient C, 2C, 3C, etc., votre sortie commencera à se répéter. c'est-à-dire que votre sortie sera distribuée uniquement aux positions C. Notez que C ici est (T/HCF (table-size, hash)).

Ce problème peut être éliminé en fabriquant HCF 1. Les nombres premiers sont très bons pour cela.

Une autre chose intéressante est lorsque T est 2 ^ N. Celles-ci donneront une sortie identique à celle de tous les N bits inférieurs du hash d’entrée. Comme chaque nombre peut être représenté puissances de 2, lorsque nous prendrons modulo d'un nombre quelconque avec T, nous soustrayons toutes les puissances de 2 nombres de forme, qui sont> = N, donnant ainsi toujours le numéro du motif spécifique, dépendant de l'entrée . C'est aussi un mauvais choix.

De même, T comme 10 ^ N est également mauvais pour des raisons similaires (modèle en notation décimale de nombres au lieu de binaire).

Ainsi, les nombres premiers ont tendance à donner des résultats mieux distribués, donc sont un bon choix pour la taille de la table.

2

Copie de mon autre réponse https://stackoverflow.com/a/43126969/917428 . Voir pour plus de détails et des exemples.

Je pense que cela a à voir avec le fait que les ordinateurs fonctionnent en base 2. Pensez à la même chose pour la base 10:

  • 8% 10 = 8
  • 18% 10 = 8
  • 87865378% 10 = 8

Peu importe le nombre: tant qu'il se termine par 8, son modulo 10 sera 8.

Choisir un nombre assez grand, sans puissance de deux, permettra de s'assurer que la fonction de hachage est vraiment une fonction de tous les bits d'entrée, plutôt que d'un sous-ensemble d'entre eux.

1
Ste_95

J'aimerais ajouter quelque chose à la réponse de Steve Jessop (je ne peux pas en parler car je n'ai pas assez de réputation). Mais j'ai trouvé du matériel utile. Sa réponse est très utile, mais il a commis une erreur: la taille du seau ne devrait pas être une puissance de 2. Je vais citer le livre "Introduction to Algorithm" de Thomas Cormen, Charles Leisersen, et al, page 263:

Lorsque vous utilisez la méthode de division, nous évitons généralement certaines valeurs de m. Par exemple, m ne doit pas être une puissance de 2, car si m = 2 ^ p, alors h(k) n'est que les p bits de poids faible de k. À moins que nous ne sachions que tous les modèles de bits p d'ordre faible ont la même probabilité, nous ferions mieux de concevoir la fonction de hachage pour qu'elle dépende de tous les bits de la clé. Comme l'exercice 11.3-3 vous demande de le montrer, choisir m = 2 ^ p-1 lorsque k est une chaîne de caractères interprétée à la base 2 ^ p peut être un mauvais choix, car la permutation des caractères de k ne modifie pas sa valeur de hachage.

J'espère que ça aide.

1
iefgnoix

J'ai lu le site Web populaire wordpress lié dans certaines des réponses populaires ci-dessus au sommet. De ce que j'ai compris, j'aimerais partager une observation simple que j'ai faite.

Vous pouvez trouver tous les détails dans l'article ici , mais supposons que ce qui suit est vrai:

  • L'utilisation d'un nombre premier nous donne la "meilleure chance" d'une valeur unique

Une implémentation générale de hashmap veut que 2 choses soient uniques. 

  • Unique code de hachage pour la touche
  • Unique index pour stocker le valeur actuel

Comment obtenons-nous l'index unique? En faisant en sorte que la taille initiale du conteneur interne soit également primordiale. Donc, fondamentalement, prime est impliqué car il possède cette particularité unique de produire des nombres uniques que nous finissons par utiliser pour identifier des objets et pour trouver des index dans le conteneur interne.

Exemple:

clé = "clé"

valeur = "valeur" uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"

mappe vers identifiant unique 

Maintenant, nous voulons un unique emplacement pour notre valeur - donc nous 

uniqueId % internalContainerSize == uniqueLocationForValue, en supposant que internalContainerSize est également un nombre premier.

Je sais que cela est simplifié, mais j'espère faire passer l'idée générale.

0
Ryan

Pour une fonction de hachage, il est non seulement important de minimiser les collisions en général, mais également de rendre impossible le maintien du même hachage tout en modifiant quelques octets.

Supposons que vous avez une équation: (x + y*z) % key = x avec 0<x<key et 0<z<key. Si key est un numéro de primen n * y = key est vrai pour chaque n en N et faux pour tout autre nombre.

Un exemple où key n'est pas un bon exemple: X = 1, z = 2 et key = 8 Comme key/z = 4 est toujours un nombre naturel, 4 devient une solution pour notre équation et case (n/2) * y = key est vrai pour chaque n dans N. La quantité de solutions pour l'équation a pratiquement doublé car 8 n'est pas un nombre premier.

Si notre attaquant sait déjà que 8 est une solution possible pour l'équation, il peut changer le fichier de 8 à 4 et obtenir le même hachage.

0
Christian