Python中以整数对为键的字典

14

在Python中,如何创建一个键为整数对的字典?

例如,如果我这样做:

mydict=dict()
mydict[ [1,2] ] = 'xxx'

我遇到了错误TypeError: unhashable type: 'list'

所以我想到了两个不同的解决方案:将字符串或元组用作键。

第一个解决方案似乎是将一对整数转换为其字符串表示形式:

mydict=dict()
mydict[ str(1)+" "+str(2) ] = 'xxx'

而第二个解决方案涉及元组:

mydict=dict()
mydict[ tuple([1,2]) ] = 'xxx'

经过一些实验,我发现元组解决方案比字符串解决方案慢。 是否有一种更有效和快速的方法来仅使用两个整数作为键?


提示:阅读PEP-8。它可以帮助您使代码更易读。 - Tim Pietzcker
3
我认为你的时机不好。我已经计时并发现直接构建元组(mydict[(x,y)])是最快的。如果你先构建一个列表,然后再将其转换为元组(这是不必要的),它需要大约1.7倍的时间(mydict[tuple([x,y])])。字符串方法最慢,慢2.6倍。如果元组已经存在,你可以使它更快,mydict[some_tuple]mydict[(x,y)]快0.7倍。 - Lauritz V. Thaulow
@lazyr 这只是针对插入还是访问也适用? - Hans Then
@Hans 只插入。我创建了一个预填充的列表,其中包含100,000个由随机整数组成的二元组和一个空的dict,然后计时了一个for循环迭代这些元组(for x,y in ...),使用各种方法将其插入到字典中。我对所有键使用None作为插入值。 - Lauritz V. Thaulow
最好同时用于插入和访问。 - linello
1个回答

21

你应该使用元组,它可以被哈希:

mydict = {}
mydict[(1, 2)] = 'xxx'
# or more concisely (@JamesHenstridge):
mydict[1,2] = 'xxx'

如果这实际上太慢了(不要进行不必要的优化),那么就给定一个整数的最大值,构建一个索引:

def index(a, b, maxB):
    return a*maxB + b

mydict[index(1, 2, max)] = 'xxx'

但要注意,函数调用可能会进一步减慢速度,因此您可以将函数内联以换取可读性,并使其更容易在其他地方复制粘贴时引入错误:

mydict[1*max + 2] = 'xxx'

顺便提一下,在 Stack Overflow 上有一个关于使用元组作为字典键的读取速度的问题:

Python tuples as keys slow?

进行了一点点的分析表明,内联索引比元组稍微快一些(<5%),两者都比索引快两倍。如果在 PyPy 中执行此操作,我希望索引版本(无论是内联还是非内联)会更快。

此外,如果您担心将数据插入到字典中的速度,那么您可能正在使用错误的数据结构,或者可能正在做太多不必要的工作。例如,将 CSV 文件解析为每行字段中的值,并以这种方式将值存储在字典中 data[line,field],如果您可以使行解析变为惰性的,并只解析您实际提取数据的行。也就是说,当你可以使用 dataLines = somecsv.readlines(); print getField(dataLines[7], 'date') 时,就不要使用 data = parseAll(somecsv); print data[7,'date']


@tim-pietzcker:谢谢,我没有注意到字典的赋值。 - Phil H
4
两个改进点:(1) mydict[1,2] 等同于 mydict[(1,2)],且更加简洁。 (2) index 函数调用可能比构建元组更耗费资源,因此你可能需要将算术运算嵌入到代码中。 - James Henstridge
@JamesHenstridge:1:有道理,不过我正在努力弄清楚将其变成隐式元组是否更具说明性——我怀疑使用显式元组更容易看出你可以有tup=(1,2); mydict[tup],不过也许我应该指出这一点。2:如果索引在当前范围之外被重复使用,我通常更喜欢明确定义索引函数,以避免容易出错但令人沮丧的错误。尽管如此,这仍然是一个重要的观点,所以我会进行更新。 - Phil H
3
关于函数更可靠和易于调试这一点没有争议。但是,如果你正在进行像这样的优化,那么它真正变快会很有帮助 :) - James Henstridge
@PhilH:不,我没有做parseAll或类似的事情。我正在编写一个图形类,其中边缘具有属性,因此当我指定源节点和目标节点时,属性被放置在字典中。 g.addEdge(1,2,"this edge attribute")实现为 def addEdge(self,src,targ,attrib): edges[ src, targ ] = attrib我还必须从它们的源和目标节点识别边缘属性, 所以我想知道最快的方法是什么。 - linello

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接