2010-07-29 5 views
6

J'ai une très longue liste de dictionnaires avec des index de chaînes et des valeurs entières. La plupart des clés sont identiques dans les dictionnaires, mais pas toutes. Je veux générer un dictionnaire dans lequel les clés sont l'union des clés dans les dictionnaires séparés et les valeurs sont la somme de toutes les valeurs correspondant à cette clé dans chacun des dictionnaires. (Par exemple, la valeur de la clé 'apple' dans le dictionnaire combiné sera la somme de la valeur de 'apple' dans le premier, plus la somme de la valeur de 'apple' dans la seconde, etc.)Ajouter des éléments dans une liste de dictionnaires

J'ai ce qui suit, mais il est plutôt lourd et prend âges pour exécuter. Existe-t-il un moyen plus simple d'obtenir le même résultat?

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

Répondre

9

Voici quelques microbalignements qui suggèrent que f2 (voir ci-dessous) pourrait être une amélioration. f2 utilise iteritems qui vous permet d'éviter une recherche dict supplémentaire dans la boucle intérieure:

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

Voici les résultats:

% 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

Merci! f2() a effectivement coupé environ 80% du temps pour mon application particulière. YRMV, évidemment. – chimeracoder

1

Cela pourrait être rapide aussi, mais cela dépend vraiment de vos données. Il évite tous les changements dicts ou des listes supplémentaires - un seul ensemble de toutes les clés et beaucoup de lectures :-)

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

Bien que cela utilise des fonctions plus sophistiquées, je ne peux pas imaginer que ce sera plus rapide (mais je peux me tromper). Dans le code OP, la liste des dictionnaires n'est parcourue qu'une seule fois, de même que tous les dictionnaires. Dans votre code, chaque dictionnaire est traversé une fois pour créer l'ensemble des clés, puis la liste des dicts est traversée par les # 'all_keys' fois. –

+0

Felix Kling: Eh bien, j'ai juste essayé, quand j'ajoute un itérateur (voir révisions ;-) de ne le traverser qu'une fois qu'il devient encore plus lent. Devinez ce que cela implique de régler le problème. –

0

Vous pouvez prendre un peu d'inspiration de ce google map-reduce. D'après ce que je comprends, il a été conçu pour résoudre ce type de problème.

Questions connexes