匹配字典集合的优雅方案。Python

6 投票
9 回答
6687 浏览
提问于 2025-04-16 13:23

给定两个字典列表,一个是新的,一个是旧的。这些字典在两个列表中代表的是相同的对象。
我需要找出它们之间的不同之处,并生成一个新的字典列表,这个列表里只包含新的字典中的对象,以及旧字典中更新过的属性。
举个例子:

   list_new=[
             { 'id':1,
               'name':'bob',
               'desc': 'cool guy'
              },
             
             { 'id':2,
               'name':'Bill',
               'desc': 'bad guy'
              },

              { 'id':3,
               'name':'Vasya',
               'desc': None
              },
        ]

    list_old=[
             { 'id':1,
               'name':'boby',
               'desc': 'cool guy',
                'some_data' : '12345'
              },
             { 'id':2,
               'name':'Bill',
               'desc': 'cool guy',
               'some_data' : '12345'

              },
              { 'id':3,
               'name':'vasya',
               'desc': 'the man',
               'some_data' : '12345'
              },
              { 'id':4,
               'name':'Elvis',
               'desc': 'singer',
               'some_data' : '12345'
              },
            ]
            

在这个例子中,我想生成一个新的列表,里面只包含来自新列表的对象,并且这些对象的数据是更新过的。它们是通过id来匹配的。所以,Bob会变成Boby,Bill会变成酷哥,Vasya会变成那个男人。而Elvis则应该不在这个列表里。

给我一个优雅的解决方案,尽量减少循环的次数。

我有一种方法来解决这个问题,但这并不是最好的:

 def match_dict(new_list, old_list)
    ids_new=[]
    for item in new_list:
            ids_new.append(item['id'])
    result=[] 
    for item_old in old_medias:
        if item_old['id'] in ids_new:
            for item_new in new_list:
                if item_new['id']=item_old['id']
                    item_new['some_data']=item_old['some_data']
                    result.append(item_new)
    return result

我之所以有疑虑,是因为里面有一个循环嵌套循环。如果列表有2000个项目,处理的时间会变得很长。

9 个回答

2

因为我不知道你数据的具体限制,我假设每个列表中的id都是唯一的,并且你的列表只包含不可变类型(比如字符串、整数等),这些类型是可以被哈希的。

# first index each list by id
new = {item['id']: item for item in list_new}
old = {item['id']: item for item in list_old}

# now you can see which ids appeared in the new list
created = set(new.keys())-set(old.keys())
# or which ids were deleted
deleted =  set(old.keys())-set(new.keys())
# or which ids exists in the 2 lists
intersect = set(new.keys()).intersection(set(old.keys()))

# using the same 'conversion to set' trick,
# you can see what is different for each item
diff = {id: dict(set(new[id].items())-set(old[id].items())) for id in intersect}

# using your example data set, diff now contains the differences for items which exists in the two lists:
# {1: {'name': 'bob'}, 2: {'desc': 'bad guy'}, 3: {'name': 'Vasya', 'desc': None}}

# you can now add the new ids to this diff
diff.update({id: new[id] for id in created})
# and get your data back into the original format:
list_diff = [dict(data, **{'id': id}) for id,data in diff.items()]

这里使用的是Python 3的语法,但很容易转换成Python 2的语法。

编辑:下面是为Python 2.5写的相同代码:

new = dict((item['id'],item) for item in list_new)
old = dict((item['id'],item) for item in list_old)

created = set(new.keys())-set(old.keys())
deleted =  set(old.keys())-set(new.keys())
intersect = set(new.keys()).intersection(set(old.keys()))

diff = dict((id,dict(set(new[id].items())-set(old[id].items()))) for id in intersect)

diff.update(dict(id,new[id]) for id in created))
list_diff = [dict(data, **{'id': id}) for id,data in diff.items()]

(注意没有字典推导式后,代码的可读性变差了)

3

虽然没法把它写成一行,但这是一个更简单的版本:

def match_new(new_list, old_list) :
    ids = dict((item['id'], item) for item in new_list)
    return [ids[item['id']] for item in old_list if item['id'] in ids]
1

步骤:

  • 根据ID为list_old创建一个查找字典
  • 遍历list_new中的字典,如果在旧的列表中存在,就为每个创建一个合并的字典

代码:

def match_dict(new_list, old_list): 
    old = dict((v['id'], v) for v in old_list)
    return [dict(d, **old[d['id']]) for d in new_list if d['id'] in old]

编辑:函数内部的变量命名不正确。

撰写回答