Комбинации из словаря со значениями списка с использованием Python

У меня есть следующее входящее значение:

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

Я хочу обработать их, чтобы получить следующий результат:

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

Это должно работать с произвольной длиной ключей в словаре. Поигрался с itertools в Python, но ничего подходящего под эти требования не нашел.


person Sebastian Werner    schedule 06.10.2010    source источник
comment
Вы уверены, что не хотите иметь список двухэлементных диктов?   -  person SilentGhost    schedule 06.10.2010


Ответы (3)


import itertools as it

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

Хм, это возвращает:

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

что, вероятно, не совсем то, что вы хотите. Давай я адаптирую...

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

возвращается сейчас:

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

Вуаля ;-)

person eumiro    schedule 06.10.2010
comment
Ничего себе, это было быстро и именно то, что я искал. Спасибо чувак. Иногда спросить намного быстрее, чем пытаться несколько часов самостоятельно. - person Sebastian Werner; 06.10.2010
comment
На самом деле ваше первое решение лучше для меня. Было просто заблуждением, что я попросил тот, который вы построили во втором :) - person Sebastian Werner; 07.10.2010

Это то, что я использую:

from itertools import product

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

который применительно к вашему примеру дает:

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

Я считаю, что это более читабельно, чем один лайнер выше.

Кроме того, он возвращает итератор, такой как itertools.product, поэтому он оставляет пользователю решать, создавать ли экземпляр списка или просто потреблять значения по одному.

person snth    schedule 26.01.2017

Я полагаю, вам нужно декартово произведение всех ключей? Итак, если бы у вас была другая запись, «foo», со значениями [1, 2, 3], то всего у вас было бы 18 записей?

Сначала поместите значения в список, где каждая запись является одним из возможных вариантов в этом месте. В вашем случае мы хотим:

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

Для этого:

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

Затем мы можем использовать декартову функцию произведения, чтобы расширить его...

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

Обратите внимание, что это не копирует словари, поэтому все словари {'debug': 'on'} одинаковы.

person Claudiu    schedule 06.10.2010
comment
хех, забавно, что весь этот код эквивалентен предыдущим однострочникам. приятно знать, что cartesian_product встроен, я никогда не знал! - person Claudiu; 06.10.2010
comment
itertools.product (и комбинации, и перестановки) был отличным дополнением к itertools в python 2.6. Однако имя мне не нравится. Это должно быть, как вы пишете, cartesian_product, продукт должен быть произведением элементов в итерации: уменьшить (operator.mul, it) - person tokland; 06.10.2010
comment
тогда у вас будет 18 записей? Да :) Не изменил название того, что хотел бы иметь. Спасибо за совет. - person Sebastian Werner; 07.10.2010