如何合并值相同的Python字典列表中的key:value?

10 投票
3 回答
8264 浏览
提问于 2025-04-15 18:02

我是一名Python新手,想寻求一些帮助...

我有一个包含多个字典的Python列表,像这样:

list_dicts = [
{'id':'001', 'name':'jim', 'item':'pencil', 'price':'0.99'},
{'id':'002', 'name':'mary', 'item':'book', 'price':'15.49'},
{'id':'002', 'name':'mary', 'item':'tape', 'price':'7.99'},
{'id':'003', 'name':'john', 'item':'pen', 'price':'3.49'},
{'id':'003', 'name':'john', 'item':'stapler', 'price':'9.49'},
{'id':'003', 'name':'john', 'item':'scissors', 'price':'12.99'},
]

我想找出一种最佳方法,将那些“id”值相同的字典分组,然后把任何独特的键值对合并起来,创建一个新的字典列表,像这样:

list_dicts2 = [
{'id':'001', 'name':'jim', 'item1':'pencil', 'price1':'0.99'},
{'id':'002', 'name':'mary', 'item1':'book', 'price1':'15.49', 'item2':'tape', 'price2':'7.99'},
{'id':'003', 'name':'john', 'item1':'pen', 'price1':'3.49', 'item2':'stapler', 'price2':'9.49', 'item3':'scissors', 'price3':'12.99'},
]

到目前为止,我已经找到了如何将列表中的字典分组的方法:

myList = itertools.groupby(list_dicts, operator.itemgetter('id'))

但是我在构建新的字典列表时遇到了困难,具体来说:

1) 我想把额外的键值对添加到第一个“id”相同的字典中。

2) 我想给“item”和“price”这两个键设置新的名称(比如“item1”、“item2”、“item3”)。我觉得这样做有点笨拙,有没有更好的方法?

3) 我想遍历每个“id”的匹配项,以便构建一个字符串,方便后续输出。

我选择返回一个新的字典列表,主要是因为这样在传递给模板函数时,使用描述性键来设置变量会更方便(变量很多)。如果有更简洁的方法来实现这一点,我很想了解一下。再次强调,我对Python还很陌生,尤其是在处理这样的数据结构时。

3 个回答

0

我想把 list_dicts 里的东西合并成一个看起来更像这样的东西会更简单:

list_dicts2 = [{'id':1, 'name':'jim', 'items':[{'itemname':'pencil','price':'0.99'}], {'id':2, 'name':'mary', 'items':[{'itemname':'book','price':'15.49'}, {'itemname':'tape','price':'7.99'}]]

你也可以用一个元组的列表来表示 'items',或者用一个命名元组。

0

这看起来很像是个作业题。

正如上面提到的,有一些更合适的数据结构来处理这种数据,下面的几种变体可能会比较合理:

[ ('001', 'jim', [('pencil', '0.99')]), 
('002', 'mary', [('book', '15.49'), ('tape', '7.99')]), 
('003', 'john', [('pen', '3.49'), ('stapler', '9.49'), ('scissors', '12.99')])]

这可以用相对简单的方法来实现:

list2 = []
for id,iter in itertools.groupby(list_dicts,operator.itemgetter('id')):
  idList = list(iter)
  list2.append((id,idList[0]['name'],[(z['item'],z['price']) for z in idList]))

这个问题有趣的地方在于,当使用 groupby 时,提取 'name' 会比较困难,因为你不能直接跳过这个项目。

不过,回到最初的目标,你可以使用这样的代码(正如提问者所建议的):

list3 = []
for id,name,itemList in list2:
    newitem = dict({'id':id,'name':name})
    for index,items in enumerate(itemList):
        newitem['item'+str(index+1)] = items[0]
        newitem['price'+str(index+1)] = items[1]
    list3.append(newitem)
10

尽量避免使用复杂的嵌套数据结构。我觉得人们通常只有在频繁使用这些数据结构时才能理解它们。一旦程序完成,或者放置一段时间后,这些数据结构就会变得让人摸不着头脑。

对象可以用来更好地组织和丰富数据结构。例如,item(物品)和 price(价格)总是一起出现。那么这两条信息不如放在一个对象里:

class Item(object):
    def __init__(self,name,price):
        self.name=name
        self.price=price

同样,一个人似乎有一个 id(身份标识)、name(名字)和一堆个人物品:

class Person(object):
    def __init__(self,id,name,*items):
        self.id=id
        self.name=name
        self.items=set(items)

如果你接受使用这样的类的想法,那么你的 list_dicts 可以变成:

list_people = [
    Person('001','jim',Item('pencil',0.99)),
    Person('002','mary',Item('book',15.49)),
    Person('002','mary',Item('tape',7.99)),
    Person('003','john',Item('pen',3.49)),
    Person('003','john',Item('stapler',9.49)),
    Person('003','john',Item('scissors',12.99)), 
]

然后,为了根据 id 合并这些人,你可以使用 Python 的 reduce 函数,配合 take_items,这个函数可以把一个人的物品合并到另一个人那里:

def take_items(person,other):
    '''
    person takes other's items.
    Note however, that although person may be altered, other remains the same --
    other does not lose its items.    
    '''
    person.items.update(other.items)
    return person

把这些都放在一起:

import itertools
import operator

class Item(object):
    def __init__(self,name,price):
        self.name=name
        self.price=price
    def __str__(self):
        return '{0} {1}'.format(self.name,self.price)

class Person(object):
    def __init__(self,id,name,*items):
        self.id=id
        self.name=name
        self.items=set(items)
    def __str__(self):
        return '{0} {1}: {2}'.format(self.id,self.name,map(str,self.items))

list_people = [
    Person('001','jim',Item('pencil',0.99)),
    Person('002','mary',Item('book',15.49)),
    Person('002','mary',Item('tape',7.99)),
    Person('003','john',Item('pen',3.49)),
    Person('003','john',Item('stapler',9.49)),
    Person('003','john',Item('scissors',12.99)), 
]

def take_items(person,other):
    '''
    person takes other's items.
    Note however, that although person may be altered, other remains the same --
    other does not lose its items.    
    '''
    person.items.update(other.items)
    return person

list_people2 = [reduce(take_items,g)
                for k,g in itertools.groupby(list_people, lambda person: person.id)]
for person in list_people2:
    print(person)

撰写回答