在我的程序中,我需要存储与许多(我们谈论数十万,数百万)游戏板状态相关的数据。为此,我使用了一个词典。
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
的字符串表示作为索引可以很好地解决我的问题。
答案 0 :(得分:1)
简答:改用 hashlib
。
如果您的程序无法处理冲突,或者您想要 save hash values 或使用多处理,则不应依赖 hash
。
Python 哈希函数将映射数据转换为 64 位(整数范围)。散列最基本的分析仅限于将其视为生日问题。有一个很好的 SO answer 和一个详细的 wiki page。典型的引用是“如果您的元素少于数十亿,则不必担心”。然而,这是非常简单的观点。
作为一个轶事:我最近对 hash
由人类手动创建的独特短字符串运行了 8.7e6
。 64 位散列的冲突次数的 mathematical expectation 是 4e-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(散列函数的结果)。
碰撞的风险来自两件事:
board_tuple
种可能性而B的大小为10⁶。然后碰撞的可能性很小。另一方面,如果你有1000 board_tuple
而H导致空间B为16,那么几乎可以确定会发生碰撞。但是不要太担心,哈希函数很好,我几乎可以肯定他们正在巧妙地处理碰撞与一些经典策略: