Buona funzione hash per archi


160

Sto cercando di escogitare una buona funzione hash per le stringhe. E stavo pensando che potrebbe essere una buona idea riassumere i valori unicode per i primi cinque caratteri nella stringa (supponendo che abbia cinque, altrimenti fermati dove finisce). Sarebbe una buona idea o è una cattiva?

Lo sto facendo in Java, ma non immaginerei che farebbe molta differenza.


4
Le buone funzioni di hash dipendono fortemente dall'input dell'hash e dai requisiti dell'algoritmo. Un tale hash non sarà molto buono se tutte le tue stringhe iniziano con gli stessi cinque caratteri, per esempio. Inoltre tenderà a provocare una distribuzione normale.
WhirlWind,

1
Possibile duplicato di 98153
Michael Mrozek,

14
Perché non puoi usare Stringil proprio hashCode()?
Bart Kiers,

@WhirlWind, vero, non sono sicuro di cosa avranno le stringhe, a parte questo probabilmente sarà un testo inglese.
Leif Andersen,

@Barl, principalmente perché il mio professore ci ha detto di implementare il nostro hash functor ... e il motivo per cui non volevo usare Java, era perché era generico, e immagino che un funzione di hash più specifico sarebbe meglio.
Leif Andersen,

Risposte:


161

Di solito gli hash non farebbero somme, altrimenti stope potsavranno lo stesso hash.

e non lo limiteresti ai primi n personaggi perché altrimenti house e houses avrebbero lo stesso hash.

Generalmente gli hash prendono i valori e li moltiplicano per un numero primo (aumenta la probabilità di generare hash univoci) Quindi potresti fare qualcosa del tipo:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}

@jonathanasdf Come puoi dire che ti dà sempre una chiave hash unica. C'è qualche prova matematica? Penso che dobbiamo prendere mod di hash con un altro numero primo più grande, altrimenti si verifica un problema di overflow.
devsda,

17
@devsda Non ha detto sempre unico, ha detto più probabilità di essere unico. Per quanto riguarda il motivo, una rapida ricerca su Google rivela questo articolo: computinglife.wordpress.com/2008/11/20/… che spiega perché 31 è stato utilizzato per l'hash delle stringhe Java. Non viene fornita alcuna prova matematica, ma spiega il concetto generale sul perché i numeri primi funzionano meglio.
Pharap,

2
Grazie mille per aver chiarito l'idea di fare hashing migliore. Solo per ricontrollare: il valore restituito di hashCode () verrà utilizzato da Java per mappare un indice della tabella prima di memorizzare l'oggetto. Quindi, se hashCode () restituisce m, fa qualcosa come (m mod k) per ottenere un indice della tabella di dimensioni k. È giusto?
whitehat,

1
"hash = hash * 31 + charAt (i);" produce lo stesso hash per spot, top, stop, opts e pot.
Jack Straub,

1
@maq credo che tu abbia ragione. Non so cosa stavo pensando.
Jack Straub il

139

Se è una cosa di sicurezza, puoi usare Java crypto:

import java.security.MessageDigest;

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(stringToEncrypt.getBytes());
String encryptedString = new String(messageDigest.digest());

93
Bello. Ho un'applicazione di apprendimento automatico, che esegue la PNL statistica su un grande corpus. Dopo alcuni passaggi iniziali di normalizzazione morfologica delle parole originali nel testo, butto via i valori di stringa e invece uso i codici hash. In tutto il mio corpus, ci sono circa 600.000 parole uniche e, usando la funzione hashcode java predefinita, ho avuto circa il 3,5% di collisioni. Ma se SHA-256 il valore della stringa e quindi genera un hashcode dalla stringa digerita, il rapporto di collisione è inferiore allo 0,0001%. Grazie!
benjismith

3
Grazie per aver fornito informazioni sulle collisioni e sul numero di parole. Molto utile.
philipp,

19
@benjismith Uno su un milione è troppo grande ... "meno dello 0,0001%" è un modo obliquo di dire "esattamente 0"? Dubito davvero che tu abbia visto una collisione SHA-256 perché non è mai stata osservata, mai e ovunque; nemmeno per SHA-1 a 160 bit. Se hai due stringhe che producono lo stesso SHA-256, la comunità della sicurezza vorrebbe vederle; sarai famoso in tutto il mondo ... in un modo molto oscuro. Vedi Confronto delle funzioni SHA
Tim Sylvester,

7
@ TimSylvester, hai frainteso. Non ho trovato collisioni SHA-256. Ho calcolato l'SHA-256 e quindi ho inserito le sequenze di byte risultanti in una tipica funzione "hashCode" Java, perché avevo bisogno di un hash a 32 bit. È lì che ho trovato le collisioni. Niente di straordinario :)
benjismith

1
Non c'è differenza tra "hashing" e "crittografia"? Capisco MessageDigest è una funzione di hashing unidirezionale, giusto? Inoltre, quando ho usato la funzione, ho ottenuto la stringa con hash come molti caratteri UTF spazzatura quando ho aperto il file in LibreOffice. È possibile ottenere la stringa con hash come un gruppo casuale di caratteri alfanumerici anziché caratteri junk UTF?
Nav

38

Probabilmente dovresti usare String.hashCode () .

Se vuoi davvero implementare hashCode da solo:

Non essere tentato di escludere parti significative di un oggetto dal calcolo del codice hash per migliorare le prestazioni - Joshua Bloch, Java efficace

Usare solo i primi cinque personaggi è una cattiva idea . Pensa ai nomi gerarchici, come gli URL: avranno tutti lo stesso codice hash (perché iniziano tutti con "http: //", il che significa che sono archiviati sotto lo stesso bucket in una mappa hash, esibendo prestazioni terribili.

Ecco una storia di guerra parafrasata sul String hashCode di " Effective Java ":

La funzione hash String implementata in tutte le versioni precedenti alla 1.2 esaminava al massimo sedici caratteri, distribuiti uniformemente su tutta la stringa, a partire dal primo carattere. Per grandi raccolte di nomi gerarchici, come gli URL, questa funzione hash ha mostrato un comportamento terribile.


1
Se si utilizza una raccolta con doppio hash, potrebbe essere utile avere il primo hash davvero veloce e sporco. Se uno ha mille stringhe lunghe, la metà delle quali è mappata da una funzione scadente su un valore particolare e la metà delle quali è mappata su valori distinti, le prestazioni in una tabella con hash singolo sarebbero pessime, ma le prestazioni in un doppio- tabella con hash, in cui il secondo hash ha esaminato l'intera stringa, potrebbe essere quasi il doppio di quella di una tabella con hash singolo (poiché metà delle stringhe non dovrebbero essere completamente hash). Nessuna delle raccolte Java standard ha comunque un doppio hashing.
supercat

Il collegamento efficace Java è rotto @Frederik
KGs

17

Se lo stai facendo in Java, allora perché lo fai? Basta chiamare .hashCode()la stringa


2
Lo sto facendo come parte della classe e parte del compito è scrivere diverse funzioni hash. Il professore ci ha detto di ottenere un aiuto esterno per quelli "migliori".
Leif Andersen,

20
Se hai bisogno di essere coerente tra le versioni e le implementazioni JVM, non dovresti fare affidamento .hashCode(). Piuttosto, usa qualche algoritmo noto.
Stephen Ostermiller,

7
L'algoritmo per String::hashCodeè specificato nel JDK, quindi è portatile come l'esistenza stessa della classe java.lang.String.
yshavit,


8

Questa funzione fornita da Nick è buona, ma se usi la nuova stringa (byte [] byte) per effettuare la trasformazione in stringa, non è riuscita. È possibile utilizzare questa funzione per farlo.

private static final char[] hex = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };

public static String byteArray2Hex(byte[] bytes) {
    StringBuffer sb = new StringBuffer(bytes.length * 2);
    for(final byte b : bytes) {
        sb.append(hex[(b & 0xF0) >> 4]);
        sb.append(hex[b & 0x0F]);
    }
    return sb.toString();
}

public static String getStringFromSHA256(String stringToEncrypt) throws NoSuchAlgorithmException {
    MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
    messageDigest.update(stringToEncrypt.getBytes());
    return byteArray2Hex(messageDigest.digest());
}

Può essere questo può aiutare qualcuno


Potresti semplicemente passare l'array di byte a messageDigest.update ().
szgal

byteArray2Hex () - questo è perfettamente quello che stavo cercando! Grazie mille :)
Krzysiek,


5

Si dice che FNV-1 sia una buona funzione hash per le stringhe.

Per stringhe lunghe (più lunghe, per esempio, di circa 200 caratteri), puoi ottenere buone prestazioni dalla funzione hash MD4 . Come funzione crittografica, è stata interrotta circa 15 anni fa, ma per scopi non crittografici, è ancora molto buona e sorprendentemente veloce. Nel contesto di Java, è necessario convertire i charvalori a 16 bit in parole a 32 bit, ad esempio raggruppando tali valori in coppie. Una rapida implementazione di MD4 in Java può essere trovata in sphlib . Probabilmente eccessivo nel contesto di un compito in classe, ma per il resto merita una prova.


Questa funzione hash è molto meglio di quella fornita con Java.
clankill3r

3

Se vuoi vedere le implementazioni standard del settore, darei un'occhiata a java.security.MessageDigest .

"I digest dei messaggi sono funzioni hash unidirezionali sicure che accettano dati di dimensioni arbitrarie e producono un valore hash di lunghezza fissa."


1

ecco un link che spiega molte diverse funzioni hash, per ora preferisco la funzione hash ELF per il tuo problema specifico. Prende come input una stringa di lunghezza arbitraria.


1

sdbm: questo algoritmo è stato creato per la libreria di database sdbm (reimplementazione di dominio pubblico di ndbm)

static unsigned long sdbm(unsigned char *str)
{   
    unsigned long hash = 0;
    int c;
    while (c = *str++)
            hash = c + (hash << 6) + (hash << 16) - hash;

    return hash;
}

0
         public String hashString(String s) throws NoSuchAlgorithmException {
    byte[] hash = null;
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        hash = md.digest(s.getBytes());

    } catch (NoSuchAlgorithmException e) { e.printStackTrace(); }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < hash.length; ++i) {
        String hex = Integer.toHexString(hash[i]);
        if (hex.length() == 1) {
            sb.append(0);
            sb.append(hex.charAt(hex.length() - 1));
        } else {
            sb.append(hex.substring(hex.length() - 2));
        }
    }
    return sb.toString();
}

-1

È una buona idea lavorare con un numero dispari quando si cerca di sviluppare una buona funzione hast per la stringa. questa funzione prende una stringa e restituisce un valore di indice, finora ha funzionato abbastanza bene. e ha meno collisioni. l'indice varia da 0 a 300 forse anche più di così, ma finora non sono andato più in alto, anche con parole lunghe come "ingegneria elettromeccanica"

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += 7*n%31;
    }
    return u%139;
}

un'altra cosa che puoi fare è moltiplicare ogni carattere int per l'indice man mano che aumenta come la parola "orso" (0 * b) + (1 * e) + (2 * a) + (3 * r) che ti darà un valore int con cui giocare. la prima funzione di hash sopra si scontrano su "qui" e "ascolta", ma è comunque eccezionale nel dare dei buoni valori unici. quello qui sotto non si scontra con "qui" e "ascolta" perché moltiplico ogni carattere con l'indice man mano che aumenta.

int keyHash(string key)
{
    unsigned int k = (int)key.length();
    unsigned int u = 0,n = 0;

    for (Uint i=0; i<k; i++)
    {
        n = (int)key[i];
        u += i*n%31;
    }
    return u%139;
}

-1

Ecco una semplice funzione hash che uso per una tabella hash che ho creato. Fondamentalmente è per prendere un file di testo e memorizza ogni parola in un indice che rappresenta l'ordine alfabetico.

int generatehashkey(const char *name)
{
        int x = tolower(name[0])- 97;
        if (x < 0 || x > 25)
           x = 26;
        return x;
}

Ciò che sostanzialmente fa è che le parole sono tratteggiate secondo la loro prima lettera. Quindi, la parola che inizia con 'a' otterrebbe una chiave hash di 0, 'b' otterrebbe 1 e così via e 'z' sarebbe 25. Numeri e simboli avrebbero una chiave hash di 26. C'è un vantaggio che fornisce ; Puoi calcolare facilmente e rapidamente dove una determinata parola verrebbe indicizzata nella tabella hash dato che è tutto in un ordine alfabetico, qualcosa del genere: Il codice può essere trovato qui: https://github.com/abhijitcpatil/general

Dando il seguente testo come input: Atticus un giorno disse a Jem: “Preferirei che sparassi alle lattine nel cortile di casa, ma so che inseguirai gli uccelli. Spara a tutti i ghiandaie blu che vuoi, se riesci a colpirli, ma ricorda che è un peccato uccidere un uccello beffardo. " Quella è stata l'unica volta in cui ho sentito Atticus dire che era un peccato fare qualcosa, e ho chiesto a Miss Maudie a riguardo. "Tuo padre ha ragione", disse. “I mockingbirds non fanno nulla se non fare musica per farci divertire. Non mangiano nei giardini della gente, non nidificano nei presepi di mais, non fanno nulla ma cantano il loro cuore per noi. Ecco perché è un peccato uccidere un mockingbird.

Questo sarebbe l'output:

0 --> a a about asked and a Atticus a a all after at Atticus
1 --> but but blue birds. but backyard
2 --> cribs corn can cans
3 --> do dont dont dont do dont do day
4 --> eat enjoy. except ever
5 --> for for fathers
6 --> gardens go
7 --> hearts heard hit
8 --> its in it. I it I its if I in
9 --> jays Jem
10 --> kill kill know
11 --> 
12 --> mockingbird. music make Maudie Miss mockingbird.”
13 --> nest
14 --> out one one only one
15 --> peoples
16 --> 17 --> right remember rather
18 --> sin sing said. she something sin say sin Shoot shot said
19 --> to Thats their thing they They to thing to time the That to the the tin to
20 --> us. up us
21 --> 
22 --> why was was want
23 --> 
24 --> you you youll you
25 --> 
26 --> Mockingbirds  Your em Id

2
Una buona funzione hash distribuisce i valori equamente tra i bucket.
Jonathan Peterson,

-1

Ciò eviterà qualsiasi collisione e sarà veloce fino a quando non utilizzeremo lo spostamento nei calcoli.

 int k = key.length();
    int sum = 0;
    for(int i = 0 ; i < k-1 ; i++){
        sum += key.charAt(i)<<(5*i);
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.