2008-09-22 8 views
61

Tengo muchas cosas con nombres no relacionados que me gustaría hacer búsquedas rápidas. Un "oso hormiguero" es siempre un "oso hormiguero" en todas partes, por lo que mezclar el hilo y reutilizar el entero funcionaría bien para acelerar las comparaciones. El conjunto completo de nombres es desconocido (y cambia con el tiempo). ¿Qué es un algoritmo rápido de hash de cadenas que generará valores de bits pequeños (32 o 16) y tendrá una baja tasa de colisión?Algoritmo hashing de cadenas rápidas con bajas tasas de colisión con entero de 32 bits

Me gustaría ver una implementación optimizada específica para C/C++.

+0

por favor agregue palabras clave: algoritmo hash unique colisión baja – slashmais

+24

La siguiente página tiene varias implementaciones de funciones hash de propósito general que son "performant" y tienen bajas "tasas de colisión": http://partow.net/programming/hashfunctions/index.html –

Respuesta

28

Uno de los FNV variants debe cumplir con sus requisitos. Son rápidos y producen salidas bastante uniformemente distribuidas.

+0

Si va a usar FNV, adhiérase a FNV-1a, ya que tiene resultados aceptables en la prueba de avalancha (vea http: // home .comcast.net/~ bretm/hash/6.html). O simplemente use MurmurHash2, que es mejor tanto en velocidad como en distribución (http://murmurhash.googlepages.com/). –

+7

@Steven: MurmurHash hash solo ha sido analizado por su autor. Lo he usado en algunos escenarios diferentes y la versión más nueva de FNV parece hacer un mejor trabajo. –

+0

@sonicoder: Si bien no voy a vender demasiado a MurmurHash, el simple FNV es francamente terrible y el FNV-1a solo es aceptable. Da la casualidad de que MurmurHash ha sido ampliamente analizado y encontrado útil. Todavía no es un hash criptográfico y habrá colisiones sin importar qué, pero sigue siendo una gran mejora con respecto a cualquier tipo de FNV. –

3

Eche un vistazo a GNU gperf.

+0

¡Yay para generadores de hash perfectos! –

+3

El hash perfecto NO es apropiado para esta aplicación, ya que el conjunto de nombres es desconocido y cambia. Por lo tanto, gperf no funcionará para esto. – TimB

-3

CRC-32. Hay alrededor de un billón de enlaces en google para ello.

+7

Los CRC están diseñados para la detección y corrección de errores. Sus características de distribución generalmente no son muy buenas. –

+1

Arácnido, obviamente, nunca ha probado CRC32 como hash. Ellos funcionan bien –

+8

"CRC32 nunca fue pensado para el uso de la tabla hash. Realmente no hay una buena razón para usarlo con este propósito". cf. http://home.comcast.net/~bretm/hash/8.html – obecalp

32

Murmur Hash es bastante agradable.

+3

Sí, esta es la función hash de propósito general actual para tablas hash. Es no criptográfico, por supuesto, con un par de diferencial obvio. – obecalp

+0

Nota: la nueva URL para MurmurHash3 es https://code.google.com/p/smhasher/ –

7

Otra solución que podría ser aún mejor dependiendo de su caso de uso es interned strings. Así es como funcionan los símbolos, p. en Lisp.

Una cadena interna es un objeto de cadena cuyo valor es la dirección de los bytes de serie reales. Entonces usted crea un objeto de cadena interna ingresando en una tabla global: si la cadena está ahí, inicializa la cadena interna a la dirección de esa cadena. Si no, lo inserta, y luego inicializa su cadena interna.

Esto significa que dos cadenas internas construidas a partir de la misma cadena tendrán el mismo valor, que es una dirección. Así que si N es el número de cadenas internadas en su sistema, las características son:

  • construcción lenta (necesita operaciones de búsqueda y posiblemente la asignación de memoria)
  • requiere datos globales y de sincronización en el caso de hilos concurrentes
  • Compare es O (1), porque está comparando direcciones, no bytes de cadenas reales (esto significa que la ordenación funciona bien, pero no será alfabética).

Cheers,

Carl

3

La función hash Hsieh es bastante bueno, y tiene algunos puntos de referencia/comparaciones, como una función hash en general en C. En función de lo que quiere (no es completamente obvio) es posible que desee considerar algo como cdb en su lugar.

2

Hay algunas buenas discusión en este previous question

Y una buena visión general de cómo elegir las funciones de hash, así como estadísticas sobre la distribución de varias de las más comunes here

4

¿Por qué no sólo tiene que utilizar Boost libraries? Su función de hashing es simple de usar y la mayoría de las cosas en Boost pronto formarán parte del estándar de C++. Algo de eso ya es.

Boost hash es tan fácil como

#include <boost/functional/hash.hpp> 

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

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

puede encontrar impulso en boost.org

+4

Tanto STL como boost tr1 tienen una función hash extremadamente débil para las cadenas. – obecalp

3

Bob Jenkins has many hash functions available, todos los cuales son rápidos y tienen tasas bajas de colisión.

+1

Los valores hash son bastante sólidos y técnicamente interesantes, pero no necesariamente rápidos. Tenga en cuenta que el hash One-to-a-Time procesa byte de entrada por byte, donde otros hashes toman 4 o incluso 8 bytes a la vez. ¡La diferencia de velocidad es sustancial! –

+0

Los valores hash de Bob son muy rápidos: http://www.azillionmonkeys.com/qed/hash.html – user7116

2

Puede ver lo que .NET usa en el método String.GetHashCode() usando Reflector.

Supongo que Microsoft pasó un tiempo considerable optimizando esto. También han impreso en toda la documentación de MSDN que está sujeta a cambios todo el tiempo. Así que claramente está en su "radar de ajuste de rendimiento" ;-)

Sería bastante trivial para portar a C++ también, lo hubiera pensado.

15

También hay un nice article en eternallyconfuzzled.com.

Jenkins' hash de una en-un-tiempo para las cadenas debería ser algo como esto:

#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; 
} 
4

Nunca es tarde para un buen tema y estoy seguro que la gente estaría interesada en mis conclusiones.

que necesitaba una función hash y después de leer este post y hacer un poco de investigación sobre los vínculos que se dan aquí, me ocurrió con esta variación del algoritmo de Daniel J. Bernstein, que yo solía hacer una prueba interesante:

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; 
} 

Esta variante hashes de cadenas ignorando el caso, que se adapta a mi necesidad de hashing credenciales de inicio de sesión de los usuarios. 'clave' es 'clave' en español. Lo siento por el español, pero es mi lengua materna y el programa está escrito en él.

Bueno, escribí un programa que generará nombres de usuario de 'test_aaaa' a 'test_zzzz', y -para alargar las cadenas- les agregué un dominio aleatorio en esta lista: 'cloud-nueve.com', 'yahoo.com', 'gmail.com' y 'hotmail.com'. Por lo tanto cada uno de ellos sería el resultado:

 

[email protected], [email protected], 
[email protected], [email protected] and so on. 

Aquí está la salida de la prueba -'Colision Entre XXX y XXX' significa 'colisión de XXX y XXX'. 'palabras' significa 'palabras' y 'Total' es el mismo en ambos idiomas-.

 

    Buscando Colisiones... 
    Colision entre '[email protected]' y '[email protected]' (1DB903B7) 
    Colision entre '[email protected]' y '[email protected]' (2F5BC088) 
    Colision entre '[email protected]' y '[email protected]' (51FD09CC) 
    Colision entre '[email protected]' y '[email protected]' (52F5480E) 
    Colision entre '[email protected]' y '[email protected]' (74FF72E2) 
    Colision entre '[email protected]' y '[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]' y '[email protected]' (D0523F88) 
    Colision entre '[email protected]' y '[email protected]' (DEE08108) 
    Total de Colisiones: 11 
    Total de Palabras : 456976 

Eso no es malo, 11 colisiones de cada 456.976 (por supuesto usando el pleno de 32 bits como longitud de la tabla).

Ejecutando el programa usando 5 caracteres, que es de 'test_aaaaa' a 'test_zzzzz', se está quedando sin memoria creando la tabla. Debajo está la salida.'No hay memoria para insertar XXXX (insertadas XXX)' significa 'No queda memoria para insertar XXX (XXX insertado)'. Básicamente malloc() falló en ese punto.

 

    No hay memoria para insertar 'test_epjcv' (insertadas 2097701). 

    Buscando Colisiones... 

    ...451 'colision' strings... 

    Total de Colisiones: 451 
    Total de Palabras : 2097701 

Lo que significa solo 451 colisiones en 2,097,701 cadenas. Tenga en cuenta que en ninguna de las ocasiones, hubo más de 2 colisiones por código. Lo cual confirmo que es un gran hash para mí, ya que lo que necesito es convertir el ID de inicio de sesión en una identificación única de 40 bits para indexar. Así que utilizo esto para convertir las credenciales de inicio de sesión a un hash de 32 bits y uso los 8 bits adicionales para manejar hasta 255 colisiones por código, lo que indica que los resultados de la prueba serían casi imposibles de generar.

Espero que esto sea útil para alguien.

EDIT:

Al igual que la caja de prueba es AIX, lo corro usando LDR_CNTRL = MAXDATA = 0x20000000 para darle más memoria y más largo plazo, los resultados están aquí:

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

Eso es 2908 después de 5,366,384 intentos !!

MUY IMPORTANTE: La compilación del programa con -maix64 (por lo que unsigned long es de 64 bits), el número de colisiones es 0 para todos los casos !!!

0

descrito aquí es una forma sencilla de implementar por sí mismo: http://www.devcodenote.com/2015/04/collision-free-string-hashing.html

Un fragmento del mensaje:

si decir que tenemos un conjunto de caracteres de las letras inglesas de capital, entonces la longitud del conjunto de caracteres es 26 donde A podría representarse por el número 0, B por el número 1, C por el número 2 y así sucesivamente hasta Z por el número 25. Ahora, siempre que queramos asignar una cadena de este juego de caracteres a un número único, realizamos la misma conversión que en el caso del formato binario

+0

¿Cómo se relaciona eso (dado un navegador de hipertexto que muestra los contenidos de los enlaces) con '(32 o 16) valores de bit', juegos de caracteres dados, digamos, de 24 a 1.111.998 puntos de código? La conversión base no es una función hash útil. – greybeard

Cuestiones relacionadas