2012-03-03 16 views
21

Voglio hash una stringa di lunghezza fino a 30. Quale sarà l'idea migliore per farlo se il tempo è la mia preoccupazione. La funzione verrà chiamata oltre 100 milioni di volte. Attualmente sto usando il seguente codice,Una funzione di hash veloce per la stringa in 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

C'è un motivo per cui il metodo 'Object.GetHashCode()' non funzionerà per te? Sembra che tu stia praticamente reimplementando lo stesso concetto. –

+3

Tutto ciò che non usa * la matematica in virgola mobile * sarà più veloce. –

+0

GetHashCode non è persistibile, quindi se ha bisogno di memorizzare il codice hash in un database, non è utile. Quindi di nuovo, nemmeno questo è. Qual è il tuo utilizzo? Hai solo bisogno di hash la stringa in fase di esecuzione, o cosa devi fare con l'hash? Adler-32 potrebbe essere un'opzione se è necessario memorizzarlo e non incorrere in troppe collisioni. –

risposta

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

Questo è un hash Knuth. Puoi anche usare Jenkins.

+1

Secondo il mio test, questa funzione non raggiunge la valanga. YMMV. – Fantius

+0

@Fantius: puoi provare a utilizzare '11400714819306691477ul 'invece, per favore. (Per entrambi i valori.) –

+2

È peggio. Ma dovrei quantificare la mia affermazione originale. La commutazione di un singolo bit sull'ingresso risulta in circa il 49,40% dei bit di uscita che commutano (usando la costante originale), che è MOLTO meglio delle funzioni basate su Bernstein. Probabilmente è abbastanza buono per la maggior parte degli usi. Ad esempio, SuperFastHash (http://landman-code.blogspot.com/2009/02/c-superfasthash-and-murmurhash2.html) mi dà il 50.02%. E Murmur2 sulla stessa pagina mi sta dando il 50,04%. – Fantius

1

Ho giocato con le implementazioni di Paul Hsieh, e sembrano essere veloce con piccole collisioni (per i miei scenari in ogni caso)

+0

Sì, scusa, leggi la domanda in modo diverso la prima volta. Modificato! – skub

+0

ciao sembra migliore. Lo implementerò in C# e vedrò. –

1

Per accelerare l'implementazione, la chiamata (UInt64)Math.Pow(31, i) deve essere sostituita da una ricerca: calcola una tabella delle prime 30 potenze di 31 e utilizzala in fase di runtime. Dal momento che il limite di lunghezza è di 30, è necessario solo 31 elementi:

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

Non sarei così sicuro che una ricerca tabella sia più veloce di una moltiplicazione intera. – CodesInChaos

+0

@CodeInChaos È sicuramente più veloce di 'Math.Pow (31, i)'. Inoltre avrei bisogno di una moltiplicazione aggiuntiva quando 'i' sale di 2 in una condizione, quindi proverei prima la ricerca. – dasblinkenlight

6

Prima di tutto, considerare l'utilizzo di GetHashCode().

Un semplice miglioramento dell'implementazione esistente:

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 il costoso calcolo in virgola mobile, e l'overhead di ElementAt.

Btw (UInt64)Math.Pow(31, i) non funziona bene per stringhe più lunghe. L'arrotondamento a virgola mobile porterà a un moltiplicatore di 0 per i caratteri oltre i 15 circa.

+0

Il moltiplicatore deve iniziare con un valore primo maggiore di 256 o questo si interrompe orribilmente se il primo byte è piccolo. –

+0

@DavidSchwartz Un primo grande più grande è certamente migliore, ma rompere orribilmente è un po 'un'esagerazione. – CodesInChaos

+0

Se una funzione di hash a 64 bit ha numerosi input a 2 byte che si scontrano, IMO si interrompe in modo orribile. (Ma vista la pessima funzionalità dell'OP, forse i miei standard sono troppo alti.) –

Problemi correlati