【发布时间】:2016-09-05 08:01:26
【问题描述】:
在How to hash lists? 中,有人告诉我应该先转换为元组,例如[1,2,3,4,5] 到 (1,2,3,4,5)。
所以第一个不能散列,但第二个可以。为什么*?
*我并不是真的在寻找详细的技术解释,而是在寻找直觉
【问题讨论】:
标签: python list python-2.7 hash tuples
在How to hash lists? 中,有人告诉我应该先转换为元组,例如[1,2,3,4,5] 到 (1,2,3,4,5)。
所以第一个不能散列,但第二个可以。为什么*?
*我并不是真的在寻找详细的技术解释,而是在寻找直觉
【问题讨论】:
标签: python list python-2.7 hash tuples
主要是因为元组是不可变的。假设以下工作:
>>> l = [1, 2, 3]
>>> t = (1, 2, 3)
>>> x = {l: 'a list', t: 'a tuple'}
现在,当您执行l.append(4) 时会发生什么?您已经修改了字典中的键!远道而来!如果您熟悉散列算法的工作原理,这应该会吓到您。另一方面,元组是绝对不可变的。 t += (1,) 可能看起来像是在修改元组,但实际上并非如此:它只是创建了一个 new 元组,而您的字典键保持不变。
【讨论】:
你完全可以做到这一点,但我敢打赌你不会喜欢这种效果。
from functools import reduce
from operator import xor
class List(list):
def __hash__(self):
return reduce(xor, self)
现在让我们看看会发生什么:
>>> l = List([23,42,99])
>>> hash(l)
94
>>> d = {l: "Hello"}
>>> d[l]
'Hello'
>>> l.append(7)
>>> d
{[23, 42, 99, 7]: 'Hello'}
>>> l
[23, 42, 99, 7]
>>> d[l]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: [23, 42, 99, 7]
编辑:所以我想了更多。如果您将列表的 id 作为其哈希值返回,则可以使上述示例工作:
class List(list):
def __hash__(self):
return id(self)
在这种情况下,d[l] 会给你'Hello',但d[[23,42,99,7]] 和d[List([23,42,99,7])] 都不会(因为你正在创建一个新的[Ll]ist。
【讨论】:
由于列表是可变的,如果你修改它,你也会修改它的散列,这会破坏拥有散列的意义(比如在集合或字典键中)。
编辑:我很惊讶这个答案经常得到新的支持,它写得很快。我觉得我现在需要让它变得更好。
所以 set 和 dict 原生数据结构是用 hashmap 实现的。 Python 中的数据类型可能有一个神奇的方法 __hash__() 将用于 hashmap 的构造和查找。
只有不可变的数据类型(int, string, tuple, ...)才有这个方法,并且hash值是基于数据而不是对象的身份。 您可以通过
进行检查>>> a = (0,1)
>>> b = (0,1)
>>> a is b
False # Different objects
>>> hash(a) == hash(b)
True # Same hash
如果我们遵循这个逻辑,改变数据会改变散列,但是改变散列有什么意义呢?它违背了集合和字典或其他哈希用法的全部目的。
有趣的事实:如果您尝试使用字符串或整数的示例 -5 a is b 由于微优化(至少在 CPython 中)返回 True。
【讨论】:
因为列表是可变的,而元组不是。
【讨论】:
答案很好。原因是可变性。如果我们可以使用字典中的列表作为键; (或任何可变对象)然后我们将能够通过改变该键(意外或有意)来更改该键。这将导致字典中键的哈希值发生变化,因此我们将无法通过该键从该数据结构中追溯值。 哈希值和哈希表用于通过将大数据映射到存储实际值条目的索引来轻松映射大数据。
在此处阅读有关它们的更多信息:-
【讨论】:
并非每个元组都是可散列的。例如,元组包含列表作为元素。
x = (1,[2,3])
print(type(x))
print(hash(x))
【讨论】: