Zwykle prosta funkcja skrótu działa poprzez pobranie „części składowych” danych wejściowych (znaków w przypadku ciągu znaków) i pomnożenie ich przez potęgę pewnej stałej i dodanie ich razem do pewnego rodzaju liczb całkowitych. Na przykład typowy (choć niezbyt dobry) skrót łańcucha może być:
(first char) + k * (second char) + k^2 * (third char) + ...
Następnie, jeśli zostanie wprowadzonych kilka ciągów znaków o tym samym pierwszym znaku, wówczas wszystkie wyniki będą tego samego modulo k, przynajmniej do momentu przepełnienia typu liczby całkowitej.
[Na przykład łańcuch hashCode Javy jest niesamowicie podobny do tego - robi odwrotną kolejność znaków, przy k = 31. Otrzymujesz więc uderzające relacje modulo 31 między ciągami, które kończą się w ten sam sposób, i uderzające relacje modulo 2 ^ 32 między ciągami, które są takie same, z wyjątkiem końca. Nie powoduje to poważnego bałaganu przy zachowaniu hashtable.]
Tablica skrótów działa, przyjmując moduł skrótu względem liczby segmentów.
W tablicy mieszającej ważne jest, aby nie wywoływać kolizji w prawdopodobnych przypadkach, ponieważ kolizje zmniejszają wydajność tablicy mieszającej.
Załóżmy teraz, że ktoś umieszcza całą masę wartości w tablicy mieszającej, która ma pewien związek między przedmiotami, na przykład wszystkie mają tę samą pierwszą postać. Jest to dość przewidywalny wzorzec użytkowania, powiedziałbym, więc nie chcemy, aby powodował zbyt wiele kolizji.
Okazuje się, że „ze względu na naturę matematyki”, jeśli stała używana w haszu i liczba segmentów są chronione prawem autorskim , to w niektórych typowych przypadkach kolizje są minimalizowane. Jeśli nie są chronione prawem autorskim, istnieją pewne dość proste relacje między danymi wejściowymi, dla których kolizje nie są minimalizowane. Wszystkie skróty wychodzą równe modulo wspólny czynnik, co oznacza, że wszystkie wpadną do 1 / nth segmentów, które mają tę wartość modulo wspólny czynnik. Otrzymujesz n razy więcej kolizji, gdzie n jest wspólnym czynnikiem. Ponieważ n wynosi co najmniej 2, powiedziałbym, że niedopuszczalne jest, aby dość prosty przypadek użycia generował co najmniej dwa razy więcej kolizji niż normalnie. Jeśli jakiś użytkownik podzieli naszą dystrybucję na segmenty, chcemy, aby był to dziwny wypadek, a nie jakieś proste, przewidywalne użycie.
Teraz implementacje hashtable oczywiście nie mają kontroli nad umieszczonymi w nich elementami. Nie mogą zapobiec ich powiązaniu. Należy więc upewnić się, że stała i liczba segmentów są pierwszymi. W ten sposób nie polegasz na samym „ostatnim” elemencie, aby określić moduł kubła w odniesieniu do jakiegoś małego wspólnego czynnika. O ile wiem, nie muszą być najważniejsze, aby to osiągnąć, po prostu coprime.
Ale jeśli funkcja skrótu i tablica skrótów są zapisywane niezależnie, to tablica skrótów nie wie, jak działa funkcja skrótu. Może używać stałej z małymi czynnikami. Jeśli masz szczęście, może działać zupełnie inaczej i być nieliniowy. Jeśli skrót jest wystarczająco dobry, każda liczba łyżek jest w porządku. Ale paranoiczna tablica haszująca nie może przyjąć dobrej funkcji haszującej, dlatego powinna używać największej liczby segmentów. Podobnie paranoiczna funkcja skrótu powinna używać dużej stałej podstawowej, aby zmniejszyć prawdopodobieństwo, że ktoś użyje wielu segmentów, które mają wspólny czynnik ze stałą.
W praktyce myślę, że dość normalne jest użycie siły 2 jako liczby segmentów. Jest to wygodne i pozwala uniknąć konieczności wyszukiwania lub wstępnego wyboru liczby pierwszej o odpowiedniej wielkości. Dlatego polegasz na funkcji skrótu, aby nie używać nawet mnożników, co jest ogólnie bezpiecznym założeniem. Ale nadal można od czasu do czasu zachowywać się przy złym haszowaniu na podstawie funkcji haszujących, takich jak powyższa, a liczba głównych segmentów może pomóc dalej.
Wprowadzenie zasady, że „wszystko musi być liczbą pierwszą” jest, o ile wiem, wystarczającym, ale nie koniecznym warunkiem dobrego podziału na tablice skrótów. Pozwala to wszystkim współpracować bez konieczności zakładania, że inni przestrzegali tej samej zasady.
[Edycja: istnieje inny, bardziej wyspecjalizowany powód, aby korzystać z największej liczby segmentów, np. W przypadku kolizji z sondowaniem liniowym. Następnie obliczasz krok na podstawie kodu skrótu, a jeśli ten krok okaże się czynnikiem liczenia segmentu, możesz wykonać tylko (bucket_count / stride) sondy, zanim wrócisz do miejsca, w którym zacząłeś. Przypadek, którego najbardziej chcesz uniknąć, to stride = 0, oczywiście, które musi być w specjalnej obudowie, ale aby uniknąć także specjalnej obudowy bucket_count / stride równej małej liczbie całkowitej, możesz po prostu ustawić wartość bucket_count jako pierwszą i nie dbając o to, co krok jest pod warunkiem, że nie jest to 0.]