短版本:对于作为无序项字典实现的多集,最好的哈希算法是什么?
我试图将一个不可变的multiset(在其他语言中是一个袋子或multiset:就像一个数学集,除了它可以容纳每个元素中的多个元素之外)作为字典来实现。我创建了标准库类collections.Counter
的一个子类,类似于这里的建议:Python hashable dicts,它建议使用这样的哈希函数:
class FrozenCounter(collections.Counter):
# ...
def __hash__(self):
return hash(tuple(sorted(self.items())))
创建完整的项元组会占用大量内存(比如说,相对于使用生成器),并且哈希将发生在我的应用程序中非常占用内存的部分。更重要的是,我的字典键(multiset元素)可能无法排序。
我正在考虑使用这个算法:
def __hash__(self):
return functools.reduce(lambda a, b: a ^ b, self.items(), 0)
我认为使用逐位XOR意味着顺序对哈希值来说无关紧要,这与元组的哈希不同吗我想我可以在数据的无序元组流上半实现Python元组哈希算法。看见https://github.com/jonashaag/cpython/blob/master/Include/tupleobject.h(在页面中搜索单词"hash")——但我几乎不知道足够的C来阅读它。
想法?建议?谢谢
(如果你想知道我为什么要对多集进行散列:我的问题的输入数据是多集集,在每一组多集中,每个多集都必须是唯一的。我正在赶最后期限,我不是一个经验丰富的程序员,所以我想尽可能避免发明新的算法f是将它们放在
set()
中,但这些东西必须是可散列的。)我从评论中收集到的内容
@marcin和@senderle给出了几乎相同的答案:使用hash(frozenset(self.items()))
。这是有道理的,因为items()
的"视图"设置类似@marcin是第一个,但我给@senderle打了勾号,因为我对不同解决方案的big-O运行时间进行了很好的研究@marcin还提醒我包含一个__eq__
方法,但从dict
继承的方法会很好地工作。这就是我实现一切的方式——欢迎基于此代码的进一步评论和建议:
class FrozenCounter(collections.Counter):
# Edit: A previous version of this code included a __slots__ definition.
# But, from the Python documentation: "When inheriting from a class without
# __slots__, the __dict__ attribute of that class will always be accessible,
# so a __slots__ definition in the subclass is meaningless."
# http://docs.python.org/py3k/reference/datamodel.html#notes-on-using-slots
# ...
def __hash__(self):
"Implements hash(self) -> int"
if not hasattr(self, '_hash'):
self._hash = hash(frozenset(self.items()))
return self._hash
由于字典是不可变的,因此可以在创建字典时创建哈希并直接返回。我的建议是从items
(在3+中;iteritems
在2.7中)创建一个frozenset
,对其进行散列,并存储散列。
提供一个明确的例子:
>>>> frozenset(Counter([1, 1, 1, 2, 3, 3, 4]).iteritems())
frozenset([(3, 2), (1, 3), (4, 1), (2, 1)])
>>>> hash(frozenset(Counter([1, 1, 1, 2, 3, 3, 4]).iteritems()))
-3071743570178645657
>>>> hash(frozenset(Counter([1, 1, 1, 2, 3, 4]).iteritems()))
-6559486438209652990
为了澄清为什么我更喜欢frozenset
而不是排序项的元组:frozenset
不必对项进行排序,因此初始哈希在O(n)时间而不是O(n log n)时间内完成。这可以从frozenset_hash
和set_next
的实现中看出。
另请参阅Raymond Hettinger在描述他对frozenset
散列函数的实现时给出的这个好答案。在那里,他明确解释了散列函数如何避免对值进行排序,以获得稳定的、不区分顺序的值。
您考虑过hash(sorted(hash(x) for x in self.items()))
吗?这样,您只需要对整数进行排序,而不必构建列表。
你也可以将元素散列异或在一起,但坦率地说,我不知道这会有多好(你会有很多碰撞吗?)。说到碰撞,难道不需要实现__eq__
方法吗?
或者,类似于我在这里的回答,hash(frozenset(self.items()))
。