2012-02-23 13 views
22

Muszę połączyć listę słownika Pythona. Dla npscalanie kilku słowników python

dicts[0] = {'a':1, 'b':2, 'c':3} 
dicts[1] = {'a':1, 'd':2, 'c':'foo'} 
dicts[2] = {'e':57,'c':3} 

super_dict = {'a':[1], 'b':[2], 'c':[3,'foo'], 'd':[2], 'e':[57]}  

Napisałem następujący kod:

super_dict = {} 
for d in dicts: 
    for k, v in d.items(): 
     if super_dict.get(k) is None: 
      super_dict[k] = [] 
     if v not in super_dict.get(k): 
      super_dict[k].append(v) 

Może on być przedstawiony bardziej elegancko/zoptymalizowana?

Uwaga znalazłem inny question na SO ale jego o scalanie dokładnie 2 słowniki.

+0

@SvenMarnach Thats zbyt hojne z was! Gotowe. + 1-ta odpowiedź też :) – jerrymouse

+0

** Zobacz także **: [scalony słownik zagnieżdżony] (https://stackoverflow.com/questions/7204805/dictionaries-of-dictionaries-merge) – dreftymac

+0

** Zobacz także: ** [ podobne pytanie] (https://stackoverflow.com/questions/2365921/merging-python-dictionaries) – dreftymac

Odpowiedz

24

Można iteracyjne nad słownikami bezpośrednio - bez konieczności korzystania range. Metoda dyktowania setdefault wyszukuje klucz i zwraca wartość, jeśli została znaleziona. Jeśli nie zostanie znaleziony, zwraca wartość domyślną, a także przypisuje tę wartość domyślną do klucza.

super_dict = {} 
for d in dicts: 
    for k, v in d.iteritems(): # d.items() in Python 3+ 
     super_dict.setdefault(k, []).append(v) 

Ponadto, można rozważyć użycie defaultdict. To po prostu automatyzuje setdefault, wywołując funkcję zwracającą wartość domyślną, gdy klucz nie zostanie znaleziony.

import collections 
super_dict = collections.defaultdict(list) 
for d in dicts: 
    for k, v in d.iteritems(): # d.items() in Python 3+ 
     super_dict[k].append(v) 

Ponadto, jak Sven Marnach wnikliwie obserwowane, to wydaje się chcieć nie powielania wartości w swoich listach. W takim przypadku, set dostaje to, czego chce:

import collections 
super_dict = collections.defaultdict(set) 
for d in dicts: 
    for k, v in d.iteritems(): # d.items() in Python 3+ 
     super_dict[k].add(v) 
2

Może to być nieco bardziej eleganckie:

super_dict = {} 
for d in dicts: 
    for k, v in d.iteritems(): 
     l=super_dict.setdefault(k,[]) 
     if v not in l: 
      l.append(v) 

UPDATE: wykonana zmiana zaproponowana przez Sven

UPDATE: zmienione w celu uniknięcia duplikatów (dzięki Marcin i Steven)

+0

Nice. Proponuję 'dla d in dyktów:' zamiast 'dla i w xrange (len (dicts))'. –

+1

@SvenMarnach Nice, dokonałem tej zmiany. –

+1

Drobny nit. Duplikaty na kluczach '' c'': '[3, 'foo', 3]'. Przykładowy kod OP pokazuje, że '3' się nie powtórzy. –

11

Merge klucze wszystkie dyktety i dla każdego klucza należy zestawić listę wartości:

super_dict = {} 
for k in set(k for d in dicts for k in d): 
    super_dict[k] = [d[k] for d in dicts if k in d] 

The e xpression set(k for d in dicts for k in d) buduje zestaw wszystkich unikalnych kluczy wszystkich słowników. Dla każdego z tych unikalnych kluczy używamy listy zrozumienie [d[k] for d in dicts if k in d] do budowania listy wartości ze wszystkich dykt dla tego klucza.

Ponieważ wydaje się tylko do jednego z unikalny wartość każdego klawisza, możesz zamiast używać zestawów:

super_dict = {} 
for k in set(k for d in dicts for k in d): 
    super_dict[k] = set(d[k] for d in dicts if k in d) 
+0

Całkiem solidne. Myślę, że można to poprawić za pomocą wyjaśnienia. – Edwin

+0

@Edwin: Dzięki, dodałem trochę wyjaśnienia. –

+0

@SvenMarnach minor thing - z drugą wersją dostajemy dyktat zestawów zamiast dyktowania list - łatwo się z nimi obchodzi, jeśli ma to znaczenie dla OP. –

3

Nigdy nie zapominaj, że standardowe biblioteki mają bogactwo narzędzi do radzenia sobie z dicts i iteracji:

from itertools import chain 
from collections import defaultdict 
super_dict = defaultdict(list) 
for k,v in chain.from_iterable(d.iteritems() for d in dicts): 
    if v not in super_dict[k]: super_dict[k].append(v) 

Uwaga że if v not in super_dict[k] można uniknąć używając defaultdict(set) według odpowiedzi Stevena Rumbalskiego.

17
from collections import defaultdict 

dicts = [{'a':1, 'b':2, 'c':3}, 
     {'a':1, 'd':2, 'c':'foo'}, 
     {'e':57, 'c':3} ] 

super_dict = defaultdict(set) # uses set to avoid duplicates 

for d in dicts: 
    for k, v in d.iteritems(): 
     super_dict[k].add(v) 
+1

+1: Wydaje się dokładnie to, o co pytało (unikatowe elementy w wartościach), wykonane w sposób stosunkowo przejrzysty i na pewno efektywny (słowniki zniknęły za jednym razem, a wbudowany zestaw sprawia, że ​​utrzymanie tylko unikalne elementy szybko). – EOL

-2

Jestem trochę późno do gry, ale zrobiłem to w 2 linie bez uzależnień poza pytona samego:

flatten = lambda *c: (b for a in c for b in (flatten(*a) if isinstance(a, (tuple, list)) else (a,))) 
o = reduce(lambda d1,d2: dict((k, list(flatten([d1.get(k), d2.get(k)]))) for k in set(d1.keys() + d2.keys())), dicts) 
# output: 
# {'a': [1, 1, None], 'c': [3, 'foo', 3], 'b': [2, None, None], 'e': [None, 57], 'd': [None, 2, None]} 

choć jeśli nie dbają o zagnieżdżonych list, a następnie:

o2 = reduce(lambda d1,d2: dict((k, [d1.get(k), d2.get(k)]) for k in set(d1.keys() + d2.keys())), dicts) 
# output: 
# {'a': [[1, 1], None], 'c': [[3, 'foo'], 3], 'b': [[2, None], None], 'e': [None, 57], 'd': [[None, 2], None]} 
+1

Nie należy tworzyć wartości 'None', zgodnie z pytaniem. – EOL

1

Dla oneliner dodaje można stosować:

{key: {d[key] for d in dicts if key in d} for key in {key for d in dicts for key in d}}

choć czytelność skorzystaliby z nazewnictwa połączeniu zestawu kluczy:

combined_key_set = {key for d in dicts for key in d} 
super_dict = {key: {d[key] for d in dicts if key in d} for key in combined_key_set} 

Elegance może być przedmiotem dyskusji, ale osobiście wolę listowych przez pętle. :)

(Słownik i ustawione Ułatwienia są dostępne w Python 2.7/3.1 i nowsze).

-1

Wydaje się, że większość odpowiedzi korzystających listowych nie są takie czytelne. W przypadku zagubienia się w bałaganie odpowiedzi powyżej może to być pomocne (choć bardzo późno ...). Po prostu zapętluj elementy każdego z dyktów i umieść je w oddzielnym.

super_dict = {key:val for d in dicts for key,val in d.items()} 
+1

OP chciał zachować zachowane wartości '' c'': '{'c': [3, 'foo']}'. –

0

Moje rozwiązanie jest podobne do @senderle zaproponował, ale zamiast do pętli użyłem mapy

super_dict = defaultdict(set) 
map(lambda y: map(lambda x: super_dict[x].add(y[x]), y), dicts) 
Powiązane problemy