2010-01-14 16 views
9

Python Novato aquí en busca de un poco de ayuda ...lista de Python de los dictados cómo fusionar la clave: valor donde los valores son los mismos?

Para un número variable de dicts en una lista de Python como:

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

Estoy tratando de encontrar la mejor manera de dicts grupo donde el valor de la tecla "id" es igual, a continuación, añadir/fusionar cualquier tecla único: valor y crear una nueva lista de dicts como:

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

hasta ahora, he descubierto la manera de agrupar los dicts en la lista con :

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

Pero estoy luchando con la forma de construir la nueva lista de dicts a:

1) Añadir las claves y valores adicionales a la primera instancia dict que tiene el mismo "id"

2) Establezca el nuevo nombre para las teclas "item" y "price" (p. Ej. "item1", "item2", "item3"). Esto me parece torpe, ¿hay una mejor manera?

3) bucle sobre cada partido "id" para construir una cadena para la salida tarde

que he elegido para devolver una nueva lista de dicts sólo por la conveniencia de pasar un diccionario a una función de plantillas donde Establecer variables con una clave descriptiva es útil (hay muchos vars). Si hay una manera más limpia y concisa para lograr esto, me gustaría aprender. De nuevo, soy bastante nuevo en Python y en el trabajo con estructuras de datos como esta.

Respuesta

9

tratar de evitar estructuras complejas de datos anidados. Creo que las personas tienden a asimilarlas solo mientras están usando intensamente la estructura de datos. Después de que el programa finaliza o se deja de lado por un tiempo, la estructura de datos rápidamente se vuelve desconcertante.

Los objetos se pueden utilizar para retener o incluso agregar riqueza a la estructura de datos de una manera más sana y organizada. Por ejemplo, parece que item y price siempre van de la mano. Así que las dos piezas de datos puede ser que también pueden emparejar en un objeto:

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

Del mismo modo, una persona parece tener una id y name y un conjunto de posesiones:

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

Si usted compra en la idea de utilizar clases como éstas, entonces su list_dicts podría convertirse en

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

entonces, para fusionar las personas en función de id, se puede utilizar la función de Python reduce, junto con take_items, que toma (fusiones) los artículos de una persona y les da a otro:

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 

Poniendo todo junto:

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

me imagino que sería más fácil de combinar los artículos en list_dicts en algo que se parece más a esto:

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

También es posible usar una lista de tuplas de 'artículos' o tal vez una tupla llamada .

0

Esto se parece mucho a un problema de tarea.

Como el cartel antes mencionadas, hay algunas estructuras de datos más adecuados para este tipo de datos, alguna variante en el siguiente podría ser razonable:

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

Esto se puede hacer con la relativamente simple:

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

Lo interesante de esta pregunta es la dificultad de extraer 'nombre' cuando se utiliza groupby, sin iterar más allá del elemento.

para volver a la meta original, sin embargo, se puede usar un código como éste (como lo sugiere el OP):

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) 
Cuestiones relacionadas