Boa função hash para strings

160

Eu estou tentando pensar em uma boa função de hash para strings. E eu estava pensando que seria uma boa idéia resumir os valores unicode para os cinco primeiros caracteres da string (supondo que ele possua cinco, caso contrário, pare onde termina). Seria uma boa ideia ou ruim?

Estou fazendo isso em Java, mas não imaginaria que isso faria muita diferença.

Leif Andersen
fonte
4
Boas funções de hash dependem muito da entrada no hash e dos requisitos do algoritmo. Esse hash não será muito bom se todas as suas strings começarem com os mesmos cinco caracteres, por exemplo. Também tenderá a resultar em uma distribuição normal.
turbilhão
1
Possível duplicado de 98.153
Michael Mrozek
14
Por que você não pode usar Stringos seus hashCode()?
Bart Kiers 12/04/10
@ WhirlWind, é verdade, não sei ao certo o que as cordas terão, exceto que provavelmente será em inglês.
Leif Andersen
@Barl, principalmente porque meu professor nos disse para implementar nosso próprio hash functor ... e a razão pela qual eu não queria usar Java, era porque era genérico, e eu imaginaria que um functor hash mais específico seria melhor.
Leif Andersen

Respostas:

161

Geralmente, os hashes não fazem somas, caso contrário, stope potsterão o mesmo hash.

e você não o limitaria aos primeiros n caracteres porque, caso contrário, house e houses teriam o mesmo hash.

Geralmente, os hashs pegam valores e multiplicam-no por um número primo (aumenta a probabilidade de gerar hashes exclusivos). Assim, você pode fazer algo como:

int hash = 7;
for (int i = 0; i < strlen; i++) {
    hash = hash*31 + charAt(i);
}
jonathanasdf
fonte
@jonathanasdf Como você pode dizer que sempre fornece uma chave de hash exclusiva. Existe alguma prova matemática? Eu acho que temos que pegar mod de hash com outro número primo maior, caso contrário, ocorre um problema de estouro.
devsda
17
@ devsda Ele não disse sempre único, ele disse que é mais provável que seja único. Quanto ao porquê, uma rápida pesquisa no google revela este artigo: computinglife.wordpress.com/2008/11/20/… explicando por que 31 foi usado para o hash de string Java. Não existe prova matemática, mas explica o conceito geral de por que os primos funcionam melhor.
Pharap
2
Muito obrigado por esclarecer a idéia de melhorar o hash. Apenas para verificar novamente - O valor de retorno hashCode () será usado pelo Java para mapear para algum índice da tabela antes de armazenar o objeto. Portanto, se o hashCode () retornar m, ele fará algo como (m mod k) para obter um índice da tabela de tamanho k. Isso está certo?
whitehat
1
"hash = hash * 31 + charAt (i);" produz o mesmo hash para spot, tops, stop, opts e pots.
26418 Jack Straub
1
@maq Eu acredito que você está correto. Não sei o que eu estava pensando.
Jack Straub
139

Se é algo de segurança, você pode usar criptografia Java:

import java.security.MessageDigest;

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

fonte
93
Agradável. Eu tenho um aplicativo de aprendizado de máquina, fazendo PNL estatística em um corpus grande. Depois de algumas passagens iniciais de normalização morfológica nas palavras originais do texto, jogo fora os valores das strings e uso códigos de hash. Em todo o meu corpus inteiro, existem cerca de 600.000 palavras únicas e, usando a função java hashcode padrão, eu estava recebendo cerca de 3,5% de colisões. Mas se eu SHA-256 o valor da sequência e gerar um código de hash a partir da sequência digerida, a taxa de colisão é menor que 0,0001%. Obrigado!
benjismith
3
Obrigado por fornecer informações sobre as colisões e o número de palavras. Muito útil.
12266 philipp
19
@ benjismith Um em um milhão é muito grande ... é "menos de 0,0001%" uma maneira oblíqua de dizer "exatamente 0"? Eu realmente duvido que você tenha visto uma colisão SHA-256, porque isso nunca foi observado, em lugar algum, jamais; nem mesmo para SHA-1 de 160 bits. Se você tiver duas cadeias que produzem o mesmo SHA-256, a comunidade de segurança adoraria vê-las; você será mundialmente famoso ... de uma maneira muito obscura. Veja Comparação de funções SHA
Tim Sylvester
7
@ TimSylvester, você não entendeu. Não encontrei colisões SHA-256. Eu calculei o SHA-256 e, em seguida, alimentei as seqüências de bytes resultantes em uma função típica de Java "hashCode", porque eu precisava de um hash de 32 bits. Foi aí que encontrei as colisões. Nada notável :)
benjismith
1
Não há diferença entre 'hash' e 'criptografia'? Entendo que o MessageDigest é uma função de hash unidirecional, certo? Além disso, quando usei a função, obtive a cadeia de hash como muitos caracteres UTF indesejados quando abri o arquivo no LibreOffice. É possível obter a cadeia de hash como um grupo aleatório de caracteres alfanuméricos em vez de caracteres UTF indesejados?
Nav
38

Você provavelmente deve usar String.hashCode () .

Se você realmente deseja implementar o hashCode você mesmo:

Não fique tentado a excluir partes significativas de um objeto da computação do código de hash para melhorar o desempenho - Joshua Bloch, Java efetivo

Usar apenas os cinco primeiros caracteres é uma má ideia . Pense em nomes hierárquicos, como URLs: todos eles terão o mesmo código de hash (porque todos começam com "http: //", o que significa que eles são armazenados no mesmo bucket em um mapa de hash, exibindo um desempenho terrível.

Aqui está uma história de guerra parafraseada no hashCode String de " Java Efetivo ":

A função hash String implementada em todas as versões anteriores à 1.2 examinou no máximo dezesseis caracteres, espaçados uniformemente por toda a string, começando com o primeiro caractere. Para grandes coleções de nomes hierárquicos, como URLs, essa função de hash exibia um comportamento terrível.

Frederik
fonte
1
Se alguém estiver usando uma coleção de hash duplo, pode valer a pena fazer com que o primeiro hash seja realmente rápido e sujo. Se você tiver mil strings longas, metade das quais a são mapeadas por uma função restritiva para um valor específico e metade das quais são mapeadas para valores distintos, o desempenho em uma tabela de hash único seria ruim, mas o desempenho em uma tabela dupla. A tabela de hash, onde o segundo hash examinava toda a string, poderia ser quase o dobro de uma tabela de hash simples (já que metade das strings não precisaria ser totalmente hash). Porém, nenhuma das coleções Java padrão faz hash duplo.
supercat
O link efetivo do Java está quebrado @Frederik
KGs 31/01
17

Se você está fazendo isso em Java, por que está fazendo isso? Basta chamar .hashCode()a corda

Pyrolistical
fonte
2
Estou fazendo isso como parte da classe, e parte da tarefa é escrever várias funções de hash diferentes. O professor nos disse para obter ajuda externa para os 'melhores'.
Leif Andersen
20
Se você precisar que seja consistente nas versões e implementações da JVM, não deve confiar .hashCode(). Em vez disso, use algum algoritmo conhecido.
Stephen Ostermiller 26/03
7
O algoritmo for String::hashCodeé especificado no JDK, portanto é tão portátil quanto a própria existência da classe java.lang.String.
yshavit
12

O goiabaHashFunction ( javadoc ) fornece um hash decente não forte em criptografia.

Mike Samuel
fonte
1
Ele ainda está na versão beta a partir deste comentário
ThomasRS 13/14
1
E agora 404d.
Shawn
8

Essa função fornecida por Nick é boa, mas se você usar a nova String (byte [] bytes) para fazer a transformação em String, ela falhará. Você pode usar esta função para fazer isso.

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

Pode ser que isso possa ajudar alguém

Festus Tamakloe
fonte
Você pode simplesmente passar a matriz de bytes para messageDigest.update ().
szgal
byteArray2Hex () - era exatamente o que eu estava procurando! Muito obrigado :)
Krzysiek
5
// djb2 hash function
unsigned long hash(unsigned char *str)
{
    unsigned long hash = 5381;
    int c;

    while (c = *str++)
        hash = ((hash << 5) + hash) + c; /* hash * 33 + c */

    return hash;
}

lógica de origem por trás da função hash djb2 - SO

Pratik Deoghare
fonte
1
Eu acho que é apenas um número primo para começar, para que tenhamos menos colisões.
CornSmith
5

Há rumores de que o FNV-1 é uma boa função de hash para strings.

Para cadeias longas (com mais de, digamos, cerca de 200 caracteres), é possível obter um bom desempenho da função de hash MD4 . Como uma função criptográfica, ela foi quebrada cerca de 15 anos atrás, mas, para fins não criptográficos, ainda é muito boa e surpreendentemente rápida. No contexto de Java, você teria que converter os charvalores de 16 bits em palavras de 32 bits, por exemplo, agrupando esses valores em pares. Uma implementação rápida do MD4 em Java pode ser encontrada no sphlib . Provavelmente exagere no contexto de uma tarefa em sala de aula, mas vale a pena tentar.

Thomas Pornin
fonte
Essa função hash é muito melhor que a que vem com o java.
clankill3r
3

Se você quiser ver as implementações padrão do setor, consulte java.security.MessageDigest .

"Os resumos de mensagens são funções de hash unidirecionais seguras que pegam dados de tamanho arbitrário e emitem um valor de hash de tamanho fixo".

Dean J
fonte
1

aqui está um link que explica muitas funções hash diferentes, por enquanto eu prefiro a função hash ELF para o seu problema específico. Toma como entrada uma sequência de comprimento arbitrário.

Yefei
fonte
1

sdbm: esse algoritmo foi criado para a biblioteca de banco de dados sdbm (uma reimplementação de domínio público do 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;
}
Anchal
fonte
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();
}
Charaf JRA
fonte
-1

É uma boa idéia trabalhar com número ímpar ao tentar desenvolver uma boa função de hast para string. Essa função pega uma string e retorna um valor de índice, até agora funciona muito bem. e tem menos colisão. o índice varia de 0 a 300, talvez até mais do que isso, mas ainda não cheguei mais alto, mesmo com palavras longas como "engenharia eletromecânica"

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

Outra coisa que você pode fazer é multiplicar cada caractere analisado pelo índice, à medida que aumenta como a palavra "urso" (0 * b) + (1 * e) + (2 * a) + (2 * a) + (3 * r), o que lhe dará um valor int para brincar. a primeira função de hash acima colide em "aqui" e "ouça", mas ainda é ótima em fornecer bons valores exclusivos. o abaixo não colide com "aqui" e "ouve" porque eu multiplico cada caractere pelo índice à medida que 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;
}
kanthonye
fonte
-1

Aqui está uma função de hash simples que eu uso para uma tabela de hash que criei. É basicamente para pegar um arquivo de texto e armazenar todas as palavras em um índice que representa a ordem alfabética.

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

O que isso basicamente faz é que as palavras sejam hash de acordo com a primeira letra. Assim, a palavra que começa com 'a' obteria uma chave de hash 0, 'b' obteria 1 e assim por diante e 'z' seria 25. Números e símbolos teriam uma chave de hash 26. Essa é uma vantagem oferecida ; Você pode calcular fácil e rapidamente onde uma determinada palavra seria indexada na tabela de hash, pois está tudo em ordem alfabética, algo como isto: Código pode ser encontrado aqui: https://github.com/abhijitcpatil/general

Entregando o seguinte texto: Atticus disse a Jem um dia: “Prefiro que você atire em latas no quintal, mas sei que você vai atrás de pássaros. Atire em todos os gaios azuis que você quiser, se conseguir atingi-los, mas lembre-se de que é pecado matar um pássaro zombeteiro. Foi a única vez que ouvi Atticus dizer que era pecado fazer alguma coisa, e perguntei a Miss Maudie sobre isso. "Seu pai está certo", disse ela. “Os zombadores não fazem uma coisa, exceto fazer música para nós apreciarmos. Eles não comem os jardins das pessoas, não se aninham nos berços de milho, eles não fazem uma coisa, mas cantam seus corações por nós. É por isso que é pecado matar um pássaro zombeteiro.

Esta seria a saída:

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
user2311285
fonte
2
Uma boa função de hash distribui os valores igualmente entre os buckets.
Jonathan Peterson
-1

Isso evitará qualquer colisão e será rápido até usarmos a mudança nos cálculos.

 int k = key.length();
    int sum = 0;
    for(int i = 0 ; i < k-1 ; i++){
        sum += key.charAt(i)<<(5*i);
    }
Kamal El-Deen Shair
fonte