12.9. Comprehension Nested

12.9.1. Syntax

result = [<RETURN> for <VARIABLE> in <ITERABLE> for <VARIABLE> in <ITERABLE>]
result = [<RETURN>
          for <VARIABLE> in <ITERABLE>
          for <VARIABLE> in <ITERABLE>]

12.9.2. Example

>>> DATA = {
...     6: ['Doctorate', 'Prof-school'],
...     5: ['Masters', 'Bachelor', 'Engineer'],
...     4: ['HS-grad'],
...     3: ['Junior High'],
...     2: ['Primary School'],
...     1: ['Kindergarten'],
... }
>>>
>>>
>>> result = {}
>>> for lvl, titles in DATA.items():
...     for title in titles:
...         result[title] = lvl
>>>
>>> print(result)  
{'Doctorate': 6,
 'Prof-school': 6,
 'Masters': 5,
 'Bachelor': 5,
 'Engineer': 5,
 'HS-grad': 4,
 'Junior High': 3,
 'Primary School': 2,
 'Kindergarten': 1}
>>> DATA = {
...     6: ['Doctorate', 'Prof-school'],
...     5: ['Masters', 'Bachelor', 'Engineer'],
...     4: ['HS-grad'],
...     3: ['Junior High'],
...     2: ['Primary School'],
...     1: ['Kindergarten'],
... }
>>>
>>>
>>> result = {title: lvl
...           for lvl, titles in DATA.items()
...           for title in titles}
>>>
>>> print(result)  
{'Doctorate': 6,
 'Prof-school': 6,
 'Masters': 5,
 'Bachelor': 5,
 'Engineer': 5,
 'HS-grad': 4,
 'Junior High': 3,
 'Primary School': 2,
 'Kindergarten': 1}

12.9.3. Microbenchmark

>>> DATA = {
...     6: ['Doctorate', 'Prof-school'],
...     5: ['Masters', 'Bachelor', 'Engineer'],
...     4: ['HS-grad'],
...     3: ['Junior High'],
...     2: ['Primary School'],
...     1: ['Kindergarten'],
... }
>>> # %%timeit -r 1000 -n 1000
>>> result = {title: lvl
...           for lvl, titles in DATA.items()
...           for title in titles}
>>> # 2.22 µs ± 138 ns per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
>>> # %%timeit -r 1000 -n 1000
>>> result = {t:l for l,ts in DATA.items() for t in ts}
>>> # 2.22 µs ± 181 ns per loop (mean ± std. dev. of 1000 runs, 1000 loops each)
>>> # %%timeit -r 1000 -n 1000
>>> result = {}
>>> for lvl, titles in DATA.items():
...     for title in titles:
...         result[title] = lvl
>>> # 2.24 µs ± 152 ns per loop (mean ± std. dev. of 1000 runs, 1000 loops each)

12.9.4. Nested

>>> DATA = [
...     ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
...     (5.8, 2.7, 5.1, 1.9, 'virginica'),
...     (5.1, 3.5, 1.4, 0.2, 'setosa'),
...     (5.7, 2.8, 4.1, 1.3, 'versicolor'),
...     (6.3, 2.9, 5.6, 1.8, 'virginica'),
...     (6.4, 3.2, 4.5, 1.5, 'versicolor'),
...     (4.7, 3.2, 1.3, 0.2, 'setosa'),
... ]
>>>
>>>
>>> result = '\n'.join(','.join(str(x) for x in row) for row in DATA)
>>>
>>> print(result)
sepal_length,sepal_width,petal_length,petal_width,species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor
6.3,2.9,5.6,1.8,virginica
6.4,3.2,4.5,1.5,versicolor
4.7,3.2,1.3,0.2,setosa

12.9.5. Hybrid Solution

>>> DATA = [
...     ('sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species'),
...     (5.8, 2.7, 5.1, 1.9, 'virginica'),
...     (5.1, 3.5, 1.4, 0.2, 'setosa'),
...     (5.7, 2.8, 4.1, 1.3, 'versicolor'),
...     (6.3, 2.9, 5.6, 1.8, 'virginica'),
...     (6.4, 3.2, 4.5, 1.5, 'versicolor'),
...     (4.7, 3.2, 1.3, 0.2, 'setosa'),
... ]
>>>
>>>
>>> data = []
>>>
>>> for row in DATA:
...     line = ','.join(str(x) for x in row)
...     data.append(line)
>>>
>>> result = '\n'.join(data)
>>>
>>> print(result)
sepal_length,sepal_width,petal_length,petal_width,species
5.8,2.7,5.1,1.9,virginica
5.1,3.5,1.4,0.2,setosa
5.7,2.8,4.1,1.3,versicolor
6.3,2.9,5.6,1.8,virginica
6.4,3.2,4.5,1.5,versicolor
4.7,3.2,1.3,0.2,setosa

12.9.6. Code Readability

>>> 
... result = [user | dict(addresses)
...           for user in json.loads(DATA)
...             for i, address in enumerate(user.pop('addresses'), start=1)
...                 if (columns := [f'{key}{i}' for key in address.keys()])
...                     and (addresses := zip(columns, address.values()))]
>>> 
... result = [user | dict(addresses)
...           for user in json.loads(DATA)
...           for i, address in enumerate(user.pop('addresses'), start=1)
...           if (columns := [f'{key}{i}' for key in address.keys()])
...           and (addresses := zip(columns, address.values()))]

12.9.7. Assignments

Code 12.8. Solution
"""
* Assignment: Comprehension Nested Dict
* Type: class assignment
* Complexity: easy
* Lines of code: 1 lines
* Time: 5 min

English:
    1. Convert to `result: dict[str, str]`
    2. Use nested dict comprehension
    3. Run doctests - all must succeed

Polish:
    1. Przekonwertuj do `result: dict[str, str]`
    2. Użyj zagnieżdżonego rozwinięcia słownikowego
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * nested `for`
    * `dict.items()`
    * `str()`

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from pprint import pprint

    >>> type(result)
    <class 'dict'>

    >>> pprint(result, sort_dicts=False)
    {'Doctorate': 6,
     'Prof-school': 6,
     'Masters': 5,
     'Bachelor': 5,
     'Engineer': 5,
     'HS-grad': 4,
     'Junior High': 3,
     'Primary School': 2,
     'Kindergarten': 1}
"""

DATA = {
    6: ['Doctorate', 'Prof-school'],
    5: ['Masters', 'Bachelor', 'Engineer'],
    4: ['HS-grad'],
    3: ['Junior High'],
    2: ['Primary School'],
    1: ['Kindergarten'],
}

# Converted DATA. Note values are str not int!
# type: dict[str,str]
result = ...