Python 在一个键上连接两个词典列表

Python 在一个键上连接两个词典列表,python,list,dictionary,Python,List,Dictionary,给定以m字典作为元素的n列表,我想生成一个新列表,其中包含一组连接的字典。每个字典都保证有一个名为“index”的键,但除此之外还可以有任意一组键。非索引键在列表中永远不会重叠。例如,设想以下两个列表: l1 = [{"index":1, "b":2}, {"index":2, "b":3}, {"index":3, "green":"eggs"}] l2 = [{"index":1, "c":4}, {"index":2, "c":5}] (“b”永远不会出现在l2中,因为它出现在l1中,同

给定以
m
字典作为元素的
n
列表,我想生成一个新列表,其中包含一组连接的字典。每个字典都保证有一个名为“index”的键,但除此之外还可以有任意一组键。非索引键在列表中永远不会重叠。例如,设想以下两个列表:

l1 = [{"index":1, "b":2}, {"index":2, "b":3}, {"index":3, "green":"eggs"}]
l2 = [{"index":1, "c":4}, {"index":2, "c":5}]
“b”
永远不会出现在
l2
中,因为它出现在
l1
中,同样地,
“c”
也永远不会出现在
l1
中,因为它出现在
l2
中。)

我想制作一份合并列表:

l3 = [{"index":1, "b":2, "c":4}, 
      {"index":2, "b":3, "c":5}, 
      {"index":3, "green":"eggs"}]
在Python中最有效的方法是什么

from collections import defaultdict

l1 = [{"index":1, "b":2}, {"index":2, "b":3}, {"index":3, "green":"eggs"}]
l2 = [{"index":1, "c":4}, {"index":2, "c":5}]

d = defaultdict(dict)
for l in (l1, l2):
    for elem in l:
        d[elem['index']].update(elem)
l3 = d.values()

# l3 is now:

[{'b': 2, 'c': 4, 'index': 1},
 {'b': 3, 'c': 5, 'index': 2},
 {'green': 'eggs', 'index': 3}]
EDIT:由于
l3
不能保证被排序(
.values()
不按特定顺序返回项目),您可以按照@user560833的建议执行:

from operator import itemgetter

...

l3 = sorted(d.values(), key=itemgetter("index"))

这里有一个单行程序可以实现这一点:

[dict(sum([z.items() for z in z2],[])) for z2 in [[x3 for x3 in l1+l2 if x3['index']==key] for key in set([x1['index'] for x1 in l1]+[x2['index'] for x2 in l2])]]
没有列表那么优雅。我也不认为结果一定会按照你想要的方式排序

扩展一个衬里:

[
    dict(sum([z.items() for z in z2],[])) 
    for z2 in [
        [
            x3 for x3 in l1+l2 if x3['index']==key
        ] for key in set(
            [x1['index'] for x1 in l1]+[x2['index'] for x2 in l2]
        )
    ]
]

第6行的set表达式从两个列表中获取所有唯一的索引值。围绕它的列表理解(第3-9行)创建了一个列表列表,其中每个内部列表都是该索引/键的字典的组合列表,具有特定的索引值。最外层的列表理解为每个键创建一个元组对列表,并将其转换回字典列表。

在python 3.5或更高版本中,您可以

因此,对于python 3.5或更高版本,快速解决方案是:

from itertools import zip_longest

l3 = [{**u, **v} for u, v in zip_longest(l1, l2, fillvalue={})]

print(l3)
#[
#    {'index': 1, 'b': 2, 'c': 4}, 
#    {'index': 2, 'b': 3, 'c': 5}, 
#    {'index': 3, 'green': 'eggs'}
#]
但是,如果两个列表的大小相同,您可以简单地使用zip:

l3 = [{**u, **v} for u, v in zip(l1, l2)]

注意:这假设列表按
索引
以相同的方式排序,即

为了推广这种情况,一种方法是创建一个自定义的zip longest类型函数,该函数只在两个列表中的值与键匹配时才从中生成值

例如:

def sortedZipLongest(l1, l2, key, fillvalue={}):  
    l1 = iter(sorted(l1, key=lambda x: x[key]))
    l2 = iter(sorted(l2, key=lambda x: x[key]))
    u = next(l1, None)
    v = next(l2, None)

    while (u is not None) or (v is not None):  
        if u is None:
            yield fillvalue, v
            v = next(l2, None)
        elif v is None:
            yield u, fillvalue
            u = next(l1, None)
        elif u.get(key) == v.get(key):
            yield u, v
            u = next(l1, None)
            v = next(l2, None)
        elif u.get(key) < v.get(key):
            yield u, fillvalue
            u = next(l1, None)
        else:
            yield fillvalue, v
            v = next(l2, None)
使用
sortedZipLongest
功能代替
itertools.zip\u longest

l3 = [{**u, **v} for u, v in sortedZipLongest(l1, l2, key="index", fillvalue={})]
print(l3)
#[{'index': 0, 'green': 'ham'},
# {'index': 1, 'b': 2, 'c': 4},
# {'index': 2, 'b': 3, 'c': 5},
# {'index': 3, 'green': 'eggs'},
# {'index': 4, 'b': 4, 'green': 'ham'}]
而原始方法会产生不正确的答案:

l3 = [{**u, **v} for u, v in zip_longest(l1, l2, fillvalue={})]
print(l3)
#[{'index': 1, 'b': 2, 'c': 4},
# {'index': 2, 'b': 3, 'c': 5},
# {'index': 0, 'green': 'ham'},
# {'index': 4, 'b': 4, 'green': 'ham'}]

即将实现与此非常接近的功能。之后需要对l3进行排序-不能保证列表按索引顺序排列。e、 g.
来自操作员导入itemgetter;l3.sort(key=itemgetter(“index”)
而不是off
用于l-in(l1,l2):对于l中的elem:
最好直接使用itertools.chain(l1,l2)中的elem
这是非常有效和可读的,并且作为一个额外的功能,不需要调用任何zip函数(这往往很快就会变得复杂)。与使用
next
生成器获取相应密钥的dict理解相比,这要快几个数量级。是否保证dict中“索引”项的值将与该dict在列表中的位置匹配?否-不保证“索引”将匹配dict在列表中的位置。使用
sum
展平列表效率低下-您应该使用
itertools.chain
来代替:这是
sortedZipLongest
的最佳解决方案。我认为您可以通过将所有
None
值替换为
fillvalue
或简单地
{}
来简化它,因为
fillvalue
必须是一个字典,以便以后使用
**
解包。
l3 = [{**u, **v} for u, v in zip_longest(l1, l2, fillvalue={})]
print(l3)
#[{'index': 1, 'b': 2, 'c': 4},
# {'index': 2, 'b': 3, 'c': 5},
# {'index': 0, 'green': 'ham'},
# {'index': 4, 'b': 4, 'green': 'ham'}]