问题:什么是实现__hash __()的正确和好方法?
什么是正确的好方法__hash__()
?
我说的是一个返回哈希码的函数,然后该哈希码用于将对象插入哈希表(又名字典)。
当__hash__()
返回一个整数并用于将对象“绑定”到哈希表中时,我假设返回的整数的值应均匀分配给公共数据(以最大程度地减少冲突)。获得这样的价值观是什么好习惯?碰撞是个问题吗?就我而言,我有一个小类,它充当一个容器类,其中包含一些整数,一些浮点数和一个字符串。
回答 0
一种简单而正确的实现方法__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):
if isinstance(other, A):
return self.__key() == other.__key()
return NotImplemented
此外,的文档__hash__
还包含更多信息,这些信息在某些特定情况下可能会很有价值。
回答 1
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 (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))
此解决方案的问题是hash(A(a, b, c)) == hash((a, b, c))
。换句话说,哈希与它的关键成员的元组冲突。也许这在实践中并不经常发生?
更新:Python文档现在建议使用上面的示例中的元组。请注意,文档说明
唯一需要的属性是比较相等的对象具有相同的哈希值
注意相反的说法是不正确的。不相等的对象可能具有相同的哈希值。这种哈希冲突在用作dict键或set元素时不会导致一个对象替换另一对象,只要这些对象也不能相等。
过时/不好的解决方案
上的Python文档,从而实现以下目的:__hash__
建议使用XOR之类的东西来组合子组件的哈希值
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)))
更新:正如Blckknght指出的那样,更改a,b和c的顺序可能会引起问题。我添加了一个附加项^ hash((self._a, self._b, self._c))
来捕获被哈希值的顺序。^ hash(...)
如果合并的值无法重新排列(例如,如果它们的类型不同,因此_a
将永远不会将的值分配给_b
或_c
,等等),则可以删除此最终形式。
回答 2
Microsoft Research的Paul Larson研究了各种哈希函数。他告诉我
for c in some_string:
hash = 101 * hash + ord(c)
对于各种各样的琴弦,效果都非常好。我发现类似的多项式技术可以很好地用于计算不同子字段的哈希。
回答 3
我可以尝试回答您问题的第二部分。
冲突可能不是哈希码本身引起的,而是哈希码映射到集合中的索引所导致的。因此,例如,您的哈希函数可以返回1到10000之间的随机值,但是如果您的哈希表只有32个条目,则在插入时会发生冲突。
另外,我认为冲突将由集合内部解决,并且有很多解决冲突的方法。最简单(也是最糟糕)的情况是,给定要插入到索引i的条目,将i加1直到找到一个空白点并插入该位置。然后,检索以相同的方式进行。这会导致某些条目的检索效率低下,因为您可能有一个条目需要遍历整个集合才能找到!
其他冲突解决方法通过在插入项目以散布事物时移动哈希表中的条目来减少检索时间。这会增加插入时间,但假定您阅读的内容多于插入内容。也有尝试将不同的冲突条目分支出来的方法,以使条目聚集在一个特定位置。
另外,如果您需要调整集合的大小,则需要重新哈希所有内容或使用动态哈希方法。
简而言之,根据您使用的哈希码,您可能必须实现自己的冲突解决方法。如果不将它们存储在集合中,则可以使用仅生成很大范围内的哈希码的哈希函数来解决。如果是这样,则可以根据您的内存问题来确保容器大于所需的容器(当然,容器越大越好)。
如果您有更多兴趣,请点击以下链接:
Wikipedia还总结了各种冲突解决方法:
此外,Tharp的“ 文件组织和处理 ”广泛涵盖了许多冲突解决方法。IMO是哈希算法的重要参考。
回答 4
__hash__
在programiz网站上很好地解释了何时以及如何实现该功能:
只是一个截图以提供概述:(检索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))
回答 5
取决于您返回的哈希值的大小。这是很简单的逻辑,如果您需要基于四个32位int的哈希值返回32位int,则会发生冲突。
我希望位操作。像下面的C伪代码:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);
如果仅将它们用作浮点值而不是实际代表浮点值,则这样的系统也可以用于浮点数,也许更好。
对于字符串,我几乎一无所知。