You ask for a list comprehension, but what you need is called a fold. In Python, that's the functools.reduce
function.
With a dictionary comprehension, you iterate over elements (list elements, dict items, whatever you want...) and you create or update an entry of the dict (you may also filter values). It's a purely sequential job : you have to take all decisions element by element, without information about the dictionary being built.
With a fold, you also iterate over elements, but you have the information about the dictionary being built, because at each step you take the current element (as in a dictionary comprehension) but also an accumulator to build the updated value of this accumulator. The return value of the fold is the last value of the accumulator, when all elements are processed.
Why do you need information about the dict being built ? Look at the data :
>>> data =[
... ['Peter', 'June', 100],
... ['Peter', 'July', 200],
... ['Peter', 'August', 120],
... ['Peter', 'September', 202],
... ['Bob', 'June', 300],
... ['Bob', 'July', 101],
... ['Bob', 'August', 200],
... ['Bob', 'September', 100]
... ]
When you read the element ['Bob', 'June', 300]
, you have two options :
- ignore the element : but you will miss the association
June -> Bob -> 300
.
- take the element : but you will replace and loose
June -> Peter -> 100
.
I'm pretty sure you don't have any way to bypass this limitation in one iteration (look at the excellent @Grismar answer: he has to perform two iterations to get the missing information).
With the fold, you can check if there are already values associated to June
, and update those values:
>>> import functools
>>> functools.reduce(lambda acc, x: {**acc, x[1]: {**acc.get(x[1], {}), x[0]: x[2]}}, data, {})
{'June': {'Peter': 100, 'Bob': 300}, 'July': {'Peter': 200, 'Bob': 101}, 'August': {'Peter': 120, 'Bob': 200}, 'September': {'Peter': 202, 'Bob': 100}}
Or with Python3.9+ merge operator:
>>> functools.reduce(lambda acc, x: acc | {x[1]: acc.get(x[1], {}) | {x[0]: x[2]}}, data, {})
{'June': {'Peter': 100, 'Bob': 300}, 'July': {'Peter': 200, 'Bob': 101}, 'August': {'Peter': 120, 'Bob': 200}, 'September': {'Peter': 202, 'Bob': 100}}
This is more complicated than a regular loop, but understandable though. The first argument of reduce
is a function that builds the updated value of the accumulator. The second argument is the iterable. The third argument is an optional initial value of the accumulator (here an empty dict). Let me explain the function.
For each new element, we update the value associated with x[1]
(the month) in the accumulator. The new value is the old one or an empty dict (acc.get(x[1], {})
) augmented with a new entry x[0]: x[2]
, that is name: value
. You can easily convince yourself that the last value of the accumulator is the expected dictionary.