17

简短版本:作为无序项字典实现的多重集的最佳散列算法是什么?

我正在尝试散列一个不可变的多重集(在其他语言中是一个包或多重集:就像一个数学集,除了它可以容纳多个元素)作为字典实现。我创建了标准库类的一个子类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)

我认为使用按位异或意味着顺序对哈希值无关紧要,这与元组的哈希不同?我想我可以在我的数据的无序元组流上半实现 Python 元组哈希算法。请参阅https://github.com/jonashaag/cpython/blob/master/Include/tupleobject.h(在页面中搜索“哈希”一词)——但我几乎不知道足够的 C 来阅读它。

想法?建议?谢谢。


如果您想知道我为什么要尝试散列一个多重集:我的问题的输入数据是多重集的集合,并且在每组多重集中,每个多重集必须是唯一的。我正在截止日期前工作而且我不是经验丰富的编码员,所以我想尽可能避免发明新算法。似乎最 Pythonic 的方法来确保我拥有一堆东西中的独特性,就是把它们放在 a 中set(),但这些东西必须是可散列的。)

我从评论中收集到的

@marcin 和 @senderle 都给出了几乎相同的答案: use hash(frozenset(self.items()))。这是有道理的,因为items()“视图”是 set-like。@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
4

2 回答 2

13

由于字典是不可变的,因此可以在创建字典时创建哈希并直接返回。我的建议是创建一个frozensetfrom items(在 3+ 中;iteritems在 2.7 中),对其进行散列并存储散列。

提供一个明确的例子:

>>>> 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

为了澄清为什么我更喜欢 afrozenset而不是排序项目的元组: afrozenset不必对项目进行排序,因此初始哈希在 O(n) 时间内完成,而不是 O(n log n) 时间。这可以从frozenset_hashset_next实现中看出。

另请参阅Raymond Hettinger 的这个很好的回答frozenset,描述了他对哈希函数的实现。在那里,他明确解释了哈希函数如何避免对值进行排序以获得稳定的、顺序不敏感的值。

于 2012-04-06T15:37:47.493 回答
1

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

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

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

于 2012-04-06T15:39:14.127 回答