在Python中哈希一个不可变的字典



短版本:对于作为无序项字典实现的多集,最好的哈希算法是什么?

我试图将一个不可变的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_hashset_next的实现中看出。

另请参阅Raymond Hettinger在描述他对frozenset散列函数的实现时给出的这个好答案。在那里,他明确解释了散列函数如何避免对值进行排序,以获得稳定的、不区分顺序的值。

您考虑过hash(sorted(hash(x) for x in self.items()))吗?这样,您只需要对整数进行排序,而不必构建列表。

你也可以将元素散列异或在一起,但坦率地说,我不知道这会有多好(你会有很多碰撞吗?)。说到碰撞,难道不需要实现__eq__方法吗?

或者,类似于我在这里的回答,hash(frozenset(self.items()))

相关内容

  • 没有找到相关文章

最新更新