2012-03-03 21 views
21

Quiero cortar una cadena de longitud de hasta 30. Cuál será la mejor idea para hacer eso si el tiempo es mi problema. La función se llamará más de 100 millones de veces. Actualmente estoy usando el siguiente código,Una función hash rápida para una cadena en C#

static UInt64 CalculateHash(string read, bool lowTolerance) 
{ 
    UInt64 hashedValue = 0; 
    int i = 0; 
    while (i < read.Length) 
    { 
     hashedValue += read.ElementAt(i) * (UInt64)Math.Pow(31, i); 
     if (lowTolerance) i += 2; 
     else i++; 
    } 
    return hashedValue; 
} 
+5

¿Hay algún motivo por el que el método 'Object.GetHashCode()' no funcione para usted? Parece que estás reimplementando el mismo concepto. –

+3

Cualquier cosa que no use * matemática de punto flotante * será más rápida. –

+0

GetHashCode no es persistente, por lo que si necesita almacenar el código hash en una base de datos, no es útil. Por otra parte, tampoco es esto. ¿Cuál es tu uso? ¿Solo necesita manipular la cadena en tiempo de ejecución, o qué debe hacer con el Hash? Adler-32 podría ser una opción si necesita almacenarlo y no encontrarse con demasiadas colisiones. –

Respuesta

37
static UInt64 CalculateHash(string read) 
{ 
    UInt64 hashedValue = 3074457345618258791ul; 
    for(int i=0; i<read.Length; i++) 
    { 
     hashedValue += read[i]; 
     hashedValue *= 3074457345618258799ul; 
    } 
    return hashedValue; 
} 

Este es un hash Knuth. También puede usar Jenkins.

+1

De acuerdo con mi propia prueba, esta función no alcanza la avalancha. YMMV. – Fantius

+0

@Fantius: ¿Puedes intentar usar '11400714819306691477ul' en su lugar, por favor. (Para ambos valores) –

+2

Es peor. Pero debería cuantificar mi declaración original. Alternar un solo bit en la entrada da como resultado un 49.40% de la conmutación de bits de salida (usando su constante original), que es MUCHO mejor que las funciones basadas en Bernstein. Eso es probablemente lo suficientemente bueno para la mayoría de los usos. Pero, por ejemplo, SuperFastHash (http://landman-code.blogspot.com/2009/02/c-superfasthash-and-murmurhash2.html) me da 50.02%. Y Murmur2 en la misma página me está dando 50.04%. – Fantius

1

He jugado con implementaciones de Paul Hsieh, y parecen ser rápido con pequeñas colisiones (por mis escenarios de todos modos)

+0

Sí, lo siento de manera diferente, la primera vez. Editado! – skub

+0

hola se ve mejor. Lo implementaré en C# y lo veré. –

1

Para acelerar su implementación, la llamada (UInt64)Math.Pow(31, i) debe reemplazarse por una búsqueda: precalcular una tabla de las primeras 30 potencias de 31, y usarla en tiempo de ejecución. Dado que el límite de la longitud es de 30, lo que necesita sólo el 31 elemento:

private static unsigned long[] Pow31 = new unsigned long[31]; 

static HashCalc() { 
    Pow31[0] = 1; 
    for (int i = 1 ; i != Pow31.Length ; i++) { 
     Pow31[i] = 31*Pow31[i-1]; 
    } 
} 

// In your hash function... 
hashedValue += read.ElementAt(i) * Pow31[i]; 
+0

No estaría tan seguro de que una búsqueda de tablas sea más rápida que una multiplicación de enteros. – CodesInChaos

+0

@CodeInChaos Ciertamente es más rápido que 'Math.Pow (31, i)'. También necesitaría una multiplicación adicional cuando 'i' sube por 2 dentro de una condición, así que probaría la búsqueda primero. – dasblinkenlight

6

Antes que nada, considere usar GetHashCode().

Una simple mejora en su aplicación existente:

static UInt64 CalculateHash(string read, bool lowTolerance) 
{ 
    UInt64 hashedValue = 0; 
    int i = 0; 
    ulong multiplier = 1; 
    while (i < read.Length) 
    { 
     hashedValue += read[i] * multiplier; 
     multiplier *= 37; 
     if (lowTolerance) i += 2; 
     else i++; 
    } 
    return hashedValue; 
} 

Evita el cálculo de coma flotante caro, y la sobrecarga de ElementAt.

Btw (UInt64)Math.Pow(31, i) no funciona bien para cadenas más largas. El redondeo de punto flotante conducirá a un multiplicador de 0 para caracteres más allá de 15 o más.

+0

El multiplicador debe comenzar en un valor principal superior a 256 o si se rompe de forma horrible si el primer byte es pequeño. –

+0

@DavidSchwartz Una prima mayor es ciertamente mejor, pero romper horriblemente es un poco exagerado. – CodesInChaos

+0

Si una función hash de 64 bits tiene numerosas entradas de 2 bytes que colisionan, IMO se rompe horriblemente. (Pero teniendo en cuenta la gravedad de la función con la que comenzó el OP, quizás mis estándares sean demasiado altos). –

Cuestiones relacionadas