列表不可散列,但元组可散列?

Posted

技术标签:

【中文标题】列表不可散列,但元组可散列?【英文标题】:List unhashable, but tuple hashable? 【发布时间】:2016-09-05 08:01:26 【问题描述】:

在How to hash lists? 中,有人告诉我应该先转换为元组,例如[1,2,3,4,5](1,2,3,4,5)

所以第一个不能散列,但第二个可以。为什么*


*我并不是真的在寻找详细的技术解释,而是在寻找直觉

【问题讨论】:

【参考方案1】:

主要是因为元组是不可变的。假设以下工作:

>>> l = [1, 2, 3]
>>> t = (1, 2, 3)
>>> x = l: 'a list', t: 'a tuple'

现在,当您执行l.append(4) 时会发生什么?您已经修改了字典中的键!远道而来!如果您熟悉散列算法的工作原理,这应该会吓到您。另一方面,元组是绝对不可变的。 t += (1,) 可能看起来像是在修改元组,但实际上并非如此:它只是创建了一个 new 元组,而您的字典键保持不变。

【讨论】:

val,很好的解释!你想在这里说什么:从远处! ?您是否认为这个问题太糟糕以至于无法投反对票? 我的意思是你已经从字典外部修改了字典的键:因为哈希表依赖于键和哈希的 1:1(ish) 对应,所以修改哈希背后的关键确实是一个非常糟糕的主意。 你还没有真正说为什么修改一个键是不好的——因为它改变了键的哈希值,这意味着存储键/值对的位置变得无效,这意味着你可以' t 不再检索键/值对。此外,哈希表将使用 ∞:1 键与哈希对应关系(所有键具有相同的哈希值)。受影响的只是他们的表现。 @Dunes 你能扩展一下吗?【参考方案2】:

你完全可以做到这一点,但我敢打赌你不会喜欢这种效果。

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

【讨论】:

【参考方案3】:

由于列表是可变的,如果你修改它,你也会修改它的散列,这会破坏拥有散列的意义(比如在集合或字典键中)。

编辑:我很惊讶这个答案经常得到新的支持,它写得很快。我觉得我现在需要让它变得更好。

所以 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。

【讨论】:

你能提供更多关于你的说法的合法引用和内容吗? 迟到总比没有好我猜:“高性能 python”o'reilly 书,有内置数据结构实现的描述。【参考方案4】:

因为列表是可变的,而元组不是。

【讨论】:

【参考方案5】:

答案很好。原因是可变性。如果我们可以使用字典中的列表作为键; (或任何可变对象)然后我们将能够通过改变该键(意外或有意)来更改该键。这将导致字典中键的哈希值发生变化,因此我们将无法通过该键从该数据结构中追溯值。 哈希值和哈希表用于通过将大数据映射到存储实际值条目的索引来轻松映射大数据。

在此处阅读有关它们的更多信息:-

Hash Tables & Hash Functions & Assosiative Arrays

【讨论】:

【参考方案6】:

并非每个元组都是可散列的。例如,元组包含列表作为元素。

x = (1,[2,3])
print(type(x))
print(hash(x))

【讨论】:

是的,所以你需要将这些嵌入列表变成元组。

以上是关于列表不可散列,但元组可散列?的主要内容,如果未能解决你的问题,请参考以下文章

使 python 用户定义的类可排序、可散列

为啥列表有 __reverse__() 特殊方法但元组在 Python 中没有?

自动使类可散列

Numpy 数组是可散列的吗?

Swift 中的可散列枚举

一个协议的可散列协议