Pracuję na tablicy mieszającej w języku C i testuję funkcję skrótu dla ciągu znaków.
Pierwszą funkcją, którą wypróbowałem, jest dodanie kodu ascii i użycie modulo (% 100), ale mam słabe wyniki przy pierwszym teście danych: 40 kolizji na 130 słów.
Ostateczne dane wejściowe będą zawierały 8 000 słów (jest to słownik przechowywany w pliku). Tablica skrótów jest zadeklarowana jako int table [10000] i zawiera pozycję słowa w pliku txt.
Pierwsze pytanie brzmi: jaki jest najlepszy algorytm dla ciągu haszującego? i jak określić rozmiar tablicy skrótów?
z góry dziękuję !
:-)
Odpowiedzi:
Miałem dobre wyniki z
djb2
Danem Bernsteinem.źródło
size_t
lub inną taką wartość bez znaku (taką jak długość bez znaku w tym kodzie). Rozmówca jest odpowiedzialny za podejmowanie modulo rezultatu, aby dopasować go do tablicy mieszającej. Obiekt wywołujący kontroluje przedział tabeli, do którego jest mieszany; nie funkcja. Po prostu zwraca pewną liczbę bez znaku.Po pierwsze, generalnie nie chcesz używać kryptograficznego skrótu do tabeli skrótów. Algorytm, który jest bardzo szybki jak na standardy kryptograficzne, jest nadal potwornie powolny jak na standardy tablic mieszania.
Po drugie, chcesz mieć pewność, że każdy bit danych wejściowych może / wpłynie na wynik. Prostym sposobem na to jest obrócenie bieżącego wyniku o pewną liczbę bitów, a następnie XOR aktualnego kodu skrótu z bieżącym bajtem. Powtarzaj, aż dojdziesz do końca struny. Zauważ, że generalnie nie chcesz, aby rotacja była parzystą wielokrotnością rozmiaru bajtu.
Na przykład, zakładając typowy przypadek 8-bitowych bajtów, możesz obrócić o 5 bitów:
Edycja: Należy również pamiętać, że 10000 gniazd rzadko jest dobrym wyborem dla rozmiaru tabeli mieszania. Zwykle potrzebujesz jednej z dwóch rzeczy: albo potrzebujesz liczby pierwszej jako rozmiaru (wymaganej do zapewnienia poprawności przy niektórych typach rozdzielczości skrótu), albo potęgi 2 (więc zmniejszenie wartości do prawidłowego zakresu można zrobić prostym maska bitowa).
źródło
Wikipedia pokazuje ładną funkcję skrótu ciągów o nazwie Jenkins One At A Time Hash. Cytuje również ulepszone wersje tego skrótu.
źródło
Istnieje wiele istniejących implementacji haszujących dla C, od standardowej biblioteki C hcreate / hdestroy / hsearch, po te w APR i glib , które także zapewniają wbudowane funkcje haszujące. Zdecydowanie polecam używanie ich zamiast wymyślania własnej tablicy mieszającej lub funkcji skrótu; zostały mocno zoptymalizowane pod kątem typowych zastosowań.
Jeśli jednak zbiór danych jest statyczny, najlepszym rozwiązaniem jest prawdopodobnie użycie idealnego skrótu . gperf wygeneruje dla Ciebie idealny hash dla danego zbioru danych.
źródło
djb2 ma 317 kolizji dla tego 466k angielskiego słownika, podczas gdy MurmurHash nie ma żadnego dla 64-bitowych haszów i 21 dla 32-bitowych haszów (około 25 należy się spodziewać dla 466k losowych 32-bitowych haszów). Zalecam używanie MurmurHash, jeśli jest dostępne, jest bardzo szybkie, ponieważ zajmuje kilka bajtów na raz. Ale jeśli potrzebujesz prostej i krótkiej funkcji skrótu do skopiowania i wklejenia do swojego projektu, polecam użycie szmerów w wersji jednobajtowej:
Optymalny rozmiar tabeli skrótów jest - w skrócie - możliwie największy, a jednocześnie mieści się w pamięci. Ponieważ zwykle nie wiemy lub nie chcemy sprawdzić, ile mamy dostępnej pamięci, a może się to nawet zmienić, optymalny rozmiar tablicy mieszania to około dwukrotność oczekiwanej liczby elementów, które mają być przechowywane w tabeli. Alokacja znacznie większej ilości sprawi, że tabela haszująca będzie szybsza, ale przy szybko malejących zwrotach, co spowoduje, że tabela będzie mniejsza niż ta, spowoduje to wykładnicze spowolnienie. Dzieje się tak, ponieważ istnieje nieliniowy kompromis między złożonością przestrzenną i czasową dla tabel skrótów, przy optymalnym współczynniku obciążenia 2-sqrt (2) = 0,58 ... najwyraźniej.
źródło
Po pierwsze, czy 40 kolizji dla 130 słów zostało zakodowanych do 0..99? Nie możesz oczekiwać idealnego haszowania, jeśli nie podejmujesz działań, aby to się stało. Zwykła funkcja skrótu przez większość czasu nie będzie miała mniej kolizji niż generator losowy.
Funkcja skrótu o dobrej reputacji to MurmurHash3 .
Wreszcie, jeśli chodzi o rozmiar tabeli skrótów, to naprawdę zależy, jaki rodzaj tablicy masz na myśli, szczególnie, czy kosze są rozszerzalne, czy jednopunktowe. Jeśli łyżki są rozszerzalne, znowu jest wybór: wybierasz średnią długość łyżki dla posiadanej pamięci / ograniczeń prędkości.
źródło
n - m * (1 - ((m-1)/m)^n) = 57.075...
. 40 kolizji jest lepszych niż można by się spodziewać przypadkowo (46 do 70 przy p-score 0,999). Omawiana funkcja skrótu jest bardziej jednolita, niż gdyby była losowa lub jesteśmy świadkami bardzo rzadkiego zdarzenia.Chociaż
djb2
, jak pokazano na stackoverflow przez cnicutar , jest prawie na pewno lepszy, myślę, że warto również pokazać hashe K&R :1) Najwyraźniej okropny algorytm haszujący, przedstawiony w pierwszej edycji K&R ( źródło )
2) Prawdopodobnie całkiem przyzwoity algorytm haszujący przedstawiony w wersji 2 K&R (zweryfikowany przeze mnie na str. 144 książki); Uwaga: pamiętaj o usunięciu
% HASHSIZE
z instrukcji return, jeśli planujesz wykonać pomiar modułu do długości swojej tablicy poza algorytmem wyznaczania wartości skrótu. Polecam również wykonanie zwrotu i typu „hashval”unsigned long
zamiast simpleunsigned
(int).Zauważ, że z tych dwóch algorytmów jasno wynika, że jednym z powodów, dla których hash 1.edycji jest tak okropny, jest to, że NIE bierze pod uwagę kolejności znaków w łańcuchach , więc
hash("ab")
zwróci tę samą wartość cohash("ba")
. Jednak nie jest tak w przypadku skrótu drugiej edycji, który zwróciłby (znacznie lepiej!) Dwie różne wartości dla tych ciągów.Funkcje mieszające GCC C ++ 11 używane dla
unordered_map
(szablonu tablicy skrótów) iunordered_set
(szablonu zestawu skrótów) wyglądają następująco.Kod:
źródło
Wypróbowałem te funkcje skrótu i otrzymałem następujący wynik. Mam około 960 ^ 3 wpisów, każdy o długości 64 bajtów, 64 znaki w innej kolejności, wartość skrótu 32bit. Kody stąd .
Dziwną rzeczą jest to, że prawie wszystkie funkcje skrótu mają 6% współczynnik kolizji moich danych.
źródło
Jedną z rzeczy, których użyłem z dobrymi wynikami, jest następująca (nie wiem, czy została już wspomniana, ponieważ nie pamiętam jej nazwy).
Obliczasz wstępnie tabelę T z losową liczbą dla każdego znaku w alfabecie twojego klucza [0,255]. Haszujesz swój klucz 'k0 k1 k2 ... kN', biorąc T [k0] xor T [k1] xor ... xor T [kN]. Możesz łatwo pokazać, że jest to tak samo losowe, jak twój generator liczb losowych, a jego obliczeniowo bardzo wykonalne, a jeśli naprawdę napotkasz bardzo złą instancję z dużą ilością kolizji, możesz po prostu powtórzyć całość, używając świeżej partii liczb losowych.
źródło