实施__hash__()
的正确和好方法是什么?
我在谈论返回哈希码的函数,该哈希码随后用于将对象插入到哈希表中,即字典。
当__hash__()
返回一个整数并用于将对象“分箱”为哈希表时,我假设返回的整数的值应该为公共数据均匀分布(以最小化冲突)。
获得这些价值观的好习惯是什么?碰撞是一个问题吗?
在我的例子中,我有一个小类,它充当一个容器类,包含一些整数,一些浮点数和一个字符串。
答案 0 :(得分:136)
实现__hash__()
的一种简单,正确的方法是使用关键元组。它不会像专门的哈希那么快,但如果你需要那么你应该在C中实现类型。
以下是使用密钥进行哈希和相等的示例:
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):
return isinstance(self, type(other)) and self.__key() == other.__key()
此外,documentation of __hash__
有更多信息,在某些特定情况下可能很有价值。
答案 1 :(得分:19)
John Millikin提出了类似的解决方案:
class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return ((self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return hash((self._a, self._b, self._c))
此解决方案的问题在于hash(A(a, b, c)) == hash((a, b, c))
。换句话说,哈希与其关键成员的元组的哈希冲突。也许这在实践中经常无关紧要?
Python documentation on __hash__
建议使用XOR之类的东西组合子组件的哈希值,这样就可以了:
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)))
奖励:更加强大的__eq__
投入其中以获得良好的衡量标准。
更新:正如Blckknght指出的那样,改变a,b和c的顺序可能会导致问题。我添加了一个额外的^ hash((self._a, self._b, self._c))
来捕获被散列的值的顺序。如果要合并的值无法重新排列,则可以删除此最终^ hash(...)
(例如,如果它们具有不同的类型,则_a
的值永远不会分配给_b
或{{ 1}}等。)。
答案 2 :(得分:16)
Microsoft Research的Paul Larson研究了各种哈希函数。他告诉我那个
for c in some_string:
hash = 101 * hash + ord(c)
对各种各样的琴弦都做得非常好。我发现类似的多项式技术可以很好地计算不同子场的散列。
答案 3 :(得分:3)
我可以尝试回答你问题的第二部分。
冲突可能不是来自哈希代码本身,而是来自哈希代码映射到集合中的索引。因此,例如,您的哈希函数可以返回从1到10000的随机值,但如果您的哈希表只有32个条目,您将在插入时发生冲突。
此外,我认为冲突将由内部集合解决,并且有许多方法可以解决冲突。最简单的(也是最差的)是,如果在索引i处插入一个条目,则向i添加1,直到找到空白点并插入其中。然后检索以相同的方式工作。这会导致某些条目的检索效率低下,因为您可能需要遍历整个集合才能找到该条目!
其他冲突解决方法通过在插入项目时移动哈希表中的条目来扩展检索时间,从而缩短检索时间。这会增加插入时间,但假设您阅读的内容超过了插入时间。还有一些方法可以尝试将不同的碰撞条目分开,以便条目聚集在一个特定的位置。
此外,如果您需要调整集合的大小,则需要重新散列所有内容或使用动态散列方法。
简而言之,根据您使用哈希码的内容,您可能必须实现自己的冲突解决方法。如果你没有将它们存储在一个集合中,你可能会使用一个只在很大范围内生成哈希码的哈希函数。如果是这样,你可以确保你的容器比你需要的更大(当然越大越好),这取决于你的记忆问题。
如果您对此感兴趣,请点击以下链接:
coalesced hashing on wikipedia
维基百科还有summary种各种冲突解决方法:
此外,Tharp的“File Organization And Processing”广泛涵盖了很多碰撞解决方法。 IMO它是散列算法的一个很好的参考。
答案 4 :(得分:1)
实现哈希(以及列表、字典、元组)的一个好方法是通过使用 __iter__
使其可迭代来使对象具有可预测的项目顺序。所以要修改上面的一个例子:
class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __iter__(self):
yield "a", self._a
yield "b", self._b
yield "c", self._c
def __hash__(self):
return hash(tuple(self))
def __eq__(self, other):
return (isinstance(other, type(self))
and tuple(self) == tuple(other))
(这里 __eq__
不是 hash 所必需的,但它很容易实现)。
现在添加一些可变成员来看看它是如何工作的:
a = 2; b = 2.2; c = 'cat'
hash(A(a, b, c)) # -5279839567404192660
dict(A(a, b, c)) # {'a': 2, 'b': 2.2, 'c': 'cat'}
list(A(a, b, c)) # [('a', 2), ('b', 2.2), ('c', 'cat')]
tuple(A(a, b, c)) # (('a', 2), ('b', 2.2), ('c', 'cat'))
如果您尝试将不可散列的成员放入对象模型中,事情只会崩溃:
hash(A(a, b, [1])) # TypeError: unhashable type: 'list'
答案 5 :(得分:0)
取决于您返回的哈希值的大小。这是一个简单的逻辑,如果你需要根据四个32位整数的散列返回一个32位的int,你就会发生碰撞。
我赞成比特操作。比如,下面的C伪代码:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);
这样的系统也可以用于浮点数,如果你只是将它们作为比特值而不是实际表示浮点值,可能更好。
对于字符串,我很少/不知道。
答案 6 :(得分:0)
关于何时以及如何实现programiz website的__hash__
函数的很好的解释:
仅提供截图以提供概述: (检索2019-12-13)
关于该方法的个人实现,上述站点提供了一个与 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))