5.1. Loop Comprehension

5.1.1. Rationale

>>> result = []
>>>
>>> for x in range(0,5):
...     result.append(x)
>>>
>>> print(result)
[0, 1, 2, 3, 4]
>>> result = [x for x in range(0,5)]
>>>
>>> print(result)
[0, 1, 2, 3, 4]

Syntax:

result = [<RETURN> for <VARIABLE> in <ITERABLE>]
result = [<RETURN> for <VARIABLE> in <ITERABLE> if <CONDITION>]
result = [<RETURN>
          for <VARIABLE> in <ITERABLE>
          for <VARIABLE> in <ITERABLE>
          if <CONDITION>
          and <CONDITION>
          or <CONDITION>]

Convention:

  • Use shorter variable names

  • x is common name

5.1.2. Comprehensions and Generator Expression

  • Comprehensions executes instantly

  • Generator expression executes lazily

List Comprehension:

>>> list(x for x in range(0,5))
[0, 1, 2, 3, 4]
>>>
>>> [x for x in range(0,5)]
[0, 1, 2, 3, 4]

Set Comprehension:

>>> set(x for x in range(0,5))
{0, 1, 2, 3, 4}
>>>
>>> {x for x in range(0,5)}
{0, 1, 2, 3, 4}

Dict Comprehension:

>>> dict((x,x) for x in range(0,5))
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
>>>
>>> {x:x for x in range(0,5)}
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}

Tuple Comprehension:

>>> tuple(x for x in range(0,5))
(0, 1, 2, 3, 4)

Generator Expression:

>>> (x for x in range(0,5))  # doctest: +ELLIPSIS
<generator object <genexpr> at 0x...>
>>> _ = list(x for x in range(0,5))      # list comprehension
>>> _ = tuple(x for x in range(0,5))     # tuple comprehension
>>> _ = set(x for x in range(0,5))       # set comprehension
>>> _ = dict((x,x) for x in range(0,5))  # dict comprehension
>>> _ = [x for x in range(0,5)]          # list comprehension
>>> _ = (x for x in range(0,5))          # generator expression
>>> _ = {x for x in range(0,5)}          # set comprehension
>>> _ = {x:x for x in range(0,5)}        # dict comprehension

5.1.3. Comprehensions or Generator Expression

>>> data = [x for x in range(0,5)]
>>>
>>> list(data)
[0, 1, 2, 3, 4]
>>> print(data)
[0, 1, 2, 3, 4]
>>> data = (x for x in range(0,5))
>>>
>>> list(data)
[0, 1, 2, 3, 4]
>>> print(data)  # doctest: +ELLIPSIS
<generator object <genexpr> at 0x...>
>>> from inspect import isgenerator
>>>
>>>
>>> data = [x for x in range(0,5)]
>>> isgenerator(data)
False
>>> from inspect import isgenerator
>>>
>>>
>>> data = (x for x in range(0,5))
>>> isgenerator(data)
True

Comprehension:

>>> data = [x for x in range(0,10)]
>>>
>>> for x in data:  # doctest: +NORMALIZE_WHITESPACE
...     print(x, end=' ')
...     if x == 3:
...         break
0 1 2 3
>>>
>>> for x in data:  # doctest: +NORMALIZE_WHITESPACE
...     print(x, end=' ')
...     if x == 6:
...         break
0 1 2 3 4 5 6
>>>
>>> print(list(data))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>>
>>> print(list(data))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Generator:

>>> data = (x for x in range(0,10))
>>>
>>> for x in data:  # doctest: +NORMALIZE_WHITESPACE
...     print(x, end=' ')
...     if x == 3:
...         break
0 1 2 3
>>>
>>> for x in data:  # doctest: +NORMALIZE_WHITESPACE
...     print(x, end=' ')
...     if x == 6:
...         break
4 5 6
>>>
>>> print(list(data))
[7, 8, 9]
>>>
>>> print(list(data))
[]

5.1.4. List Comprehension

Pattern:

>>> result = []
>>>
>>> for x in range(0,5):
...     result.append(x)
>>>
>>> print(result)
[0, 1, 2, 3, 4]

List comprehension:

>>> [x for x in range(0,5)]
[0, 1, 2, 3, 4]
>>>
>>> list(x for x in range(0,5))
[0, 1, 2, 3, 4]

Examples:

>>> [x+1 for x in range(0,5)]
[1, 2, 3, 4, 5]
>>>
>>> [x-1 for x in range(0,5)]
[-1, 0, 1, 2, 3]
>>>
>>> [x**2 for x in range(0,5)]
[0, 1, 4, 9, 16]
>>>
>>> [2**x for x in range(0,5)]
[1, 2, 4, 8, 16]
>>> list(x+1 for x in range(0,5))
[1, 2, 3, 4, 5]
>>>
>>> list(x-1 for x in range(0,5))
[-1, 0, 1, 2, 3]
>>>
>>> list(x**2 for x in range(0,5))
[0, 1, 4, 9, 16]
>>>
>>> list(2**x for x in range(0,5))
[1, 2, 4, 8, 16]

5.1.5. Set Comprehension

Pattern:

>>> result = set()
>>>
>>> for x in range(0,5):
...     result.add(x)
>>>
>>> print(result)
{0, 1, 2, 3, 4}

Set comprehension:

>>> {x for x in range(0,5)}
{0, 1, 2, 3, 4}
>>>
>>> set(x for x in range(0,5))
{0, 1, 2, 3, 4}

Examples:

>>> {x+10 for x in range(0, 5)}
{10, 11, 12, 13, 14}
>>>
>>> set(x+10 for x in range(0, 5))
{10, 11, 12, 13, 14}

5.1.6. Dict Comprehension

Pattern:

>>> result = dict()
>>>
>>> for x in range(0,5):
...     result.update({x:x})
>>>
>>> print(result)
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}

Dict comprehension:

>>> {x:x for x in range(0,5)}
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}
>>>
>>> dict((x,x) for x in range(0,5))
{0: 0, 1: 1, 2: 2, 3: 3, 4: 4}

Modify dict key:

>>> {x+10:x for x in range(0,5)}
{10: 0, 11: 1, 12: 2, 13: 3, 14: 4}
>>>
>>> dict((x+10,x) for x in range(0,5))
{10: 0, 11: 1, 12: 2, 13: 3, 14: 4}

Modify dict value:

>>> {x:x+10 for x in range(0,5)}
{0: 10, 1: 11, 2: 12, 3: 13, 4: 14}
>>>
>>> dict((x,x+10) for x in range(0,5))
{0: 10, 1: 11, 2: 12, 3: 13, 4: 14}

Modify dict key and value:

>>> {x+10:x+10 for x in range(0,5)}
{10: 10, 11: 11, 12: 12, 13: 13, 14: 14}
>>>
>>> dict((x+10,x+10) for x in range(0,5))
{10: 10, 11: 11, 12: 12, 13: 13, 14: 14}

5.1.7. Tuple Comprehension

Pattern:

>>> result = tuple()
>>>
>>> for x in range(0,5):
...     result += (x,)
>>>
>>> print(result)
(0, 1, 2, 3, 4)

Tuple comprehension:

>>> tuple(x for x in range(0,5))
(0, 1, 2, 3, 4)

Generator Expression:

>>> (x for x in range(0,5))  # doctest: +ELLIPSIS
<generator object <genexpr> at 0x...>

Example:

>>> tuple(x+10 for x in range(0,5))
(10, 11, 12, 13, 14)
>>> (x+10 for x in range(0,5))  # doctest: +ELLIPSIS
<generator object <genexpr> at 0x...>
>>> result = (x+10 for x in range(0,5))
>>> tuple(result)
(10, 11, 12, 13, 14)

5.1.8. Appending

>>> result = [1, 2, 3]
>>> result += [x for x in range(4,10)]
>>>
>>> print(result)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> result = [1, 2, 3] + [x for x in range(4,10)]
>>>
>>> print(result)
[1, 2, 3, 4, 5, 6, 7, 8, 9]

5.1.9. Map

Applying function to each output element:

>>> [float(x) for x in range(0,5)]
[0.0, 1.0, 2.0, 3.0, 4.0]

Applying function to each output element:

>>> [pow(2,x) for x in range(0,5)]
[1, 2, 4, 8, 16]

Small notation explanation:

>>> *X,y = (5.7, 2.8, 4.1, 1.3, 'versicolor')
>>>
>>> X = [5.7, 2.8, 4.1, 1.3]
>>> y = 'versicolor'
>>>
>>> for x in X:
...     print(x)
5.7
2.8
4.1
1.3
>>>
>>> x1 = 5.7
>>> x2 = 2.8
>>> x3 = 4.1
>>> x4 = 1.3

Using list comprehension for filtering:

>>> 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'),
...         (7.0, 3.2, 4.7, 1.4, 'versicolor')]
>>>
>>>
>>> [features for *features,label in DATA]  # doctest: +NORMALIZE_WHITESPACE
[['Sepal length', 'Sepal width', 'Petal length', 'Petal width'],
 [5.8, 2.7, 5.1, 1.9],
 [5.1, 3.5, 1.4, 0.2],
 [5.7, 2.8, 4.1, 1.3],
 [6.3, 2.9, 5.6, 1.8],
 [6.4, 3.2, 4.5, 1.5],
 [4.7, 3.2, 1.3, 0.2],
 [7.0, 3.2, 4.7, 1.4]]
>>>
>>> [tuple(features) for *features,label in DATA]  # doctest: +NORMALIZE_WHITESPACE
[('Sepal length', 'Sepal width', 'Petal length', 'Petal width'),
 (5.8, 2.7, 5.1, 1.9),
 (5.1, 3.5, 1.4, 0.2),
 (5.7, 2.8, 4.1, 1.3),
 (6.3, 2.9, 5.6, 1.8),
 (6.4, 3.2, 4.5, 1.5),
 (4.7, 3.2, 1.3, 0.2),
 (7.0, 3.2, 4.7, 1.4)]
>>>
>>> [tuple(X) for *X,y in DATA]  # doctest: +NORMALIZE_WHITESPACE
[('Sepal length', 'Sepal width', 'Petal length', 'Petal width'),
 (5.8, 2.7, 5.1, 1.9),
 (5.1, 3.5, 1.4, 0.2),
 (5.7, 2.8, 4.1, 1.3),
 (6.3, 2.9, 5.6, 1.8),
 (6.4, 3.2, 4.5, 1.5),
 (4.7, 3.2, 1.3, 0.2),
 (7.0, 3.2, 4.7, 1.4)]

5.1.10. Filter

Example 1:

>>> result = []
...
>>> for x in range(0,5):
...     if x % 2 == 0:
...         result.append(x)
>>>
>>> print(result)
[0, 2, 4]
>>> result = [x for x in range(0,5) if x%2==0]
>>>
>>> print(result)
[0, 2, 4]

Example 2:

>>> 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'),
...         (7.0, 3.2, 4.7, 1.4, 'versicolor')]
>>>
>>>
>>> [features for *features,label in DATA if label=='setosa']  # doctest: +NORMALIZE_WHITESPACE
[[5.1, 3.5, 1.4, 0.2],
 [4.7, 3.2, 1.3, 0.2]]
>>>
>>> [X for *X,y in DATA if y=='setosa']  # doctest: +NORMALIZE_WHITESPACE
[[5.1, 3.5, 1.4, 0.2],
 [4.7, 3.2, 1.3, 0.2]]

Using list comprehension for filtering with more complex expression:

>>> 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'),
...         (7.0, 3.2, 4.7, 1.4, 'versicolor')]
>>>
>>>
>>> def is_setosa(species):
...     if species == 'setosa':
...         return True
...     else:
...         return False
>>>
>>>
>>> [X for *X,y in DATA if is_setosa(y)]  # doctest: +NORMALIZE_WHITESPACE
[[5.1, 3.5, 1.4, 0.2],
 [4.7, 3.2, 1.3, 0.2]]

5.1.11. Value Leaking

Single value leaking:

>>> result = []
>>>
>>> for x in range(0,5):
...     result.append(x)
>>>
>>> print(x)
4
>>> result = [x for x in range(0,5)]
>>>
>>> print(x)   # doctest: +SKIP
Traceback (most recent call last):
NameError: name 'x' is not defined

Multiple values leaking:

>>> DATA = {'commander': 'Melissa Lewis',
...         'pilot': 'Rick Martinez',
...         'botanist': 'Mark Watney'}
>>>
>>> result = []
>>>
>>> for role, astronaut in DATA.items():
...     result.append((role, astronaut))
>>>
>>> print(role)
botanist
>>>
>>> print(astronaut)
Mark Watney
>>> DATA = {'commander': 'Melissa Lewis',
...         'pilot': 'Rick Martinez',
...         'botanist': 'Mark Watney'}
>>>
>>> result = [(role, astronaut) for role, astronaut in DATA.items()]
>>>
>>> print(role)  # doctest: +SKIP
Traceback (most recent call last):
NameError: name 'role' is not defined
>>>
>>> print(astronaut)  # doctest: +SKIP
Traceback (most recent call last):
NameError: name 'astronaut' is not defined

5.1.12. Nested Loops

>>> DATA = {
...     6: ['Doctorate', 'Prof-school'],
...     5: ['Masters', 'Bachelor', 'Engineer'],
...     4: ['HS-grad'],
...     3: ['Junior High'],
...     2: ['Primary School'],
...     1: ['Kindergarten']}
>>>
>>>
>>> result = {}
>>>
>>> for i, titles in DATA.items():
...     for title in titles:
...         result[title] = str(i)
>>>
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
{'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 = {t: str(i) for i, ts in DATA.items() for t in ts}
>>>
>>> result = {title: str(i) for i, titles in DATA.items() for title in titles}
>>>
>>> result = {title: str(i)
...           for i, titles in DATA.items()
...           for title in titles}
>>>
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
{'Doctorate': '6',
 'Prof-school': '6',
 'Masters': '5',
 'Bachelor': '5',
 'Engineer': '5',
 'HS-grad': '4',
 'Junior High': '3',
 'Primary School': '2',
 'Kindergarten': '1'}

5.1.13. Nested Comprehensions

>>> DATA = ['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']
>>>
>>>
>>> result = []
>>>
>>> for line in DATA:
...     line = line.split(',')
...     result.append(line[0:4])
>>>
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
[['5.8', '2.7', '5.1', '1.9'],
 ['5.1', '3.5', '1.4', '0.2'],
 ['5.7', '2.8', '4.1', '1.3']]
>>>
>>> result = [line.split(',')[0:4] for line in DATA]
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
[['5.8', '2.7', '5.1', '1.9'],
 ['5.1', '3.5', '1.4', '0.2'],
 ['5.7', '2.8', '4.1', '1.3']]
>>> DATA = ['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']
>>>
>>>
>>> result = []
>>>
>>> for line in DATA:
...     X = [float(x) for x in line.split(',')[0:4]]
...     result.append(X)
>>>
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
[[5.8, 2.7, 5.1, 1.9],
 [5.1, 3.5, 1.4, 0.2],
 [5.7, 2.8, 4.1, 1.3]]
>>>
>>> result = [[float(x) for x in line.split(',')[0:4]]
...           for line in DATA]
>>> print(result)  # doctest: +NORMALIZE_WHITESPACE
[[5.8, 2.7, 5.1, 1.9],
 [5.1, 3.5, 1.4, 0.2],
 [5.7, 2.8, 4.1, 1.3]]

5.1.14. Indent and Whitespaces

>>> result = [pow(x,2) for x in range(0,5)]
>>>
>>> result = [pow(x,2)
...           for x in range(0,5)]
>>> result = [pow(x, 2) for x in range(0, 5) if x % 2 == 0]
>>>
>>> result = [pow(x,2) for x in range(0,5) if x%2==0]
>>> result = [pow(x,2) for x in range(0,5) if x % 2 == 0]
>>>
>>> result = [pow(x,2)
...           for x in range(0,5)
...               if x % 2 == 0]
>>>
>>> result = [pow(x,2)
...           for x in range(0,5)
...           if x % 2 == 0]
>>> DATA = [{'a':1, 'b':2, 'c': 3},
...         {'a':1, 'b':2, 'c': 3},
...         {'a':1, 'b':2, 'c': 3}]
>>>
>>> result = [value for row in DATA for key, value in row.items()]
>>>
>>> result = [value
...           for row in DATA
...             for key, value in row.items()]
>>>
>>> result = [value
...           for row in DATA
...           for key, value in row.items()]
>>>
>>> # doctest: +SKIP
... result = [astronaut | dict(addresses)
...           for astronaut in json.loads(DATA)
...             for i, address in enumerate(astronaut.pop('addresses'), start=1)
...                 if (columns := [f'{key}{i}' for key in address.keys()])
...                     and (addresses := zip(columns, address.values()))]
>>>
>>> # doctest: +SKIP
... result = [astronaut | dict(addresses)
...           for astronaut in json.loads(DATA)
...           for i, address in enumerate(astronaut.pop('addresses'), start=1)
...           if (columns := [f'{key}{i}' for key in address.keys()])
...           and (addresses := zip(columns, address.values()))]

5.1.15. Examples

Increment and decrement:

>>> [x+1 for x in range(0,5)]
[1, 2, 3, 4, 5]
>>>
>>> [x-1 for x in range(0,5)]
[-1, 0, 1, 2, 3]

Sum:

>>> sum(x for x in range(0,5))
10
>>>
>>> sum(x for x in range(0,5) if x%2==0)
6

Power:

>>> [pow(x,2) for x in range(0,5)]
[0, 1, 4, 9, 16]
>>>
>>> [x**2 for x in range(0,5)]
[0, 1, 4, 9, 16]
>>>
>>> [pow(2,x) for x in range(0,5)]
[1, 2, 4, 8, 16]
>>>
>>> [2**x for x in range(0,5)]
[1, 2, 4, 8, 16]

Even or Odd:

>>> [x for x in range(0,5)]
[0, 1, 2, 3, 4]
>>>
>>> [x%2==0 for x in range(0,5)]
[True, False, True, False, True]

Even or Odd:

>>> result = {}
>>>
>>> for x in range(0,5):
...     is_even = (x % 2 == 0)
...     result.update({x: is_even})
>>>
>>> print(result)
{0: True, 1: False, 2: True, 3: False, 4: True}
>>> {x: (x%2==0) for x in range(0,5)}
{0: True, 1: False, 2: True, 3: False, 4: True}

Filtering:

>>> DATA = [{'is_astronaut': True,  'name': 'Jan Twardowski'},
...         {'is_astronaut': True,  'name': 'Mark Watney'},
...         {'is_astronaut': False, 'name': 'José Jiménez'},
...         {'is_astronaut': True,  'name': 'Melissa Lewis'},
...         {'is_astronaut': False, 'name': 'Alex Vogel'}]
>>>
>>>
>>> astronauts = [person
...               for person in DATA
...               if person['is_astronaut']]
>>>
>>> print(astronauts)  # doctest: +NORMALIZE_WHITESPACE
[{'is_astronaut': True, 'name': 'Jan Twardowski'},
 {'is_astronaut': True, 'name': 'Mark Watney'},
 {'is_astronaut': True, 'name': 'Melissa Lewis'}]
>>> DATA = [{'is_astronaut': True,  'name': 'Jan Twardowski'},
...         {'is_astronaut': True,  'name': 'Mark Watney'},
...         {'is_astronaut': False, 'name': 'José Jiménez'},
...         {'is_astronaut': True,  'name': 'Melissa Lewis'},
...         {'is_astronaut': False, 'name': 'Alex Vogel'}]
>>>
>>>
>>> astronauts = [person['name']
...               for person in DATA
...               if person['is_astronaut']]
>>>
>>> print(astronauts)
['Jan Twardowski', 'Mark Watney', 'Melissa Lewis']
>>> DATA = [{'is_astronaut': True,  'name': 'Jan Twardowski'},
...         {'is_astronaut': True,  'name': 'Mark Watney'},
...         {'is_astronaut': False, 'name': 'José Jiménez'},
...         {'is_astronaut': True,  'name': 'Melissa Lewis'},
...         {'is_astronaut': False, 'name': 'Alex Vogel'}]
>>>
>>>
>>> astronauts = [{'firstname': person['name'].split()[0],
...                'lastname': person['name'].split()[1]}
...                for person in DATA
...                if person['is_astronaut']]
>>>
>>> print(astronauts)  # doctest: +NORMALIZE_WHITESPACE
[{'firstname': 'Jan', 'lastname': 'Twardowski'},
 {'firstname': 'Mark', 'lastname': 'Watney'},
 {'firstname': 'Melissa', 'lastname': 'Lewis'}]
>>> DATA = [{'is_astronaut': True,  'name': 'Jan Twardowski'},
...         {'is_astronaut': True,  'name': 'Mark Watney'},
...         {'is_astronaut': False, 'name': 'José Jiménez'},
...         {'is_astronaut': True,  'name': 'Melissa Lewis'},
...         {'is_astronaut': False, 'name': 'Alex Vogel'}]
>>>
>>>
>>> astronauts = [{'firstname': person['name'].split()[0].capitalize(),
...                'lastname': person['name'].split()[1][0]+'.'}
...                for person in DATA
...                if person['is_astronaut']]
>>>
>>> print(astronauts)  # doctest: +NORMALIZE_WHITESPACE
[{'firstname': 'Jan', 'lastname': 'T.'},
 {'firstname': 'Mark', 'lastname': 'W.'},
 {'firstname': 'Melissa', 'lastname': 'L.'}]

In this example, using Assignment Expression would be more efficient and readable. More information in Assignment Expression

Reversing dict keys with values:

>>> DATA = {'a': 1, 'b': 2}
>>>
>>> list(DATA.items())  # doctest: +NORMALIZE_WHITESPACE
[('a', 1),
 ('b', 2)]
>>>
>>> [(k,v) for k,v in DATA.items()]  # doctest: +NORMALIZE_WHITESPACE
[('a', 1),
 ('b', 2)]
>>>
>>> [(v,k) for k,v in DATA.items()]  # doctest: +NORMALIZE_WHITESPACE
[(1, 'a'),
 (2, 'b')]
>>>
>>> {v:k for k,v in DATA.items()}
{1: 'a', 2: 'b'}

Value collision while reversing dict:

>>> DATA = {'a': 1, 'b': 2, 'c': 2}
>>>
>>> {v:k for k,v in DATA.items()}
{1: 'a', 2: 'c'}

5.1.16. Conditional Expression

>>> result = ['even' if x % 2 == 0 else 'odd'
...           for x in range(0,10)]
>>>
>>> print(result)
['even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd', 'even', 'odd']
>>> result = ['even' if x % 2 == 0 else 'odd'
...           for x in range(0,10)
...           if x % 3 == 0]
>>>
>>> print(result)
['even', 'odd', 'even', 'odd']

5.1.17. Assignments

Code 5.56. Solution
"""
* Assignment: Idioms Comprehension Create
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min

English:
    1. Use list comprehension
    2. Generate `result: list[int]` of even numbers from 5 to 20 (without 20)
    3. Run doctests - all must succeed

Polish:
    1. Użyj rozwinięcia listowego
    2. Wygeneruj `result: list[int]` parzystych liczb z przedziału 5 do 20 (bez 20)
    3. Uruchom doctesty - wszystkie muszą się powieść

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

    >>> assert type(result) is list
    >>> assert all(type(x) is int for x in result)

    >>> result
    [6, 8, 10, 12, 14, 16, 18]
"""

result: list


Code 5.57. Solution
"""
* Assignment: Idioms Comprehension Months
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min

English:
    1. Use dict comprehension
    2. Convert `MONTH` into dict:
        a. Keys: month number
        b. Values: month name
    3. Month number must be two letter string (zero padded) - `f'{number:02}'`
    4. Run doctests - all must succeed

Polish:
    1. Użyj rozwinięcia słownikowego
    2. Przekonwertuj `MONTH` w słownik:
        a. klucz: numer miesiąca
        b. wartość: nazwa miesiąca
    3. Numer miesiąca ma być dwuznakowym stringiem (wypełnij zerem) - `f'{number:02}'`
    4. Uruchom doctesty - wszystkie muszą się powieść

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

    >>> type(result)
    <class 'dict'>
    >>> '00' not in result
    True
    >>> '13' not in result
    True
    >>> result['01'] == 'January'
    True
    >>> assert all(type(x) is str for x in result.keys())
    >>> assert all(type(x) is str for x in result.values())
    >>> assert all(len(x) == 2 for x in result.keys())

    >>> result  # doctest: +NORMALIZE_WHITESPACE
    {'01': 'January',
     '02': 'February',
     '03': 'March',
     '04': 'April',
     '05': 'May',
     '06': 'June',
     '07': 'July',
     '08': 'August',
     '09': 'September',
     '10': 'October',
     '11': 'November',
     '12': 'December'}
"""

MONTHS = ['January', 'February', 'March', 'April',
          'May', 'June', 'July', 'August',
          'September', 'October', 'November', 'December']

result: dict


Code 5.58. Solution
"""
* Assignment: Idioms Comprehension Translate
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min

English:
    1. Define `result: list`
    2. Use list comprehension to iterate over `DATA`
    3. If letter is in `PL` then use conversion value as letter
    4. Add letter to `result`
    5. Redefine `result: str` as a joined `result`
    6. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: list`
    2. Użyj rozwinięcia listowego do iteracji po `DATA`
    3. Jeżeli litera jest w `PL` to użyj skonwertowanej wartości jako litera
    4. Dodaj literę do `result`
    5. Przedefiniuj `result: str` jako złączony `result`
    6. Uruchom doctesty - wszystkie muszą się powieść

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

    >>> assert type(result) is str
    >>> result
    'zazolc gesla jazn'
"""

PL = {'ą': 'a', 'ć': 'c', 'ę': 'e',
      'ł': 'l', 'ń': 'n', 'ó': 'o',
      'ś': 's', 'ż': 'z', 'ź': 'z'}

DATA = 'zażółć gęślą jaźń'

result: str


Code 5.59. Solution
"""
* Assignment: Idioms Comprehension Train/Test
* Complexity: medium
* Lines of code: 9 lines
* Time: 13 min

English:
    1. Using List Comprehension split `DATA` into:
        a. `features_train: list[tuple]` - 60% of first features in `DATA`
        b. `features_test: list[tuple]` - 40% of last features in `DATA`
        c. `labels_train: list[str]` - 60% of first labels in `DATA`
        d. `labels_test: list[str]` - 40% of last labels in `DATA`
    2. In order to do so, calculate pivot point:
        a. length of `DATA` times given percent (60% = 0.6)
        b. remember, that slice indicies must be `int`, not `float`
        c. for example: if dataset has 10 rows, then 6 rows will be for training, and 4 rows for test
    3. Run doctests - all must succeed

Polish:
    1. Używając List Comprehension podziel `DATA` na:
        a. `features_train: list[tuple]` - 60% pierwszych features w `DATA`
        b. `features_test: list[tuple]` - 40% ostatnich features w `DATA`
        c. `labels_train: list[str]` - 60% pierwszych labels w `DATA`
        d. `labels_test: list[str]` - 40% ostatnich labels w `DATA`
    2. Aby to zrobić, wylicz punkt podziału:
        a. długość `DATA` razy zadany procent (60% = 0.6)
        b. pamiętaj, że indeksy slice muszą być `int` a nie `float`
        c. na przykład: if zbiór danych ma 10 wierszy, to 6 wierszy będzie do treningu, a 4 do testów
    3. Uruchom doctesty - wszystkie muszą się powieść

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

    >>> assert type(features_train) is list
    >>> assert type(features_test) is list
    >>> assert type(labels_train) is list
    >>> assert type(labels_test) is list
    >>> assert all(type(x) is tuple for x in features_train), 'features_train: expected type list[tuple]'
    >>> assert all(type(x) is tuple for x in features_test), 'features_test: expected type list[tuple]'
    >>> assert all(type(x) is str for x in labels_train)
    >>> assert all(type(x) is str for x in labels_test)
    >>> features_train  # doctest: +NORMALIZE_WHITESPACE
    [(5.8, 2.7, 5.1, 1.9),
     (5.1, 3.5, 1.4, 0.2),
     (5.7, 2.8, 4.1, 1.3),
     (6.3, 2.9, 5.6, 1.8),
     (6.4, 3.2, 4.5, 1.5),
     (4.7, 3.2, 1.3, 0.2)]
    >>> features_test  # doctest: +NORMALIZE_WHITESPACE
    [(7.0, 3.2, 4.7, 1.4),
     (7.6, 3.0, 6.6, 2.1),
     (4.9, 3.0, 1.4, 0.2),
     (4.9, 2.5, 4.5, 1.7)]
    >>> labels_train
    ['virginica', 'setosa', 'versicolor', 'virginica', 'versicolor', 'setosa']
    >>> labels_test
    ['versicolor', 'virginica', 'setosa', 'virginica']
"""

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'),
        (7.0, 3.2, 4.7, 1.4, 'versicolor'),
        (7.6, 3.0, 6.6, 2.1, 'virginica'),
        (4.9, 3.0, 1.4, 0.2, 'setosa'),
        (4.9, 2.5, 4.5, 1.7, 'virginica')]

features_train: list
features_test: list
labels_train: list
labels_test: list