views:

69

answers:

3

I have the following incoming value:

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

I want to process them so I get the following result:

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

This should work with arbitrary length of keys in the dictionary. Played with itertools in Python, but did not found anything matching these requirements.

+5  A: 
import itertools as it

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

Hm, this returns:

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

which is probably not exactly, what you want. Let me adapt it...

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

returns now:

[[{'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à ;-)

eumiro
Wow, that was fast and exactly what I was looking for. Thanks man. Sometimes asking is a lot faster than trying multiple hours on your own.
Sebastian Werner
the product argument is a bit convoluted: variants[varName] for varName in varNames -> variants.values()
tokland
In fact your first solution is better usable for me. Was just a misconception of me to ask for the one you built in the second :)
Sebastian Werner
A: 

I assume you want the cartesian product of all the keys? So if you had another entry, "foo", with values [1, 2, 3], then you'd have 18 total entries?

First, put the values in a list, where each entry is one of the possible variants in that spot. In your case, we want:

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

To do that:

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

Next we can use a Cartesian product function to expand it...

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

Note that this doesn't copy the dicts, so all the {'debug': 'on'} dicts are the same.

Claudiu
heh funny how all this code is equivalent to the one-liners before. nice to know that cartesian_product is built-in, i never knew!
Claudiu
itertools.product (and combinations, and permutations) was a great addition to itertools in python 2.6. However, I don't like the name. It should be, as you write, "cartesian_product", product should be the product of elements in an iterable: reduce(operator.mul, it)
tokland
then you'd have 18 total entries? Yes :) Didn't new the name of what I would like to have. Thanks for the tip.
Sebastian Werner
+2  A: 
[[{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'}]]
tokland
nice, much more concise
Claudiu