合并具有相同键的两个或多个字典

5

我有两个字典,分别是itemsu_items

items = {"A": 1, "B": 2, "C": 3}

u_items = {"D": 4, "B": 4, "E": 8, "C": 4}

我希望更新items字典,使用u_items实现,我这样做了:
items.update((k + '_1' if k in items else k, v) for k, v in u_items.items())

这样我就可以区分来自两个字典的键。

输出:

items = {'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4}

但是当我用另一个字典 n_items 更新 items 字典时,它会替换 B_1 的值,而不是让它变成 B_1_1。

n_items = {"C":7, "B":9}

items.update((k + '_1' if k in items else k, v) for k, v in n_items.items())

输出:

{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 9, 'E': 8, 'C_1': 7}

但是我希望输出结果是这样的:
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'B_1_1':9,'C_1_1':7}

或者像这样:
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'B_2':9,'C_2':7}

我该怎样做呢?

1
这是因为您没有检查是否存在 C_1C_2,等等。无论如何,随着 items 的增长,这将变得非常低效。不使用 list 的原因是什么? - Chris
我能否像 k + '_1' if k 一样检查 C_1 吗? - Rahul Sharma
这似乎应该使用列表来存储你的值,然后你可以使用 defaultdict 并追加值。 - Mark
7个回答

2
你可以迭代地完成这个操作:

最初的回答:

def combine(*args):
    result = {}
    for d in args:
        for key, value in d.items():
            key = str(key)
            while key in result:
                key += '_1'
            result[key] = value

    return result

print(combine(items, u_items, n_items))

输出:

{'A': 1,
 'B': 2,
 'C': 3,
 'D': 4,
 'B_1': 4,
 'E': 8,
 'C_1': 4,
 'C_1_1': 7,
 'B_1_1': 9}

谢谢gmds!但我正在另一个函数中使用它,所以是否可能在不创建函数的情况下执行此操作,因为我需要多次调用它,所以它需要成为一行或两行代码? - Rahul Sharma
1
@RahulSharma 我真的不明白那有什么关系...? - gmds

1
尽管这似乎有点像一个XY问题,但这里有一个丑陋的(我很确定是低效的)也不是非常通用的解决方案,其中包括:
通过将“_1”附加到现有键上来合并字典(一行完成所有操作),正如您要求的那样,虽然我建议(因为有时最短不一定是最好的):
  • 使用函数(避免重复代码(表达式))
  • 子类化dict,并覆盖其update方法(前一种方法的更好变体)
>>> items = {"A": 1, "B": 2, "C": 3}
>>> u_items = {"D": 4, "B": 4, "E": 8, "C": 4}
>>> n_items = {"C": 7, "B": 9}
>>>
>>> items.update((max([k1 + "_1" for k1 in items if k1 == k or k1.startswith(k + "_1")], key=len, default=k), v) for k, v in u_items.items())
>>> items
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4}
>>>
>>> items.update((max([k1 + "_1" for k1 in items if k1 == k or k1.startswith(k + "_1")], key=len, default=k), v) for k, v in n_items.items())
>>> items
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'C_1_1': 7, 'B_1_1': 9}
>>>
>>>
>>> # Merging an additional dictionary
...
>>> v_items = {"C": 25}
>>>
>>> items.update((max([k1 + "_1" for k1 in items if k1 == k or k1.startswith(k + "_1")], key=len, default=k), v) for k, v in v_items.items())
>>> items
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'C_1_1': 7, 'B_1_1': 9, 'C_1_1_1': 25}

0

由于我们通过添加 _count 处理重复项,例如下一个是 C_1,因此我们只需要检查第一个字符 lambda x:x.startswith(key[0]) 基本上遍历 result.keys() 并检查是否有任何共享第一个字符 因此,如果 keys 有 C 和 C_1,则我们会得到一个包含 2 个元素的列表 一旦我们有了那个列表,该列表的长度就是下一个 C 的计数,即 len('C','C_1') 是 2,因此接下来我们有 'C_2'

def combine(*args):
    result = {}
    for d in args:
        for key, value in d.items():
            key = str(key)
            if key in result:
                keys = filter( lambda x:x.startswith(key[0]), result.keys())
                keys = len(list(keys))
                key = f'{key}_{keys}'
            result[key] = value

    return result

items = {"A": 1, "B": 2, "C": 3}

u_items = {"D": 4, "B": 4, "E": 8, "C": 4}

n_items = {"C":7, "B":9}

print(combine(items, u_items, n_items))

{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'C_2': 7, 'B_2': 9}

[Program finished]

0

使用 operator.itemgetter

items = {'A':1, 'B':2, 'C':3}
u_items = {'D':4, 'B':4, 'E':8, 'C':4}
n_items = {"C":7, "B":9}

def update_dict(d1, d2):
    l = list(map(itemgetter(0), d1))
    d1.update(('_'.join([k,str(l.count(k))]) if k in l else k, v) 
             for k,v in d2.items())

update_dict(items, u_items)
update_dict(items, n_items)

使用u_items在第一个更新时输出:

{'A': 1, 'B': 2, 'B_1': 4, 'C': 3, 'C_1': 4, 'D': 4, 'E': 8}

输出在第二次更新使用:

{'A': 1,
 'B': 2,
 'B_1': 4,
 'B_2': 9,
 'C': 3,
 'C_1': 4,
 'C_2': 7,
 'D': 4,
 'E': 8}

0
你可以使用一个小的辅助函数:
d1 = {'A':1, 'B':2, 'B_1':3, 'B_1_1':4}
d2 = {'A':1, 'B':2}

def gen_key(key, dct):
    while key in dct:
        key += '_1'
    return key

d1.update((gen_key(k, d1), v) for k, v in d2.items())

print(d1)
# {'A': 1, 'B': 2, 'B_1': 3, 'B_1_1': 4, 'A_1': 1, 'B_1_1_1': 2}

0
海报的更新方法和if/else列表推导式是一个非常好的开始。 我认为关键的事情(请原谅双关语)是引入循环来找到一个可接受的键。这是Python3,但可以使用reduce来使其成为一行代码:
>>> import functools
>>> items = {"A": 1, "B": 2, "C": 3}
>>> u_items = {"D": 4, "B": 4, "E": 8, "C": 4}
>>> n_items = {"C":7, "B":9}
>>> items.update({functools.reduce(lambda c, n: c+n if c in items else c, ['_1']*2, k):*3 v for k, v in u_items.items()})
>>> items
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4}
>>> items.update({functools.reduce(lambda c,n: c+n if c in items else c, ['_1']*2, k): v for k, v in n_items.items()})
>>> items
{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'B_1': 4, 'E': 8, 'C_1': 4, 'C_1_1': 7, 'B_1_1': 9}

注意 - 你的渐进扩展列表 ['_1']*2 的长度必须与你要合并的列表数量一样长。如果需要类似于 C_1, C_2, ... 的键,可以尝试构建一个更有趣的 lambda 函数。


0

试试这个:

没有函数,也没有任何模块。

>>> for k, v in n_items.items():
...   _k = {k: v}
...   if k in items:
...      _k = {max(i for i in items if k in i)+'_1': v}
...   items.update(_k)
... 
>>> items
{'A': 1, 'D': 4, 'B_1': 4, 'B': 2, 'D_1': 5, 'X': 1, 'C_1_1': 7, 'C': 3, 'E': 8, 'C_1': 4, 'B_1_1': 9}
>>> 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接