2010-10-06 21 views
23

ho il seguente valore in ingresso:Combinazioni da dizionario con i valori della lista utilizzando Python

variants = { 
    "debug" : ["on", "off"], 
    "locale" : ["de_DE", "en_US", "fr_FR"], 
    ... 
} 

voglio elaborarli in modo da ottenere il seguente risultato:

combinations = [ 
    [{"debug":"on"},{"locale":"de_DE"}], 
    [{"debug":"on"},{"locale":"en_US"}], 
    [{"debug":"on"},{"locale":"fr_FR"}], 
    [{"debug":"off"},{"locale":"de_DE"}], 
    [{"debug":"off"},{"locale":"en_US"}], 
    [{"debug":"off"},{"locale":"fr_FR"}] 
] 

Questo dovrebbe funzionare con lunghezza arbitraria di chiavi nel dizionario. Giocato con itertools in Python, ma non ha trovato nulla che corrisponda a questi requisiti.

+1

sei sicuro di non voler avere un elenco di dict a due elementi? – SilentGhost

risposta

31
import itertools as it 

varNames = sorted(variants) 
combinations = [dict(zip(varNames, prod)) for prod in it.product(*(variants[varName] for varName in varNames))] 

Hm, questo ritorna:

[{'debug': 'on', 'locale': 'de_DE'}, 
{'debug': 'on', 'locale': 'en_US'}, 
{'debug': 'on', 'locale': 'fr_FR'}, 
{'debug': 'off', 'locale': 'de_DE'}, 
{'debug': 'off', 'locale': 'en_US'}, 
{'debug': 'off', 'locale': 'fr_FR'}] 

, che probabilmente non è esattamente, ciò che si desidera. Lasciatemi adattarlo ...

combinations = [ [ {varName: val} for varName, val in zip(varNames, prod) ] for prod in it.product(*(variants[varName] for varName in varNames))] 

torna ora:

[[{'debug': 'on'}, {'locale': 'de_DE'}], 
[{'debug': 'on'}, {'locale': 'en_US'}], 
[{'debug': 'on'}, {'locale': 'fr_FR'}], 
[{'debug': 'off'}, {'locale': 'de_DE'}], 
[{'debug': 'off'}, {'locale': 'en_US'}], 
[{'debug': 'off'}, {'locale': 'fr_FR'}]] 

Voilà ;-)

+0

Wow, è stato veloce ed esattamente quello che stavo cercando. Grazie uomo. A volte chiedere è molto più veloce di provare più ore da solo. –

+2

In effetti la tua prima soluzione è meglio utilizzabile per me. Era solo un equivoco di me per chiedere quello che hai costruito nel secondo :) –

1

Presumo che si desidera che il prodotto cartesiano di tutte le chiavi? Quindi se avessi un'altra voce, "pippo", con valori [1, 2, 3], allora avresti 18 voci totali?

Innanzitutto, inserire i valori in un elenco, in cui ogni voce è una delle possibili varianti in quel punto. Nel tuo caso, vogliamo:

[[{'debug': 'on'}, {'debug': 'off'}], [{'locale': 'de_DE'}, {'locale': 'en_US'}, {'locale': 'fr_FR'}]] 

per farlo:

>>> stuff = [] 
>>> for k,v in variants.items(): 
    blah = [] 
    for i in v: 
     blah.append({k:i}) 
    stuff.append(blah) 


>>> stuff 
[[{'debug': 'on'}, {'debug': 'off'}], [{'locale': 'de_DE'}, {'locale': 'en_US'}, {'locale': 'fr_FR'}]] 

Poi si può usare una funzione di prodotto cartesiano per espanderlo ...

>>> def cartesian_product(lists, previous_elements = []): 
if len(lists) == 1: 
    for elem in lists[0]: 
     yield previous_elements + [elem, ] 
else: 
    for elem in lists[0]: 
     for x in cartesian_product(lists[1:], previous_elements + [elem, ]): 
      yield x 


>>> list(cartesian_product(stuff)) 
[[{'debug': 'on'}, {'locale': 'de_DE'}], [{'debug': 'on'}, {'locale': 'en_US'}], [{'debug': 'on'}, {'locale': 'fr_FR'}], [{'debug': 'off'}, {'locale': 'de_DE'}], [{'debug': 'off'}, {'locale': 'en_US'}], [{'debug': 'off'}, {'locale': 'fr_FR'}]] 

Si noti che questo doesn copiare i dict, quindi tutti i dit {'debug': 'on'} sono uguali.

+0

eh divertente come tutto questo codice è equivalente alle one-liner prima. bello sapere che cartesian_product è built-in, non ho mai saputo! – Claudiu

+0

itertools.product (e combinazioni e permutazioni) è stata una grande aggiunta a itertools in Python 2.6. Tuttavia, non mi piace il nome.Dovrebbe essere, come si scrive, "cartesian_product", il prodotto dovrebbe essere il prodotto di elementi in un iterable: riduci (operator.mul, it) – tokland

+0

allora avresti 18 voci totali? Sì :) Non è nuovo il nome di ciò che vorrei avere. Grazie per il consiglio. –

7
combinations = [[{key: value} for (key, value) in zip(variants, values)] 
       for values in itertools.product(*variants.values())] 

[[{'debug': 'on'}, {'locale': 'de_DE'}], 
[{'debug': 'on'}, {'locale': 'en_US'}], 
[{'debug': 'on'}, {'locale': 'fr_FR'}], 
[{'debug': 'off'}, {'locale': 'de_DE'}], 
[{'debug': 'off'}, {'locale': 'en_US'}], 
[{'debug': 'off'}, {'locale': 'fr_FR'}]] 
3

Questo è quello che uso:

from itertools import product 

def dictproduct(dct): 
    for t in product(*dct.itervalues()): 
     yield dict(zip(dct.iterkeys(), t)) 

che applicato al tuo esempio dà:

>>> list(dictproduct({"debug":["on", "off"], "locale":["de_DE", "en_US", "fr_FR"]})) 
[{'debug': 'on', 'locale': 'de_DE'}, 
{'debug': 'on', 'locale': 'en_US'}, 
{'debug': 'on', 'locale': 'fr_FR'}, 
{'debug': 'off', 'locale': 'de_DE'}, 
{'debug': 'off', 'locale': 'en_US'}, 
{'debug': 'off', 'locale': 'fr_FR'}] 

Trovo che questo è più leggibile di quella fodere di cui sopra.

Inoltre, restituisce un iteratore come itertools.product in modo che lasci all'utente se creare un'istanza di un elenco o semplicemente consumare i valori uno alla volta.

Problemi correlati