假设有这样的结构:
{'key1' : { 'key2' : { .... { 'keyn' : 'value' } ... } } }
使用python,我试图确定两种不同方法的优点/缺点:
{'key1' : { 'key2' : { .... { 'keyn' : 'value' } ... } } } # A. nested dictionary
{('key1', 'key2', ...., 'keyn') : 'value'} # B. a dictionary with a tuple used like key
然后我有兴趣知道,最好的(A或B)是什么:
答案 0 :(得分:10)
不进入细节(无论如何都是高度依赖于实现的,并且可能会被下一个天才无效并调整字典实现):
你知道,虽然存在一些记忆差异,但性能上应该没有明显的差异。我想,后者不会引人注目。单元素dict是140字节,十元素元组也是140字节(根据Python 3.2 sys.getsizeof
)。因此即使使用(已经不切实际的,我的直觉)十级嵌套,你的差异也会略微超过一KB - 如果嵌套的dicts有多个项目(取决于确切的加载因子),可能会更少。对于拥有数百个这样的数据结构内存的数据运算应用程序来说,这太过分了,但大多数对象都不是经常创建的。
您应该问自己哪种型号更适合您的问题。考虑到第二种方式要求您一次获得值的所有键,而第二种方法允许逐步获得值。
答案 1 :(得分:2)
如果你需要使用key1
到keyn
的整个组合来获取value
,你可以按照我在下面建议的O(nk * nv)翻转dict(数量为密钥*值的数量)或使用上面的tuple
方法。
假设您需要在插入时构建tuple
,并在需要获取值时再次构建{两者将是O(nk),其中nk
是键的数量。
嵌套的dict
版本可能会更加节省空间,如果你的嵌套相当深(有很多值共享一个部分有序的键列表),获取一个值仍然是O(nk) ,但可能比元组版本慢。
然而,插入速度会慢一些,但我无法量化它的速度。您必须为每个插入构建至少一层dict
,并测试是否存在
dict
以前的水平。
递归defaultdict
有many recipes,可以简化从编码角度的插入,但实际上并不会加快速度。
tuple
方法更简单,插入更快,但可能会占用更多内存,具体取决于您的嵌套。
在我了解要求之前的原始答案
为什么不
{'key1' : 'value', 'key2' : 'value', .... 'keyn' : 'value' }
它只是在每个位置存储value
的引用,而不是value
本身,所以内存使用量 less 比嵌套的dict
版本,并且没有比tuple
版本大得多,除非你有非常多的value
。
有关Python标准类型操作的时间复杂性,请参阅the Python wiki。
基本上,平均插入或获得一个项目是O(1)。
获取值的所有键平均为O(n):
[key for key in mydict if mydict[key] == value]
但是,如果通常的操作是添加密钥或搜索所有密钥,则会翻转dict
。你想要:
{'value': [key1, key2, ... keyn]}
通过这种方式,您只需mydict[value].append(newkey)
即可添加密钥,只需mydict[value]
即可获得所有密钥,两者的平均值均为O(1)。
答案 2 :(得分:0)
我写了一个小脚本来测试它。但是它有一些限制,键是由线性分布的整数(即range(N)
)组成的,我的发现如下。
使用3级嵌套,即dict[a][b][c]
vs dict[a,b,c]
,其中每个子索引从0到99,我发现以下内容:
使用较大的值(list(x for x in range(100)
)):
> memory.py nested Memory usage: 1049.0 MB > memory.py flat Memory usage: 1149.7 MB
并且值较小([]
):
> memory.py nested Memory usage: 134.1 MB > memory.py flat Memory usage: 234.8 MB
#!/usr/bin/env python3
import resource
import random
import itertools
import sys
import copy
from os.path import basename
from collections import defaultdict
# constants
index_levels = [100, 100, 100]
value_size = 100 # try values like 0
def memory_usage():
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
_object_mold = list(x for x in range(value_size)) # performance hack
def create_object():
return copy.copy(_object_mold)
# automatically create nested dict
# http://code.activestate.com/recipes/578057-recursive-defaultdict/
f = lambda: defaultdict(f)
my_dict = defaultdict(f)
# options & usage
try:
dict_mode = sys.argv[1]
if dict_mode not in ['flat', 'nested']: # ugly hack
raise Error()
except:
print("Usage: {} [nested | flat]".format(basename(sys.argv[0])))
exit()
index_generator = [range(level) for level in index_levels]
if dict_mode == "flat":
for index in itertools.product(*index_generator):
my_dict[index] = create_object()
elif dict_mode == "nested":
for index in itertools.product(*index_generator):
sub_dict = my_dict
for sub_index in index[:-1]: # iterate into lowest dict
sub_dict = sub_dict[sub_index]
sub_dict[index[-1]] = create_object() # finally assign value
print("Memory usage: {:.1f} MB".format(memory_usage() / 1024**2))
答案 3 :(得分:0)
我对嵌套字典和带元组的字典进行了循环,检索和插入的测试。它们是一层深的2000.000值。我还使用已创建的元组对元组dict进行了检索和插入。
这些是结果。我认为您不能真正将结论绑定到标准开发人员。
-
keydictinsertion: Mean +- std dev: 615 ms +- 42 ms
keydictretrieval: Mean +- std dev: 475 ms +- 77 ms
keydictlooping: Mean +- std dev: 76.2 ms +- 7.4 ms
nesteddictinsertion: Mean +- std dev: 200 ms +- 7 ms
nesteddictretrieval: Mean +- std dev: 256 ms +- 32 ms
nesteddictlooping: Mean +- std dev: 88.5 ms +- 14.4 ms
Test were the tuple was already created for the keydict
keydictinsertionprepared: Mean +- std dev: 432 ms +- 26 ms
keydictretrievalprepared: Mean +- std dev: 267 ms +- 14 ms
-
如您所见,nesteddict通常比使用单个键的dict更快。即使在不使用元组创建步骤的情况下直接给keydict一个元组,插入仍然要慢得多。似乎额外创建一个内部dict不需要花费太多。 Defaultdict可能有一个快速的实现。实际上,不必创建元组时,检索实际上几乎相等,与循环相同。
使用perf从命令行进行测试。脚本如下。
>>>>>>> nesteddictinsertion
python -m perf timeit -v -s "
from collections import defaultdict
" "
d = defaultdict(dict)
for i in range(2000):
for j in range(1000):
d[i][j] = 1
"
>>>>>>> nesteddictlooping
python -m perf timeit -v -s "
from collections import defaultdict
d = defaultdict(dict)
for i in range(2000):
for j in range(1000):
d[i][j] = 1
" "
for i, inner_dict in d.items():
for j, val in inner_dict.items():
i
j
val
"
>>>>>>> nesteddictretrieval
python -m perf timeit -v -s "
from collections import defaultdict
d = defaultdict(dict)
for i in range(2000):
for j in range(1000):
d[i][j] = 1
" "
for i in range(2000):
for j in range(1000):
d[i][j]
"
>>>>>>> keydictinsertion
python -m perf timeit -v -s "
from collections import defaultdict
" "
d = {}
for i in range(2000):
for j in range(1000):
d[i, j] = 1
"
>>>>>>> keydictinsertionprepared
python -m perf timeit -v -s "
from collections import defaultdict
keys = [(i, j) for i in range(2000) for j in range(1000)]
" "
d = {}
for key in keys:
d[key] = 1
"
>>>>>>> keydictlooping
python -m perf timeit -v -s "
from collections import defaultdict
d = {}
for i in range(2000):
for j in range(1000):
d[i, j] = 1
" "
for key, val in d.items():
key
val
"
>>>>>>> keydictretrieval
python -m perf timeit -v -s "
from collections import defaultdict
d = {}
for i in range(2000):
for j in range(1000):
d[i, j] = 1
" "
for i in range(2000):
for j in range(1000):
d[i, j]
"
>>>>>>> keydictretrievalprepared
python -m perf timeit -v -s "
from collections import defaultdict
d = {}
keys = [(i, j) for i in range(2000) for j in range(1000)]
for key in keys:
d[key] = 1
" "
for key in keys:
d[key]
"