Desafio de função hash Tweetable


73

Neste você escreverá uma função de hash em 140 bytes 1 ou menos do código-fonte. A função hash deve receber uma sequência ASCII como entrada e retornar um número inteiro não assinado de 24 bits ([0, 2 24 -1]) como saída.

Sua função de hash será avaliada para cada palavra neste grande dicionário de inglês britânico 2 . Sua pontuação é a quantidade de palavras que compartilham um valor de hash com outra palavra (uma colisão).

A pontuação mais baixa ganha, empates quebrados pelo primeiro pôster.

Caso de teste

Antes de enviar, teste seu script de pontuação na seguinte entrada:

duplicate
duplicate
duplicate
duplicate

Se der uma pontuação diferente de 4, é de buggy.


Regras de esclarecimento:

  1. Sua função hash deve ser executada em uma única sequência, não em uma matriz inteira. Além disso, sua função hash pode não fazer nenhuma outra E / S além da sequência de entrada e do número inteiro de saída.
  2. Funções de hash embutidas ou funcionalidade semelhante (por exemplo, criptografia para embaralhar bytes) não são permitidas.
  3. Sua função de hash deve ser determinística.
  4. Ao contrário da maioria dos outros concursos, é permitido otimizar especificamente a entrada de pontuação.

1 Estou ciente de que o Twitter limita caracteres em vez de bytes, mas por simplicidade, usaremos bytes como limite para esse desafio.
2 Modificado a partir do wbritish-enorme do Debian , removendo quaisquer palavras não-ASCII.


11
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch's? O que...?
Luis Mendo

8
@DonMuesli en.wikipedia.org/wiki/Llanfairpwllgwyngyll (Curiosidade: essa palavra também está no dicionário de compactação built-in de Jelly)
Martin Ender

8
Eu acho que você deve proibir dicionários internos.
Dennis19

4
Para referência: Tomando a 24 MSB de SHA-512 iria atingir uma pontuação de 6816.
Dennis

10
Alguns cálculos detalhados: Com saída de D=340275palavras e R=2^24hash, um hash aleatório tem D^2/(2*R) = 3450pares esperados de colisão, alguns dos quais se sobrepõem. Há uma D^3/(6*R^2) = 23tripla esperada de colisão e um número insignificante de colisões maiores, o que significa que essas triplas provavelmente são desarticuladas. Isso fornece as 6829palavras esperadas que compartilham um valor de hash, ~ 70em triplos e o restante em pares. O desvio padrão é estimado em 118, portanto, obter <6200um hash aleatório é aproximadamente um evento 5 sigma.
Xnor #

Respostas:


11

Tudo bem, eu vou aprender um idioma de golfe.

CJam, 140 bytes, 3314 palavras em colisão

00000000: 7b5f 3162 225e d466 4a55 a05e 9f47 fc51  {_1b"^.fJU.^.G.Q
00000010: c45b 4965 3073 72dd e1b4 d887 a4ac bcbd  .[Ie0sr.........
00000020: 9c8f 70ca 2981 b2df 745a 10d0 dfca 6cff  ..p.)...tZ....l.
00000030: 7a3b 64df e730 54b4 b068 8584 5f6c 9f6b  z;d..0T..h.._l.k
00000040: b7f8 7a1f a2d3 b2b8 bcf5 cfa6 1ef7 a55c  ..z............\
00000050: dca8 795c 2492 dc32 1fb6 f449 f9ca f6b7  ..y\$..2...I....
00000060: a2cf 4772 266e ad4f d90c d236 b51d c5d5  ..Gr&n.O...6....
00000070: 5c46 3f9b 7cb4 f195 4efc fe4a ce8d 9aee  \F?.|...N..J....
00000080: 9dbc 223d 6962 3443 2329 257d            .."=ib4C#)%}

Define um bloco (função anônima). Para testar, você pode adicionar qN%%N*Na lista de palavras separada por nova linha no stdin e escrever uma lista separada por nova linha de hashes no stdout. Código Python equivalente:

b=lambda s,a:reduce(lambda n,c:n*a+ord(c),s,0)
f=lambda s:b(s,ord('^\xd4fJU\xa0^\x9fG\xfcQ\xc4[Ie0sr\xdd\xe1\xb4\xd8\x87\xa4\xac\xbc\xbd\x9c\x8fp\xca)\x81\xb2\xdftZ\x10\xd0\xdf\xcal\xffz;d\xdf\xe70T\xb4\xb0h\x85\x84_l\x9fk\xb7\xf8z\x1f\xa2\xd3\xb2\xb8\xbc\xf5\xcf\xa6\x1e\xf7\xa5\\\xdc\xa8y\\$\x92\xdc2\x1f\xb6\xf4I\xf9\xca\xf6\xb7\xa2\xcfGr&n\xadO\xd9\x0c\xd26\xb5\x1d\xc5\xd5\\F?\x9b|\xb4\xf1\x95N\xfc\xfeJ\xce\x8d\x9a\xee\x9d\xbc'[b(s,1)%125]))%(8**8+1)

Pitão, 140 bytes, 3535 3396 palavras em colisão

00000000: 4c25 4362 2d68 5e38 2038 2a36 3643 4022  L%Cb-h^8 8*66C@"
00000010: aa07 f29a 27a7 133a 3901 484d 3f9b 1982  ....'..:9.HM?...
00000020: d261 79ab adab 9d92 888c 3012 a280 76cf  .ay.......0...v.
00000030: a2e5 8f81 7039 acee c42e bc18 28d8 efbf  ....p9......(...
00000040: 0ebe 2910 9c90 158e 3742 71b4 bdf5 59c2  ..).....7Bq...Y.
00000050: f90b e291 8673 ea59 6975 10be e750 84c8  .....s.Yiu...P..
00000060: 0b0f e7e8 f591 f628 cefa 1ab3 2e3c 72a3  .......(.....<r.
00000070: 7f09 6190 dbd2 d54e d6d0 d391 a780 ebb6  ..a....N........
00000080: ae86 2d1e 49b0 552e 7522 4362            ..-.I.U.u"Cb

Define uma função chamada y. Para testar, você pode adicionar jmyd.za lista de palavras separada por nova linha no stdin e escrever uma lista separada por nova linha de hashes no stdout. Código Python equivalente:

b=lambda s,a:reduce(lambda n,c:n*a+ord(c),s,0)
f=lambda s:b(s,256)%(8**8+1-66*ord("\xaa\x07\xf2\x9a'\xa7\x13:9\x01HM?\x9b\x19\x82\xd2ay\xab\xad\xab\x9d\x92\x88\x8c0\x12\xa2\x80v\xcf\xa2\xe5\x8f\x81p9\xac\xee\xc4.\xbc\x18(\xd8\xef\xbf\x0e\xbe)\x10\x9c\x90\x15\x8e7Bq\xb4\xbd\xf5Y\xc2\xf9\x0b\xe2\x91\x86s\xeaYiu\x10\xbe\xe7P\x84\xc8\x0b\x0f\xe7\xe8\xf5\x91\xf6(\xce\xfa\x1a\xb3.<r\xa3\x7f\ta\x90\xdb\xd2\xd5N\xd6\xd0\xd3\x91\xa7\x80\xeb\xb6\xae\x86-\x1eI\xb0U.u"[b(s,256)%121]))

Limites teóricos

Quão bem podemos esperar fazer? Aqui está um gráfico de x, o número de palavras em colisão, vs. y, a entropia em bytes necessária para obter no máximo x palavras em colisão. Por exemplo, o ponto (2835, 140) nos diz que uma função aleatória obtém no máximo 2835 palavras colididas com probabilidade 1/256 ** 140, por isso é extremamente improvável que possamos fazer muito melhor do que isso com 140 bytes de código.

gráfico


Boa análise dos limites teóricos. Para superar esse limite teórico, provavelmente seria necessário usar uma linguagem com funções internas otimizadas para o dicionário da pergunta (o que seria trapaça). Se o idioma tiver um hash criptográfico interno, o limite poderá ser transformado em um método mais ou menos construtivo para encontrar uma solução ideal. Considere o seguinte: $ h (w || c)% 2 ^ {24} $ em que $ c $ é uma constante de cadeia de bytes. Em um modelo de oráculo aleatório que pode ser mostrado para se aproximar do ideal com alta probabilidade. É claro que a força bruta a $ c $ não seria viável.
kasperd

Como você calculou a fórmula para o gráfico? Muito interessante!
NikoNyrh

@NikoNyrh Programação dinâmica. Let ( w , c , h ) representa um estado com w palavras, das quais c estão colidindo com h hashes distintos, e o restante w - c todos têm hashes distintos. Se adicionarmos uma palavra aleatória, o estado se tornará ( w + 1, c , h ) com probabilidade 1 - ( h + w - c ) / 2 ^ 24, ou ( w + 1, c + 1, h ) com probabilidade h / 2 ^ 24, ou ( w + 1, c+ 2, h + 1) com probabilidade ( w - c ) / 2 ^ 24. Então a entropia final representada graficamente com x palavras em colisão é a base logarítmica 1/256 da soma das probabilidades nos estados (340275, c , h ) com cx .
Anders Kaseorg 04/04

Não acredito que ninguém perguntou como você criou a função hash? Eu ficaria muito interessado em saber.
Anush em 19/03

22

Python, 5333 4991

Acredito que este seja o primeiro candidato a pontuar significativamente melhor do que um oráculo aleatório.

def H(s):n=int(s.encode('hex'),16);return n%(8**8-ord('+%:5O![/5;QwrXsIf]\'k#!__u5O}nQ~{;/~{CutM;ItulA{uOk_7"ud-o?y<Cn~-`bl_Yb'[n%70]))

11
Feitiçaria! def H(s):n=int(s.encode('hex'),16);return n%...economiza 5 bytes, no caso de você pode usá-los de alguma forma ...
Dennis

3
@ Dennis Eu poderia ter usado 5 bytes para tornar a string constante 5 bytes mais. Mas eu teria que começar de novo construindo a string constante do zero se eu alterar o comprimento. E não tenho certeza de que esses 5 bytes me proporcionem melhorias suficientes para que valha a pena começar a construir a string. Já gastei horas de tempo da CPU otimizando a constante de cadeia de caracteres.
kasperd

@ Dennis Acho que alguns bytes extras me dariam a liberdade de usar alguns caracteres na constante necessidade de escapar. Dessa forma, eu poderia usar alguns bytes extras sem ter que construir a string novamente.
kasperd

7
Se você quiser outro byte 2**24 == 8**8,.
Anders Kaseorg

20

Python 2, 140 bytes, 4266 palavras em colisão

Eu realmente não queria começar com a coisa de bytes não imprimíveis, dada a sua tweetabilidade pouco clara, mas bem, eu não a iniciei. :-P

00000000: efbb bf64 6566 2066 2873 293a 6e3d 696e  ...def f(s):n=in
00000010: 7428 732e 656e 636f 6465 2827 6865 7827  t(s.encode('hex'
00000020: 292c 3336 293b 7265 7475 726e 206e 2528  ),36);return n%(
00000030: 382a 2a38 2b31 2d32 3130 2a6f 7264 2827  8**8+1-210*ord('
00000040: 6f8e 474c 9f5a b49a 01ad c47f cf84 7b53  o.GL.Z........{S
00000050: 49ea c71b 29cb 929a a53b fc62 3afb e38e  I...)....;.b:...
00000060: e533 7360 982a 50a0 2a82 1f7d 768c 7877  .3s`.*P.*..}v.xw
00000070: d78a cb4f c5ef 9bdb 57b4 7745 3a07 8cb0  ...O....W.wE:...
00000080: 868f a927 5b6e 2536 375d 2929            ...'[n%67]))

Python 2, 140 bytes imprimíveis, 4662 4471 4362 palavras colidindo

def f(s):n=int(s.encode('hex'),16);return n%(8**8+3-60*ord('4BZp%(jTvy"WTf.[Lbjk6,-[LVbSvF[Vtw2e,NsR?:VxC0h5%m}F5,%d7Kt5@SxSYX-=$N>'[n%71]))

Inspirado na forma da solução de Kasperd, obviamente - mas com a adição importante de uma transformação afim no espaço do módulo e parâmetros totalmente diferentes.


+1 Eu não vou desistir sem lutar. Mas acho que tenho que parar de otimizar minha solução atual e encontrar outra abordagem, porque não vou vencê-lo se continuar usando minha abordagem atual para otimizar os parâmetros. Eu estarei de volta com uma edição para a minha solução depois de já ter batido o seu ....
kasperd

@kasperd: Incrível, vamos lá. :-P
Anders Kaseorg

11
@AndersKaseorg Como você encontra a string?
somente ASCII

@AndersKaseorg Consegui acelerar muito a minha pesquisa de parâmetros. E removi um obstáculo que estava fazendo com que minha pesquisa ficasse presa em soluções abaixo do ideal. Mas eu ainda não conseguia melhorar o resultado em 4885. Depois de refletir sobre o porquê de não conseguir avançar mais, percebi subitamente o que havia de errado com a minha solução e como ela pode ser corrigida. Agora, a transformação afiada em sua solução faz todo sentido para mim. Acho que a única maneira de me recuperar é usando uma transformação afim.
kasperd

11
@kasperd: Muito bom. Ao procurar uma string melhor n%(8**8-ord('…'[n%70]))sem outras alterações de parâmetro, eu consegui chegar ao 4995, então parece que seu novo otimizador alcançou o meu. Agora isso fica mais interessante!
Anders Kaseorg 02/04

16

CJam, 4125 3937 3791 3677

0000000: 7b 5f 39 62 31 31 30 25 5f 22 7d 13 25 77  {_9b110%_"}.%w
000000e: 77 5c 22 0c e1 f5 7b 83 45 85 c0 ed 08 10  w\"...{.E.....
000001c: d3 46 0c 5c 22 59 f8 da 7b f8 18 14 8e 4b  .F.\"Y..{....K
000002a: 3a c1 9e 97 f8 f2 5c 18 21 63 13 c8 d3 86  :.....\.!c....
0000038: 45 8e 64 33 61 50 96 c4 48 ea 54 3b b3 ab  E.d3aP..H.T;..
0000046: bc 90 bc 24 21 20 50 30 85 5f 7d 7d 59 2c  ...$! P0._}}Y,
0000054: 4a 67 88 c8 94 29 1a 1a 1a 0f 38 c5 8a 49  Jg...)....8..I
0000062: 9b 54 90 b3 bd 23 c6 ed 26 ad b6 79 89 6f  .T...#..&..y.o
0000070: bd 2f 44 6c f5 3f ae af 62 9b 22 3d 69 40  ./Dl.?..b."=i@
000007e: 62 31 35 32 35 31 39 25 31 31 30 2a 2b 7d  b152519%110*+}

Essa abordagem divide domínio e codomain em 110 conjuntos disjuntos e define uma função de hash ligeiramente diferente para cada par.

Pontuação / Verificação

$ echo $LANG
en_US
$ cat gen.cjam
"qN%{_9b110%_"
[125 19 37 119 119 34 12 225 245 123 131 69 133 192 237 8 16 211 70 12 34 89 248 218 123 248 24 20 142 75 58 193 158 151 248 242 92 24 33 99 19 200 211 134 69 142 100 51 97 80 150 196 72 234 84 59 179 171 188 144 188 36 33 32 80 48 133 95 125 125 89 44 74 103 136 200 148 41 26 26 26 15 56 197 138 73 155 84 144 179 189 35 198 237 38 173 182 121 137 111 189 47 68 108 245 63 174 175 98 155]
:c`"=i@b152519%110*+}%N*N"
$ cjam gen.cjam > test.cjam
$ cjam test.cjam < british-english-huge.txt | sort -n > temp
$ head -1 temp
8
$ tail -1 temp
16776899
$ all=$(wc -l < british-english-huge.txt)
$ unique=$(uniq -u < temp | wc -l)
$ echo $[all - unique]
3677

A seguinte porta para Python pode ser usada com o snippet de pontuação oficial:

h=lambda s,b:len(s)and ord(s[-1])+b*h(s[:-1],b)

def H(s):
 p=h(s,9)%110
 return h(s,ord(
  '}\x13%ww"\x0c\xe1\xf5{\x83E\x85\xc0\xed\x08\x10\xd3F\x0c"Y\xf8\xda{\xf8\x18\x14\x8eK:\xc1\x9e\x97\xf8\xf2\\\x18!c\x13\xc8\xd3\x86E\x8ed3aP\x96\xc4H\xeaT;\xb3\xab\xbc\x90\xbc$! P0\x85_}}Y,Jg\x88\xc8\x94)\x1a\x1a\x1a\x0f8\xc5\x8aI\x9bT\x90\xb3\xbd#\xc6\xed&\xad\xb6y\x89o\xbd/Dl\xf5?\xae\xafb\x9b'
  [p]))%152519*110+p

11
Portamos meu código para Python para facilitar a verificação.
Dennis

Será que hnaquele porto Python correspondem a um builtin CJam?
kasperd

Sim. É do CJam b(conversão básica).
Dennis

Seu processo de pontuação está no bash?
GamrCorps

@GamrCorps Sim, isso é Bash.
Dennis

11

Python, 6446 6372


Esta solução alcança uma contagem de colisões mais baixa do que todas as entradas anteriores e precisa apenas de 44 dos 140 bytes permitidos para o código:

H=lambda s:int(s.encode('hex'),16)%16727401

2
@ própria submissão mbomb007 de orlp faz %(2**24-1), então eu acho que pode ser bom para pedir esclarecimentos
SP3000

12
@ mbomb007 O desafio não diz isso. Ele diz que a função deve receber uma string ASCII como entrada e gerar um número inteiro nesse intervalo. Independentemente de qual entrada você dê a minha função, a saída estará nesse intervalo. A definição matemática da palavra função não requer que ela produza todas as saídas permitidas. Se era isso que você queria, o termo matemático que você usaria era função adjetiva. Mas a palavra adjetivo não foi usada nos requisitos.
kasperd

@ mbomb007: Não há requisitos para que as funções hash sejam adjetivas. Por exemplo, muitas funções de hash baseadas em endereço de memória podem produzir apenas múltiplos de uma pequena potência de 2 devido ao alinhamento da memória, incluindo o hash de objeto padrão nas versões mais antigas do Python. Muitas funções de hash têm até um domínio menor que o codomain, portanto, elas não poderiam ser subjetivas de qualquer maneira.
user2357112

3
@ mbomb007 - De fato, dado que existem muito mais valores numéricos do [0, 2**24-1]que palavras no idioma inglês, seria matematicamente impossível criar um hash onde todos os valores desse intervalo eram possíveis.
Darrel Hoffman

7

CJam, 6273

{49f^245b16777213%}

XOR cada caractere com 49 , reduza a sequência resultante via x, y ↦ 245x + y , e use o resíduo módulo 16.777.213 (o maior prime de 24 bits).

Pontuação

$ cat hash.cjam
qN% {49f^245b16777213%} %N*N
$ all=$(wc -l < british-english-huge.txt)
$ unique=$(cjam hash.cjam < british-english-huge.txt | sort | uniq -u | wc -l)
$ echo $[all - unique]
6273

Reimplementei o algoritmo em python a partir da sua descrição. Posso confirmar que sua pontuação está correta com o cálculo oficial da pontuação.
kasperd

7

JavaScript (ES6), 6389

A função hash (105 bytes):

s=>[...s.replace(/[A-Z]/g,a=>(b=a.toLowerCase())+b+b)].reduce((a,b)=>(a<<3)*28-a^b.charCodeAt(),0)<<8>>>8

A função de pontuação (NodeJS) (170 bytes):

h={},c=0,l=require('fs').readFileSync(process.argv[2],'utf8').split('\n').map(a=>h[b=F(a)]=-~h[b])
for(w of Object.getOwnPropertyNames(h)){c+=h[w]>1&&h[w]}
console.log(c)

Chame como node hash.js dictionary.txt, onde hash.jsestá o script, dictionary.txté o arquivo de texto do dicionário (sem a nova linha final) e Fé definido como a função de hash.

Obrigado Neil por remover 9 bytes da função hash!


Por que a atribuição a um? Além disso, em vez de ((...)>>>0)%(1<<24)você provavelmente pode usar (...)<<8>>>8.
Neil

@ Neil Porque alfabeto, e eu sou chato = P Além disso, bom bit a bit mathing! Isso salvou 7 bytes =)
Mwr247

Ainda bem que isso não é código de golfe, caso contrário, eu também precisaria usar a variável não utilizada i.
Neil

@Neil Crap> _ <Eu uso isso ao testar algumas idéias alternativas de hash e esqueci de remover o XD Sim, é bom que isso não seja um golfe, apesar de tudo, eu adoraria se pudesse comprimir as funções de hash e pontuação para os mesmos 140 bytes, para cada bit ajuda;)
Mwr247

11
@ Sp3000 Gah, entendo o que você quer dizer. O meu não está contando os que estão lá inicialmente quando a colisão é encontrada. Eu vou consertar isso.
precisa saber é o seguinte

5

Mathematica, 6473

O próximo passo ... em vez de somar os códigos de caracteres, nós os tratamos como os dígitos de um número base-151, antes de levá-los ao módulo 2 24 .

hash[word_] := Mod[FromDigits[ToCharacterCode @ word, 151], 2^24]

Aqui está um script curto para determinar o número de colisões:

Total[Last /@ DeleteCases[Tally[hash /@ words], {_, 1}]]

Acabei de tentar todas as bases sistematicamente a partir 1de então, e até agora a base 151 produziu o menor número de colisões. Vou tentar um pouco mais para diminuir ainda mais a pontuação, mas o teste é um pouco lento.


5

Javascript (ES5), 6765

Esse é o CRC24 reduzido para 140 bytes. Poderia jogar mais, mas queria obter minha resposta em :)

function(s){c=0xb704ce;i=0;while(s[i]){c^=(s.charCodeAt(i++)&255)<<16;for(j=0;j++<8;){c<<=1;if(c&0x1000000)c^=0x1864cfb}}return c&0xffffff}

Validador em node.js:

var col = new Array(16777215);
var n = 0;

var crc24_140 = 
function(s){c=0xb704ce;i=0;while(s[i]){c^=(s.charCodeAt(i++)&255)<<16;for(j=0;j++<8;){c<<=1;if(c&0x1000000)c^=0x1864cfb}}return c&0xffffff}

require('fs').readFileSync('./dict.txt','utf8').split('\n').map(function(s){ 
    var h = crc24_140(s);
    if (col[h]===1) {
        col[h]=2;
        n+=2;
    } else if (col[h]===2) {
        n++;
    } else {
        col[h]=1;
    }
});

console.log(n);

Bem-vindo à programação de quebra-cabeças e código de golfe!
Alex A.

... E obrigado pela calorosa recepção @AlexA.!
Binarymax 01/04

5

Python, 340053

Uma pontuação terrível de um algoritmo terrível, essa resposta existe mais para fornecer um pequeno script Python que exibe pontuação.

H=lambda s:sum(map(ord, s))%(2**24)

Pontuar:

hashes = []
with open("british-english-huge.txt") as f:
    for line in f:
        word = line.rstrip("\n")
        hashes.append(H(word))

from collections import Counter
print(sum(v for k, v in Counter(hashes).items() if v > 1))

11
Pode ser útil fazer com que o código de pontuação afirme que o valor de retorno da função hash é um número inteiro no intervalo permitido.
kasperd

4

Python, 6390 6376 6359

H=lambda s:reduce(lambda a,x:a*178+ord(x),s,0)%(2**24-48)

Pode ser considerada uma modificação trivial na resposta de Martin Büttner .


3
@ mbomb007 Isso não é verdade. Se a sua função sempre gera 4, ainda está no intervalo [0, 2**24-1]. A única coisa que não é permitida é a saída de qualquer número que não esteja dentro desse intervalo, por exemplo, -1ou 2**24.
orlp

3

Python, 9310


Sim, não é o melhor, mas pelo menos é alguma coisa. Como dizemos em criptografia, nunca escreva sua própria função de hash .

Isso tem exatamente 140 bytes de comprimento também.

F=lambda x,o=ord,m=map:int((int(''.join(m(lambda z:str(o(z)^o(x[-x.find(z)])^o(x[o(z)%len(x)])),x)))^(sum(m(int,m(o,x))))^o(x[-1]))%(2**24))

2

Matlab, 30828 8620 6848

Ele cria o hash atribuindo um número primo a cada combinação de caracteres / posição ascii e calculando seu produto para cada módulo de palavra, o maior primo menor que 2 ^ 24. Observe que, para o teste, movi a chamada para primos do lado de fora para o testador diretamente antes do loop while e a passei para a função hash, porque a acelerou em um fator de 1000, mas essa versão funciona e é independente. Pode falhar com palavras com mais de 40 caracteres.

function h = H(s)
p = primes(1e6);
h = 1;
for i=1:length(s)
    h = mod(h*p(double(s(i))*i),16777213);
end
end

Testador:

clc
clear variables
close all

file = fopen('british-english-huge.txt');
hashes = containers.Map('KeyType','uint64','ValueType','uint64');

words = 0;
p = primes(1e6);
while ~feof(file)
    words = words + 1;
    word = fgetl(file);
    hash = H(word,p);
    if hashes.isKey(hash)
        hashes(hash) = hashes(hash) + 1;
    else
        hashes(hash) = 1;
    end
end

collisions = 0;
for key=keys(hashes)

    if hashes(key{1})>1
        collisions = collisions + hashes(key{1});
    end
end

Se você deseja economizar espaço no seu programa, não precisa converter seu char para um doubleexplicitamente. Além disso, você poderia usar numelao invés de length. Não tenho certeza do que você faria com todos esses bytes extras!
Suever

1

Ruby, 9309 colisões, 107 bytes

def hash(s);require'prime';p=Prime.first(70);(0...s.size).reduce(0){|a,i|a+=p[i]**(s[i].ord)}%(2**24-1);end 

Não é um bom candidato, mas queria explorar uma ideia diferente de outras entradas.

Atribua os primeiros n primos às primeiras n posições da string, depois some todos os primos [i] ** (código ascii da string [i]) e mod 2 ** 24-1.


1

Java 8, 7054 6467

Isso é inspirado (mas não copiado) pela função interna java.lang.String.hashCode; portanto, sinta-se livre para desabilitar de acordo com a regra nº 2.

w -> { return w.chars().reduce(53, (acc, c) -> Math.abs(acc * 79 + c)) % 16777216; };

Pontuar:

import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

public class TweetableHash {
    public static void main(String[] args) throws Exception {
        List<String> words = Files.readAllLines(Paths.get("british-english-huge.txt"));

        Function<String, Integer> hashFunc = w -> { return w.chars().reduce(53, (acc, c) -> Math.abs(acc * 79 + c)) % 16777216; };

        Map<Integer, Integer> hashes = new HashMap<>();
        for (String word : words) {
            int hash = hashFunc.apply(word);
            if (hash < 0 || hash >= 16777216) {
                throw new Exception("hash too long for word: " + word + " hash: " + hash);
            }

            Integer numOccurences = hashes.get(hash);
            if (numOccurences == null) {
                numOccurences = 0;
            }
            numOccurences++;

            hashes.put(hash, numOccurences);
        }

        int numCollisions = hashes.values().stream().filter(i -> i > 1).reduce(Integer::sum).get();
        System.out.println("num collisions: " + numCollisions);
    }
}

@muddyfish, você pode conferir a versão atual? Eu acho que foram responsáveis ​​por colisões de três vias e ainda estou obtendo o mesmo resultado.
Bewusstsein

Isso não explica colisões de três vias. Se você substituir hashespor Map<Integer, Integer> hashes = new HashMap<>()e contar o número de palavras para cada hash, poderá contabilizá-las corretamente.
Peter Taylor

Sua pontuação ainda parece incorreta. Penso que, para calcular uma pontuação correta, é necessário gerar numHashes + numCollisions. (O que eu acho que vai colocá-lo fechar a minha estimativa 6832 para um oráculo aleatório.)
kasperd

Modificar as porções de classificação a esta: pastebin.com/nLeg4qut
TheNumberOne

sim, fixa a classificação e parece que um valor muito mais razoável agora, ty
Bewusstsein

1

Python, 6995 6862 6732

Apenas uma função RSA simples. Bastante coxo, mas supera algumas respostas.

M=0x5437b3a3b1
P=0x65204c34d
def H(s):
    n=0
    for i in range(len(s)):
        n+=pow(ord(s[i]),P,M)<<i
    return n%(8**8)

1

C ++: 7112 6694 6483 6479 6412 6339 colisões, 90 bytes

Eu implementei um algoritmo genético ingênuo para minha matriz de coeficientes. Vou atualizar esse código, pois ele encontra os melhores. :)

int h(const char*s){uint32_t t=0,p=0;while(*s)t="cJ~Z]q"[p++%6]*t+*s++;return t%16777213;}

Função de teste:

int main(void)
{
    std::map<int, int> shared;

    std::string s;
    while (std::cin >> s) {
        shared[h(s.c_str())]++;
    }

    int count = 0;
    for (auto c : shared) {
        if ((c.first & 0xFFFFFF) != c.first) { std::cerr << "invalid hash: " << c.first << std::endl; }
        if (c.second > 1) { count += c.second; }
    }

    std::cout << count << std::endl;
    return 0;
}

1

C #, 6251 6335

int H(String s){int h = 733;foreach (char c in s){h = (h * 533 + c);}return h & 0xFFFFFF;}

As constantes 533 e 733 889 e 155 dão a melhor pontuação de todas as que pesquisei até agora.


1

tcl

88 bytes, colisões 6448/3233

Vejo que as pessoas estão contando o número de palavras em colisão ou o número de palavras colocadas em baldes não vazios. Dou as duas contagens - a primeira é de acordo com a especificação do problema e a segunda é o que mais pôsteres estão relatando.

# 88 bytes, 6448 collisions, 3233 words in nonempty buckets

puts "[string length {proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}}] bytes"

proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}

# change 2551 above to:
#   7: 85 bytes, 25839 colliding words, 13876 words in nonempty buckets
#   97: 86 bytes, 6541 colliding words, 3283 words in nonempty buckets
#   829: 87 bytes, 6471 colliding words, 3251 words in nonempty buckets


# validation program

set f [open ~/Downloads/british-english-huge.txt r]
set words [split [read $f] \n]
close $f

set have {};                        # dictionary whose keys are hash codes seen
foreach w $words {
    if {$w eq {}} continue
    set h [H $w]
    dict incr have $h
}
set coll 0
dict for {- count} $have {
    if {$count > 1} {
        incr coll $count
    }
}
puts "found $coll collisions"

2
Onde você vê respostas usando o método incorreto para calcular a pontuação? Houve muito, mas todos foram corrigidos ou excluídos anos atrás. Vejo quatro respostas restantes com pontuações inferiores a 6000, porque essas quatro respostas foram realmente otimizadas para obter pontuações tão baixas.
kasperd

11
Até onde eu sei, seu código é proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}... certo?
Erik the Outgolfer

@EriktheOutgolfer: Sim, é
sergiol

11
Segundo @kasperd: Você pode apontar quais respostas não estão contabilizando as colisões de acordo com as especificações da pergunta? Você realmente tentou executá-los?
sergiol

1

Python 3, 89 bytes, 6534 colisões de hash

def H(x):
 v=846811
 for y in x:
  v=(972023*v+330032^ord(y))%2**24
 return v%2**24

Todos os grandes números mágicos que você vê aqui são constantes de falsificação.


1

Colisões JavaScript, 121 bytes, 3268 3250 3244 6354 (3185)

s=>{v=i=0;[...s].map(z=>{v=((((v*13)+(s.length-i)*7809064+i*380886)/2)^(z.charCodeAt(0)*266324))&16777215;i++});return v}

Os parâmetros (13, 7809064, 380886, 2, 266324) são por tentativa e erro.

Ainda otimizável, eu acho, e ainda há espaço para adicionar parâmetros extras, trabalhando para uma otimização adicional ...

Verificação

hashlist = [];
conflictlist = [];
for (x = 0; x < britain.length; x++) {
    hash = h(britain[x]);                      //britain is the 340725-entry array
    hashlist.push(hash);
}

conflict = 0; now_result = -1;
(sortedlist = sort(hashlist)).map(v => {
    if (v == now_result) {
        conflict++;
        conflictlist.push(v);
    }
    else
        now_result = v;
});

console.log(conflictlist);

var k = 0;
while (k < conflictlist.length) {
    if (k < conflictlist.length - 1 && conflictlist[k] == conflictlist[k+1])
        conflictlist.splice(k,1);
    else
        k++;
}

console.log(conflict + " " + (conflict+conflictlist.length));

3268> 3250 - Alterou o terceiro parâmetro de 380713 para 380560.

3250> 3244 - Alterou o terceiro parâmetro de 380560 para 380886.

3244> 6354 - Alterou o segundo parâmetro de 7809143 para 7809064 e descobriu que usei o método de cálculo errado; P


1

Aqui estão algumas construções semelhantes, que são bastante "semeadoras" e possibilitam a otimização incremental de parâmetros. Porra, é difícil ficar abaixo de 6k! Supondo que a pontuação tenha a média de 6829 e o padrão de 118, também calculei a probabilidade de obter pontuações tão baixas aleatoriamente.

Clojure A, 6019, Pr = 1: 299.5e9

 #(reduce(fn[r i](mod(+(* r 811)i)16777213))(map *(cycle(map int"~:XrBaXYOt3'tH-x^W?-5r:c+l*#*-dtR7WYxr(CZ,R6J7=~vk"))(map int %)))

Clojure B, 6021, Pr = 1: 266.0e9

#(reduce(fn[r i](mod(+(* r 263)i)16777213))(map *(cycle(map int"i@%(J|IXt3&R5K'XOoa+Qk})w<!w[|3MJyZ!=HGzowQlN"))(map int %)(rest(range))))

Clojure C, 6148, Pr = 1: 254.0e6

#(reduce(fn[r i](mod(+(* r 23)i)16777213))(map *(cycle(map int"ZtabAR%H|-KrykQn{]u9f:F}v#OI^so3$x54z2&gwX<S~"))(for[c %](bit-xor(int c)3))))

Clojure, 6431, Pr = 1: 2.69e3 (algo diferente)

#(mod(reduce bit-xor(map(fn[i[a b c]](bit-shift-left(* a b)(mod(+ i b c)19)))(range)(partition 3 1(map int(str"w"%"m")))))16776869)

Esta foi a minha função hash ad-hoc original, possui quatro parâmetros ajustáveis.


O truque para obter uma pontuação baixa é uma constante de sequência na qual cada caractere pode ser otimizado independentemente, sem arruinar a otimização que você fez para os outros caracteres.
kasperd

Sim, tentei otimizar apenas as seqüências mais curtas primeiro, pois o acréscimo de mais caracteres à sequência "entropia" não os afeta (uma vez que o multiplicador ré corrigido). Mas meu algoritmo de busca ainda é essencialmente força bruta, e não tenho certeza se a escolha inicial do multiplicador de ré importante ou não.
NikoNyrh

Talvez apenas multiplicar valores ASCII não traga entropia suficiente ao jogo. Muitos algoritmos com boa pontuação parecem ter a forma f(n) % (8^8 - g(n)).
NikoNyrh

Há uma resposta que explica como ficou tão baixo quanto 3677. Os que pontuam ainda mais que isso têm pouca explicação.
kasperd

0

Ruby, 6473 colisões, 129 bytes

h=->(w){@p=@p||(2..999).select{|i|(2..i**0.5).select{|j|i%j==0}==[]};c=w.chars.reduce(1){|a,s|(a*@p[s.ord%92]+179)%((1<<24)-3)}}

A variável @p é preenchida com todos os números primos abaixo de 999.

Isso converte valores ASCII em números primos e leva seu módulo de produto um primo grande. O fator de correção de 179 lida com o fato de que o algoritmo original foi usado para encontrar anagramas, onde todas as palavras que são rearranjos das mesmas letras obtêm o mesmo hash. Ao adicionar o fator no loop, faz com que os anagramas tenham códigos distintos.

Eu poderia remover o ** 0,5 (teste do sqrt para prime) às custas do pior desempenho para encurtar o código. Eu poderia até executar o localizador de números primos no loop para remover mais nove caracteres, deixando 115 bytes.

Para testar, o seguinte tenta encontrar o melhor valor para o fator de correção no intervalo de 1 a 300. Supõe-se que o arquivo de palavras esteja no diretório / tmp:

h=->(w,y){
  @p=@p||(2..999).
    select{|i|(2..i**0.5). 
    select{|j|i%j==0}==[]};
  c=w.chars.reduce(1){|a,s|(a*@p[s.ord%92]+y)%((1<<24)-3)}
}

american_dictionary = "/usr/share/dict/words"
british_dictionary = "/tmp/british-english-huge.txt"
words = (IO.readlines british_dictionary).map{|word| word.chomp}.uniq
wordcount = words.size

fewest_collisions = 9999
(1..300).each do |y|
  whash = Hash.new(0)
  words.each do |w|
    code=h.call(w,y)
    whash[code] += 1
  end
  hashcount = whash.size
  collisions = whash.values.select{|count| count > 1}.inject(:+)
  if (collisions < fewest_collisions)
    puts "y = #{y}. #{collisions} Collisions. #{wordcount} Unique words. #{hashcount} Unique hash values"
    fewest_collisions = collisions
  end
end

11
A pontuação parece suspeita. Tem certeza de que está contando todas as palavras que colidem? Várias respostas anteriores contaram erroneamente apenas uma palavra para cada valor de hash em colisão.
kasperd

Tu podes estar certo. Devo considerar como contei e ver se é o mesmo que sua definição. Estou contando quantas palavras existem e subtraindo quantos códigos hash únicos foram gerados. Se as palavras A e B obtiverem o mesmo código hash, isso é uma colisão ou duas? Eu conto isso como um.
Paul Chernoch

11
Não defini a função de pontuação. Acabei de copiá-lo da resposta de exemplo postada pelo mesmo usuário que postou o desafio. A maioria das respostas possui pontuações que variam entre 6273 e 6848. Houve várias respostas cometendo o mesmo erro no cálculo da pontuação, levando a uma pontuação aproximadamente a metade do que deveria ter sido. (Exatamente a metade da pontuação correta se não há casos de três palavras em colisão.)
kasperd

11
Sim, cometi o mesmo erro. Vou alterar minha resposta mais tarde. Tenho que pegar um ônibus.
Paul Chernoch 01/04

Corrigida a pontuação.
Paul Chernoch

0

tcl

# 91 bytes, 6508 colisões

91 bytes, 6502 colisões

proc H s {lmap c [split $s ""] {incr h [expr [scan $c %c]*875**[incr i]]};expr $h&0xFFFFFF}

O computador ainda está realizando uma pesquisa para avaliar se existe um valor que causa menos colisões do que a base 147 875, que ainda é a gravadora.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.