Python使用值来处理重复键合并两个或多个字典

4
我正在合并一些有重复键的字典。值将不同,我想忽略较低值的记录。
dict1 = {1 :["in",1], 2 :["out",1], 3 :["in",1]}
dict2 = {1 :["out",2], 2 :["out",1]}

如果键相等,我希望新字典中包含key[0][1]值最大的键值对。合并这两个字典后的输出应为:
dict3 = {1 :["out",2], 2 :["out",1], 3 :["in",1]}

我所知道的唯一解决方法是运行一个循环,并使用条件来确定要添加到合并字典中的内容。有没有更符合Python风格的方法呢?

重复的键将非常少,不到1%,如果这对最终解决方案有任何影响。


如果您的字典值存储为[1,"in"]而不是["in",1](假设您只有2个值或多个值按重要性顺序排序),则这将像Counter(dict1) | Counter(dict2)一样简单,即多集的并集。 - jamylak
5个回答

3
一条单独的字典推导式可以实现这个功能。
from operator import itemgetter
{k: max(dict1.get(k, (None, float('-Inf'))), dict2.get(k, (None,float('-Inf'))),
key=itemgetter(1)) for k in dict1.viewkeys() | dict2.viewkeys()}

我想出了一个更hack的版本{k: max(dict1.get(k, []), dict2.get(k, []), key=lambda v: v[::-1]) for k in set(dict1).union(dict2)},但我更喜欢这个。 - jamylak
@fairywings78,你正在运行哪个版本的Python? - iruvar
这取决于None与任何其他值相比较都将小于它,但在3.x中不起作用,也不是最好的风格。 - Karl Knechtel
@KarlKnechtel,好的。已解决。 - iruvar
1
@fairywings78,如果你的合并字典被命名为dict3,只需执行以下操作即可获得它:dict3 = {k: max(dict1.get(k, (None, float('-Inf'))), dict2.get(k, (None,float('-Inf'))), key=itemgetter(1)) for k in dict1.viewkeys() | dict2.viewkeys()} - iruvar
显示剩余2条评论

3
一种Pythonic的解决方案应该严重依赖于Python标准库和可用的语法结构。这不仅可以简化代码,还可以提高性能。
在您的情况下,您可以从以下事实中受益:只有1%的键同时出现在两个字典中:
 conflictKeys = set(dict1) & set(dict2)      # get all keys, that are in both dictionaries
 solvedConflicts = { key: dict1[key] 
                          if dict1[key][1] > dict2[key][1] 
                          else dict2[key] 
                     for key in conflictKeys }  # dictionary with conflict keys only and their wanted value

 result = dict1.copy()                       # add values unique to dict1 to result
 result.update(dict2)                        # add values unique to dict2 to result
 result.update(solvedConflicts)              # add values occuring in both dicts to result

这个解决方案将尝试避免为两个字典的每个键运行“缓慢”的Python解释器,而是使用快速的Python库例程(编写在C中)。也就是说:
  • dict.update() 合并两个字典
  • set.intersection()(等同于 set1 & set2) 获取所有冲突
仅为解决冲突键,您需要使用Python解释器循环遍历所有条目。但即使在这里,您也可以从Pythonic结构"列表推导式"中受益,以获得更好的性能(与命令式for循环相比)。这是因为已解决冲突的内存可以一次性分配,而无需任何重新分配。命令式for循环需要逐个增加所得到的solvedConflicts元素,这需要大量的内存重新分配。

这是一个很棒的答案。 - John R

1
dict1 = {1 :["in",1], 2 :["out",1], 3 :["in",1]}

dict2 = {1 :["out",2], 2 :["out",1]}
vals = []
# get items from dict1 and common keys with largest values
for k, v in dict1.iteritems():
    if k in dict2:
        if dict2[k][1] > v[1]:
            vals.append((k, dict2[k]))
        else:
            vals.append((k,v))
    else:
        vals.append((k,v))
new_d = {}
# add all dict2 to a new dict
new_d.update(dict2) 

# add dict1 items and overwrite common keys with larger value
for k,v in vals:
    new_d[k] = v
print(new_d)
{1: ['out', 2], 2: ['out', 1], 3: ['in', 1]}

你也可以复制和删除:

cp_d1 = dict1.copy()
cp_d2 = dict2.copy()

for k, v in dict1.iteritems():
    if k in dict2:
        if dict2[k][1] > v[1]:
            del cp_d1[k]
        else:
            del cp_d2[k]
cp_d1.update(cp_d2)

print(cp_d1)
{1: ['out', 2], 2: ['out', 1], 3: ['in', 1]}

一些时间显示复制是最有效的,而使用 groupby 是最不有效的:
In [9]: %%timeit
   ...: vals = []
   ...: cp_d1 = dict1.copy()
   ...: cp_d2 = dict2.copy()
   ...: for k, v in dict1.iteritems():
   ...:     if k in dict2:
   ...:         if dict2[k][1] > v[1]:
   ...:             del cp_d1[k]
   ...:         else:
   ...:             del cp_d2[k]
   ...: cp_d1.update(cp_d2)
   ...: 

1000000 loops, best of 3: 1.61 µs per loop
In [20]: %%timeit


 ....: vals = []
   ....: for k, v in dict1.iteritems():
   ....:     if k in dict2:
   ....:         if dict2[k][1] > v[1]:
   ....:             vals.append((k, dict2[k]))
   ....:         else:
   ....:             vals.append((k,v))
   ....:     else:
   ....:         vals.append((k,v))
   ....: new_d = {}
   ....: new_d.update(dict2)
   ....: for k,v in vals:
   ....:     new_d[k] = v
   ....: 
100000 loops, best of 3: 2.11 µs per loop


In [10]: %%timeit                 
 {k: max(dict1.get(k), dict2.get(k), key=lambda x: x[1] if x else None)
  for k in dict1.viewkeys() | dict2.viewkeys()}
   ....: 
100000 loops, best of 3: 3.71 µs per loop

In [22]: %%timeit
   ....: l=dict2.items() +dict1.items() # if you are in python 3 use : list(dict1.items()) + list(dict2.items())
   ....: g=[list(g) for k,g in groupby(sorted(l),lambda x : x[0])]
   ....: dict([max(t,key=lambda x: x[1][1]) for t in g])
   ....: 
100000 loops, best of 3: 10.1 µs per loop


In [61]: %%timeit
   ....: conflictKeys = set(dict1) & set(dict2)  
   ....: solvedConflicts = { key: dict1[key] 
   ....:                       if dict1[key][1] > dict2[key][1] 
   ....:                       else dict2[key] 
   ....:                  for key in conflictKeys } 
   ....: result = dict1.copy()                     
   ....: result.update(dict2)                       
   ....: result.update(solvedConflicts)  
   ....: 

100000 loops, best of 3: 2.34 µs per loop

1

如果元素的交集较低,使用集合也很有帮助

def out_dict(dict1, dict2):
    dict3 = {}
    s1 = set(dict1)
    s2 = set(dict2)
    for i in s1-s2:
        dict3[i] = dict1[i]
    for i in s2-s1:
        dict3[i] = dict2[i]
    for i in s1.intersection(s2):
        dict3[i] = dict1[i] if dict1[i] >= dict2[i] else dict2[i]
    return dict3

集合差确保从列表中删除不同的元素,而交集则是用于字典之间的共同键。

我认为这种方法可能更快,因为很少会有重复的情况。今天下班后再仔细看看。 - fairywings78
谢谢您的建议。 - fairywings78
鉴于dict.viewkeys()(在2.7中)和dict.keys()(在3.x中)已经是类似集合的对象,因此无需创建新的集合。 - iruvar
我们可以使用viewkeys方法来进行交集操作吗? - thiruvenkadam

0
import operator

def choose_value(key, x, y):
    """Choose a value from either `x` or `y` per the problem requirements."""
    if key not in x:
        return y[key]
    if key not in y:
        return x[key]
    # "The maximum of x[key] and y[key], ordered by their [1] element"
    return max((x[key], y[key]), key=operator.itemgetter(1))

def merge(x, y):
    # "a dict mapping keys to the chosen value, using the union of the keys
    # from x and y as the result keys"
    return {
        key: choose_value(key, x, y)
        for key in x.keys() | y.keys()
    }

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