Jaki jest prawidłowy i dobry sposób wdrożenia __hash__()
?
Mówię o funkcji, która zwraca kod skrótu, który jest następnie używany do wstawiania obiektów do tabel skrótów, czyli słowników.
Ponieważ __hash__()
zwraca liczbę całkowitą i służy do „dzielenia” obiektów na tablice mieszające, zakładam, że wartości zwracanej liczby całkowitej powinny być równomiernie rozłożone na wspólne dane (aby zminimalizować kolizje). Jaka jest dobra praktyka, aby uzyskać takie wartości? Czy kolizje są problemem? W moim przypadku mam małą klasę, która zachowuje się jak klasa kontenera zawierająca kilka int, kilka pływaków i string.
źródło
__key
funkcji, jest to tak szybkie, jak to tylko możliwe. Jasne, jeśli wiadomo, że atrybuty są liczbami całkowitymi, a nie ma ich zbyt wiele, przypuszczam, że można by potencjalnie działać nieco szybciej z jakimś hashem wyrzuconym na miejsce, ale prawdopodobnie nie byłby tak dobrze rozłożony.hash((self.attr_a, self.attr_b, self.attr_c))
będzie zaskakująco szybkie (i poprawne ), ponieważ tworzenie małychtuple
s jest specjalnie zoptymalizowane i popycha pracę nad pobieraniem i łączeniem skrótów do wbudowanych C, co jest zwykle szybsze niż kod na poziomie Pythona.None
po zmianie klucza. Sposób, w jaki to rozwiązałem, polegał na przechowywaniu identyfikatora obiektu jako klucza, a nie tylko obiektu.John Millikin zaproponował rozwiązanie podobne do tego:
Problem z tym rozwiązaniem polega na tym, że plik
hash(A(a, b, c)) == hash((a, b, c))
. Innymi słowy, hash koliduje z krotką jego kluczowych członków. Może w praktyce nie ma to zbyt często znaczenia?Aktualizacja: dokumentacja Pythona zaleca teraz używanie krotki, jak w powyższym przykładzie. Zauważ, że w dokumentacji podano
Zauważ, że nie jest odwrotnie. Obiekty, które nie są ze sobą równe, mogą mieć tę samą wartość skrótu. Taka kolizja skrótów nie spowoduje, że jeden obiekt zastąpi inny, gdy zostanie użyty jako klucz dict lub element zestawu, o ile obiekty nie są również równe .
Nieaktualne / złe rozwiązanie
Dokumentacja Pythona, co daje nam to:__hash__
sugeruje połączenie skrótów podskładników za pomocą czegoś takiego jak XORAktualizacja: jak wskazuje Blckknght, zmiana kolejności a, b i c może powodować problemy. Dodałem dodatkowy,
^ hash((self._a, self._b, self._c))
aby uchwycić kolejność haszowanych wartości. To końcowe^ hash(...)
można usunąć, jeśli łączonych wartości nie można zmienić (na przykład, jeśli mają one różne typy i dlatego wartość_a
nigdy nie zostanie przypisana do_b
lub_c
itp.).źródło
hash(A(1, 2, 3))
będzie równahash(A(3, 1, 2))
(a oni zarówno hash równać do innychA
instancji z permutacji1
,2
a3
jako jej wartości). Jeśli chcesz, aby Twoja instancja nie miała tego samego skrótu co krotka swoich argumentów, po prostu utwórz wartość wartą (jako zmienną klasy lub jako globalną), a następnie umieść ją w krotce do zaszyfrowania: return hash ((_ sentinel , self._a, self._b, self._c))isinstance
może być problematyczne, ponieważ obiekt podklasy klasytype(self)
może być teraz równy obiektowi klasytype(self)
. Może się więc okazać, że dodanie aCar
i aFord
do aset()
może spowodować wstawienie tylko jednego obiektu, w zależności od kolejności wstawiania. Dodatkowo możesz napotkać sytuację, w któreja == b
jest prawda, aleb == a
jest fałszem.B
, możesz zmienić to naisinstance(othr, B)
hash((type(self), self._a, self._b, self._c))
.B
zamiasttype(self)
, często uważa się również, że lepszą praktyką jest zwracanie się wNotImplemented
przypadku napotkania nieoczekiwanego typu in__eq__
zamiastFalse
. To pozwala innym typom zdefiniowanym przez użytkownika na zaimplementowanie tego,__eq__
który wie o tymB
i może go porównać, jeśli chcą.Paul Larson z Microsoft Research zbadał szeroką gamę funkcji skrótu. Powiedział mi to
działał zaskakująco dobrze dla szerokiej gamy strun. Odkryłem, że podobne techniki wielomianowe dobrze się sprawdzają przy obliczaniu skrótu różnych podpól.
źródło
(hash * 1000003) XOR ord(c)
dla łańcuchów z 32-bitowym mnożeniem z zawijaniem. [Citation ]__hash__
metodę; nie musimy tworzyć własnych. Pytanie brzmi, jak zaimplementować__hash__
dla typowej klasy zdefiniowanej przez użytkownika (z kilkoma właściwościami wskazującymi na typy wbudowane lub być może na inne takie klasy zdefiniowane przez użytkownika), których ta odpowiedź w ogóle nie dotyczy.Mogę spróbować odpowiedzieć na drugą część twojego pytania.
Kolizje prawdopodobnie wynikną nie z samego kodu skrótu, ale z odwzorowania kodu skrótu na indeks w kolekcji. Na przykład twoja funkcja skrótu może zwracać losowe wartości od 1 do 10000, ale jeśli twoja tabela skrótów ma tylko 32 wpisy, po wstawieniu wystąpią kolizje.
Poza tym wydaje mi się, że kolizje byłyby rozwiązywane wewnętrznie przez kolekcję i istnieje wiele metod rozwiązywania kolizji. Najprostszym (i najgorszym) jest, mając wpis do wstawienia pod indeksem i, dodaj 1 do i, aż znajdziesz puste miejsce i wstaw je tam. Odzyskiwanie działa wtedy w ten sam sposób. Powoduje to nieefektywne pobieranie niektórych wpisów, ponieważ możesz mieć wpis, którego znalezienie wymaga przeszukania całej kolekcji!
Inne metody rozwiązywania kolizji skracają czas pobierania, przenosząc wpisy w tablicy skrótów, gdy element jest wstawiany w celu rozłożenia elementów. Zwiększa to czas wstawiania, ale zakłada, że więcej czytasz niż wkładasz. Istnieją również metody, które próbują rozgałęziać różne kolidujące wpisy, tak aby wpisy były zgrupowane w jednym określonym miejscu.
Ponadto, jeśli chcesz zmienić rozmiar kolekcji, będziesz musiał ponownie zhasować wszystko lub użyć dynamicznej metody haszowania.
Krótko mówiąc, w zależności od tego, co używasz kodu skrótu, może być konieczne zaimplementowanie własnej metody rozwiązywania kolizji. Jeśli nie przechowujesz ich w kolekcji, prawdopodobnie możesz uciec dzięki funkcji skrótu, która po prostu generuje kody skrótu w bardzo dużym zakresie. Jeśli tak, możesz upewnić się, że twój pojemnik jest większy niż powinien (im większy, tym lepszy) w zależności od twoich problemów z pamięcią.
Oto kilka linków, jeśli jesteś zainteresowany bardziej:
skondensowany haszowanie na Wikipedii
Wikipedia zawiera również podsumowanie różnych metod rozwiązywania kolizji:
Ponadto „ Organizacja i przetwarzanie plików ” Tharp obszernie omawia wiele metod rozwiązywania kolizji. IMO to świetne odniesienie do algorytmów haszujących.
źródło
Bardzo dobre wyjaśnienie, kiedy i jak zaimplementować tę
__hash__
funkcję na stronie programiz :Wystarczy zrzut ekranu, aby przedstawić przegląd: (Źródło: 13.12.2019)
Jeśli chodzi o osobistą implementację metody, powyższa strona zawiera przykład pasujący do odpowiedzi millerdev .
źródło
Zależy od rozmiaru zwracanej wartości skrótu. Jest to prosta logika, że jeśli chcesz zwrócić 32-bitową liczbę int w oparciu o hash czterech 32-bitowych int, wystąpią kolizje.
Wolałbym operacje bitowe. Na przykład następujący pseudokod w C:
Taki system mógłby również działać dla liczb zmiennoprzecinkowych, gdybyś po prostu wziął je jako wartość bitową, a nie reprezentował wartość zmiennoprzecinkową, może lepiej.
Jeśli chodzi o struny, nie mam pojęcia.
źródło