Jaki jest prawidłowy i dobry sposób implementacji hash ()?

Jaki jest prawidłowy i dobry sposób wdrożenia __hash__()?

Mówię o funkcji, która zwraca hashcode, który jest następnie używany do wstawiania obiektów do hashtables aka słowników.

Ponieważ __hash__() zwraca liczbę całkowitą i służy do" binowania " obiektów do hashtabli zakładam, że wartości zwracanej liczby całkowitej powinny być równomiernie rozłożone dla wspólnych danych (aby zminimalizować kolizje). Jaka jest dobra praktyka, aby uzyskać takie wartości? Czy kolizje to problem? W moim przypadek mam małą klasę, która działa jako klasa kontenera posiadająca kilka ints, niektóre pływaki i ciąg.

Author: Jean-François Corbett, 2010-05-25

6 answers

Łatwym, poprawnym sposobem implementacji __hash__() jest użycie krotki klucza. Nie będzie tak szybki jak specialized hash, ale jeśli tego potrzebujesz, prawdopodobnie powinieneś zaimplementować typ w C.

Oto przykład użycia klucza dla hash i równości:
class A:
    def __key(self):
        return (self.attr_a, self.attr_b, self.attr_c)

    def __hash__(self):
        return hash(self.__key())

    def __eq__(self, other):
        if isinstance(other, A):
            return self.__key() == other.__key()
        return NotImplemented

Również dokumentacja __hash__ ma więcej informacji, które mogą być cenne w niektórych szczególnych okolicznościach.

 204
Author: John Millikin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-07-07 11:12:03

John Millikin zaproponował rozwiązanie podobne do tego:

class A(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        return (isinstance(othr, type(self))
                and (self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))

    def __hash__(self):
        return hash((self._a, self._b, self._c))

Problem z tym rozwiązaniem polega na tym, że hash(A(a, b, c)) == hash((a, b, c)). Innymi słowy, hash koliduje z krotką jego kluczowych członków. Może to nie ma znaczenia w praktyce?

Aktualizacja: dokumenty Pythona zalecają teraz użycie krotki, jak w powyższym przykładzie. Zauważ, że dokumentacja stwierdza

Jedyną wymaganą właściwością jest to, że obiekty, które porównują równe mają ten sam hash wartość

Zauważ, że przeciwieństwo nie jest prawdą. Obiekty, które nie są równe mogą mieć tę samą wartość skrótu. Taka kolizja skrótu nie spowoduje, że jeden obiekt zastąpi inny, gdy zostanie użyty jako klucz dict lub element zestawu , o ile obiekty nie będą porównywać sobie równych .

Przestarzałe / złe rozwiązanie

dokumentacja Pythona na __hash__ sugeruje połączenie hashów podsystemów za pomocą czegoś takiego jak XOR , co daje nam to:

class B(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        if isinstance(othr, type(self)):
            return ((self._a, self._b, self._c) ==
                    (othr._a, othr._b, othr._c))
        return NotImplemented

    def __hash__(self):
        return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
                hash((self._a, self._b, self._c)))

Aktualizacja: jak Blckknght wskazuje, Zmiana kolejności a, b i c może spowodować problemy. Dodałem dodatkowe ^ hash((self._a, self._b, self._c)), aby uchwycić kolejność wartości, które są hashowane. Ta końcowa ^ hash(...) może zostać usunięta, jeśli wartości łączone nie mogą być przestawione (na przykład, jeśli mają różne typy i dlatego wartość _a nigdy nie zostanie przypisana do _b lub _c, itd.).

 24
Author: millerdev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-11-06 19:13:28

Paul Larson z Microsoft Research badał szeroką gamę funkcji skrótu. Powiedział mi, że

for c in some_string:
    hash = 101 * hash  +  ord(c)

Sprawdził się zaskakująco dobrze dla szerokiej gamy strun. Odkryłem, że podobne techniki wielomianowe działają dobrze do obliczania hashu różnych pól podrzędnych.

 16
Author: George V. Reilly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 01:05:53

Mogę spróbować odpowiedzieć na drugą część twojego pytania.

Kolizje prawdopodobnie nie wynikną z samego kodu hash, ale z mapowania kodu hash do indeksu w kolekcji. Na przykład funkcja skrótu może zwracać losowe wartości od 1 do 10000, ale jeśli twoja tabela skrótu zawiera tylko 32 wpisy, otrzymasz kolizje podczas wstawiania.

Ponadto myślę, że kolizje będą rozwiązywane wewnętrznie przez zbiór i istnieje wiele metod rozwiązywania kolizje. Najprostszym (i najgorszym) jest, biorąc pod uwagę wpis do wstawienia w indeksie i, dodaj 1 do i, aż znajdziesz puste miejsce i wstawisz tam. Odzyskiwanie działa wtedy w ten sam sposób. Powoduje to nieefektywne pobieranie niektórych wpisów, ponieważ możesz mieć wpis, który wymaga przemierzenia całej kolekcji, aby znaleźć!

Inne metody rozwiązywania kolizji skracają czas pobierania poprzez przesuwanie wpisów w tabeli hash, gdy element jest wstawiany w celu rozłożenia rzeczy. Zwiększa to Wkładanie czas, ale zakłada, że czytasz więcej niż wstawiasz. Istnieją również metody, które próbują rozgałęziać różne kolidujące wpisy tak, że wpisy do klastra w jednym konkretnym miejscu.

Ponadto, jeśli chcesz zmienić rozmiar kolekcji, musisz ponownie wszystko zmienić lub użyć dynamicznej metody mieszania.

Krótko mówiąc, w zależności od tego, do czego używasz kodu hashowego, może być konieczne zaimplementowanie własnej metody rozwiązywania kolizji. Jeśli nie przechowujesz ich w kolekcji, prawdopodobnie możesz uzyskać z dala od funkcji hash, która po prostu generuje kody hash w bardzo dużym zakresie. Jeśli tak, możesz upewnić się, że Twój kontener jest większy niż musi być (im większy, tym lepiej oczywiście), w zależności od twoich problemów z pamięcią.

Oto kilka linków, jeśli jesteś zainteresowany bardziej:

Haszowanie na Wikipedii

Wikipedia ma również podsumowanie różnych metod rozwiązywania kolizji:

Również " Organizacja i przetwarzanie plików" przez Tharp obejmuje wiele metod rozwiązywania kolizji obszernie. IMO jest to świetne odniesienie do algorytmów haszujących.

 3
Author: Kryptic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-26 00:58:39

Bardzo dobre wyjaśnienie kiedy i jak zaimplementować funkcję __hash__ jest na stronie programiz :

Wystarczy zrzut ekranu, aby uzyskać przegląd: (2019-12-13)

Zrzut ekranu z https://www.programiz.com/python-programming/methods/built-in/hash 2019-12-13

Jeśli chodzi o osobistą implementację metody, wyżej wymieniona strona dostarcza przykład, który pasuje do odpowiedzi millerdev.

class Person:
def __init__(self, age, name):
    self.age = age
    self.name = name

def __eq__(self, other):
    return self.age == other.age and self.name == other.name

def __hash__(self):
    print('The hash is:')
    return hash((self.age, self.name))

person = Person(23, 'Adam')
print(hash(person))
 1
Author: loved.by.Jesus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-13 11:14:18

Zależy od wielkości zwracanej wartości skrótu. To prosta logika, że jeśli chcesz zwrócić 32-bitowy int oparty na hash czterech 32-bitowych int, dostaniesz kolizje.

Preferowałbym operacje bitowe. Jak następujący pseudo kod C:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);

Taki system może działać również dla floatów, jeśli po prostu przyjmiesz je jako ich wartość bitową, a nie faktycznie reprezentującą wartość zmiennoprzecinkową, może lepiej.

Jeśli chodzi o struny, to nie mam pojęcia.
 0
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-14 07:35:01