Python 键的嵌套字典或元组?
假设有这样一个结构:Python 键的嵌套字典或元组?,python,optimization,dictionary,Python,Optimization,Dictionary,假设有这样一个结构: {'key1' : { 'key2' : { .... { 'keyn' : 'value' } ... } } } 使用python,我试图确定两种不同方法的优缺点: {'key1' : { 'key2' : { .... { 'keyn' : 'value' } ... } } } # A. nested dictionary {('key1', 'key2', ...., 'keyn') : 'value'} # B. a dictionary with a tup
{'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):
- 内存占用
- 插入的复杂性(考虑冲突避免算法等)
- 查找中的复杂性
key1
到keyn
的整个组合来获得值
,您可以像下面我建议的那样翻转dict以获得O(nk*nv)(键数*值数)或使用上面的元组
方法
假设您需要在插入时构建元组
,并在需要获取值时再次构建,两者都将是O(nk),其中nk
是键的数量
嵌套的dict
版本可能更节省空间,如果嵌套相当深(有许多值共享一个键的偏序列表),并且获取值仍然是O(nk),但可能比元组版本慢
然而,插入速度会慢一些,尽管我无法量化它的速度。您必须为每次插入至少构造一层dict
,并测试是否存在
dict
s在以前的级别
递归defaultdict
s可以从编码的角度简化插入,但实际上不会加快速度
tupletuple
方法对于插入来说既简单又快速,但可能需要更多内存,具体取决于嵌套
我知道要求之前的原始答案 为什么不呢
{'key1' : 'value', 'key2' : 'value', .... 'keyn' : 'value' }
它只是在每个位置存储对value
的引用,而不是value
本身,因此内存使用量将小于嵌套的dict
版本,并且不会比元组
版本大很多,除非您有大量的value
s
有关Python标准类型操作的时间复杂性,请参阅
基本上,平均插入或获取一项是O(1)
获取某个值的所有键的平均值为O(n):
但是,如果添加键或搜索所有键是常规操作,则会翻转您的dict
。你想要:
{'value': [key1, key2, ... keyn]}
通过这种方式,您可以添加带有just
mydict[value]的键。append(newkey)
并获得带有justmydict[value]
的所有键,这两个键的平均值为O(1)。无需详细说明(无论如何,它们都高度依赖于实现,并且可能会被下一个天才(genius)宣布无效,并调整字典实现):
- 对于内存开销:每个对象都有一些开销(例如refcount和type;一个空对象是8字节,一个空元组是28字节),但哈希表需要存储哈希、键和值,并且通常使用比当前需要更多的存储桶来避免冲突。另一方面,元组不能调整大小,也不会发生冲突,即一个N元组可以简单地将N个指针分配给包含的对象并进行操作。这导致内存消耗方面的显著差异
- 对于查找和插入复杂性(两者在这方面是相同的):无论是字符串还是元组,在CPython的dict实现中冲突都不太可能发生,并且解决得非常有效。更多的键(因为您通过将键组合到元组中来展平键空间)可能会增加冲突的可能性,更多的键也会导致更多的bucket(请注意,当前的实现试图将负载因子保持在2/3之间),这反过来会降低冲突的可能性。此外,您不需要更多的散列(嗯,对于元组散列,再进行一次函数调用和一些C级xor,但这是可以忽略的)获得一个值
sys.getsizeof
),所以即使有(我的直觉说,已经不现实了)十级嵌套,您将有略大于1KB的差异-如果嵌套的dict有多个项(取决于确切的负载系数),那么差异可能会更小。对于一个拥有数百个这样的数据结构im内存的数据处理应用程序来说,这太大了,但大多数对象并没有那么频繁地创建
你应该简单地问问自己哪个模型更适合你的问题。考虑使用一组密钥,你需要一次获得一个可用值的所有键,而使用嵌套字典允许增量地到达那里。 我已经编写了一个小脚本来测试它。虽然它有一些局限性,但键是由线性分布的整数组成的(即
range(N)
),我的发现如下
通过三级嵌套,即dict[a][b][c]
vsdict[a,b,c]
每个子索引从0到99,我发现:
具有较大的值(列表(x代表范围(100)
):
开放性问题
- 为什么会这样
- 这是否会随着不同的指数而变化,例如非连续指数
{'value': [key1, key2, ... keyn]}
> 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))
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
>>>>>>> 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]
"