web-dev-qa-db-fra.com

Qu'est-ce qu'une prime sensible pour le calcul hashcode?

Eclipse 3.5 a une très belle fonctionnalité pour générer Java hashcode (). Cela générerait par exemple (légèrement raccourci :)

class HashTest {
    int i;
    int j;        
    public int hashCode() {
        final int prime = 31;
        int result = prime + i;
        result = prime * result + j;
        return result;
    }
}

(Si vous avez plus d'attributs dans la classe, result = prime * result + attribute.hashCode(); est répété pour chaque attribut supplémentaire. Pour INTS .HashCode () peut être omis.)

Cela semble bien mais pour le choix 31 pour le premier. Il est probablement tiré de la mise en oeuvre de hashcode de Java string , utilisée pour des raisons de performance qui sont longues après l'introduction de multiplicateurs matériels. Ici vous avez beaucoup de hashcode Collisions pour de petites valeurs de I et J: par exemple (0,0) et (-1,31) ont la même valeur. Je pense que c'est une mauvaise chose (TM), puisque les petites valeurs se produisent souvent. Pour String.HashCode vous 'll trouvera également de nombreuses chaînes courtes avec le même hashcode, par exemple "CA" et "DB". Si vous prenez une grande prime, ce problème disparaît si vous choisissez le premier droit.

Donc, ma question: Qu'est-ce qui est un bon choix à choisir? Quels critères postulez-vous pour le trouver?

Ceci est signifié comme une question générale - donc je ne veux donc pas donner une gamme pour i et j. Mais je suppose que dans la plupart des applications, des valeurs relativement petites se produisent plus souvent que de grandes valeurs. (Si vous avez de grandes valeurs, le choix de la prime est probablement sans importance.) Cela pourrait ne pas faire une grande différence, mais un meilleur choix est un moyen facile et évident d'améliorer cela - alors pourquoi ne pas le faire? Commons Lang HashcodeBuilder suggère également des valeurs curieuses.

( Clarification : Ceci est pas Un duplicata de Pourquoi le hashcode de Java () dans la chaîne d'utilisation 31 En multiplicateur? Étant donné que ma question n'est pas concernée par l'histoire des 31 du JDK, mais sur quelle valeur serait une meilleure valeur dans le nouveau code utilisant le même modèle de base. Aucune des réponses là-bas essaie de répondre à cela.)

55
Hans-Peter Störr

Je recommande d'utiliser 92821. Voici pourquoi.

Pour donner une réponse significative à cela, vous devez savoir quelque chose sur les valeurs possibles de i et j. La seule chose que je puisse penser en général est que, dans de nombreux cas, de petites valeurs seront plus courantes que les valeurs importantes. (Les chances de 15 apparaissant sous forme de valeur dans votre programme sont bien meilleures que, par exemple, 438281923.) Il semble donc une bonne idée de rendre la plus petite collision de hashcode aussi grande que possible en choisissant un choix approprié. Pour 31, ce mauvais mauvais - déjà pour i=-1 Et j=31 Vous avez la même valeur de hachage que pour i=0 Et j=0.

Comme cela est intéressant, j'ai écrit un petit programme qui a recherché la gamme INT pour le meilleur prime en ce sens. C'est-à-dire que pour chaque prime, j'ai cherché la valeur minimale de Math.abs(i) + Math.abs(j) sur toutes les valeurs de i,j Qui ont le même hashcode que 0,0, Puis a pris la prime où cela La valeur minimale est aussi grande que possible.

Drumroll : Le meilleur choix dans ce sens est 486187739 (avec la plus petite collision étant i=-25486, j=67194). Presque aussi bien et beaucoup plus facile à retenir est 92821 avec la plus petite collision étant i=-46272 and j=46016.

Si vous donnez "petit" autre sens et que vous voulez être le minimum de Math.sqrt(i*i+j*j) pour la collision aussi grande que possible, les résultats sont un peu différents: le meilleur serait 1322837333 avec i=-6815 and j=70091, Mais mon préféré 92821 (plus petite collision -46272,46016) est à nouveau presque aussi bon que la meilleure valeur.

Je reconnais qu'il est assez discutable si ces calculs ont beaucoup de sens dans la pratique. Mais je pense que prendre 92821 en tant que Prime fait beaucoup plus de sens que 31 ans, à moins que vous n'ayez de bonnes raisons de ne pas le faire.

71

Les collisions peuvent ne pas être un problème si gros ... L'objectif principal du hachage est d'éviter d'utiliser des égaux pour 1: 1 comparaisons. Si vous avez une implémentation dans laquelle des égaux est "généralement" extrêmement bon marché pour des objets qui ont collaissé des haches, ce n'est pas un problème (du tout).

En fin de compte, quelle est la meilleure façon de hachager dépend de ce que vous comparez. Dans le cas d'une paire INT (comme dans votre exemple), l'utilisation des opérateurs BIASE BitWise pourrait être suffisante (comme utilisez et ^).

5
Romain

Vous devez définir votre gamme pour i et j. Vous pouvez utiliser un nombre premier pour les deux.

public int hashCode() {
   http://primes.utm.edu/curios/ ;)
   return 97654321 * i ^ 12356789 * j;
}
3
Peter Lawrey

Je choisirais 7243. Assez grand pour éviter les collisions avec de petits nombres. Ne débordne pas rapidement aux petits nombres.

3
Erich Kitzmueller

Je veux juste souligner que HashCode n'a rien à voir avec Premier. Dans la mise en œuvre JDK

for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }

J'ai trouvé si vous remplacez 1 avec 27, le résultat est très similaire.

1
neoedmund