交换嵌套字典的内部和外部键

时间:2018-03-17 06:49:45

标签: python dictionary

我有以下字典

{'a':{'se':3, 'op':2}, 'b':{'se':4, 'op':3}}

我需要将其转换如下:

{'se':{'a':3, 'b': 4}, 'op':{'a':2,'b':3}}

这是我可以提出的以下代码:

from collections import defaultdict

a = {'a':{'se':3, 'op':2}, 'b':{'se':4, 'op':3}}
b = defaultdict(dict)
for key1, value1 in a.items():
    for key2, value2 in value1.items():
        b[key2].update({key1: value2})

以下完成工作,但我喜欢单行。是否有一个单行 - 或更好的方式(更好的性能,如消除两个循环)?

3 个答案:

答案 0 :(得分:1)

首先,初始化对内键和外键的引用:

outer_keys = a.keys()
inner_keys = list(a.values())[0].keys()

现在,使用字典理解来构建一个带有交换键的新字典:

b = { i : {o : v for o in outer_keys for v in a[o].values()} for i in inner_keys}

print(b)
{'op': {'a': 2, 'b': 3}, 'se': {'a': 2, 'b': 3}}

虽然我注意到非常不可读,但我更喜欢使用嵌套循环,就像你已经完成的那样。

答案 1 :(得分:1)

将其转变为单行可以多种方式实现,但所有这些都将是丑陋的。例如:

# Gather k2-k1-v2
g = ((k2,k1,v2) for k1,v1 in a.items() for k2,v2 in v1.items())
# Sort by k2
sg = sorted(g)
# Group by k2
gsg = itertools.groupby(sg, key=operator.itemgetter(0))
# Turn it into a dict of dicts
b = {k: {ksk[1]: ksk[2] for ksk in group} for (k, group) in gsg}

全部放在一起:

b = {k: {ksk[1]: ksk[2] for ksk in group} 
     for (k, group) in itertools.groupby(
         sorted(((k2,k1,v2) for k1,v1 in a.items() for k2,v2 in v1.items())),
         key=operator.itemgetter(0))}

嗯,这是一个表达式,如果您不知道它有多少列,您可以将它全部放在一行上。但它肯定不像原始版本那么可读。

至于表现?快速测试需要大约两倍的时间。 Coldspeed的版本介于两者之间。将一个列表更改为迭代器会使得它像原始示例一样在较小的dicts上稍微慢一些,但对于大型的更快,但是无论哪种方式,它在任何测试中都没有超过原始值,并且比使用itertools版本慢得多非常大的价值观。但是,如果性能确实很重要,那么你应该根据实际数据对它们进行测量。

如果你考虑一下,就不可能有任何方法来消除嵌套循环(除非用等效的东西替换其中一个循环,比如递归,或者根据你的例子发生的事实展开它在每个内部字典中恰好有两个项目 - 对于你真正的问题,这可能不是真的。毕竟,你必须访问每个内部字典的每个键,你不能在外部字典上循环。有可能将这些循环转换为理解循环而不是语句,或者在maplist内部(或者在某些Pandas函数内部?)中的C循环。我的版本和Coldspeed都把嵌套循环放在一个理解中,并且至少有一个额外的线性循环(它不会增加算法的复杂性,但可能会大大增加现实生活中的时间)对于像你的例子这样的小集合)被埋在内置函数中。但是,在进行更多整体工作的同时加快循环并不总是值得的权衡。

答案 2 :(得分:1)

因此,这改进了@ cs95,并提供了更具可读性的1行。 这里有2行-但其中一行可能已经具有内部键('k')。 关键是您可以使用字典'a'来传递值。

a = {'a':{'se':3, 'op':2}, 'b':{'se':4, 'op':3}}
k = list(a.values())[0].keys()
b = {i: {o: a[o][i] for o in a} for i in k}  # one line dict inversion
print(f'{a}\n{b}')

但是,如果执行此操作,则可能未使用最佳的数据结构。相反,您可以使用以元组为键的字典,例如

a = {('a', 'se'):3, ('a', 'op'):2, ('b', 'se'):4, ('b', 'op'):3}

然后您可以按元组位置进行排序,并按元组键进行过滤

c = sorted(a, key=lambda x:x[1])
d = sorted(a, key=lambda x:x[0])
e = list(filter(lambda x:x[0] == 'a', a))  # list 
print(f'a: {a}\nc: {c}\nd: {d}\ne: {e}')

收益

a: {('a', 'se'): 3, ('a', 'op'): 2, ('b', 'se'): 4, ('b', 'op'): 3}
c: [('a', 'op'), ('b', 'op'), ('a', 'se'), ('b', 'se')]
d: [('a', 'se'), ('a', 'op'), ('b', 'se'), ('b', 'op')]
e: [('a', 'se'), ('a', 'op')]

当然,您仍然可以使用键访问对象:

x = a['a', 'op']  # returns 2

如果您使用一组固定的键,也许最好使用枚举的元组而不是str。

相关问题