41 votes

Comment calculer un bon code de hachage pour une liste de chaînes de caractères ?

Le contexte :

  • J'ai une courte liste de cordes.
  • Le nombre de cordes n'est pas toujours le même, mais il est presque toujours de l'ordre d'une "poignée".
  • Dans notre base de données, nous stockons ces chaînes dans une deuxième table normalisée.
  • Ces chaînes sont jamais modifiés une fois qu'ils sont écrits dans la base de données.

Nous souhaitons être en mesure de faire correspondre ces chaînes rapidement dans une requête sans que les performances ne soient affectées par un grand nombre de jointures.

Je pense donc stocker un code de hachage de toutes ces chaînes dans la table principale et l'inclure dans notre index, de sorte que les jointures ne soient traitées par la base de données que lorsque le code de hachage correspond.

Alors comment obtenir un bon code de hachage ? Je pourrais :

  • Xor les codes de hachage de toutes les chaînes ensemble
  • Xor avec multiplication du résultat après chaque chaîne (disons par 31)
  • Rassemblez toutes les chaînes et obtenez le code de hachage.
  • D'une autre manière

Alors, qu'en pensent les gens ?


En fin de compte, je me contente de concaténer les chaînes et de calculer le code de hachage pour la concaténation, car c'est simple et cela fonctionne assez bien.

(Si cela vous intéresse, nous utilisons .NET et SqlServer)


Bug !, Bug !

Citation des directives et règles pour GetHashCode par Eric Lippert

La documentation de System.String.GetHashCode note spécifiquement que deux chaînes identiques identiques peuvent avoir des codes de hachage différents dans différentes versions du CLR, et en fait, c'est le cas. Ne stockez pas de chaînes dans des bases de données et ne vous attendez pas à ce qu'ils qu'ils soient toujours les mêmes, car ils car ce ne sera pas le cas.

Donc String.GetHashcode() ne doit pas être utilisé pour cela.

0 votes

50voto

Geoff Points 1616

La pratique standard de Java est d'écrire simplement

final int prime = 31;
int result = 1;
for( String s : strings )
{
    result = result * prime + s.hashCode();
}
// result is the hashcode.

6 votes

0 votes

Cela donnera-t-il une meilleure distribution des codes de hachage que de prendre le code de hachage de la chaîne concaténée, et si oui, pourquoi ?

1 votes

Je n'en ai aucune idée, mais si vous placez les éléments dans une liste (telle qu'une ArrayList), et demandez le hoshCode, voici ce que vous obtiendrez (avec la contrainte supplémentaire que les éléments nuls ont un hashCode de 0). [java.sun.com/javase/6/docs/api/java/util/List.html#hashCode()](http://java.sun.com/javase/6/docs/api/java/util/List.html#hashCode())

3voto

leonbloy Points 27119

Votre première option n'a que l'inconvénient de (String1, String2) produisant le même code de hachage de (String2, String1) . Si ce n'est pas un problème (par exemple, parce que vous avez un ordre fixe), c'est parfait.

" Rassemblez toutes les chaînes et obtenez le code de hachage. "me semble plus naturel et plus sûr.

Mise à jour : Comme le souligne un commentaire, cela présente l'inconvénient que la liste ("x", "yz") et ("xy", "z") donneraient le même hachage. Pour éviter cela, vous pourriez joindre les chaînes de caractères avec un délimiteur de chaîne qui ne peut pas apparaître à l'intérieur des chaînes de caractères.

Si les chaînes de caractères sont grandes, vous préférerez peut-être hacher chacune d'entre elles, catcher les codes de hachage et ré-hausser le résultat. Plus de CPU, moins de mémoire.

1 votes

Si vous regroupez toutes les chaînes de caractères, vous obtenez que HASH("firststring "+"secondstring") == HASH("first "+"stringsecondstring"), ce qui n'est pas bon.

3voto

Andreas Brinck Points 23806

Je ne vois aucune raison de ne pas concaténer les chaînes de caractères et de ne pas calculer le code de hachage pour la concaténation.

Par analogie, si je voulais calculer une somme de contrôle MD5 pour un bloc de mémoire, je ne diviserais pas le bloc en petits morceaux et je calculerais des sommes de contrôle MD5 individuelles pour chacun d'eux, puis je les combinerais avec une méthode ad hoc.

4 votes

C'était génial ! Cependant, aquí est la raison pour laquelle vous ne voulez pas faire cela, car vous pourriez perdre des informations si vos chaînes n'étaient pas distinctes. Tous les ["", "aa"] , ["a", "a"] et ["aa", ""] auront le même code de hachage ! C'est pourquoi on utilise l'addition de nombres premiers.

1 votes

Oui. Pour un hachage correct, le personnel de IT Security Stack Exchange vous dira systématiquement de ne pas concaténer des chaînes de longueur variable dans le but de réaliser un hachage. Si vous effectuez un XOR de 2 MD5 distincts, cela devrait être correct. Vous pouvez également ajouter un séparateur entre les deux chaînes, mais cela n'est sûr que si aucune de ces deux chaînes ne contient jamais le caractère séparateur. (généralement quelque chose que vous ne pouvez pas garantir) "A"+"|"+"|A" serait la même chose que "A|"+"|"+"A" .

0 votes

@CarlWalsh Le hachage ne signifie-t-il pas toujours la perte d'informations ? Tant que le code de hachage est plus petit que les données d'origine, il y aura toujours des collisions entre certaines entrées.

2voto

fortran Points 26495

Un autre moyen qui me vient à l'esprit est de chaîner des xors avec des hachages tournés basés sur l'index :

int shift = 0;
int result = 1;
for(String s : strings)
{
    result ^= (s.hashCode() << shift) | (s.hashCode() >> (32-shift)) & (1 << shift - 1);
    shift = (shift+1)%32;
}

edit : en lisant l'explication donnée dans java effective, je pense que le code de geoff serait beaucoup plus efficace.

0 votes

Enchaîner les xors était ma première pensée aussi. Puisqu'un bon hashCode ne devrait pas avoir de motif, pourquoi s'embêter avec le décalage ? Pourquoi ne pas simplement faire un xor de tous les hashs ensemble ?

2 votes

@Bill K car si c'est le cas, ["hello", "world"] aurait le même hash que ["world", "hello"] :-)

1voto

Philip Kelley Points 19032

Une solution SQL pourrait être basée sur les fonctions checksum et checksum_agg. Si je suis bien, vous avez quelque chose comme.. :

MyTable
  MyTableId
  HashCode

MyChildTable
  MyTableId  (foreign key into MyTable)
  String

avec les différentes chaînes de caractères pour un élément donné (MyTableId) stocké dans MyChildTable. Pour calculer et stocker une somme de contrôle reflétant ces chaînes (qui ne seront jamais modifiées), quelque chose comme ceci devrait fonctionner :

UPDATE MyTable
 set HashCode = checksum_agg(checksum(string))
 from MyTable mt
  inner join MyChildTable ct
   on ct.MyTableId = mt.MyTableId
 where mt.MyTableId = @OnlyForThisOne

Je crois que c'est indépendant de l'ordre, donc les chaînes "The quick brown" produiraient la même somme de contrôle que "brown The quick".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X