Sfida della funzione hash Tweetable


73

In questa scriverai una funzione hash in 140 byte 1 o meno del codice sorgente. La funzione hash deve accettare una stringa ASCII come input e restituire un intero senza segno a 24 bit ([0, 2 24 -1]) come output.

La tua funzione hash verrà valutata per ogni parola in questo grande dizionario inglese britannico 2 . Il tuo punteggio è la quantità di parole che condividono un valore di hash con un'altra parola (una collisione).

Vince il punteggio più basso, pareggi interrotti dal primo poster.

Caso di prova

Prima di inviare, prova lo script di punteggio sul seguente input:

duplicate
duplicate
duplicate
duplicate

Se dà un punteggio diverso da 4, è difettoso.


Regole per chiarire:

  1. La funzione hash deve essere eseguita su una singola stringa, non su un intero array. Inoltre, la funzione hash non può eseguire alcun altro I / O diverso dalla stringa di input e dall'intero di output.
  2. Le funzioni hash integrate o funzionalità simili (ad es. Crittografia per cifrare i byte) non sono consentite.
  3. La tua funzione hash deve essere deterministica.
  4. Contrariamente alla maggior parte degli altri concorsi, è consentita l'ottimizzazione specifica per l'input del punteggio.

1 Sono consapevole che Twitter limita i caratteri anziché i byte, ma per semplicità useremo i byte come limite per questa sfida.
2 Modificato dall'enorme wbritish di Debian , rimuovendo qualsiasi parola non ASCII.


11
Llanfairpwllgwyngyllgogerychwyrndrobwllllantysiliogogogoch's? Cosa ...
Luis Mendo,

8
@DonMuesli en.wikipedia.org/wiki/Llanfairpwllgwyngyll (fatto divertente: quella parola è anche nel dizionario di compressione integrato di Jelly)
Martin Ender

8
Penso che dovresti vietare i dizionari integrati.
Dennis

4
Per riferimento: Prendendo il 24 MSB di SHA-512 si otterrebbe un punteggio di 6816.
Dennis

10
Alcuni calcoli back-of-the-envelope: con le D=340275parole e gli R=2^24output di hash, un hash casuale ha una D^2/(2*R) = 3450coppia in collisione prevista , alcuni dei quali si sovrappongono. Sono previste D^3/(6*R^2) = 23tre triple in collisione e un numero trascurabile di collisioni maggiori, il che significa che queste triple sono probabilmente disgiunte. Questo dà le 6829parole attese che condividono un valore di hash, ~ 70in triple e il resto in coppie. La deviazione standard è stimata in 118, quindi ottenere <6200con un hash casuale è all'incirca un evento a 5 sigma.
xnor

Risposte:


11

Va bene, vado a imparare una lingua da golf.

CJam, 140 byte, 3314 parole in collisione

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#)%}

Definisce un blocco (funzione anonima). Per provare, è possibile aggiungere qN%%N*Nper prendere l'elenco di parole separato da una nuova riga su stdin e scrivere un elenco di hash separato da una nuova riga su stdout. Codice 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)

Pyth, 140 byte, 3535 3396 parole in collisione

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

Definisce una funzione denominata y. Per testare, è possibile aggiungere jmyd.zper prendere l'elenco di parole separato da una nuova riga su stdin e scrivere un elenco di hash separato da una nuova riga su stdout. Codice 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]))

Limiti teorici

Quanto possiamo aspettarci di fare? Ecco un diagramma di x, il numero di parole in collisione, rispetto a y, l'entropia in byte richiesta per ottenere al massimo x parole in collisione. Ad esempio, il punto (2835, 140) ci dice che una funzione casuale ottiene al massimo 2835 parole in collisione con probabilità 1/256 ** 140, quindi è estremamente improbabile che saremo mai in grado di fare molto meglio di così con 140 byte di codice.

grafico


Bella analisi dei limiti teorici. Per superare quel limite teorico si dovrebbe probabilmente usare un linguaggio con funzioni integrate ottimizzate per il dizionario nella domanda (che sarebbe barare). Se la lingua ha un hash crittografico incorporato, il limite può essere trasformato in un metodo più o meno costruttivo per trovare una soluzione ottimale. Considera questo: $ h (w || c)% 2 ^ {24} $ dove $ c $ è una costante di stringa di byte. In un modello di oracolo casuale che potrebbe essere mostrato per avvicinarsi all'ottimale con alta probabilità. Certamente la forza bruta forzare $ c $ non sarebbe fattibile.
Kasperd,

Come hai calcolato la formula per il grafico? Molto interessante!
NikoNyrh,

@NikoNyrh Programmazione dinamica. Let ( w , c , h ) rappresenta uno stato con w parole, di cui c si scontrano con h hash distinti e i restanti w - c hanno tutti hash distinti. Se aggiungiamo una parola casuale, lo stato diventa ( w + 1, c , h ) con probabilità 1 - ( h + w - c ) / 2 ^ 24, oppure ( w + 1, c + 1, h ) con probabilità h / 2 ^ 24 o ( w + 1, c+ 2, h + 1) con probabilità ( w - c ) / 2 ^ 24. Quindi l'entropia finale rappresentata con x parole in collisione è la base log 1/256 della somma delle probabilità negli stati (340275, c , h ) con cx .
Anders Kaseorg,

Non riesco a credere che nessuno ti abbia chiesto come ti è venuta la funzione hash? Sarei molto interessato a saperlo.
Anush,

22

Python, 5333 4991

Credo che questo sia il primo contendente a segnare punteggi significativamente migliori di un oracolo casuale.

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]))

1
Stregoneria! def H(s):n=int(s.encode('hex'),16);return n%...salva 5 byte, nel caso in cui sia possibile usarli in qualche modo ...
Dennis

3
@Dennis avrei potuto usare 5 byte per rendere la stringa costante di 5 byte più a lungo. Ma dovrei ricominciare da capo a costruire la costante di stringa da zero se cambio la lunghezza. E non sono sicuro che quei 5 byte mi daranno abbastanza miglioramenti che vale la pena ricominciare a costruire la stringa. Ho trascorso ore di tempo CPU ottimizzando già la costante di stringa.
Kasperd,

@Dennis Immagino che qualche byte in più mi darebbe la libertà di usare alcuni personaggi nella costante necessità di fuga. In questo modo potrei potenzialmente usare qualche byte in più senza dover ricostruire la stringa da capo.
Kasperd,

7
Se si desidera un altro byte, 2**24 == 8**8.
Anders Kaseorg,

20

Python 2, 140 byte, 4266 parole in collisione

Non volevo davvero iniziare con la cosa dei byte non stampabili data la loro tweetability poco chiara, ma bene, non l'ho avviata. :-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 byte stampabili, 4662 4471 4362 parole in collisione

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]))

Ispirato dalla forma della soluzione di Kasperd, ovviamente - ma con l'importante aggiunta di una trasformazione affine sullo spazio del modulo e parametri completamente diversi.


+1 Non mi arrendo senza combattere. Ma penso di dover smettere di ottimizzare la mia attuale soluzione e trovare un altro approccio, perché non ti batterò se continuo a usare il mio attuale approccio per ottimizzare i parametri. Tornerò con una modifica alla mia soluzione dopo aver battuto il tuo ....
Kasperd

@kasperd: Fantastico, portalo avanti. :-P
Anders Kaseorg,

1
@AndersKaseorg Come trovi la stringa?
ASCII,

@AndersKaseorg Sono riuscito a velocizzare molto la mia ricerca dei parametri. E ho rimosso un ostacolo che stava bloccando la mia ricerca su soluzioni non ottimali. Ma non riuscivo ancora a farlo meglio di 4885. Dopo aver riflettuto sul perché non potevo farcela oltre, improvvisamente mi sono reso conto di cosa non va nella mia soluzione e come può essere risolto. Ora la trasformazione affine nella tua soluzione ha perfettamente senso per me. Penso che l'unico modo in cui riesco a raggiungerlo sia usando una trasformazione affine me stesso.
Kasperd,

1
@kasperd: Very nice. Quando n%(8**8-ord('…'[n%70]))cercavo una stringa migliore senza altre modifiche ai parametri, ero riuscito a raggiungere solo 4995, quindi sembra che il tuo nuovo ottimizzatore abbia raggiunto il mio. Ora questo diventa più interessante!
Anders Kaseorg,

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*+}

Questo approccio divide dominio e codomain in 110 insiemi disgiunti e definisce una funzione hash leggermente diversa per ogni coppia.

Punteggio / verifica

$ 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

La seguente porta su Python può essere utilizzata con lo snippet di punteggio ufficiale:

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

1
Ho portato il mio codice su Python per una facile verifica.
Dennis,

Fa hin quel porto Python corrispondere ad un incorporato CJam?
Kasperd,

Sì. È di CJam b(conversione di base).
Dennis,

Il tuo processo di punteggio è in bash?
GamrCorps,

@GamrCorps Sì, quello è Bash.
Dennis,

11

Python, 6446 6372


Questa soluzione ottiene un conteggio delle collisioni inferiore rispetto a tutte le voci precedenti e richiede solo 44 dei 140 byte consentiti per il codice:

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

2
@ mbomb007 sostiene la stessa orlp %(2**24-1), quindi penso che potrebbe essere utile chiedere un chiarimento
Sp3000

12
@ mbomb007 La sfida non dice nulla del genere. Dice che la funzione deve prendere una stringa ASCII come input e produrre un numero intero in quell'intervallo. Indipendentemente da quale input date la mia funzione, l'output sarà compreso in tale intervallo. La definizione matematica della parola funzione non richiede che produca tutti gli output consentiti. Se è quello che volevi, il termine matematico che avresti usato era la funzione suriettiva. Ma la parola suriettiva non è stata utilizzata nei requisiti.
Kasperd,

@ mbomb007: non è necessario che le funzioni hash siano suriettive. Ad esempio, molte funzioni hash basate su indirizzi di memoria possono produrre solo multipli di una piccola potenza di 2 a causa dell'allineamento della memoria, incluso l'hash dell'oggetto predefinito nelle versioni precedenti di Python. Molte funzioni hash hanno anche un dominio più piccolo del codomain, quindi non possono essere comunque suriettive.
user2357112

3
@ mbomb007 - In effetti, dato che ci sono molti più valori numerici [0, 2**24-1]rispetto alle parole in lingua inglese, sarebbe matematicamente impossibile creare un hash dove ogni singolo valore in quell'intervallo fosse possibile.
Darrel Hoffman,

7

CJam, 6273

{49f^245b16777213%}

XOR ogni carattere con 49 , riduci la stringa risultante tramite x, y ↦ 245x + y e prendi il modulo residuo 16.777.213 (il primo più grande a 24 bit).

punteggio

$ 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

Ho reimplementato l'algoritmo in Python dalla tua descrizione. Posso confermare che il tuo punteggio viene verificato con il calcolo del punteggio ufficiale.
Kasperd,

7

JavaScript (ES6), 6389

La funzione hash (105 byte):

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

La funzione di punteggio (NodeJS) (170 byte):

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)

Chiama come node hash.js dictionary.txt, dove si hash.jstrova lo script, dictionary.txtè il file di testo del dizionario (senza la nuova riga finale) ed Fè definito come funzione di hashing.

Grazie Neil per aver rasato 9 byte dalla funzione di hashing!


Perché l'incarico a? Inoltre, al posto ((...)>>>0)%(1<<24)tuo probabilmente puoi usare (...)<<8>>>8.
Neil,

@Neil Perché alfabeto, e sono noioso = P Inoltre, bella matematica bit a bit! Che ha salvato 7 byte =)
Mwr247

Meno imale che questo non è il golf del codice, altrimenti dovrei darti anche per la variabile non utilizzata .
Neil

@Neil Crap> _ <Lo uso durante il test di alcune idee di hashing alternative e ho dimenticato di rimuovere XD Sì, per fortuna questo non è un golf, anche se lo stesso, mi piacerebbe se potessi comprimere l'hash e le funzioni di punteggio negli stessi 140 byte, quindi ogni bit aiuta;)
Mwr247

1
@ Sp3000 Gah, capisco cosa intendi. Il mio non sta contando quelli che ci sono inizialmente quando viene rilevata la collisione. Lo aggiusterò.
Mwr247,

5

Mathematica, 6473

Il prossimo passo ... invece di sommare i codici dei caratteri li trattiamo come le cifre di un numero base-151, prima di prenderli modulo 2 24 .

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

Ecco un breve script per determinare il numero di collisioni:

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

Ho appena provato sistematicamente tutte le basi da quel momento in 1poi, e finora la base 151 ha prodotto il minor numero di collisioni. Proverò ancora un po 'a ridurre ulteriormente il punteggio, ma i test sono un po' lenti.


5

Javascript (ES5), 6765

Questo è CRC24 ridotto a 140 byte. Potrei golf di più, ma volevo ottenere la mia risposta :)

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}

Convalida in 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);

Benvenuto in Programmazione di puzzle e codice golf!
Alex A.

... E grazie per il caloroso benvenuto @AlexA.!
binarymax,

5

Python, 340053

Un punteggio terribile da un terribile algoritmo, questa risposta esiste più per dare un piccolo script Python che mostra il punteggio.

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

Fare punto, segnare:

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))

1
Potrebbe essere utile che il codice di punteggio asserisca che il valore restituito dalla funzione hash è un numero intero nell'intervallo consentito.
Kasperd,

4

Python, 6390 6376 6359

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

Può essere considerata una banale modifica alla risposta di Martin Büttner .


3
@ mbomb007 Non è vero. Se la tua funzione emette sempre 4 è ancora in uscita nell'intervallo [0, 2**24-1]. L'unica cosa che non è permesso è l'output di ogni numero non all'interno di tale intervallo, ad esempio -1o 2**24.
orlp

3

Python, 9310


Sì, non il migliore, ma almeno è qualcosa. Come diciamo in criptovaluta, non scrivere mai la tua funzione hash .

Anche questa è esattamente lunga 140 byte.

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

Crea l'hash assegnando un numero primo a ciascuna combinazione carattere / posizione ASCII e calcolando il loro prodotto per ogni parola modulo il primo più grande minore di 2 ^ 24. Si noti che per il test ho spostato la chiamata ai numeri primi all'esterno nel tester direttamente prima del ciclo while e l'ho passata alla funzione hash, perché ha accelerato di circa un fattore 1000, ma questa versione funziona ed è indipendente. Potrebbe bloccarsi con parole più lunghe di circa 40 caratteri.

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

Tester:

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 vuoi risparmiare spazio nel tuo programma, non devi convertire il tuo carattere in modo doubleesplicito. Inoltre potresti usare numelpiuttosto che length. Non sono sicuro di cosa faresti con tutti quei byte extra!
Suever,

1

Rubino, 9309 collisioni, 107 byte

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 

Non un buon concorrente, ma volevo esplorare un'idea diversa dalle altre voci.

Assegna i primi n numeri primi alle prime n posizioni della stringa, quindi somma tutti i primi [i] ** (codice ascii della stringa [i]), quindi mod 2 ** 24-1.


1

Java 8, 7054 6467

Questo è ispirato (ma non copiato da) dalla funzione java.lang.String.hashCode integrata, quindi sentiti libero di non consentire secondo la regola # 2.

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

Fare punto, segnare:

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 potresti dare un'occhiata alla versione attuale? penso che abbia tenuto conto delle collisioni a tre vie e sto ancora ottenendo lo stesso risultato.
Bewusstsein,

Questo non tiene conto delle collisioni a tre vie. Se lo sostituisci hashescon Map<Integer, Integer> hashes = new HashMap<>()e poi conti il ​​numero di parole per ogni hash, puoi spiegarle correttamente.
Peter Taylor,

Il tuo punteggio sembra ancora errato. Penso che per calcolare un punteggio corretto, devi produrre numHash + numCollisions. (Il che immagino ti
avvicinerà

Modificare le porzioni di classificazione in questo modo: pastebin.com/nLeg4qut
TheNumberOne

sì, riparato il voto e sembra un valore molto più ragionevole ora, ty
Bewusstsein

1

Python, 6995 6862 6732

Solo una semplice funzione RSA. Abbastanza zoppo, ma batte alcune risposte.

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 collisioni, 90 byte

Ho implementato un algoritmo genetico ingenuo per il mio array di coefficienti. Aggiornerò questo codice quando ne trova di migliori. :)

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

Funzione di test:

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

Le costanti 533 e 733 889 e 155 danno il miglior punteggio tra tutti quelli che ho cercato finora.


1

TCL

88 byte, 6448/3233 collisioni

Vedo che le persone hanno contato il numero di parole in conflitto, oppure il numero di parole inserite in secchi non vuoti. Do entrambi i conteggi: il primo è in base alle specifiche del problema, e il secondo è ciò che più poster hanno segnalato.

# 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
Dove vedi le risposte usando il metodo errato per calcolare il punteggio? Ci sono stati molti, ma sono stati tutti corretti o cancellati anni fa. Vedo che rimangono quattro risposte con punteggi inferiori a 6000 perché quelle quattro risposte sono state effettivamente ottimizzate per avere punteggi così bassi.
Kasperd,

1
Per quanto ne so, il tuo codice è proc H w {incr h;lmap c [split $w {}] {set h [expr (2551*$h+[scan $c %c])%2**24]};set h}... giusto?
Erik the Outgolfer,

@EriktheOutgolfer: Sì, lo è
sergiol

1
I secondo @kasperd: Puoi indicare quali risposte non tengono conto delle collisioni secondo le specifiche della domanda? Hai davvero provato a farli funzionare?
sergiol

1

Python 3, 89 byte, 6534 collisioni di hash

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

Tutti i grandi numeri magici che vedi qui sono costanti fondenti.


1

JavaScript, 121 byte, 3268 3250 3244 6354 (3185) collisioni

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}

I parametri (13, 7809064, 380886, 2, 266324) sono di prova ed errore.

Penso ancora ottimizzabile, e c'è ancora spazio per aggiungere parametri extra, lavorando per un'ulteriore ottimizzazione ...

Verifica

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 - Modificato il 3o parametro da 380713 a 380560.

3250> 3244 - Modificato il 3o parametro da 380560 a 380886.

3244> 6354 - Modificato il secondo parametro da 7809143 a 7809064, e ho scoperto che ho usato il metodo di calcolo errato; P


1

Qui ci sono alcuni costrutti simili, che sono piuttosto "seminabili" e rendono possibile l'ottimizzazione dei parametri incrementali. Accidenti è difficile scendere a meno di 6k! Supponendo che il punteggio abbia una media di 6829 e una media di 118 ho anche calcolato la probabilità di ottenere casualmente punteggi così bassi.

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 (qualcosa di diverso)

#(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)

Questa era la mia funzione hash ad hoc originale, ha quattro parametri sintonizzabili.


Il trucco per ottenere un punteggio basso è una costante di stringa in cui ogni personaggio può essere ottimizzato indipendentemente senza rovinare l'ottimizzazione che hai fatto per gli altri personaggi.
Kasperd,

Sì, ho provato prima a ottimizzare solo per stringhe più brevi, poiché l'aggiunta di più caratteri alla stringa "entropia" non influisce su quelli (una volta che il moltiplicatore di rè stato risolto). Ma il mio algoritmo di ricerca è essenzialmente una forza bruta e non sono sicuro che la scelta iniziale del moltiplicatore di rsia importante o meno.
NikoNyrh,

Forse semplicemente moltiplicare i valori ASCII non porta abbastanza entropia al gioco. Molti algoritmi con un buon punteggio sembrano avere la forma f(n) % (8^8 - g(n)).
NikoNyrh,

C'è una risposta che spiega come è arrivato a un minimo di 3677. Quelli che segnano ancora più in basso di questo hanno poche spiegazioni.
Kasperd,

0

Rubino, 6473 collisioni, 129 byte

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

La variabile @p è piena di tutti i numeri primi inferiori a 999.

Questo converte i valori ASCII in numeri primi e prende il loro modulo modulo un grande numero primo. Il fattore di sfumatura di 179 riguarda il fatto che l'algoritmo originale era usato per trovare anagrammi, in cui tutte le parole che sono riarrangiamenti delle stesse lettere ottengono lo stesso hash. Aggiungendo il fattore nel ciclo, rende gli anagrammi hanno codici distinti.

Potrei rimuovere il ** 0,5 (sqrt test per prime) a scapito delle prestazioni più scarse per abbreviare il codice. Potrei anche far eseguire il cercatore di numeri primi nel ciclo per rimuovere altri nove caratteri, lasciando 115 byte.

Per verificare, quanto segue cerca di trovare il valore migliore per il fattore di sfumatura nell'intervallo da 1 a 300. Si assume che il file di parole sia nella directory / 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

1
Il punteggio sembra sospetto. Sei sicuro di contare tutte le parole in collisione? Diverse risposte precedenti hanno erroneamente contato solo una parola per ogni valore di hash in collisione.
Kasperd,

Potresti avere ragione. Devo considerare come ho contato e vedere se è uguale alla tua definizione. Sto contando quante parole ci sono e sottraendo quanti hashcode unici sono stati generati. Se le parole A e B ottengono lo stesso hashcode, si tratta di una o due collisioni? Lo conto come uno.
Paul Chernoch,

1
Non ho definito la funzione di punteggio. L'ho appena copiato dalla risposta di esempio pubblicata dallo stesso utente che ha pubblicato la sfida. La maggior parte delle risposte ha punteggi che vanno da 6273 a 6848. Vi sono state più risposte ciascuna che ha commesso lo stesso errore nel calcolo del punteggio portando a calcolare un punteggio circa la metà di quello che avrebbe dovuto essere. (Esattamente la metà del punteggio corretto se non ci sono casi di tre parole in collisione.)
Kasperd

1
Sì, ho fatto lo stesso errore. Modificherò la mia risposta in seguito. Devo prendere un autobus.
Paul Chernoch,

Risolto il punteggio.
Paul Chernoch,

0

TCL

# 91 byte, 6508 collisioni

91 byte, 6502 collisioni

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

Il computer sta ancora eseguendo una ricerca per valutare se esiste un valore che causa meno collisioni rispetto alla base 147 875, che è ancora il registratore.

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.