关于python:实现__hash __()的正确和好方法是什么?

What's a correct and good way to implement __hash__()?

实施__hash__()的正确和好方法是什么?

我说的是一个函数,它返回一个哈希代码,然后将对象插入哈希表(又名字典)。

由于__hash__()返回一个整数,并用于将对象"binning"到哈希表中,因此我假设返回的整数的值应该为公共数据均匀分布(以尽量减少冲突)。获得这样的价值观有什么好的做法?碰撞是个问题吗?在我的例子中,我有一个小类,它充当一个容器类,包含一些int、一些float和一个字符串。


实现__hash__()的一个简单、正确的方法是使用键元组。它不会像专用散列那样快,但是如果需要,那么您可能应该在C中实现该类型。

下面是一个使用键进行哈希和相等的示例:

1
2
3
4
5
6
7
8
9
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):
        return isinstance(self, type(other)) and self.__key() == other.__key()

另外,__hash__的文档中有更多的信息,在某些特定情况下可能有价值。


约翰米利金提出了一个类似的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A(object):

    def __init__(self, a, b, c):
        self._a = a
        self._b = b
        self._c = c

    def __eq__(self, othr):
        return ((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))。换句话说,哈希值与它的关键成员的元组相冲突。也许这在实践中并不经常重要?

__hash__上的python文档建议使用类似于xor的东西组合子组件的散列,这给了我们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class B(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) ^ hash(self._b) ^ hash(self._c) ^
                hash((self._a, self._b, self._c)))

额外的好处:为了更好的衡量,我们投入了更强大的__eq__

更新:正如Blckknght指出的,更改A、B和C的顺序可能会导致问题。我添加了一个额外的^ hash((self._a, self._b, self._c)),以捕获要散列的值的顺序。如果合并的值不能重新排列(例如,如果它们的类型不同,因此不会将EDOCX1的值(7)分配给_b_c等),则可以删除最终的^ hash(...)


微软研究院的保罗·拉森研究了各种各样的哈希函数。他告诉我的

1
2
for c in some_string:
    hash = 101 * hash  +  ord(c)

在各种各样的琴弦上都表现得出奇的好。我发现类似的多项式技术对于计算不同子字段的散列很有效。


我可以试着回答你问题的第二部分。

冲突可能不是由哈希代码本身引起的,而是由将哈希代码映射到集合中的索引引起的。例如,散列函数可以返回从1到10000的随机值,但如果散列表只有32个条目,则插入时会发生冲突。

此外,我认为冲突将由集合内部解决,并且有许多方法可以解决冲突。最简单(也是最糟糕)的是,给定一个要在索引i处插入的条目,向i添加1,直到找到一个空点并插入其中。然后检索的工作方式相同。这会导致某些条目的检索效率低下,因为您可能有一个需要遍历整个集合才能找到的条目!

其他冲突解决方法通过在插入一个项以分散数据时移动哈希表中的条目来减少检索时间。这会增加插入时间,但假定读取的内容多于插入的内容。还有一些方法尝试将不同的碰撞条目分支出来,以便将条目聚集到一个特定的点上。

此外,如果需要调整集合的大小,则需要重新刷新所有内容或使用动态哈希方法。

简而言之,根据您使用的哈希代码,您可能需要实现自己的冲突解决方法。如果您不将它们存储在集合中,那么您可能可以使用一个哈希函数,该函数只在非常大的范围内生成哈希代码。如果是这样,您可以根据您的内存问题确定容器比需要的大(当然,越大越好)。

以下是一些链接,如果您更感兴趣:

维基百科上的合并散列

维基百科还总结了各种冲突解决方法:

THARP的"文件组织与处理"也广泛地涵盖了许多冲突解决方法。在我看来,这是一个很好的参考桥接算法。


取决于返回的哈希值的大小。这是一个简单的逻辑,如果您需要基于4个32位整数的散列返回一个32位整数,就会发生冲突。

我赞成钻头操作。例如,以下C伪代码:

1
2
3
4
5
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);

这样的系统也可以为浮点工作,如果您只是将它们作为位值,而不是实际表示浮点值,也许更好。

对于弦乐,我几乎不知道。