Let's work through the problem step by step.
The core difficulty is that we have a sequence of lists:
['a', 'b', 'x']
[1, 2, 3]
['123', '321', '456']
And we want to produce sequences consisting of element 0 of each list, element 1 of each list, etc. (Each of these sequences contains all the values for an output dict.) Which is to say, we want to transpose the lists, which is exactly what the built-in zip
is for:
# Feed the generator to `list` to unpack and view them
list(zip(
['a', 'b', 'x'],
[1, 2, 3],
['123', '321', '456']
))
Now we can sketch out a complete process:
- Get the keys and values of the input (in the same order).
- Transpose the values.
- For each sequence in the transposed values, match that sequence up with the keys to make a new dict.
The first two parts are easy:
keys, value_lists = d.keys(), d.values()
# Since the .values() are a sequence, while `zip` accepts multiple
# arguments, we need to use the `*` operator to unpack them as
# separate arguments.
grouped_values = zip(*value_lists)
To finish up, let's first figure out how to create a single result dict from a single one of the new_values
. It's easy to make a dict from a bunch of key-value pairs - we can feed that directly to dict
. However, we have instead a pair of sequences - the original .keys()
, and a result from the zip
. Clearly, the solution is to zip
again - we can make a trivial helper function to make sure everything is as clear as possible:
def new_dict(keys, values):
return dict(zip(keys, values))
Then all we need to do is repeatedly apply that function:
new_dicts = [new_dict(keys, values) for values in grouped_values]
Putting everything inline with short names for the sake of showing off gives:
new_dicts = [dict(zip(d.keys(), v)) for v in zip(*d.values())]
which is almost exactly Jab's answer (note that iterating over a dictionary gives the keys, so you can pass d
directly rather than d.keys()
to zip
since zip
will only be iterating over it).