Python快速哈希可变对象

时间:2012-10-24 00:32:28

标签: python bytearray

我有一个bytearray,我需要将其用作字典的键。理想情况下,我不想做像bytearray大小的内存副本。反正有没有这样做? 基本上,

b = some bytearray
d[byte(b)] = x

有没有更快的方法呢?字节(b)是O(len(bytearray))操作,这是不可取的。

3 个答案:

答案 0 :(得分:6)

任何实际正常工作的哈希算法都将使用O(len(b))时间。所以答案是"有更快的方法来做到这一点"没有。

如果您的实际问题是内存用法,那么原则上您可以将__hash__方法添加到bytearray的子​​类中。但这是一个非常糟糕的主意。看看会发生什么:

>>> class HashableBytearray(bytearray):
...     def __hash__(self):
...         return hash(str(self))
... 
>>> h = HashableBytearray('abcd')
>>> hash(h)
-2835746963027601024
>>> h[2] = 'z'
>>> hash(h)
-2835746963002600949

因此,相同的对象可以散列到字典中的两个不同的位置,这不应该发生。它变得更糟:

>>> d = dict()
>>> hb1 = HashableBytearray('abcd')
>>> hb2 = HashableBytearray('abcd')
>>> d[hb1] = 0
>>> d[hb2] = 1
>>> d
{bytearray(b'abcd'): 1}

好的,到目前为止,非常好。值相等,因此字典中应该只有一个项目。一切都按预期工作。现在让我们看看当我们更改hb1时会发生什么:

>>> hb1[2] = 'z'
>>> d[hb2] = 2
>>> d
{bytearray(b'abzd'): 1, bytearray(b'abcd'): 2}

看看虽然hb2根本没有变化,但这次在词典中创建了一个新的键值对?

每次我将密钥传递给d时,该密钥等于'abcd'。但是因为第一个键的值在添加到字典后发生了,所以Python无法告诉新键的值与添加它时的旧键相同。现在字典中有两个键值对,当时应该只有一个键值对。

这只是使用可变值作为键可能导致不可预测和非常错误行为的许多方法之一。只需将bytearray转换为不可变类型,或者首先使用不可变类型。


对于好奇的人:肯定,buffer缓存了第一个哈希值,但这根本没有帮助。只有两个键值,因此这应该只生成两个dict条目:

>>> a, b, c = bytearray('abcd'), bytearray('abcd'), bytearray('abzd')
>>> a_buf, b_buf, c_buf = buffer(a), buffer(b), buffer(c)
>>> d = {b_buf:1, c_buf:2}
>>> b[2] = 'z'
>>> d[a_buf] = 0

但它产生了三个:

>>> d
{<read-only buffer for 0x1004a2300, size -1, offset 0 at 0x100499cb0>: 1, 
 <read-only buffer for 0x1004a2420, size -1, offset 0 at 0x100499cf0>: 0, 
 <read-only buffer for 0x1004a22d0, size -1, offset 0 at 0x100499c70>: 2}

答案 1 :(得分:4)

如果您关注时间,并且您使用的密钥始终是相同的对象,则可以使用其id(内存中的位置)作为您的密钥词典:

b = some byte array
d[id(b)] = x

如果您关心内存,可以在字节数组上使用良好的加密哈希函数,并且可能永远不会发生冲突(git,例如,使用sha1,并且some discussions关于internet关于无意中发生sha1碰撞的可能性{{3}}。如果您对这种无限小的风险感到满意,您可以:

b = some byte array
d[hashlib.sha1(b).hexdigest()] = x

time (每次计算哈希值)时,你的字节数组的大小为O(n),但是你可以读入不同的字节数组在稍后的时间,但表示相同的字节序列,将散列到相同的字典键。

而@senderle是绝对正确的;你不想使用一个实际上是可变的对象,当按值使用它时(而不是它的不可变函数,如id())作为字典的关键。用作字典键的对象的哈希值不得更改;它违反了字典对象期望的散列函数的不变量。

答案 2 :(得分:1)

我认为这可能接近你想要的。它相对较快,不会复制内存与bytearray的大小,但它 O(len(bytearray)) - 因为我想不出任何方法可以避免这种情况,并且总是产生独特的价值观。

def byte(ba):
    """ decode a bytearray as though it were a base 256 number """
    return reduce(lambda a,d: a*256 + d, ba, 0)

ba = bytearray('this is a bytearray')
d = {}
d[byte(ba)] = 42
ba[8] = 'X'  # now 'this is X bytearray'
d[byte(ba)] = 17  # accesses a separate entry in dict
print d