11.6. Function Generator-Like

  • It is not a generator

  • Generator-like objects

  • range(start, stop, step)

  • reversed(iterable)

  • enumerate(iterable, start=0)

  • zip(*iterable, strict=False)

  • map(func, iterable)

  • filter(func, iterable)

  • next(iterable)

  • iter(iterable)

11.6.1. Range

  • range([start], <stop>, [step])

  • optional start, inclusive, default: 0

  • required stop, exclusive

  • optional step, default: 1

range() syntax:

>>> range(0,3)
range(0, 3)
>>> list(range(0,3))
[0, 1, 2]
>>>
>>> tuple(range(0,3))
(0, 1, 2)
>>>
>>> set(range(0,3))
{0, 1, 2}
>>>
>>> list(range(4,11,2))
[4, 6, 8, 10]

11.6.2. Reversed

  • reversed() - Return a reverse iterator over the values of the given sequence

>>> data = (1, 2, 3)
>>> list(reversed(data))
[3, 2, 1]

11.6.3. Enumerate

  • enumerate(*iterables)

>>> months = ['January', 'February', 'March']
>>> result = enumerate(months)
>>>
>>> list(result)
[(0, 'January'), (1, 'February'), (2, 'March')]
>>> months = ['January', 'February', 'March']
>>> result = enumerate(months)
>>>
>>> dict(result)
{0: 'January', 1: 'February', 2: 'March'}
>>> months = ['January', 'February', 'March']
>>> result = enumerate(months, start=1)
>>>
>>> dict(result)
{1: 'January', 2: 'February', 3: 'March'}
>>> months = ['January', 'February', 'March']
>>> result = {f'{i:02}':month for i,month in enumerate(months, start=1)}
>>>
>>> print(result)
{'01': 'January', '02': 'February', '03': 'March'}

11.6.4. Zip

  • zip(*iterables)

>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> list(zip(firstnames, lastnames))
[('Mark', 'Watney'), ('Melissa', 'Lewis'), ('Rick', 'Martinez')]
>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> dict(zip(firstnames, lastnames))
{'Mark': 'Watney', 'Melissa': 'Lewis', 'Rick': 'Martinez'}
>>> roles = ['botanist', 'commander', 'pilot']
>>> names = ['Mark Watney', 'Melissa Lewis', 'Rick Martinez']
>>>
>>> dict(zip(roles, names))  
{'botanist': 'Mark Watney',
 'commander': 'Melissa Lewis',
 'pilot': 'Rick Martinez'}

zip() adjusts to the shortest:

>>> firstnames = ['Mark', 'Melissa']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> list(zip(firstnames, lastnames))
[('Mark', 'Watney'), ('Melissa', 'Lewis')]

Three-way:

>>> roles = ['botanist', 'commander', 'pilot']
>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(roles, firstnames, lastnames)
>>>
>>>
>>> next(result)
('botanist', 'Mark', 'Watney')
>>>
>>> next(result)
('commander', 'Melissa', 'Lewis')
>>>
>>> next(result)
('pilot', 'Rick', 'Martinez')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

11.6.5. Map

  • Apply function to all elements of data

  • map(callable, *iterables)

>>> def square(x):
...     return x ** 2
>>> data = [1, 2, 3]
>>> result = map(square, data)
>>> list(result)
[1, 4, 9]

11.6.6. Filter

  • filter(callable, *iterables)

>>> def even(x):
...     return x % 2 == 0
>>> data = [0, 1, 2, 3, 4]
>>> result = filter(even, data)
>>> list (result)
[0, 2, 4]

11.6.7. Next

Range:

>>> result = range(0,5)
>>>
>>> next(result)
Traceback (most recent call last):
TypeError: 'range' object is not an iterator

Zip two-way:

>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(firstnames, lastnames)
>>>
>>>
>>> next(result)
('Mark', 'Watney')
>>>
>>> next(result)
('Melissa', 'Lewis')
>>>
>>> next(result)
('Rick', 'Martinez')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

Enumerate:

>>> months = ['January', 'February', 'March']
>>> result = enumerate(months)
>>>
>>> next(result)
(0, 'January')
>>>
>>> next(result)
(1, 'February')
>>>
>>> next(result)
(2, 'March')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

Zip n-way:

>>> roles = ['botanist', 'commander', 'pilot']
>>> firstnames = ['Mark', 'Melissa', 'Rick']
>>> lastnames = ['Watney', 'Lewis', 'Martinez']
>>>
>>> result = zip(roles, firstnames, lastnames)
>>>
>>>
>>> next(result)
('botanist', 'Mark', 'Watney')
>>>
>>> next(result)
('commander', 'Melissa', 'Lewis')
>>>
>>> next(result)
('pilot', 'Rick', 'Martinez')
>>>
>>> next(result)
Traceback (most recent call last):
StopIteration

Map:

>>> def square(x):
...     return x ** 2
>>>
>>> data = [1, 2, 3]
>>> result = map(square, data)
>>>
>>>
>>> next(result)
1
>>>
>>> next(result)
4
>>>
>>> next(result)
9
>>> next(result)
Traceback (most recent call last):
StopIteration

Filter:

>>> def even(x):
...     return x % 2 == 0
>>>
>>> data = [0, 1, 2, 3, 4]
>>> result = filter(even, data)
>>>
>>>
>>> next(result)
0
>>>
>>> next(result)
2
>>>
>>> next(result)
4
>>> next(result)
Traceback (most recent call last):
StopIteration

11.6.8. Iter

Range:

>>> for i in range(0,3):
...     print(i)
0
1
2

Enumerate:

>>> months = ['January', 'February', 'March']
>>>
>>> for i, month in enumerate(months, start=1):
...     print(f'{i=}, {month=}')
i=1, month='January'
i=2, month='February'
i=3, month='March'

Zip:

>>> roles = ['botanist', 'commander', 'pilot']
>>> names = ['Mark Watney', 'Melissa Lewis', 'Rick Martinez']
>>>
>>> for role, name in zip(roles, names):
...     print(f'{role=}, {name=}')
role='botanist', name='Mark Watney'
role='commander', name='Melissa Lewis'
role='pilot', name='Rick Martinez'

11.6.9. Generator Chain

  • Function composition

>>> def square(x):
...     return x ** 2
>>>
>>> def even(x):
...     return x % 2 == 0
>>>
>>>
>>> result = range(0,10)
>>> result = map(square, result)
>>> result = filter(even, result)
>>>
>>> for value in result:
...     print(value)
...     if value > 3:
...         break
0
4
>>>
>>> next(result)
16
>>>
>>> list(result)
[36, 64]

11.6.10. Itertools

  • Learn more at https://docs.python.org/3/library/itertools.html

  • More information in Itertools

  • itertools.count(start=0, step=1)

  • itertools.cycle(iterable)

  • itertools.repeat(object[, times])

  • itertools.accumulate(iterable[, func, *, initial=None])

  • itertools.chain(*iterables)

  • itertools.compress(data, selectors)

  • itertools.islice(iterable, start, stop[, step])

  • itertools.starmap(function, iterable)

  • itertools.product(*iterables, repeat=1)

  • itertools.permutations(iterable, r=None)

  • itertools.combinations(iterable, r)

  • itertools.combinations_with_replacement(iterable, r)

  • itertools.groupby(iterable, key=None)

11.6.11. Use Case - 0x01

>>> def increment(x):
...     return x + 1
>>>
>>>
>>> data = [1, 2, 3, 4]
>>> result = map(increment, data)
>>>
>>> list(result)
[2, 3, 4, 5]

11.6.12. Use Case - 0x02

>>> def square(x):
...     return x ** 2
>>>
>>>
>>> data = [1, 2, 3]
>>> result = map(square, data)
>>>
>>> list(result)
[1, 4, 9]

11.6.13. Use Case - 0x03

>>> PL = {'ą': 'a', 'ć': 'c', 'ę': 'e',
...       'ł': 'l', 'ń': 'n', 'ó': 'o',
...       'ś': 's', 'ż': 'z', 'ź': 'z'}
>>>
>>>
>>> def translate(letter):
...     return PL.get(letter, letter)
>>>
>>>
>>> text = 'zażółć gęślą jaźń'
>>> result = map(translate, text)
>>>
>>> ''.join(result)
'zazolc gesla jazn'

11.6.14. Use Case - 0x04

>>> people = [
...     {'age': 21, 'name': 'Mark Watney'},
...     {'age': 25, 'name': 'Melissa Lewis'},
...     {'age': 18, 'name': 'Rick Martinez'}]
>>>
>>>
>>> def adult(person):
...     return person['age'] >= 21
>>>
>>>
>>> result = filter(adult, people)
>>>
>>> list(result)  
[{'age': 21, 'name': 'Mark Watney'},
 {'age': 25, 'name': 'Melissa Lewis'}]

11.6.15. Use Case - 0x05

>>> people = [
...     {'is_astronaut': False, 'name': 'Mark Watney'},
...     {'is_astronaut': True, 'name': 'Melissa Lewis'},
...     {'is_astronaut': True, 'name': 'Rick Martinez'}]
>>>
>>>
>>> def astronaut(person):
...     return person['is_astronaut']
>>>
>>>
>>> result = filter(astronaut, people)
>>>
>>> list(result)  
[{'is_astronaut': True, 'name': 'Melissa Lewis'},
 {'is_astronaut': True, 'name': 'Rick Martinez'}]

11.6.16. Use Case - 0x06

Sum stdin (standard input):

>>> import sys
>>>
>>> 
... print(sum(map(int, sys.stdin)))
$ cat ~/.profile |grep addnum
alias addnum='python -c"import sys; print(sum(map(int, sys.stdin)))"'

11.6.17. Assignments

Code 11.19. Solution
"""
* Assignment: Function Generator Map
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Define function `cube()`:
       a. takes one argument
       b. returns its argument cubed (raised to the power of 3)
    2. Use `map()` to apply function `cube()` to DATA
    3. Define `result: list[int]` with evaluated result
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funckję `cube()`:
       a. przyjmuje jeden argument
       b. zwraca argument podniesiony do sześcianu (do 3 potęgi)
    2. Użyj `map()` zaaplikować funkcję `cube()` do DATA
    3. Zdefiniuj `result: list[int]` z ewaluaownym wynikiem
    4. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * map()
    * list()

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(cube), \
    'Object `cube` must be a function'
    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert type(result) is list, \
    'Variable `result` has invalid type, should be list'
    >>> assert all(type(x) is int for x in result), \
    'All rows in `result` should be int'

    >>> result
    [0, 1, 8, 27, 64, 125, 216, 343, 512, 729]
"""


DATA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


# Returns its argument cubed (raised to the power of 3)
# type: Callable[[int], int]
def cube():
    ...

# Cube numbers in DATA
# type: list[float]
result = ...


Code 11.20. Solution
"""
* Assignment: Function Generator Filter
* Required: yes
* Complexity: easy
* Lines of code: 3 lines
* Time: 3 min

English:
    1. Define function `odd()`:
       a. takes one argument
       b. returns True if argument is odd
       c. returns False if argument is even
    2. Use `filter()` to apply function `odd()` to DATA
    3. Define `result: list[int]` with evaluated result
    4. Run doctests - all must succeed

Polish:
    1. Zdefiniuj funckję `odd()`:
       a. przyjmuje jeden argument
       b. zwraca True jeżeli argument jest nieparzysty
       c. zwraca False jeżeli argument jest parzysty
    2. Użyj `filter()` zaaplikować funkcję `odd()` do DATA
    3. Zdefiniuj `result: list[int]` z ewaluaownym wynikiem
    4. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * filter()
    * list()

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(odd), \
    'Object `odd` must be a function'

    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert type(result) is list, \
    'Variable `result` has invalid type, should be list'
    >>> assert all(type(x) is int for x in result), \
    'All rows in `result` should be int'

    >>> result
    [1, 3, 5, 7, 9]
"""


DATA = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


# Returns its argument cubed (raised to the power of 3)
# type: Callable[[int], int]
def odd():
    ...

# Cube numbers in DATA
# type: list[float]
result = ...


Code 11.21. Solution
"""
* Assignment: Function Generator Chain
* Required: yes
* Complexity: easy
* Lines of code: 4 lines
* Time: 3 min

English:
    1. Use `range()` to get numbers:
       a. from 0 (inclusive)
       b. to 10 (exclusive)
    2. Redefine `result` with odd numbers from `result`
    3. Redefine `result` with cubed numbers from `result`
    4. Redefine `result` with evaluated `result`
    5. Run doctests - all must succeed

Polish:
    1. Użyj `range()` aby otrzymać liczby:
       a. od 0 (włącznie)
       b. do 10 (rozłącznie)
    2. Przedefiniuj `result` z nieparzystymi liczbami z `result`
    3. Przedefiniuj `result` z podniesionymi do sześcianiu liczbami z `result`
    4. Przedefiniuj `result` z ewaluaownym `result`
    5. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * range()
    * map()
    * filter()
    * list()

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(odd), \
    'Object `odd` must be a function'
    >>> assert isfunction(cube), \
    'Object `cube` must be a function'
    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert type(result) is list, \
    'Variable `result` has invalid type, should be list'
    >>> assert all(type(x) is int for x in result), \
    'All rows in `result` should be int'

    >>> result
    [1, 27, 125, 343, 729]
"""

def odd(x):
    return x % 2


def cube(x):
    return x ** 3


# Range from 0 to 10 (exclusive)
# type: Iterator[int]
result = ...

# Filter odd numbers
# type: Iterator[int]
result = ...

# Cube result
# type: Iterator[int]
result = ...

# Get list of results
# type: list[int]
result = ...


Code 11.22. Solution
"""
* Assignment: Function Generator Chain
* Required: yes
* Complexity: easy
* Lines of code: 2 lines
* Time: 3 min

English:
    1. Define `result: float` with arithmetic mean of `DATA`
    2. Note, that all the time you are working on a data stream
    3. Run doctests - all must succeed

Polish:
    1. Zdefiniuj `result: float` ze średnią arytmetyczną z `DATA`
    2. Zwróć uwagę, że cały czas pracujesz na strumieniu danych
    3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
    * type cast to `list()` to expand generator before calculating mean
    * `mean = sum(...) / len(...)`
    * TypeError: object of type 'map' has no len()
    * ZeroDivisionError: division by zero

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isfunction

    >>> assert isfunction(odd), \
    'Object `odd` must be a function'
    >>> assert isfunction(cube), \
    'Object `cube` must be a function'
    >>> assert result is not Ellipsis, \
    'Assign result to variable: `result`'
    >>> assert type(result) is float, \
    'Variable `result` has invalid type, should be float'

    >>> result
    245.0
"""

def odd(x):
    return x % 2


def cube(x):
    return x ** 3


DATA = range(0, 10)
DATA = filter(odd, DATA)
DATA = map(cube, DATA)

# Calculate mean of DATA
# type: float
result = ...