简短版本:作为无序项目字典实现的多集合的最佳散列算法是什么?
我正在尝试散列一个不可变的multiset(这是一个包或其他语言的多重集:就像一个数学集,除了它可以容纳多个元素)作为字典实现。我已经创建了标准库类collections.Counter
的子类,类似于这里的建议:Python hashable dicts,它建议使用这样的哈希函数:
class FrozenCounter(collections.Counter):
# ...
def __hash__(self):
return hash(tuple(sorted(self.items())))
创建完整的项目元组会占用大量内存(相对于使用生成器而言),并且哈希将在我的应用程序的内存密集型部分中发生。更重要的是,我的字典键(多字节元素)可能无法订购。
我正在考虑使用这个算法:
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来阅读它。
思考?建议?感谢。
<小时/> (如果你想知道我为什么要乱用多线程:我的问题的输入数据是多集的集合,并且在每组多集中,每个多集必须是唯一的。我正在按期完成工作,而且我不是一个经验丰富的编码员,所以我想避免在可能的情况下发明新算法。似乎最恐怖的方法是确保我拥有一堆独特的东西就是把它们放进去一个set()
,但事情必须是可以清洗的。)
@marcin和@senderle给出了几乎相同的答案:使用hash(frozenset(self.items()))
。这是有道理的,因为items()
"views" are set-like。 @marcin是第一个,但我给@senderle打了一个复选标记,因为对不同解决方案的大O运行时间进行了很好的研究。 @marcin也提醒我include an __eq__
method - 但是从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
答案 0 :(得分:13)
由于字典是不可变的,您可以在创建字典时创建哈希并直接返回。我的建议是从items
(3 +; 2.7中的iteritems
)创建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
实现中看出。
答案 1 :(得分:1)
您考虑过hash(sorted(hash(x) for x in self.items()))
了吗?这样,您只需对整数进行排序,而不必构建列表。
你也可以将元素哈希值合并在一起,但坦率地说,我的效果不是很好(你会有很多碰撞吗?)。说到碰撞,您不必实施__eq__
方法吗?
或者,类似于我的回答here,hash(frozenset(self.items()))
。