Por que as funções de hash devem usar um módulo de número primo?

335

Há muito tempo, comprei um livro de estruturas de dados da tabela de pechinchas por US $ 1,25. Nele, a explicação para uma função de hash dizia que ela deveria ser modificada por um número primo por causa da "natureza da matemática".

O que você espera de um livro de US $ 1,25?

Enfim, tive anos para pensar sobre a natureza da matemática e ainda não consigo descobrir.

A distribuição dos números é realmente mais uniforme quando há um número primo de baldes? Ou isso é um conto antigo de programador que todo mundo aceita porque todo mundo aceita?

theschmitzer
fonte
11
Pergunta perfeitamente razoável: Por que deveria haver um número primo de baldes?
Draemon
11
Esta questão parece estar fora de tópico, porque provavelmente pertence à Ciência da Computação .
Corridas de leveza em órbita
2
cs.stackexchange.com/a/64191/64222 outra explicação bem argumentada.
Green Tree
Aqui está outra ótima explicação para uma pergunta um tanto relacionada com alguns números surpreendentes de evidências - quora.com/…
AnBisw

Respostas:

242

Geralmente, uma função simples de hash funciona pegando as "partes componentes" da entrada (caracteres no caso de uma string) e multiplicando-as pelos poderes de alguma constante e adicionando-as em algum tipo inteiro. Portanto, por exemplo, um hash típico (embora não especialmente bom) de uma sequência pode ser:

(first char) + k * (second char) + k^2 * (third char) + ...

Então, se um punhado de strings, todas com o mesmo primeiro caractere, forem alimentadas, os resultados serão todos do mesmo módulo k, pelo menos até o tipo inteiro exceder.

[Como exemplo, o hashCode da string de Java é estranhamente semelhante a isso - ele faz a ordem inversa dos caracteres, com k = 31. Portanto, você obtém relações de módulo 31 entre seqüências que terminam da mesma maneira e relações de módulo 2 ^ 32 entre seqüências que são iguais, exceto no final. Isso não atrapalha seriamente o comportamento de hashtable.]

Uma hashtable funciona assumindo o módulo do hash sobre o número de buckets.

Em uma hashtable, é importante não produzir colisões para casos prováveis, pois as colisões reduzem a eficiência da hashtable.

Agora, suponha que alguém coloque um monte de valores em uma hashtable que tenha algum relacionamento entre os itens, como todos tendo o mesmo primeiro caractere. Esse é um padrão de uso bastante previsível, eu diria, portanto não queremos que ele produza muitas colisões.

Acontece que "devido à natureza da matemática", se a constante usada no hash e o número de buckets forem coprimes , as colisões serão minimizadas em alguns casos comuns. Se eles não são coprime, existem alguns relacionamentos bastante simples entre entradas para as quais as colisões não são minimizadas. Todos os hashes saem iguais ao fator comum, o que significa que todos caem no 1/1 dos baldes que têm esse valor modulado no fator comum. Você recebe n vezes mais colisões, onde n é o fator comum. Como n é pelo menos 2, eu diria que é inaceitável que um caso de uso bastante simples gere pelo menos o dobro de colisões do que o normal. Se algum usuário dividir nossa distribuição em buckets, queremos que seja um acidente estranho, não um uso previsível simples.

Agora, implementações de hashtable obviamente não têm controle sobre os itens colocados nelas. Eles não podem impedir que eles sejam relacionados. Portanto, a coisa a fazer é garantir que a constante e a contagem do balde sejam coprime. Dessa forma, você não depende apenas do "último" componente para determinar o módulo do bucket em relação a algum pequeno fator comum. Até onde eu sei, eles não precisam ser os melhores para conseguir isso, apenas coprime.

Mas se a função hash e a hashtable forem gravadas independentemente, a hashtable não saberá como funciona a função hash. Pode estar usando uma constante com pequenos fatores. Se você tiver sorte, pode funcionar de maneira completamente diferente e não linear. Se o hash for bom o suficiente, qualquer contagem de buckets será boa. Mas uma hashtable paranóica não pode assumir uma boa função de hash, portanto, use um número primo de buckets. Da mesma forma, uma função hash paranóica deve usar uma constante prime grande, para reduzir a chance de alguém usar vários buckets que, por acaso, possuem um fator comum com a constante.

Na prática, acho bastante normal usar uma potência de 2 como o número de baldes. Isso é conveniente e evita a necessidade de procurar ou pré-selecionar um número primo da magnitude certa. Portanto, você depende da função hash para não usar multiplicadores, o que geralmente é uma suposição segura. Mas você ainda pode obter comportamentos ocasionais de hash ruim com base em funções de hash como a acima, e a contagem principal de buckets pode ajudar ainda mais.

Adotar o princípio de que "tudo tem que ser primordial" é, tanto quanto eu conheço, uma condição suficiente, mas não necessária, para uma boa distribuição das hashtables. Ele permite que todos interoperem sem precisar assumir que os outros seguiram a mesma regra.

[Editar: há outro motivo mais especializado para usar um número primo de caçambas, ou seja, se você lidar com colisões com sondagem linear. Em seguida, você calcula um passo a partir do código de hash e, se esse passo for um fator da contagem de buckets, você só poderá fazer análises (bucket_count / stride) antes de voltar para onde começou. O caso que você mais deseja evitar é stride = 0, é claro, que deve ter uma caixa especial, mas para evitar também uma caixa especial bucket_count / stride igual a um número inteiro pequeno, você pode simplesmente fazer o bucket_count ser primo e não se importar com o que passo é fornecido, não é 0.]

Steve Jessop
fonte
Assim como uma nota lateral: uma discussão para uma escolha sensata do k fator para hashcodes está aqui: stackoverflow.com/q/1835976/21499
Hans-Peter Storr
9
Esta é uma resposta incrível. você pode, por favor, explicar isso mais "Então, você obtém um módulo 31 de relacionamentos marcantes entre seqüências que terminam da mesma maneira, e um módulo 2 ^ 32 de relacionamentos marcantes entre seqüências iguais, exceto no final. Isso não atrapalha seriamente o comportamento de hashtable. " Eu particularmente não entendo a 2 ^ 32 parte
ordinária
2
Nota adicional para deixar as coisas mais claras sobre isso: "Todos os hashes saem iguais ao módulo do fator comum" -> Isso ocorre porque, se você considerar o exemplo da função hash, hash = 1º char + 2 char * k + ... e pegue strings com o mesmo primeiro caractere, o hash% k será o mesmo para essas strings. Se M é o tamanho da hashtable eg é o mcd de M e k, então (hash% k)% g é igual a hash% g (já que g divide k) e, portanto, o hash% g também será o mesmo para essas seqüências. Agora considere (hash% M)% g, isso é igual a hash% g (já que g divide M). Então (hash% M)% g é igual para todas essas cadeias.
Quark
11
@DanielMcLaury Joshua Bloch explicou o porquê do Java - foi recomendado em dois livros populares (K&R, Dragon book) e teve um bom desempenho com baixas colisões no dicionário inglês. É rápido (usa o método de Horner ). Aparentemente, nem a K&R se lembra de onde veio. Função semelhante é a impressão digital de Rabin do algoritmo Rabin-Karp (1981), mas K&R (1978) é anterior a isso.
bain
11
@SteveJessop, por favor, você pode explicar "relações impressionantes no módulo 2 ^ 32 entre cadeias iguais, exceto no final". Obrigado.
Khanna111
29

A primeira coisa que você faz ao inserir / recuperar da tabela de hash é calcular o hashCode para a chave especificada e, em seguida, encontrar o intervalo correto aparando o hashCode no tamanho da hashTable executando hashCode% table_length. Aqui estão duas 'declarações' que você provavelmente já leu em algum lugar

  1. Se você usar uma potência de 2 para table_length, encontrar (hashCode (key)% 2 ^ n) é tão simples e rápido quanto (hashCode (key) & (2 ^ n -1)). Mas se sua função de calcular o hashCode para uma determinada chave não for boa, você definitivamente sofrerá com o agrupamento de muitas chaves em alguns buckets de hash.
  2. Mas se você usar números primos para table_length, os hashCodes calculados poderão mapear para os diferentes buckets de hash, mesmo se você tiver uma função hashCode um pouco estúpida.

E aqui está a prova.

Se suponha que sua função hashCode resulte nos seguintes códigos hash, entre outros {x, 2x, 3x, 4x, 5x, 6x ...}, todos eles serão agrupados em apenas m número de buckets, em que m = table_length / GreatestCommonFactor (comprimento_tabela, x). (É trivial verificar / derivar isso). Agora você pode executar um dos seguintes procedimentos para evitar o armazenamento em cluster

Certifique-se de não gerar muitos códigos de hash múltiplos de outro código de hash como em {x, 2x, 3x, 4x, 5x, 6x ...}. Mas isso pode ser um pouco difícil se a sua hashTable tiver milhões de entradas. Ou simplesmente faça m igual ao comprimento da tabela, tornando GreatestCommonFactor (comprimento da tabela, x) igual a 1, ou seja, tornando o comprimento da tabela coprime com x. E se x pode ser praticamente qualquer número, verifique se o comprimento da tabela é um número primo.

From - http://srinvis.blogspot.com/2006/07/hash-table-lengths-and-prime-numbers.html


fonte
11

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

Explicação bastante clara, com fotos também.

Editar: Como um resumo, os números primos são usados ​​porque você tem a melhor chance de obter um valor único ao multiplicar valores pelo número primo escolhido e somar todos eles. Por exemplo, dada uma string, multiplicar cada valor de letra pelo número primo e, em seguida, somar todos, fornecerá seu valor de hash.

Uma pergunta melhor seria: por que exatamente o número 31?

AlbertoPL
fonte
5
Embora, creio que um resumo possa ser útil, caso esse site esteja morto, algum resquício de seu conteúdo será salvo aqui no SO.
21415 Thomas
2
O artigo não explica o porquê, mas diz: "Os pesquisadores descobriram que o uso de um número primo de 31 oferece uma melhor distribuição das chaves e menor número de colisões. Ninguém sabe o porquê ..." Engraçado, fazendo a mesma pergunta que eu em vigor .
Theschmitzer 17/07/09
> Uma pergunta melhor seria: por que exatamente o número 31? Se você quer dizer por que o número 31 é usado, o artigo que você aponta diz o porquê, ou seja, porque é rápido para múltiplos e os testes cos mostram que é o melhor para usar. O outro multiplicador popular que eu já vi é o 33, que empresta peso à teoria de que a questão da velocidade era (pelo menos inicialmente) um fator importante. Se você quer dizer, o que significa 31 que o torna melhor nos testes, acho que não sei.
219109 sgmoore
Exatamente, o único motivo pelo qual ele poderia ter sido usado como multiplicador foi porque era fácil multiplicá-lo. (Quando digo que vi 33 usados ​​como multiplicadores, não quero dizer recentemente, isso provavelmente ocorreu décadas atrás, e possível antes de muitas análises serem feitas sobre hash).
sgmoore 17/07/09
3
@SteveJessop O número 31 é facilmente otimizado pela CPU como uma operação (x * 32) -1, na qual *32é uma simples troca de bits ou, melhor ainda, um fator de escala de endereço imediato (por exemplo, lea eax,eax*8; leax, eax,eax*4em x86 / x64). Portanto, *31é um bom candidato à multiplicação de números primos. Este foi praticamente verdadeiro alguns anos atrás - agora mais recente arquitetura de CPUs têm uma multiplicação quase instantânea - divisão é sempre mais lento ...
Arnaud Bouchez
10

tl; dr

index[hash(input)%2]resultaria em uma colisão para metade de todos os hashes possíveis e uma faixa de valores. index[hash(input)%prime]resulta em uma colisão de <2 de todos os hashes possíveis. A fixação do divisor ao tamanho da tabela também garante que o número não possa ser maior que a tabela.

Indolering
fonte
11
2 é um número cara nobre
Ganesh Chowdhary Sadanala
8

Primes são usados ​​porque você tem boas chances de obter um valor exclusivo para uma função hash típica que usa o módulo P. de polinômios. Digamos que você use essa função hash para cadeias de comprimento <= N e tenha uma colisão. Isso significa que 2 polinômios diferentes produzem o mesmo valor de módulo P. A diferença desses polinômios é novamente um polinômio do mesmo grau N (ou menos). Ele não tem mais que N raízes (é aqui que a natureza da matemática se mostra, uma vez que essa afirmação é verdadeira apenas para um polinômio sobre um campo => número primo). Portanto, se N for muito menor que P, é provável que você não tenha uma colisão. Depois disso, o experimento provavelmente pode mostrar que 37 é grande o suficiente para evitar colisões para uma tabela de hash de seqüências de caracteres com comprimento de 5 a 10 e é pequeno o suficiente para ser usado nos cálculos.

TT_
fonte
11
Embora a explicação agora pareça óbvia, ela chegou a mim depois de ler um livro de A.Shen "Programação: teoremas e problemas" (em russo), veja a discussão sobre o algoritmo Rabin. Não tenho certeza se existe uma tradução em inglês.
TT_ 26/11/2013
5

Apenas para fornecer um ponto de vista alternativo, existe este site:

http://www.codexon.com/posts/hash-functions-the-modulo-prime-myth

Que afirma que você deve usar o maior número possível de buckets, em vez de arredondar para um número principal de buckets. Parece uma possibilidade razoável. Intuitivamente, certamente posso ver como seria melhor um número maior de buckets, mas não consigo argumentar matemático sobre isso.

Falaina
fonte
Um número maior de caçambas significa menos colisões: veja o princípio do buraco de pombo.
Desconhecido
11
@Desconhecido: não acredito que seja verdade. Corrija-me se estiver errado, mas acredito que a aplicação do princípio pigeonhole às tabelas de hash apenas permite que você afirme que haverá colisões se você tiver mais elementos que bandejas, para não tirar conclusões sobre a quantidade ou densidade de colisões. Ainda acredito que o maior número de caixas é a rota correta, no entanto.
Falaina 10/09/09
Se você assumir que as colisões são aleatórias para todos os efeitos, então, pelo paradoxo do aniversário, um espaço maior (buckets) reduzirá a probabilidade de ocorrência de uma colisão.
Desconhecido
11
@Desconhecido, você perdeu que as colisões também dependem da própria função hash. Portanto, se a tem a função é muito ruim, então não importa o quão grande você aumenta o tamanho, ainda pode haver quantidade significativa de colisões
Suraj Chandran
O artigo original parece ter sumido, mas há alguns comentários interessantes aqui, incluindo uma discussão com o autor original. news.ycombinator.com/item?id=650487
Adrian McCarthy
3

Primes são números únicos. Eles são únicos, pois o produto de um primo com qualquer outro número tem a melhor chance de ser único (não tão único quanto o próprio primo, é claro), devido ao fato de um primo ser usado para compô-lo. Esta propriedade é usada nas funções de hash.

Dada uma sequência "Samuel", você pode gerar um hash exclusivo, multiplicando cada um dos dígitos ou letras constituintes por um número primo e somando-os. É por isso que primos são usados.

No entanto, o uso de números primos é uma técnica antiga. A chave aqui para entender que, desde que você possa gerar uma chave suficientemente única, você também pode passar para outras técnicas de hash. Clique aqui para obter mais informações sobre este tópico sobre http://www.azillionmonkeys.com/qed/hash.html

http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/

user105033
fonte
11
hahahah .... na verdade, o produto de 2 números primos não tem mais chance de ser "único" do que o produto de um número primo e qualquer outro número?
21119 HasaniH
@Beska aqui "singularidade" é definida de forma recursiva, então eu acredito "não-singularidade" deve ser definido da mesma maneira :)
TT_
3

Depende da escolha da função hash.

Muitas funções de hash combinam os vários elementos nos dados, multiplicando-os com alguns fatores, modulando a potência de dois correspondentes ao tamanho da palavra da máquina (esse módulo é livre, apenas deixando o cálculo exceder).

Você não deseja nenhum fator comum entre um multiplicador para um elemento de dados e o tamanho da tabela de hash, porque pode acontecer que variar o elemento de dados não espalhe os dados por toda a tabela. Se você escolher um primo para o tamanho da tabela, esse fator comum é altamente improvável.

Por outro lado, esses fatores são geralmente compostos de números primos ímpares, portanto você também deve estar seguro usando potências de dois para sua tabela de hash (por exemplo, o Eclipse usa 31 quando gera o método Java hashCode ()).

starblue
fonte
2

Suponha que o tamanho da sua tabela (ou o número do módulo) seja T = (B * C). Agora, se o hash da sua entrada for como (N * A * B), em que N pode ser qualquer número inteiro, sua saída não será bem distribuída. Como toda vez que n se tornar C, 2C, 3C etc., sua saída começará a se repetir. ou seja, sua saída será distribuída apenas nas posições C. Observe que C aqui é (T / HCF (tamanho da tabela, hash)).

Esse problema pode ser eliminado com o HCF 1. Os números primos são muito bons para isso.

Outra coisa interessante é quando T é 2 ^ N. Isso fornecerá a saída exatamente igual a todos os N bits inferiores do hash de entrada. Como todo número pode ser representado potências de 2, quando tomarmos o módulo de qualquer número com T, subtraímos todas as potências do número 2 do formulário, que são> = N, portanto, sempre emitindo número de padrão específico, dependendo da entrada . Esta também é uma má escolha.

Da mesma forma, T como 10 ^ N também é ruim por razões semelhantes (padrão na notação decimal de números em vez de binário).

Portanto, os números primos tendem a fornecer melhores resultados distribuídos, portanto, são uma boa opção para o tamanho da tabela.

nishantbhardwaj2002
fonte
2

Copiando da minha outra resposta https://stackoverflow.com/a/43126969/917428 . Veja para mais detalhes e exemplos.

Acredito que isso tenha a ver com o fato de os computadores funcionarem na base 2. Pense em como a mesma coisa funciona na base 10:

  • 8% 10 = 8
  • 18% 10 = 8
  • 87865378% 10 = 8

Não importa qual é o número: contanto que termine com 8, seu módulo 10 será 8.

Escolher um número grande o suficiente, sem potência de dois, garantirá que a função hash seja realmente uma função de todos os bits de entrada, em vez de um subconjunto deles.

Ste_95
fonte
1

Gostaria de acrescentar algo à resposta de Steve Jessop (não posso comentar, pois não tenho reputação suficiente). Mas encontrei algum material útil. Sua resposta é de grande ajuda, mas ele cometeu um erro: o tamanho do balde não deve ser uma potência de 2. Vou apenas citar o livro "Introdução ao algoritmo", de Thomas Cormen, Charles Leisersen, et al., Na página 263:

Ao usar o método de divisão, geralmente evitamos certos valores de m. Por exemplo, m não deve ser uma potência de 2, pois se m = 2 ^ p, então h (k) é apenas os bits de p de ordem mais baixa de k. A menos que saibamos que todos os padrões de bits p de ordem inferior são igualmente prováveis, é melhor projetar a função hash para depender de todos os bits da chave. Como o Exercício 11.3-3 pede para você mostrar, escolher m = 2 ^ p-1 quando k é uma sequência de caracteres interpretada na raiz 2 ^ p pode ser uma má escolha, porque permutar os caracteres de k não altera seu valor de hash.

Espero que ajude.

iefgnoix
fonte
0

Para uma função de hash, não é apenas importante minimizar as colisões em geral, mas também tornar impossível permanecer com o mesmo hash enquanto persegue alguns bytes.

Digamos que você tenha uma equação: (x + y*z) % key = xcom 0<x<keye 0<z<key. Se a chave for um número principal, n * y = a chave será verdadeira para todos os n em N e falsa para todos os outros números.

Um exemplo em que chave não é um exemplo primordial: x = 1, z = 2 e chave = 8 Como a chave / z = 4 ainda é um número natural, 4 se torna uma solução para nossa equação e, neste caso (n / 2) * y = chave é verdadeira para cada n em N. A quantidade de soluções para a equação praticamente dobrou porque 8 não é primo.

Se nosso atacante já sabe que 8 é a solução possível para a equação, ele pode alterar o arquivo de 8 para 4 e ainda obter o mesmo hash.

cristão
fonte
0

Eu li o popular site wordpress vinculado em algumas das respostas populares acima na parte superior. Pelo que entendi, gostaria de compartilhar uma observação simples que fiz.

Você pode encontrar todos os detalhes no artigo aqui , mas suponha que o seguinte seja verdadeiro:

  • Usar um número primo nos dá a "melhor chance" de um valor único

Uma implementação geral de hashmap deseja que duas coisas sejam únicas.

  • Código hash exclusivo para a chave
  • Índice exclusivo para armazenar o valor real

Como obtemos o índice exclusivo? Tornando o tamanho inicial do contêiner interno também excelente. Então, basicamente, o prime está envolvido porque possui essa característica única de produzir números únicos que acabamos usando para identificar objetos e encontrar índices dentro do contêiner interno.

Exemplo:

key = "chave"

valor = "valor" uniqueId = "k" * 31 ^ 2 + "e" * 31 ^ 1` + "y"

mapeia para o ID exclusivo

Agora queremos um local único para o nosso valor - para que

uniqueId % internalContainerSize == uniqueLocationForValue, assumindo internalContainerSizetambém é um primo.

Sei que isso é simplificado, mas espero ter uma ideia geral.

Ryan
fonte
0

"A natureza da matemática" em relação aos módulos de potência principal é que eles são um componente de um campo finito . Os outros dois blocos de construção são uma operação de adição e multiplicação. A propriedade especial dos módulos primos é que eles formam um campo finito com as operações "regulares" de adição e multiplicação, apenas levadas ao módulo. Isso significa que toda multiplicação mapeia para um módulo inteiro diferente do primo, assim como todas as adições.

Os módulos principais são vantajosos porque:

  • Eles oferecem mais liberdade ao escolher o multiplicador secundário no hash secundário, todos os multiplicadores, exceto 0, acabam visitando todos os elementos exatamente uma vez
  • Se todos os hashes forem menores que o módulo, não haverá colisões.
  • Os números primos aleatórios se misturam melhor que a potência de dois módulos e compactam as informações de todos os bits, não apenas de um subconjunto

No entanto, eles têm uma grande desvantagem, exigem uma divisão de números inteiros, que leva muitos (~ 15-40) ciclos, mesmo em uma CPU moderna. Com cerca de metade do cálculo, pode-se garantir que o hash esteja muito bem misturado. Duas operações de multiplicação e xorshift se misturam melhor que um moudulus principal. Em seguida, podemos usar qualquer tamanho de tabela de hash e a redução de hash mais rápida, fornecendo 7 operações no total para potência de 2 tamanhos de mesa e cerca de 9 operações para tamanhos arbitrários.

Recentemente, observei muitas das implementações mais rápidas da tabela de hash e a maioria delas não usa módulos principais.

Wolfgang Brehm
fonte
0

Esta questão foi mesclada à pergunta mais apropriada: por que as tabelas de hash devem usar matrizes de tamanho primordial e não a potência de 2. Para as funções de hash, há muitas boas respostas aqui, mas, para a pergunta relacionada, por que algumas tabelas de hash críticas à segurança , como a glibc, use matrizes de tamanho primário, ainda não há nenhuma.

Geralmente o poder de 2 mesas é muito mais rápido. Lá o caro h % n => h & bitmask, onde a máscara de bits pode ser calculada via clz("contar zeros à esquerda") do tamanho n. Uma função de módulo precisa fazer a divisão inteira que é cerca de 50x mais lenta que a lógica and. Existem alguns truques para evitar um módulo, como usar o https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ , mas geralmente as tabelas de hash rápidas usam o poder de 2, e as tabelas de hash seguras usam números primos.

Por quê então?

A segurança nesse caso é definida por ataques à estratégia de resolução de colisões, que na maioria das tabelas de hash são apenas pesquisas lineares em uma lista vinculada de colisões. Ou com as tabelas lineares de endereçamento mais rápido, pesquisa linear diretamente na tabela. Portanto, com o poder de 2 tabelas e algum conhecimento interno da tabela, por exemplo, o tamanho ou a ordem da lista de chaves fornecida por alguma interface JSON, você obtém o número de bits corretos usados. O número de unidades na máscara de bits. Isso geralmente é menor que 10 bits. E por 5 a 10 bits, é trivial colisões de força bruta, mesmo com as funções de hash mais fortes e lentas. Você não tem mais a segurança total de suas funções de hash de 32 bits ou 64 bits. E o objetivo é usar funções de hash pequenas e rápidas, não monstros como murmúrios ou até sifás.

Portanto, se você fornecer uma interface externa à sua tabela de hash, como um resolvedor de DNS, uma linguagem de programação, ... você quer se preocupar com as pessoas que gostam de abusar desses serviços. Normalmente, é mais fácil para essas pessoas encerrar seu serviço público com métodos muito mais fáceis, mas aconteceu. Então as pessoas se importavam.

Portanto, as melhores opções para evitar ataques de colisão são:

1) usar tabelas primárias, porque então

  • todos os 32 ou 64 bits são relevantes para encontrar o bucket, não apenas alguns.
  • a função de redimensionamento da tabela de hash é mais natural do que apenas o dobro. A melhor função de crescimento é a sequência de fibonacci e os primos aproximam-se mais do que dobrar.

2) use medidas melhores contra o ataque real, juntamente com potência rápida de 2 tamanhos.

  • conte as colisões e aborte ou durma nos ataques detectados, que são números de colisões com uma probabilidade <1%. Como 100 com tabelas de hash de 32 bits. Isto é o que, por exemplo, o dns resolvedor de djb faz.
  • converta a lista vinculada de colisões em árvore com pesquisa O (log n) e não O (n) quando um ataque de colisão é detectado. Isto é o que, por exemplo, o java faz.

Existe um mito generalizado de que funções hash mais seguras ajudam a impedir esses ataques, o que está errado, como expliquei. Não há segurança apenas com bits baixos. Isso funcionaria apenas com tabelas de tamanho primo, mas usaria uma combinação dos dois métodos mais lentos, hash lento e módulo primário lento.

As funções de hash para tabelas de hash precisam principalmente ser pequenas (para serem inlináveis) e rápidas. A segurança pode vir apenas da prevenção da pesquisa linear nas colisões. E não usar funções hash trivialmente ruins, como aquelas insensíveis a alguns valores (como \ 0 ao usar multiplicação).

O uso de sementes aleatórias também é uma boa opção, as pessoas começaram com isso primeiro, mas com informações suficientes da tabela, mesmo uma semente aleatória não ajuda muito, e linguagens dinâmicas geralmente tornam trivial obter a semente por outros métodos, como ela é armazenada em locais de memória conhecidos.

suburbano
fonte
-1
function eratosthenes(n) {

    function getPrime(x) {
        var middle = (x-(x%2))/2;
        var arr_rest = [];
        for(var j=2 ; j<=middle;j++){
            arr_rest.push(x%j);
        }

        if(arr_rest.indexOf(0) == -1) {
            return true
        }else {
            return false
        }

    }
    if(n<2)  {
        return []
    }else if(n==2){
        return [2]
    }else {
        var arr = [2]
        for(var i=3;i<n;i++) {
            if(getPrime(i)){
                arr.push(i)
            }
        }
    }

    return arr;
}
Khaireddine Hamdi
fonte
2
Você poderia adicionar comentários para explicar sua solução, por favor?
pom421 28/11/19