Qu'est-ce qu'un bon moyen d'implémenter __hash__()
?
Je parle de la fonction qui retourne un hashcode qui est ensuite utilisé pour insérer des objets dans des hashtables, des dictionnaires.
Comme __hash__()
renvoie un entier et est utilisé pour "binner" les objets en hashtables, je suppose que les valeurs de l'entier retourné doivent être uniformément réparties pour les données communes (afin de minimiser les collisions). Quelle est la bonne pratique pour obtenir de telles valeurs? Les collisions sont-elles un problème? Dans mon cas, j'ai une petite classe qui agit comme une classe conteneur contenant des entiers, des flottants et une chaîne.
Une méthode simple et correcte pour implémenter __hash__()
consiste à utiliser un tuple clé. Ce ne sera pas aussi rapide qu'un hachage spécialisé, mais si vous en avez besoin, vous devriez probablement implémenter le type en C.
Voici un exemple d'utilisation d'une clé pour le hachage et l'égalité:
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
En outre, le documentation de __hash__
a plus d’informations, qui peuvent être utiles dans certaines circonstances particulières.
John Millikin a proposé une solution semblable à celle-ci:
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))
Le problème avec cette solution est que la hash(A(a, b, c)) == hash((a, b, c))
. En d'autres termes, le hachage se heurte à celui du Tuple de ses membres clés. Peut-être que cela n'a pas d'importance très souvent dans la pratique?
La La documentation Python sur __hash__
suggère de combiner les hachages des sous-composants en utilisant quelque chose comme XOR, ce qui nous donne ceci:
class B(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) ^ hash(self._b) ^ hash(self._c) ^
hash((self._a, self._b, self._c)))
Mise à jour: comme le fait remarquer Blckknght, modifier l’ordre de a, b et c pourrait poser problème. J'ai ajouté un ^ hash((self._a, self._b, self._c))
supplémentaire pour capturer l'ordre des valeurs en cours de hachage. Cette dernière ^ hash(...)
peut être supprimée si les valeurs combinées ne peuvent pas être réorganisées (par exemple, si elles ont des types différents et que, par conséquent, la valeur de _a
Ne sera jamais affectée à _b
. ] ou _c
, etc.).
Paul Larson de Microsoft Research a étudié une grande variété de fonctions de hachage. Il m'a dit que
for c in some_string:
hash = 101 * hash + ord(c)
a fonctionné étonnamment bien pour une grande variété de cordes. J'ai constaté que des techniques polynomiales similaires fonctionnent bien pour calculer un hachage de sous-champs disparates.
Je peux essayer de répondre à la deuxième partie de votre question.
Les collisions résulteront probablement non pas du code de hachage lui-même, mais du mappage du code de hachage sur un index dans une collection. Ainsi, par exemple, votre fonction de hachage peut renvoyer des valeurs aléatoires comprises entre 1 et 10 000, mais si votre table de hachage ne comporte que 32 entrées, vous aurez des collisions lors de l'insertion.
De plus, je penserais que les collisions seraient résolues par la collection en interne, et il existe de nombreuses méthodes pour les résoudre. Le plus simple (et le pire) est, étant donné une entrée à insérer à l’index i, ajouter 1 à i jusqu’à ce que vous trouviez un emplacement vide et y insérez. La récupération fonctionne alors de la même manière. Cela se traduit par des extractions inefficaces pour certaines entrées, car vous pourriez avoir une entrée qui nécessite de parcourir toute la collection pour trouver!
D'autres méthodes de résolution de collision réduisent le temps de récupération en déplaçant des entrées dans la table de hachage lorsqu'un élément est inséré pour étaler les éléments. Cela augmente le temps d’insertion mais suppose que vous lisiez plus que vous n’en insériez. Il existe également des méthodes qui essaient de créer des liens entre différentes entrées en collision de manière à ce qu'elles soient regroupées dans un emplacement particulier.
De plus, si vous devez redimensionner la collection, vous devrez tout réorganiser ou utiliser une méthode de hachage dynamique.
En bref, selon votre utilisation du code de hachage, vous devrez peut-être implémenter votre propre méthode de résolution de collision. Si vous ne les stockez pas dans une collection, vous pouvez probablement vous en tirer avec une fonction de hachage qui ne génère que des codes de hachage dans une très grande plage. Si tel est le cas, vous pouvez vous assurer que votre conteneur est plus volumineux que nécessaire (le plus gros étant le mieux, bien sûr) en fonction de vos problèmes de mémoire.
Voici quelques liens si vous êtes plus intéressé:
hachage coalescent sur wikipedia
Wikipedia a également un résumé de différentes méthodes de résolution de collision:
En outre, " Organisation et traitement de fichiers " de Tharp couvre de nombreuses méthodes de résolution de collision. OMI est une excellente référence pour les algorithmes de hachage.
Dépend de la taille de la valeur de hachage que vous retournez. La logique est simple: si vous devez renvoyer un int de 32 bits basé sur le hachage de quatre bits de 32 bits, vous allez avoir des collisions.
Je favoriserais les opérations de bits. Comme, le pseudo code C suivant:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);
Un tel système pourrait également fonctionner pour les flottants, si vous les preniez simplement comme valeur de bit plutôt que de représenter une valeur à virgule flottante, peut-être mieux.
Pour les cordes, j'ai peu/pas d'idée.