Answers:
一种简单而正确的实现方法__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__
还包含更多信息,这些信息在某些特定情况下可能会很有价值。
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
,等等),则可以删除此最终形式。
hash(A(1, 2, 3))
将等于hash(A(3, 1, 2))
(他们俩哈希等于任何其他A
情况下用的置换1
,2
并3
作为其值)。如果要避免实例与其参数元组具有相同的哈希,只需创建一个哨兵值(作为类变量或全局变量),然后将其包含在要哈希的元组中:return hash((__ sentinel ,self._a,self._b,self._c))
isinstance
可能会出现问题,因为的子类的对象type(self)
现在可以等于的对象type(self)
。因此,您可能会发现,将a Car
和a 添加Ford
到a set()
可能只会插入一个对象,具体取决于插入的顺序。此外,您可能会遇到“ a == b
真”但“ b == a
假”的情况。
B
,则可能需要将其更改为isinstance(othr, B)
hash((type(self), self._a, self._b, self._c))
。
B
代替的要点外type(self)
,通常还会考虑NotImplemented
在__eq__
代替in遇到意外类型时返回,这是更好的做法False
。这样,其他用户定义的类型就可以实现__eq__
了解B
并可以与之进行比较的,如果他们愿意的话。
Microsoft Research的Paul Larson研究了各种哈希函数。他告诉我
for c in some_string:
hash = 101 * hash + ord(c)
对于各种各样的琴弦,效果都非常好。我发现类似的多项式技术可以很好地用于计算不同子字段的哈希。
__hash__
方法。我们不需要自己动手。问题是如何__hash__
为典型的用户定义类(具有指向内置类型或其他此类用户定义类的属性)实现,此答案根本无法解决。
我可以尝试回答您问题的第二部分。
冲突可能不是哈希码本身引起的,而是哈希码映射到集合中的索引所导致的。因此,例如,您的哈希函数可以返回1到10000之间的随机值,但是如果您的哈希表只有32个条目,则在插入时会发生冲突。
另外,我认为冲突将由集合内部解决,并且有很多解决冲突的方法。最简单(也是最糟糕)的情况是,给定要插入到索引i的条目,将i加1直到找到一个空白点并插入该位置。然后,检索以相同的方式进行。这会导致某些条目的检索效率低下,因为您可能有一个条目需要遍历整个集合才能找到!
其他冲突解决方法通过在插入项目以散布事物时移动哈希表中的条目来减少检索时间。这会增加插入时间,但假定您阅读的内容多于插入内容。也有尝试将不同的冲突条目分支出来的方法,以使条目聚集在一个特定位置。
另外,如果您需要调整集合的大小,则需要重新哈希所有内容或使用动态哈希方法。
简而言之,根据您使用的哈希码,您可能必须实现自己的冲突解决方法。如果不将它们存储在集合中,则可以使用仅生成很大范围内的哈希码的哈希函数来解决。如果是这样,则可以根据您的内存问题来确保容器大于所需的容器(当然,容器越大越好)。
如果您有更多兴趣,请点击以下链接:
Wikipedia还总结了各种冲突解决方法:
此外,Tharp的“ 文件组织和处理 ”广泛涵盖了许多冲突解决方法。IMO是哈希算法的重要参考。
__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))
取决于您返回的哈希值的大小。这是很简单的逻辑,如果您需要基于四个32位int的哈希值返回32位int,则会发生冲突。
我希望位操作。像下面的C伪代码:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);
如果仅将它们用作浮点值而不是实际代表浮点值,则这样的系统也可以用于浮点数,也许更好。
对于字符串,我几乎一无所知。
__key
函数而产生的少量开销外,这几乎与任何哈希一样快。当然,如果已知这些属性是整数,并且它们的数量不是太多,我想您可以通过一些本地滚动的散列来稍微加快运行速度,但是它可能分布得不那么理想。hash((self.attr_a, self.attr_b, self.attr_c))
由于将特别优化优化小s的创建,因此它将以惊人的速度(并且正确),tuple
并且将获取和组合哈希的工作推到了C内置函数中,这通常比Python级别的代码要快。