繁体   English   中英

在一个键上连接两个字典列表

[英]join two lists of dictionaries on a single key

给定nm字典为元素的列表,我想生成一个新列表,其中包含一组连接的字典。 每个字典都保证有一个称为“索引”的键,但可以有一组任意的键。 非索引键永远不会跨列表重叠。 例如,想象以下两个列表:

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}]

编辑:由于不能保证l3被排序( .values()没有特定顺序返回项目),你可以按照@user560833 的建议进行操作:

from operator import itemgetter

...

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

在 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)]

注意:这假设列表按index以相同的方式排序,OP 表示一般情况并非如此

为了概括这种情况,一种方法是创建一个自定义的 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)

现在,如果您有以下乱序列表:

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

使用sortedZipLongest函数代替itertools.zip_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'}]

这是执行此操作的单行:

[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 行)创建了一个列表列表,其中每个内部列表都是具有特定索引值的该索引/键的字典组合列表。 最外层的列表推导式为每个键创建一个元组对列表,并将其转换回字典列表。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM