Python список диктов, как объединить ключ: значение, где значения совпадают - PullRequest
9 голосов
/ 15 января 2010

Python newb здесь ищет некоторую помощь ...

Для переменного количества диктов в списке 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) Добавить дополнительные ключи и значения к первому экземпляру dict, который имеет тот же «id»

2) Установите новое имя для клавиш "item" и "price" (например, "item1", "item2", "item3"). Это кажется мне неуклюжим, есть ли лучший способ?

3) Зацикливаться на каждом совпадении "id", чтобы создать строку для последующего вывода

Я решил возвращать новый список диктов только из-за удобства передачи дикта в шаблонную функцию, где полезно задавать переменные с помощью описательного ключа (есть много переменных). Если есть более понятный и лаконичный способ сделать это, мне было бы интересно узнать. Опять же, я довольно новичок в Python и в работе с такими структурами данных.

Ответы [ 3 ]

9 голосов
/ 15 января 2010

Старайтесь избегать сложных вложенных структур данных. Я считаю, что люди склонны Грок их только тогда, когда они интенсивно используют структуру данных. После программа завершена или отложена на некоторое время, структура данных быстро становится загадочным.

Объекты могут использоваться для сохранения или даже добавления богатства к структуре данных более рациональным и организованным способом. Например, кажется, 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, вы можете использовать функцию reduce Python, вместе с 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)
0 голосов
/ 15 января 2010

Это очень похоже на домашнее задание.

Как упомянуто выше, есть несколько более подходящих структур данных для данных такого типа, поэтому может быть разумным вариант следующего:

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

Интересным в этом вопросе является сложность извлечения «имени» при использовании группового режима без повторения элемента.

Чтобы вернуться к исходной цели, вы можете использовать код, подобный этому (как предложено ОП):

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)
0 голосов
/ 15 января 2010

Я полагаю, было бы проще объединить элементы в 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'}]]

Вы также можете использовать список кортежей для «элементов» или, возможно, именованный кортеж.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...