2010-07-29 17 views
6

Tengo una lista muy larga de diccionarios con índices de cadenas y valores enteros. Muchas de las teclas son las mismas en todos los diccionarios, aunque no todas. Quiero generar un diccionario en el que las claves sean la unión de las claves en los diccionarios separados y los valores sean la suma de todos los valores correspondientes a esa clave en cada uno de los diccionarios. (Por ejemplo, el valor para la clave 'apple' en el diccionario combinado será la suma del valor de 'apple' en el primero, más la suma del valor de 'apple' en el segundo, etc.)Agregar elementos en una lista de diccionarios

Tengo lo siguiente, pero es bastante engorroso y toma edades para ejecutar. ¿Hay una manera más simple de lograr el mismo resultado?

comb_dict = {} 
for dictionary in list_dictionaries: 
    for key in dictionary: 
     comb_dict.setdefault(key, 0) 
     comb_dict[key] += dictionary[key] 
return comb_dict 

Respuesta

9

Aquí hay algunos microbenchmarks que sugieren f2 (ver a continuación) podría ser una mejora. f2 utiliza iteritems que le permite evitar una búsqueda dict extra en el bucle interno:

import collections 
import string 
import random 

def random_dict(): 
    n=random.randint(1,26) 
    keys=list(string.letters) 
    random.shuffle(keys) 
    keys=keys[:n] 
    values=[random.randint(1,100) for _ in range(n)]  
    return dict(zip(keys,values)) 

list_dictionaries=[random_dict() for x in xrange(100)] 

def f1(list_dictionaries): 
    comb_dict = {} 
    for dictionary in list_dictionaries: 
     for key in dictionary: 
      comb_dict.setdefault(key, 0) 
      comb_dict[key] += dictionary[key] 
    return comb_dict 

def f2(list_dictionaries):  
    comb_dict = collections.defaultdict(int) 
    for dictionary in list_dictionaries: 
     for key,value in dictionary.iteritems(): 
      comb_dict[key] += value 
    return comb_dict 

def union(dict_list): 
    all_keys = set() 
    for d in dict_list: 
     for k in d: 
      all_keys.add(k) 
    for key in all_keys: 
     yield key, sum(d.get(key,0) for d in dict_list) 

def f3(list_dictionaries): 
    return dict(union(list_dictionaries)) 

Éstos son los resultados:

% python -mtimeit -s"import test" "test.f1(test.list_dictionaries)" 
1000 loops, best of 3: 776 usec per loop 
% python -mtimeit -s"import test" "test.f2(test.list_dictionaries)" 
1000 loops, best of 3: 432 usec per loop  
% python -mtimeit -s"import test" "test.f3(test.list_dictionaries)" 
100 loops, best of 3: 2.19 msec per loop 
+0

Gracias! f2() realmente cortó aproximadamente el 80% del tiempo de espera para mi aplicación particular. YRMV, obviamente. – chimeracoder

1

Esto podría ser rápido también, pero realmente depende de sus datos. Evita todos los dicts cambiantes o listas adicionales - sólo un conjunto de todas las claves y un montón de lecturas :-)

from itertools import chain 

def union(dict_list): 
    all_keys = set(chain.from_iterable(dict_list)) 
    for key in all_keys: 
     yield key, sum(d.get(key,0) for d in dict_list) 

combined = dict(union(dict_list)) 
+0

Aunque este utiliza funciones más sofisticadas, no puedo imaginar que esto será más rápido (pero podría estar equivocado). En el código del OP, la lista de diccionarios solo se recorre una vez, al igual que todos los diccionarios. En su código, cada diccionario se cruza una vez para crear el conjunto de claves y luego la lista de dicts se cruza con las horas '# all_keys'. –

+0

Felix Kling: Bueno, acabo de intentarlo, cuando agrego un iterador (ver revisiones ;-) para atravesar solo una vez se vuelve aún más lento. Supongo que eso extra del juego es el problema. –

0

Usted podría tomar algo de inspiración de Google del mapa a reducir. Por lo que entiendo, fue diseñado para resolver solo este tipo de problema.

Cuestiones relacionadas