依赖Python哈希函数时,冲突的风险是什么?

时间:2015-12-17 08:54:05

标签: python python-2.7 hash

在我的程序中,我需要存储与许多(我们谈论数十万,数百万)游戏板状态相关的数据。为此,我使用了一个词典。

class BoardState(object):
    def __init__(self, ...):
        # ...
        self.board = [ [ None ] * self.cols for _ in xrange(self.rows) ]

    def __hash__(self):
        board_tuple = tuple([ tuple(row) for row in self.board ])
        return hash(board_tuple)

    # ...

self.board是一个2D列表,在我的主要用例中,有6行和7列。

在开始时,我使用dict个对象编入了BoardState。但是由于我没有将BoardState中存储的dict对象用于除了将来查找之外的其他目的,我注意到我可以通过使用hash(board_state)索引来节省内存(此版本使用的内存减少了4倍)

BoardState之后,两个不同的board个对象(内部具有不同的hash)会产生相同值的几率是多少?

为了澄清一下,这就是我如何存储和检索dict的值:

board_state = BoardState(...)
my_values[hash(board_state)] = { ... }
...
other_val_with_board_state = source_function()
retrieved = my_values[hash(other_val_with_board_state)]

(正如我之前提到的,我使用hash()的结果进行索引以节省内存,因为我之后不再使用BoardState个对象。)

UPDATE 现在我想知道是否使用board_state.board的字符串表示作为索引可以很好地解决我的问题。

3 个答案:

答案 0 :(得分:1)

简答:改用 hashlib


如果您的程序无法处理冲突,或者您想要 save hash values 或使用多处理,则不应依赖 hash

Python 哈希函数将映射数据转换为 64 位(整数范围)。散列最基本的分析仅限于将其视为生日问题。有一个很好的 SO answer 和一个详细的 wiki page。典型的引用是“如果您的元素少于数十亿,则不必担心”。然而,这是非常简单的观点。

作为一个轶事:我最近对 ​​hash 由人类手动创建的独特短字符串运行了 8.7e6。 64 位散列的冲突次数的 mathematical expectation4e-6。我得到了 32。有趣的事实:hash(chr(9786)) == hash(chr(58)+chr(38))('☺' 与 ':&' 碰撞)(从 Python3.8.10 开始)。

来自 hashlib 的加密函数是导致冲突的方式 more resistant。像 hashlib.sha256(pickle.dumps(my_obj,1)) 这样的东西甚至可能比转换为元组更快。


如果内存问题是散列的原因,首先应该首先考虑用较少的字节数来表示数据。首先想到的是指定 __slots__ 并减少嵌套对象的数量。然而,对于小对象来说,这将是一场艰苦的战斗,因为每个 Python 对象都需要大量的脚手架。

如果我们以国际象棋为例,完整状态 can be stored 为 24 字节或更舒适,为 32 字节(64 个单元格,每个单元格需要 4 位来表示其内容)。我们可以使用 python 获得的最好结果是 bytes,它将占用 65 位(33 字节的服务信息)并且需要额外的操作来将两个 4 位块推送到一个字节中。另一种选择可能是 bitarray.frozenbitarray,它需要 112 个字节来存储相同数量的有用信息(80 个字节的信息)。但是,嘿,它仍然胜过元组内的元组,其中每个元组有 40 bytes 的脚手架。

答案 1 :(得分:0)

虽然我不确定在散列后获得相同值的可能性是多少,但可能是有可能存在问题。

话虽如此,如果您不将查询中存储的BoardState对象用于查找以外的任何目的,您是否可以向BoardState添加id属性在__init__上唯一生成的类(即在创建每个新的BoardState对象后设置为全局计数器加1)?然后,您可以使用id作为字典的键,以便将来查找并避免任何潜在的碰撞问题。

答案 2 :(得分:0)

要知道冲突的风险,我们必须看一下哈希函数的实现。 主要思想是从空间开始,让我们通过散列函数H将A(变量board_tuple可以采用的所有形式)放到另一个空间B(散列函数的结果)。

碰撞的风险来自两件事:

  • 空格的大小:如果你有2 board_tuple种可能性而B的大小为10⁶。然后碰撞的可能性很小。另一方面,如果你有1000 board_tuple而H导致空间B为16,那么几乎可以确定会发生碰撞。
  • 哈希函数本身。如果散列函数是h(x)= 2,那么总会有碰撞。

但是不要太担心,哈希函数很好,我几乎可以肯定他们正在巧妙地处理碰撞与一些经典策略:

  • 重新运行哈希函数,直到没有冲突
  • 存储为元素数组,从而产生相同的哈希,而不是发生冲突 ....