【问题标题】:How to use a list of strings [which may contain any character] as keys?如何使用字符串列表[可能包含任何字符]作为键?
【发布时间】:2014-07-02 09:48:41
【问题描述】:

基本上,我“理解”使用其他容器作为键的歧义。 - 你比较参考还是比较其中的值(以及多深)。

你不能只专门化一个列表并制作一个自定义比较/哈希运算符吗?正如我在我的应用程序中所知道的,我希望通过字符串的值(当然还有相对顺序)来比较字符串列表。

那么我将如何为这类列表编写自定义哈希?或者以另一种方式 - 我将如何对列表进行字符串化而不导致您从引入分隔符(该分隔符可能是字符串的一部分)中得到的歧义?!

关于这个问题:https://wiki.python.org/moin/DictionaryKeys
那里直接说不能使用列表;

也就是说,为什么列表不能用作字典键的简单答案是列表不提供有效的 hash 方法。当然,显而易见的问题是,“为什么不呢?”

所以我写这篇文章是为了质疑是否有办法使列表可散列;以及如何制作令人满意的 Hash 方法。


作为我为什么想要这个的一个例子,请看下面的代码:

namelist = sorted(["Jan", "Frank", "Kim"])
commonTrait = newTraits()
traitdict = {}
traitdict[namelist] = commonTrait;

//later I wish to retrieve it:
trait = traitdict[sorted(["Jan", "Frank", "Kim"])]

在这种直接使用中,我认为“列表的排序”并不重要(排序只是在上面的代码中完成,以使列表在包含相同内容时始终相等)。

【问题讨论】:

  • 我认为代码示例将有助于阐明您的需求。
  • 您可以使用(嵌套)元组作为键很好。问题在于可变性。钥匙也仍然只是一个参考;如果您可以使用对同一对象的另一个引用来修改内容,那么突然之间就不可能在哈希表结构中再次找到相同的键。突变破坏散列表
  • 就目前而言,目前还不清楚您到底要做什么,到目前为止您已经尝试过什么以及遇到了什么问题。
  • 这类问题由我来决定使用哪种语言。首先创建一些东西然后注意到该语言没有你想要的结构是不是有点傻?我首先对其进行评估-您如何执行常见任务。 @MartijnPieters 嗯,所以 python 根本不复制密钥? - 但是两个内容相同但分别构造的列表会指向相同的值吗?
  • 这似乎是一个 XY 问题。你想通过使用字符串列表作为字典键来实现什么,对列表的内容有什么进一步的限制吗?

标签: python list dictionary stringification


【解决方案1】:

如果您需要使用字符串集合作为字典键,您有两个明显的选择:如果顺序很重要,请使用tuple

>>> d[tuple(['foo', 'bar'])] = 'baz'
>>> d['foo', 'bar']
baz
>>> d['bar', 'foo']
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ('bar', 'foo')

如果顺序不一定重要,请使用frozenset

>>> d[frozenset(['foo', 'bar'])] = 'baz'
>>> d[frozenset(['bar', 'foo'])]
'baz'
>>> d[frozenset(['bar', 'foo', 'bar'])]
'baz'

如果 count 重要但 ordering 不重要,请使用 sortedtuple

>>> d[tuple(sorted(['foo', 'bar']))] = 'baz'
>>> d[tuple(sorted(['bar', 'foo']))]
'baz'
>>> d[tuple(sorted(['bar', 'foo', 'bar']))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ('bar', 'bar', 'foo')

与 Perl 哈希或 JavaScript 对象属性不同,您不需要在 Python 中对字典键进行字符串化。


现在,关于可变的list 不可散列:Python 字典实现使用散列表结构。它特别要求并假设密钥:

  1. 该键实现了返回整数的__hash__ 方法
  2. 整数应尽可能广泛地分布在输出范围内,并尽可能统一映射。
  3. __hash__ 方法为同一对象返回一个不变的数字
  4. a == b 暗示 a.__hash__() == b.__hash__()

列表不能用作字典键,如:

>>> [].__hash__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

list 类不能提供可以同时满足所有要求的__hash__ 方法a == b 需要暗示a.__hash__() == b.__hash__()

(它*可以提供一个为每个列表返回 0 的实现,然后它会正常工作,但它会完全驳斥散列的使用,因为所有列表都将映射到字典中的同一个槽,因为哈希码会违反规则 2)。

也不能为列表创建__hash__ 方法:

>>> [].__hash__ = lambda x: 0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object attribute '__hash__' is read-only

当然,如果list 具有__hash__ 方法,我们总能看到会发生什么——我们创建一个列表的子类并在那里提供__hash__;哈希码的明显实现是tuple()

>>> class hashablelist(list):
...     def __hash__(self):
...         return hash(tuple(self))
... 
>>> x = hashablelist(['a', 'b', 'c'])
>>> y = hashablelist(['a', 'b', 'd'])
>>> d = {}
>>> d[x] = 'foo'
>>> d[y] = 'bar'
>>> d.items()
[(['a', 'b', 'c'], 'foo'), (['a', 'b', 'd'], 'bar')]
>>> y[2] = 'c'
>>> d.items()
[(['a', 'b', 'c'], 'foo'), (['a', 'b', 'c'], 'bar')]
>>> del d[x]
>>> del d[y]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: ['a', 'b', 'c']
>>> d.items()
[(['a', 'b', 'c'], 'bar')]
>>> x in d
False
>>> y in d
False
>>> x in d.keys()
True
>>> y in d.keys()
True

代码显示我们只是设法得到一个损坏的字典作为结果 - 无法直接通过键访问或删除 ['a', 'b', 'c'] -&gt; 'bar' 对,即使它在 .keys().values() 中可见和.items()

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-07-08
    • 1970-01-01
    • 2018-03-06
    • 2021-12-11
    • 2023-03-27
    • 2020-07-31
    • 2021-05-22
    • 1970-01-01
    相关资源
    最近更新 更多