# 11.10. Functional Map

• Map (convert) elements in sequence

• Generator (lazy evaluated)

• map(callable, *iterables)

• required callable - Function

• required iterables - 1 or many sequence or iterator objects

The map() function in Python is a built-in function that applies a given function to each element of an iterable (such as a list, tuple, or set) and returns a new iterable with the results. It takes two arguments: a function and an iterable.

The function is applied to each element in the iterable, and the results are collected into a new iterable. The resulting iterable can be converted to a list, tuple, or set if desired.

Here's an example of using the map() function to square each number in a list:

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

In this example, the square function is applied to each element in the numbers list using the map() function. The resulting iterable contains the squared values of each element in the original list. The list() function is used to convert the iterable to a list.

>>> from inspect import isgeneratorfunction, isgenerator
>>>
>>>
>>> isgeneratorfunction(map)
False
>>>
>>> result = map(float, [1,2,3])
>>> isgenerator(result)
False

## 11.10.1. Example

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

## 11.10.2. Problem

>>> data = [1, 2, 3]
>>> result = []
>>>
>>> for x in data:
...     result.append(float(x))
>>>
>>> print(result)
[1.0, 2.0, 3.0]

## 11.10.3. Solution

>>> data = [1, 2, 3]
>>> result = map(float, data)
>>>
>>> list(result)
[1.0, 2.0, 3.0]

## 11.10.4. Lazy Evaluation

>>> data = [1, 2, 3]
>>> result = map(float, data)
>>>
>>> next(result)
1.0
>>> next(result)
2.0
>>> next(result)
3.0
>>> next(result)
Traceback (most recent call last):
StopIteration

## 11.10.5. Multi Parameters

>>> def myfunc(x):
...     return sum(x)
>>>
>>>
>>> DATA = [
...     (1,2),
...     (3,4),
... ]
>>>
>>> result = map(myfunc, DATA)
>>> print(list(result))
[3, 7]

## 11.10.6. Starmap

>>> from itertools import starmap
>>>
>>>
>>> DATA = [
...     (3.1415, 3),
...     (2.71828, 2)]
>>>
>>> result = starmap(round, DATA)  # round(number=3.1415, ndigits=2)
>>> print(list(result))
[3.142, 2.72]

## 11.10.7. Partial

>>> from functools import partial
>>>
>>>
>>> myround = partial(round, ndigits=1)
>>> DATA = [1.111, 2.222, 3.333]
>>>
>>> result = map(myround, DATA)  # round(number=1.111, ndigits=1)
>>> print(list(result))
[1.1, 2.2, 3.3]

## 11.10.8. Performance

>>> def even(x):
...     return x % 2 == 0
>>>
... %%timeit -r 1000 -n 1000
... result = [float(x) for x in data if even(x)]
1.9 µs ± 206 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)
>>>
... %%timeit -r 1000 -n 1000
... result = list(map(float, filter(parzysta, data)))
1.66 µs ± 175 ns per loop (mean ± std. dev. of 1000 runs, 1,000 loops each)

## 11.10.9. Use Case - 0x01

Built-in functions:

>>> DATA = [1, 2, 3]
>>> result = map(float, DATA)
>>>
>>> tuple(map(float, DATA))
(1.0, 2.0, 3.0)
>>> DATA = [1, 2, 3]
>>> result = map(float, DATA)
>>>
>>> set(map(float, DATA))
{1.0, 2.0, 3.0}
>>> DATA = [1, 2, 3]
>>> result = (float(x) for x in DATA)
>>>
>>> list(result)
[1.0, 2.0, 3.0]
>>> DATA = [1.1, 2.2, 3.3]
>>> result = map(round, DATA)
>>>
>>> list(result)
[1, 2, 3]

## 11.10.10. Use Case - 0x02

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

## 11.10.11. Use Case - 0x03

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

## 11.10.12. Use Case - 0x04

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

## 11.10.13. Use Case - 0x05

Standard input:

>>> import sys
>>>
>>>
... print(sum(map(int, sys.stdin)))
alias addnum='python -c"import sys; print(sum(map(int, sys.stdin)))"'

## 11.10.14. Use Case - 0x06

>>> import httpx
>>>
>>> url = 'https://python3.info/_static/iris-dirty.csv'
>>>
>>> data = httpx.get(url).text
>>> nrows, nfeatures, *class_labels = header.strip().split(',')
>>> label_encoder = dict(enumerate(class_labels))
>>> result = []
>>> for row in rows:
...     *features, species = row.strip().split(',')
...     features = map(float, features)
...     species = label_encoder[int(species)]
...     row = tuple(features) + (species,)
...     result.append(row)
>>> def decode(row):
...     *features, species = row.strip().split(',')
...     features = map(float, features)
...     species = label_encoder[int(species)]
...     return tuple(features) + (species,)
>>>
>>> result = map(decode, rows)
>>> def decode(row):
...     *features, species = row.strip().split(',')
...     features = map(float, features)
...     species = label_encoder[int(species)]
...     return tuple(features) + (species,)
>>>
>>> with open('/tmp/myfile.csv') as file:
...     for line in map(decode, file):
...         print(line)

## 11.10.15. Use Case - 0x07

SetUp:

>>> from doctest import testmod as run_tests

Data [1]:

>>> DATA = """150,4,setosa,versicolor,virginica
... 5.1,3.5,1.4,0.2,0
... 7.0,3.2,4.7,1.4,1
... 6.3,3.3,6.0,2.5,2
... 4.9,3.0,1.4,0.2,0
... 6.4,3.2,4.5,1.5,1
... 5.8,2.7,5.1,1.9,2"""

Definition:

>>> def get_labelencoder(header: str) -> dict[int, str]:
...     """
...     >>> get_labelencoder('150,4,setosa,versicolor,virginica')
...     {0: 'setosa', 1: 'versicolor', 2: 'virginica'}
...     """
...     nrows, nfeatures, *class_labels = header.split(',')
...     return dict(enumerate(class_labels))
>>>
>>> run_tests()
TestResults(failed=0, attempted=1)
>>> def get_data(line: str) -> tuple:
...     """
...     >>> convert('5.1,3.5,1.4,0.2,0')
...     (5.1, 3.5, 1.4, 0.2, 'setosa')
...     >>> convert('7.0,3.2,4.7,1.4,1')
...     (7.0, 3.2, 4.7, 1.4, 'versicolor')
...     >>> convert('6.3,3.3,6.0,2.5,2')
...     (6.3, 3.3, 6.0, 2.5, 'virginica')
...     """
...     *values, species = line.split(',')
...     values = map(float, values)
...     species = label_encoder[int(species)]
...     return tuple(values) + (species,)
>>>
>>> run_tests()
TestResults(failed=0, attempted=3)
>>> result = map(get_data, lines)
>>> list(result)
[(5.1, 3.5, 1.4, 0.2, 'setosa'),
(7.0, 3.2, 4.7, 1.4, 'versicolor'),
(6.3, 3.3, 6.0, 2.5, 'virginica'),
(4.9, 3.0, 1.4, 0.2, 'setosa'),
(6.4, 3.2, 4.5, 1.5, 'versicolor'),
(5.8, 2.7, 5.1, 1.9, 'virginica')]

## 11.10.16. Use Case - 0x08

>>>
... import pandas as pd
...
...
... DATA = 'https://python3.info/_static/phones-pl.csv'
...
... result = (
...     pd
...     .set_index('datetime', drop=True)
...     .drop(columns=['id'])
...     .loc['2000-01-01':'2000-03-01']
...     .query('item == "sms"')
...     .groupby(['period','item'])
...     .agg(
...         duration_count = ('duration', 'count'),
...         duration_sum = ('duration', 'sum'),
...         duration_median = ('duration', 'median'),
...         duration_mean = ('duration', 'mean'),
...         duration_std = ('duration', 'std'),
...         duration_var = ('duration', 'var'),
...         value = ('duration', lambda column: column.mean().astype(int))
...     )
... )

## 11.10.18. Assignments

"""
* Assignment: Functional Apply Map
* Type: class assignment
* 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: map with 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() aby zaaplikować funkcję cube() do DATA
3. Zdefiniuj result: map z wynikiem
4. Uruchom doctesty - wszystkie muszą się powieść

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 map, \
'Variable result has invalid type, should be map'

>>> result = list(result)
>>> assert type(result) is list, \
'Evaluated 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(x):
...

# Cube numbers in DATA
# type: map
result = ...

"""
* Complexity: easy
* Lines of code: 1 lines
* Time: 3 min

English:
1. Define result: map with parsed DATA dates
2. Use map() and datetime.fromisoformat()
3. Run doctests - all must succeed

Polish:
1. Zdefiniuj result: map ze sparsowanymi datami DATA
2. Użyj map() oraz datetime.fromisoformat()
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* map()
* datetime.fromisoformat()

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

>>> assert result is not Ellipsis, \
'Assign result to variable: result'
>>> assert type(result) is map, \
'Variable result has invalid type, must be a map'

>>> result = list(result)
>>> assert type(result) is list, \
'Variable result has invalid type, must be a list'

>>> assert all(type(element) is datetime for element in result), \
'All elements in result must be a datetime'

>>> pprint(result, width=30)
[datetime.datetime(1961, 4, 12, 6, 7),
datetime.datetime(1961, 4, 12, 6, 7)]
"""

from datetime import datetime

DATA = [
'1961-04-12 06:07',
'1961-04-12 06:07:00',
]

# Define result: map with parsed DATA dates
# type: map
result = ...

"""
* Complexity: medium
* Lines of code: 7 lines
* Time: 5 min

English:
1. Define result: map with parsed DATA dates
2. Use map()
3. Run doctests - all must succeed

Polish:
1. Zdefiniuj result: map ze sparsowanymi datami DATA
2. Użyj map()
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* for ... in
* nested try ... except
* FORMATS = []
* for fmt in FORMATS
* helper function
* 24-hour clock
* map(func, iterable1, iterable1)

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

>>> assert result is not Ellipsis, \
'Assign result to variable: result'
>>> assert type(result) is map, \
'Variable result has invalid type, must be a map'
>>> result = list(result)
>>> assert type(result) is list, \
'Variable result has invalid type, must be a list'
>>> assert all(type(element) is datetime for element in result), \
'All elements in result must be a datetime'

>>> result  # doctest: +NORMALIZE_WHITESPACE
[datetime.datetime(1957, 10, 4, 19, 28, 34),
datetime.datetime(1961, 4, 12, 6, 7),
datetime.datetime(1969, 7, 21, 2, 56, 15)]
"""

from datetime import datetime

DATA = [
'Oct 4, 1957 19:28:34',  # Sputnik launch (first satellite in space)
'April 12, 1961 6:07',  # Gagarin launch (first human in space)
'July 21, 1969 2:56:15',  # Armstrong first step on the Moon
]

FORMATS = [
'%b %d, %Y %H:%M:%S',
'%B %d, %Y %H:%M',
'%B %d, %Y %H:%M:%S',
]

# DATA elements in datetime format
# type: map
result = ...

"""
* Complexity: medium
* Lines of code: 7 lines
* Time: 8 min

English:
1. Iterate over DATA with Apollo 11 timeline [1]
2. From each line extract date, time, level and message
3. Collect data to result: map
4. Run doctests - all must succeed

Polish:
1. Iteruj po DATA z harmonogramem Apollo 11 [1]
2. Dla każdej linii wyciągnij datę, czas, poziom logowania oraz wiadomość
3. Zbierz dane do result: map
4. Uruchom doctesty - wszystkie muszą się powieść

Hint:
* Note, that last time has no seconds
* This is not bug, time without seconds is in NASA history records [1]

References:
[1] National Aeronautics and Space Administration.
Apollo 11 timeline.
Year: 1969. Retrieved: 2021-03-25.
URL: https://history.nasa.gov/SP-4029/Apollo_11i_Timeline.htm

Hints:
* str.splitlines()
* str.split(', ', maxsplit=3)
* date.fromisoformat()
* time.fromisoformat()
* datetime.combine()

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

>>> assert result is not Ellipsis, \
'Assign result to variable: result'
>>> assert type(result) is map, \
'Variable result has invalid type, must be a map'

>>> result = list(result)
>>> assert all(type(row) is dict for row in result), \
'All elements in result must be dict'

>>> pprint(result)
[{'datetime': datetime.datetime(1969, 7, 14, 21, 0),
'level': 'INFO',
'message': 'Terminal countdown started'},
{'datetime': datetime.datetime(1969, 7, 16, 13, 31, 53),
'level': 'WARNING',
'message': 'S-IC engine ignition (#5)'},
{'datetime': datetime.datetime(1969, 7, 16, 13, 33, 23),
'level': 'DEBUG',
'message': 'Maximum dynamic pressure (735.17 lb/ft^2)'},
{'datetime': datetime.datetime(1969, 7, 16, 13, 34, 44),
'level': 'WARNING',
'message': 'S-II ignition'},
{'datetime': datetime.datetime(1969, 7, 16, 13, 35, 17),
'level': 'DEBUG',
'message': 'Launch escape tower jettisoned'},
{'datetime': datetime.datetime(1969, 7, 16, 13, 39, 40),
'level': 'DEBUG',
'message': 'S-II center engine cutoff'},
{'datetime': datetime.datetime(1969, 7, 16, 16, 22, 13),
'level': 'INFO',
'message': 'Translunar injection'},
{'datetime': datetime.datetime(1969, 7, 16, 16, 56, 3),
'level': 'INFO',
'message': 'CSM docked with LM/S-IVB'},
{'datetime': datetime.datetime(1969, 7, 16, 17, 21, 50),
'level': 'INFO',
'message': 'Lunar orbit insertion ignition'},
{'datetime': datetime.datetime(1969, 7, 16, 21, 43, 36),
'level': 'INFO',
'message': 'Lunar orbit circularization ignition'},
{'datetime': datetime.datetime(1969, 7, 20, 17, 44),
'level': 'INFO',
'message': 'CSM/LM undocked'},
{'datetime': datetime.datetime(1969, 7, 20, 20, 5, 5),
'level': 'WARNING',
'message': 'LM powered descent engine ignition'},
{'datetime': datetime.datetime(1969, 7, 20, 20, 10, 22),
'level': 'ERROR',
'message': 'LM 1202 alarm'},
{'datetime': datetime.datetime(1969, 7, 20, 20, 14, 18),
'level': 'ERROR',
'message': 'LM 1201 alarm'},
{'datetime': datetime.datetime(1969, 7, 20, 20, 17, 39),
'level': 'WARNING',
'message': 'LM lunar landing'},
{'datetime': datetime.datetime(1969, 7, 21, 2, 39, 33),
'level': 'DEBUG',
'message': 'EVA started (hatch open)'},
{'datetime': datetime.datetime(1969, 7, 21, 2, 56, 15),
'level': 'WARNING',
'message': '1st step taken lunar surface (CDR)'},
{'datetime': datetime.datetime(1969, 7, 21, 2, 56, 15),
'level': 'WARNING',
'message': 'Neil Armstrong first words on the Moon'},
{'datetime': datetime.datetime(1969, 7, 21, 3, 5, 58),
'level': 'DEBUG',
'message': 'Contingency sample collection started (CDR)'},
{'datetime': datetime.datetime(1969, 7, 21, 3, 15, 16),
'level': 'INFO',
'message': 'LMP on lunar surface'},
{'datetime': datetime.datetime(1969, 7, 21, 5, 11, 13),
'level': 'DEBUG',
'message': 'EVA ended (hatch closed)'},
{'datetime': datetime.datetime(1969, 7, 21, 17, 54),
'level': 'WARNING',
'message': 'LM lunar liftoff ignition (LM APS)'},
{'datetime': datetime.datetime(1969, 7, 21, 21, 35),
'level': 'INFO',
'message': 'CSM/LM docked'},
{'datetime': datetime.datetime(1969, 7, 22, 4, 55, 42),
'level': 'WARNING',
'message': 'Transearth injection ignition (SPS)'},
{'datetime': datetime.datetime(1969, 7, 24, 16, 21, 12),
'level': 'INFO',
'message': 'CM/SM separation'},
{'datetime': datetime.datetime(1969, 7, 24, 16, 35, 5),
'level': 'WARNING',
'message': 'Entry'},
{'datetime': datetime.datetime(1969, 7, 24, 16, 50, 35),
'level': 'WARNING',
'message': 'Splashdown (went to apex-down)'},
{'datetime': datetime.datetime(1969, 7, 24, 17, 29),
'level': 'INFO',
'message': 'Crew egress'}]
"""
from datetime import date, datetime, time

DATA = """1969-07-14, 21:00:00, INFO, Terminal countdown started
1969-07-16, 13:31:53, WARNING, S-IC engine ignition (#5)
1969-07-16, 13:33:23, DEBUG, Maximum dynamic pressure (735.17 lb/ft^2)
1969-07-16, 13:34:44, WARNING, S-II ignition
1969-07-16, 13:35:17, DEBUG, Launch escape tower jettisoned
1969-07-16, 13:39:40, DEBUG, S-II center engine cutoff
1969-07-16, 16:22:13, INFO, Translunar injection
1969-07-16, 16:56:03, INFO, CSM docked with LM/S-IVB
1969-07-16, 17:21:50, INFO, Lunar orbit insertion ignition
1969-07-16, 21:43:36, INFO, Lunar orbit circularization ignition
1969-07-20, 17:44:00, INFO, CSM/LM undocked
1969-07-20, 20:05:05, WARNING, LM powered descent engine ignition
1969-07-20, 20:10:22, ERROR, LM 1202 alarm
1969-07-20, 20:14:18, ERROR, LM 1201 alarm
1969-07-20, 20:17:39, WARNING, LM lunar landing
1969-07-21, 02:39:33, DEBUG, EVA started (hatch open)
1969-07-21, 02:56:15, WARNING, 1st step taken lunar surface (CDR)
1969-07-21, 02:56:15, WARNING, Neil Armstrong first words on the Moon
1969-07-21, 03:05:58, DEBUG, Contingency sample collection started (CDR)
1969-07-21, 03:15:16, INFO, LMP on lunar surface
1969-07-21, 05:11:13, DEBUG, EVA ended (hatch closed)
1969-07-21, 17:54:00, WARNING, LM lunar liftoff ignition (LM APS)
1969-07-21, 21:35:00, INFO, CSM/LM docked
1969-07-22, 04:55:42, WARNING, Transearth injection ignition (SPS)
1969-07-24, 16:21:12, INFO, CM/SM separation
1969-07-24, 16:35:05, WARNING, Entry
1969-07-24, 16:50:35, WARNING, Splashdown (went to apex-down)
1969-07-24, 17:29, INFO, Crew egress"""

# representation of DATA; dict keys: datetime, level, message
# type: map
result = ...

"""
* Complexity: easy
* Lines of code: 5 lines
* Time: 5 min

English:
1. Convert DATA to result: map
2. Convert numeric values to float
3. Run doctests - all must succeed

Polish:
1. Przekonwertuj DATA to result: map
2. Przekonwertuj wartości numeryczne do float
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* str.strip()
* str.split()
* map()
* list() + list()
* list.append()
* tuple()

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

>>> assert result is not Ellipsis, \
'Assign result to variable: result'
>>> assert type(result) is map, \
'Variable result has invalid type, must be a map'

>>> result = list(result)  # expand map object
>>> assert type(result) is list, \
'Variable result has invalid type, should be list'
>>> assert all(type(x) is tuple for x in result), \
'All rows in result should be tuple'

>>> result  # doctest: +NORMALIZE_WHITESPACE
[(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')]
"""

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"""

# values from file (note the list[tuple] format!)
# type: map
result = ...

"""
* Complexity: medium
* Lines of code: 5 lines
* Time: 8 min

English:
1. Convert from JSON format to Python using decoder function
2. Create instances of Setosa, Virginica, Versicolor
classes based on value in field "species"
3. Generate instances in result: map
4. Run doctests - all must succeed

Polish:
1. Przekonwertuj dane z JSON do Python używając dekodera funkcyjnego
2. Twórz obiekty klas Setosa, Virginica, Versicolor
w zależności od wartości pola "species"
3. Generuj instancje w result: map
4. Uruchom doctesty - wszystkie muszą się powieść

Hint:
* dict.pop()
* globals()[clsname]
* cls(**dict)
* json.loads()

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

>>> assert type(result) is map
>>> result = list(result)
>>> assert len(result) == 9

>>> classes = (Setosa, Virginica, Versicolor)
>>> assert all(type(row) in classes for row in result)

>>> result[0]
Virginica(sepal_length=5.8, sepal_width=2.7, petal_length=5.1, petal_width=1.9)

>>> result[1]
Setosa(sepal_length=5.1, sepal_width=3.5, petal_length=1.4, petal_width=0.2)
"""

import json
from dataclasses import dataclass

FILE = r'_temporary.json'

DATA = (
'[{"sepal_length":5.8,"sepal_width":2.7,"petal_length":5.1,"petal_widt'
'h":1.9,"species":"virginica"},{"sepal_length":5.1,"sepal_width":3.5,"'
'petal_length":1.4,"petal_width":0.2,"species":"setosa"},{"sepal_lengt'
'h":5.7,"sepal_width":2.8,"petal_length":4.1,"petal_width":1.3,"specie'
's":"versicolor"},{"sepal_length":6.3,"sepal_width":2.9,"petal_length"'
':5.6,"petal_width":1.8,"species":"virginica"},{"sepal_length":6.4,"se'
'pal_width":3.2,"petal_length":4.5,"petal_width":1.5,"species":"versic'
'olor"},{"sepal_length":4.7,"sepal_width":3.2,"petal_length":1.3,"peta'
'l_width":0.2,"species":"setosa"},{"sepal_length":7.0,"sepal_width":3.'
'2,"petal_length":4.7,"petal_width":1.4,"species":"versicolor"},{"sepa'
'l_length":7.6,"sepal_width":3.0,"petal_length":6.6,"petal_width":2.1,'
'"species":"virginica"},{"sepal_length":4.9,"sepal_width":3.0,"petal_l'
'ength":1.4,"petal_width":0.2,"species":"setosa"}]'
)

@dataclass
class Iris:
sepal_length: float
sepal_width: float
petal_length: float
petal_width: float

class Setosa(Iris):
pass

class Virginica(Iris):
pass

class Versicolor(Iris):
pass

# JSON decoded DATA
# type: map
result = ...

"""
* Assignment: Functional ApplyMap FlattenDicts
* Complexity: medium
* Lines of code: 7 lines
* Time: 13 min

English:
1. Convert DATA to format with one column per each attrbute for example:
a. group1_gid, group2_gid,
b. group1_name, group2_name
2. Note, that enumeration starts with one
3. Collect data to result: map
4. Run doctests - all must succeed

Polish:
1. Przekonweruj DATA do formatu z jedną kolumną dla każdego atrybutu, np:
a. group1_gid, group2_gid,
b. group1_name, group2_name
2. Zwróć uwagę, że enumeracja zaczyna się od jeden
3. Zbierz dane do result: map
4. Uruchom doctesty - wszystkie muszą się powieść

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

>>> result = list(result)
>>> assert type(result) is list
>>> assert len(result) > 0
>>> assert all(type(x) is dict for x in result)

>>> pprint(result, width=30, sort_dicts=False)
[{'firstname': 'Mark',
'lastname': 'Watney',
'group1_gid': 1,
'group1_name': 'staff'},
{'firstname': 'Melissa',
'lastname': 'Lewis',
'group1_gid': 1,
'group1_name': 'staff',
'group2_gid': 2,
{'firstname': 'Rick',
'lastname': 'Martinez'}]
"""

DATA = [
{"firstname": "Mark", "lastname": "Watney", "groups": [
{"gid": 1, "name": "staff"}]},

{"firstname": "Melissa", "lastname": "Lewis", "groups": [
{"gid": 1, "name": "staff"},

{"firstname": "Rick", "lastname": "Martinez", "groups": []},
]

# Flatten data, each mission field prefixed with mission and number
# type: list[dict]
result = ...

"""
* Assignment: Functional ApplyMap FlattenClasses
* Complexity: medium
* Lines of code: 9 lines
* Time: 13 min

English:
1. Convert DATA to format with one column per each attrbute for example:
a. Group1_year, Group2_year,
b. Group1_name, Group2_name
2. Note, that enumeration starts with one
3. Run doctests - all must succeed

Polish:
1. Przekonweruj DATA do formatu z jedną kolumną dla każdego atrybutu, np:
a. Group1_year, Group2_year,
b. Group1_name, Group2_name
2. Zwróć uwagę, że enumeracja zaczyna się od jeden
3. Uruchom doctesty - wszystkie muszą się powieść

Hints:
* vars(obj) -> dict

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

>>> result = list(result)
>>> assert type(result) is list
>>> assert len(result) > 0
>>> assert all(type(x) is dict for x in result)

>>> pprint(result, width=30, sort_dicts=False)
[{'firstname': 'Mark',
'lastname': 'Watney',
'group1_gid': 1,
'group1_name': 'staff'},
{'firstname': 'Melissa',
'lastname': 'Lewis',
'group1_gid': 1,
'group1_name': 'staff',
'group2_gid': 2,
{'firstname': 'Rick',
'lastname': 'Martinez'}]
"""

class User:
def __init__(self, firstname, lastname, groups=()):
self.firstname = firstname
self.lastname = lastname
self.groups = list(groups)

class Group:
def __init__(self, gid, name):
self.gid = gid
self.name = name

DATA = [
User('Mark', 'Watney', groups=[
Group(gid=1, name='staff')]),

User('Melissa', 'Lewis', groups=[
Group(gid=1, name='staff'),

User('Rick', 'Martinez'),
]

# Convert DATA
# type: list[dict]
result = ...

"""
* Assignment: Functional ApplyMap Model
* Complexity: easy
* Lines of code: 16 lines
* Time: 8 min

English:
1. In DATA we have two classes
2. Model data using classes and relations
3. Create instances dynamically based on DATA
4. Run doctests - all must succeed

Polish:
1. W DATA mamy dwie klasy
2. Zamodeluj problem wykorzystując klasy i relacje między nimi
3. Twórz instancje dynamicznie na podstawie DATA
4. Uruchom doctesty - wszystkie muszą się powieść

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

>>> result = list(result)
>>> assert type(result) is list
>>> assert all(type(user) is User for user in result)

...            for user in result

>>> pprint(result)  # doctest: +NORMALIZE_WHITESPACE
[User(firstname='Mark',
lastname='Watney',
city='Houston',
postcode=77058,
region='Texas',
country='USA'),
city='Kennedy Space Center',
postcode=32899,
region='Florida',
country='USA')]),
User(firstname='Melissa',
lastname='Lewis',
postcode=91109,
region='California',
country='USA'),
city='Palmdale',
postcode=93550,
region='California',
country='USA')]),
User(firstname='Alex',
lastname='Vogel',
city='Cologne',
postcode=51147,
region='North Rhine-Westphalia',
country='Germany')])]
"""

from dataclasses import dataclass
from itertools import starmap

DATA = [
{"firstname": "Mark", "lastname": "Watney", "addresses": [
{"street": "2101 E NASA Pkwy",
"city": "Houston",
"postcode": 77058,
"region": "Texas",
"country": "USA"},
{"street": "",
"city": "Kennedy Space Center",
"postcode": 32899,
"region": "Florida",
"country": "USA"}]},

{"firstname": "Melissa", "lastname": "Lewis", "addresses": [
{"street": "4800 Oak Grove Dr",
"postcode": 91109,
"region": "California",
"country": "USA"},
{"street": "2825 E Ave P",
"city": "Palmdale",
"postcode": 93550,
"region": "California",
"country": "USA"}]},

{"firstname": "Rick", "lastname": "Martinez", "addresses": []},

{"firstname": "Alex", "lastname": "Vogel", "addresses": [
{"street": "Linder Hoehe",
"city": "Cologne",
"postcode": 51147,
"region": "North Rhine-Westphalia",
"country": "Germany"}]}
]

@dataclass
street: str
city: str
postcode: int
region: str
country: str

@dataclass
class User:
firstname: str
lastname: str

# Iterate over DATA and create instances
# type: list[User]
result = ...

"""
* Assignment: Functional ApplyMap CSV
* Complexity: medium
* Lines of code: 4 lines
* Time: 13 min

English:
1. Write data with relations to CSV format
2. Convert DATA to result: list[dict[str,str]]
3. Non-functional requirements:
a. Use , to separate fields
b. Use ; to separate instances
4. Contact has zero or many addresses
5. Run doctests - all must succeed

Polish:
1. Zapisz dane relacyjne do formatu CSV
2. Przekonwertuj DATA do result: list[dict[str,str]]
3. Wymagania niefunkcjonalne:
b. Użyj , do oddzielenia pól
b. Użyj ; do oddzielenia instancji
4. Kontakt ma zero lub wiele adresów
5. Uruchom doctesty - wszystkie muszą się powieść

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

>>> result = list(result)
>>> assert type(result) is list
>>> assert len(result) > 0
>>> assert all(type(x) is dict for x in result)

>>> pprint(result, width=112, sort_dicts=False)  # doctest: +NORMALIZE_WHITESPACE
[{'firstname': 'Mark',
'lastname': 'Watney',
'addresses': '2101 E NASA Pkwy,Houston,77058,Texas,USA;,Kennedy Space Center,32899,Florida,USA'},
{'firstname': 'Melissa',
'lastname': 'Lewis',
{'firstname': 'Rick', 'lastname': 'Martinez', 'addresses': ''},
{'firstname': 'Alex',
'lastname': 'Vogel',

"""

DATA = [
{"firstname": "Mark", "lastname": "Watney", "addresses": [
{"street": "2101 E NASA Pkwy",
"city": "Houston",
"postcode": 77058,
"region": "Texas",
"country": "USA"},
{"street": "",
"city": "Kennedy Space Center",
"postcode": 32899,
"region": "Florida",
"country": "USA"}]},

{"firstname": "Melissa", "lastname": "Lewis", "addresses": [
{"street": "4800 Oak Grove Dr",
"postcode": 91109,
"region": "California",
"country": "USA"},
{"street": "2825 E Ave P",
"city": "Palmdale",
"postcode": 93550,
"region": "California",
"country": "USA"}]},

{"firstname": "Rick", "lastname": "Martinez", "addresses": []},

{"firstname": "Alex", "lastname": "Vogel", "addresses": [
{"street": "Linder Hoehe",
"city": "Cologne",
"postcode": 51147,
"region": "North Rhine-Westphalia",
"country": "Germany"}]}
]