web-dev-qa-db-fra.com

Algorithme de hachage de chaîne rapide avec de faibles taux de collision avec un entier 32 bits

J'ai beaucoup de choses nommées sans rapport avec lesquelles j'aimerais effectuer des recherches rapides. Un "aardvark" est toujours un "aardvark" partout, donc hacher la chaîne et réutiliser l'entier fonctionnerait bien pour accélérer les comparaisons. L'ensemble des noms est inconnu (et change avec le temps). Qu'est-ce qu'un algorithme de hachage de chaîne rapide qui générera de petites valeurs (32 ou 16) bits et aura un faible taux de collision?

J'aimerais voir une implémentation optimisée spécifique à C/C++.

64
Jason Citron

L'une des variantes FNV devrait répondre à vos besoins. Ils sont rapides et produisent des sorties distribuées assez uniformément.

29
Nick Johnson

Murmur Hash est plutôt sympa.

32
yrp

Il y a aussi un Nice article à eternallyconfuzzled.com .

Le hachage One-at-a-Time de Jenkins pour les chaînes devrait ressembler à ceci:

#include <stdint.h>

uint32_t hash_string(const char * s)
{
    uint32_t hash = 0;

    for(; *s; ++s)
    {
        hash += *s;
        hash += (hash << 10);
        hash ^= (hash >> 6);
    }

    hash += (hash << 3);
    hash ^= (hash >> 11);
    hash += (hash << 15);

    return hash;
}
17
Christoph

Pour un ensemble de chaînes fixe, utilisez gperf.

Si votre chaîne change, vous devez choisir une fonction de hachage. Ce sujet a déjà été discuté:

Quel est le meilleur algorithme de hachage à utiliser sur une chaîne stl lors de l'utilisation de hash_map?

17
Nils Pipenbrinck

Une autre solution qui pourrait être encore meilleure en fonction de votre cas d'utilisation est chaînes internes. Voici comment fonctionnent les symboles, par ex. dans LISP.

Une chaîne internée est un objet chaîne dont la valeur est l'adresse des octets de chaîne réels. Vous créez donc un objet chaîne internée en archivant une table globale: si la chaîne s'y trouve, vous initialisez la chaîne internée à l'adresse de cette chaîne. Sinon, vous l'insérez, puis initialisez votre chaîne interne.

Cela signifie que deux chaînes internes construites à partir de la même chaîne auront la même valeur, qui est une adresse. Donc, si N est le nombre de chaînes internes à votre système, les caractéristiques sont les suivantes:

  • Construction lente (nécessite une recherche et éventuellement une allocation de mémoire)
  • Requiert des données globales et une synchronisation dans le cas de threads simultanés
  • La comparaison est O (1), car vous comparez des adresses, pas des octets de chaîne réels (cela signifie que le tri fonctionne bien, mais ce ne sera pas un tri alphabétique).

À votre santé,

Carl

8
Carl Seleborg

Il n'est jamais tard pour un bon sujet et je suis sûr que les gens seraient intéressés par mes conclusions.

J'avais besoin d'une fonction de hachage et après avoir lu ce post et fait un peu de recherche sur les liens donnés ici, j'ai trouvé cette variation de l'algorithme de Daniel J Bernstein, que j'ai utilisé pour faire un test intéressant:

unsigned long djb_hashl(const char *clave)
{
    unsigned long c,i,h;

    for(i=h=0;clave[i];i++)
    {
        c = toupper(clave[i]);
        h = ((h << 5) + h) ^ c;
    }
    return h;
}
</code>

Cette variante hache les chaînes en ignorant le cas, ce qui correspond à mon besoin de hacher les informations de connexion des utilisateurs. "clave" est "clé" en espagnol. Je suis désolé pour l'espagnol mais c'est ma langue maternelle et le programme est écrit dessus.

Eh bien, j'ai écrit un programme qui générera des noms d'utilisateur de 'test_aaaa' à 'test_zzzz', et -pour allonger les chaînes- je leur ai ajouté un domaine aléatoire dans cette liste: 'cloud-nueve.com', 'yahoo.com "," gmail.com "et" hotmail.com ". Par conséquent, chacun d'eux ressemblerait à:

 
 [email protected], [email protected], 
 [email protected], [email protected] et ainsi de suite. 
 

Voici la sortie du test -'Colision entre XXX y XXX 'signifie' Collision de XXX et XXX '. "palabras" signifie "mots" et "Total" est le même dans les deux langues.

 
 Buscando Colisiones ... 
 Colision entre '[email protected]' et '[email protected]' (1DB903B7) 
 Colision entre ' [email protected] 'y' [email protected] '(2F5BC088) 
 Colision entre' [email protected] 'y' [email protected] '(51FD09CC) 
 Colision entre '[email protected]' et '[email protected]' (52F5480E) 
 Colision entre '[email protected]' y '[email protected]' (74FF72E2) 
 Colision entre '[email protected]' et '[email protected]' (7FD70008) 
 Colision entre '[email protected]' y '[email protected]' (9BD351C4) 
 Colision entre '[email protected]' y '[email protected]' (A86953E1) 
 Colision entre '[email protected]' y '[email protected]' ( BA6B0718) 
 Colision entre '[email protected]' et '[email protected]' (D0523F88) 
 Colision entre '[email protected]' y '[email protected]' ( DEE0 8108) 
 Total de Colisiones: 11 
 Total de Palabras: 456976 
 

Ce n'est pas mal, 11 collisions sur 456 976 (bien sûr en utilisant le 32 bits complet comme longueur de table).

L'exécution du programme en utilisant 5 caractères, c'est-à-dire de 'test_aaaaa' à 'test_zzzzz', manque en fait de mémoire pour construire la table. Ci-dessous la sortie. "No hay memoria para insertar XXXX (insertadas XXX)" signifie "Il n'y a plus de mémoire pour insérer XXX (XXX inséré)". Fondamentalement, malloc () a échoué à ce stade.

 
 Aucune mémoire de foin para insertar 'test_epjcv' (insertadas 2097701). 
 
 Buscando Colisiones ... 
 
 .. .451 'colision' strings ... 
 
 Total de Colisiones: 451 
 Total de Palabras: 2097701 
 

Ce qui signifie seulement 451 collisions sur 2 097 701 chaînes. Notez que dans aucune des occasions, il n'y a eu plus de 2 collisions par code. Ce que je confirme, c'est un bon hachage pour moi, car ce dont j'ai besoin est de convertir l'ID de connexion en un identifiant unique de 40 bits pour l'indexation. J'utilise donc cela pour convertir les informations d'identification de connexion en un hachage 32 bits et utiliser les 8 bits supplémentaires pour gérer jusqu'à 255 collisions par code, ce qui ressemblerait aux résultats du test serait presque impossible à générer.

J'espère que cela sera utile à quelqu'un.

MODIFIER:

Comme la boîte de test est AIX, je l'exécute en utilisant LDR_CNTRL = MAXDATA = 0x20000000 pour lui donner plus de mémoire et il s'exécute plus longtemps, les résultats sont ici:

Buscando Colisiones ... Total de Colisiones: 2908 Total de Palabras: 5366384

Soit 2908 après 5 366 384 essais !!

TRÈS IMPORTANT: Compilation du programme avec -maix64 (donc non signé long est 64 bits), le nombre de collisions est 0 pour tous les cas !!!

4
Antonio Morales

Pourquoi ne pas simplement utiliser bibliothèques Boost? Leur fonction de hachage est simple à utiliser et la plupart des éléments de Boost feront bientôt partie de la norme C++. Certains le sont déjà.

Booster le hachage est aussi simple que

#include <boost/functional/hash.hpp>

int main()
{
    boost::hash<std::string> string_hash;

    std::size_t h = string_hash("Hash me");
}

Vous pouvez trouver boost sur boost.org

4
Bernard Igiri

Bob Jenkins a de nombreuses fonctions de hachage disponibles , qui sont toutes rapides et ont de faibles taux de collision.

3
user7116

Jetez un œil à GNU gperf .

3
Rob Wells

La fonction de hachage Hsieh est assez bonne, et a quelques repères/comparaisons, comme une fonction de hachage générale en C. Selon ce que vous voulez (ce n'est pas complètement évident), vous voudrez peut-être envisager quelque chose comme - cdb à la place.

3
James Antill

Vous pouvez voir ce que .NET utilise sur la méthode String.GetHashCode () à l'aide de Reflector.

Je risquerais de supposer que Microsoft a passé beaucoup de temps à l'optimiser. Ils ont également imprimé dans toute la documentation MSDN qu'elle est susceptible de changer tout le temps. C'est donc clairement sur leur "radar de réglage des performances" ;-)

Ce serait assez trivial de porter en C++ aussi, j'aurais pensé.

2
nbevans

Il y a une bonne discussion dans ce question précédente

Et un bon aperçu de la façon de choisir les fonctions de hachage, ainsi que des statistiques sur la distribution de plusieurs fonctions courantes ici

2
AShelly

Décrit ici est un moyen simple de l'implémenter vous-même: http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

Un extrait du post:

si disons que nous avons un jeu de lettres majuscules en anglais, alors la longueur du jeu de caractères est 26 où A pourrait être représenté par le chiffre 0, B par le chiffre 1, C par le chiffre 2 et ainsi de suite jusqu'à Z par le chiffre 25. Maintenant, chaque fois que nous voulons mapper une chaîne de ce jeu de caractères à un nombre unique, nous effectuons la même conversion que nous l'avons fait dans le cas du format binaire

0
Abhishek Jain