#%% Generátor - funkce s jedním nebo více yield:
def ascending_1_to_5():
    print('Sophisticated number generator begins')
    i = 1
    while i < 6:
        yield i
        i += 1
    print('Generator ends')


#%% Zavoláním generátoru se "nic nestane":
values = ascending_1_to_5()


#%% Iterace:
for i in values:
    print(i)


#%% Generátor může proběhnout jen jednou:
for i in values:
    print(i)


#%% Konverze na list:
list_from_gen = list(ascending_1_to_5())
print(f'list_from_gen = {list_from_gen}')


#%% TODO: Naprogramujte generátor, který bude generovat příjmení osob ze zadaného seznamu instancí typu Person.

pass


#%% Skládání generátorů:
def gen_squares(iterable):
    for x in iterable:
        yield x ** 2


values = gen_squares(ascending_1_to_5())
print(f'Numbers squared = {list(values)}')


#%% Delegace generátorů:
def two_rounds_1_to_5():
    yield from ascending_1_to_5()
    yield from ascending_1_to_5()


print(f'Two rounds = {list(two_rounds_1_to_5())}')


#%% Návratová hodnota při delegaci generátorů:
def yield_return(iterable):
    accumulator = 0
    for x in iterable:
        accumulator += x
        yield x
    return accumulator


def delegator(iterable):
    return_value = yield from yield_return(iterable)
    yield return_value


print(f'Generator alone: {list(yield_return(ascending_1_to_5()))}')
print(f'Delegator: {list(delegator(ascending_1_to_5()))}')


#%% Vestavěné "generátory": range() - pozor, není přímo generátor, ale chováním jej připomíná:
for value in range(1, 6):
    print(value)


#%% reversed():
for value in reversed(range(0, 10)):
    print(value)


#%% reversed() - nefunguje s obecnými generátory:
for value in reversed(ascending_1_to_5()):
    print(value)


#%% enumerate():
for index, value in enumerate(range(11, 16)):
    print(f'[{index}] = {value}')


#%% filter():
for value in filter(lambda n: n % 2 == 0, range(0, 10)):
    print(value)


#%% map():
for value in map(lambda n: n ** 2, range(0, 10)):
    print(value)


#%% map() - více argumentů:
for value in map(lambda a, b, c: (a + b) * c, range(0, 5), range(5, 10), range(2, 12, 2)):
    print(value)


#%% zip():
values = range(0, 10)
for value in zip(values, map(lambda n: n ** 2, values)):
    print(value)


#%% zip() - typické použití:
values = range(0, 10)
squared_values = dict(zip(values, map(lambda n: n ** 2, values)))
print(f'squared_values = {squared_values}')


#%% zip() - skončí, až skončí první iterable:
zip_short = zip(range(0, 10), range(0, 5))
print(f'zip_short = {list(zip_short)}')


#%% contextlib.contextmanager() - dekorátor, který z generátoru udělá dekorátor:
from contextlib import contextmanager


@contextmanager
def my_context(val):
    print('Before entering the block.')
    yield val ** 2
    print('After entering the block.')


with my_context(4) as value:
    print(f'value = {value}')


#%% Generator expressions - "in-line generátory":
expr = (i ** 2 for i in range(0, 10))
print(f'expr = {repr(expr)}')


#%% Chová se stejně, jako generátor:
for i in expr:
    print(i)


#%% Pokud je jediným argumentem funkce, nemusí být v závorkách:
from math import sqrt


def square_root_generator(iterable):
    for v in iterable:
        yield sqrt(v)


for i in square_root_generator(i ** 2 for i in range(0, 10)):
    print(i)


#%% Lze i filtrovat:
for i in (i ** 2 for i in range(0, 10) if i % 2 == 0):
    print(i)


#%% for lze i zřetězit:
for i in (i ** n for i in range(0, 5) for n in range(0, 3)):
    print(i)


#%% List comprehension:
lst = [i ** 2 for i in range(0, 10)]
print(f'lst = {lst}')


#%% Dictionary comprehension:
dct = {i: i ** 2 for i in range(0, 10)}
print(f'dct = {dct}')


#%% Na set je nutné jít přes genexpr:
s = set(i ** 2 for i in range(0, 10))
print(f's = {s}')
