匹配一组字典。最优雅的解决方案。Python。

6

给定两个字典列表,一个是新的,一个是旧的。这些字典表示两个列表中相同的对象。
我需要找出它们之间的差异,并生成一个新的字典列表,其中只包含新字典中的对象和旧字典中更新过的属性。
例如:

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

在这个例子中,我希望生成一个新列表,其中只包含来自list_new的新人和更新后的数据。通过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个项目的列表,这个过程将需要相同的时间。


1
你是从某个地方检索这个列表的吗?你能否使用 id 作为键将字典列表重构为一个字典? - Mahmoud Abdelkader
我尝试使用你的代码来比较你的输出和我的输出,但是它不起作用(语法错误等)。请修复,谢谢。 - Paolo
这些字典来自于mongodb。我正在尝试通过Django管理界面使其可编辑。我有一个典型的Django表单集,不想逐个推送每个字典,因为在具有表单集的页面上每次保存都会对数据库造成很多访问量。因此,我希望获取它,匹配它,然后一次性推送。 - Pol
9个回答

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]

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

(注意,如果没有字典推导式,代码会变得难以阅读)


这很不错。有5个循环。但是x5小于xx。如果x有时等于300。谢谢。 - Pol

1

你需要的是类似这样的东西:

l = []
for d in list_old:
    for e in list_new:
        if e['id'] == d['id']:
            l.append(dict(e, **d))
print l

点击这里了解如何合并字典。


1
你可以像这样做:
def match_dict(new_list, old_list):
    new_dict = dict((obj['id'], obj) for obj in new_list)
    old_dict = dict((obj['id'], obj) for obj in old_list)
    for k in new_dict.iterkeys():
        if k in old_dict:
            new_dict[k].update(old_dict[k])
        else:
            del new_dict[k]
    return new_dict.values()

如果您经常这样做,我建议将数据存储为字典,以ID作为键,而不是列表,这样您就不必每次都进行转换。
编辑:以下是一个示例,展示如何将数据存储在字典中。
list_new = [{'desc': 'cool guy', 'id': 1, 'name': 'bob'}, {'desc': 'bad guy', 'id': 2, 'name': 'Bill'}, {'desc': None, 'id': 3, 'name': 'Vasya'}]
# create a dictionary with the value of 'id' as the key
dict_new = dict((obj['id'], obj) for obj in list_new)
# now you can access entries by their id instead of having to loop through the list
print dict_new[2]
# {'id': 2, 'name': 'Bill', 'desc': 'bad guy'}

你说的字典作为键是什么意思?我能看到一些文档链接或者示例吗? - Pol

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]

编辑:函数内变量命名错误。


我喜欢那个解决方案。很漂亮。但是它已经被koblas提供了。谢谢。 - Pol
这里只有一个问题,它不能保存新对象。这也可能出现。但我没有提到它。 - Pol
仅供参考,此函数不返回与原始match_dict()函数匹配的结果。因为它反转了列表。 - koblas
@koblas,你是在指那些错别字吗?new_list/list_new和old_list/list_old。那些都是错误的。感谢你指出了这个问题。 - kevpie

1

对于旧列表中的每个字典,搜索具有相同ID的新列表中的字典,然后执行:old_dict.update(new_dict)

在更新后从新列表中消除每个new_dict,并在循环后将剩余的未使用的字典附加到其中。


0

如果您的顶层数据结构是字典而不是列表,那么效果会好得多。这样就可以变成:

dict_new.update(dict_old)

然而,针对您实际拥有的内容,请尝试以下方法:

result_list = []
for item in list_new:
    found_item = [d for d in list_old if d["id"] == item["id"]]
    if found_item:
        result_list.append(dict(item, **found_item[0]))

实际上,这仍然在一个循环内部有另一个循环(内部循环在列表推导式中“隐藏”),因此它仍然是O(n ** 2)。对于大型数据集,将其转换为字典,更新该字典,然后将其转换回列表无疑会更快。


0
你可能会喜欢这个:
def match_dict(new_list, old_list):
    id_new = [item_new.get("id") for item_new in list_new]
    id_old = [item_old.get("id") for item_old in list_old]

    for idx_old in id_old:
        if idx_old in id_new:
            list_new[id_new.index(idx_old)].update(list_old[id_old.index(idx_old)])

    return list_new

from pprint import pprint
pprint(match_dict(list_new, list_old))

输出:

[{'desc': 'cool guy', 'id': 1, 'name': 'boby', 'some_data': '12345'},
 {'desc': 'cool guy', 'id': 2, 'name': 'Bill', 'some_data': '12345'},
 {'desc': 'the man', 'id': 3, 'name': 'vasya', 'some_data': '12345'}]

0
[od for od in list_old if od['id'] in {nd['id'] for nd in list_new}]

这个不会更新新字典中附带的额外数据。 - Pol

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