<jupyter_start><jupyter_text>Лекция 13 "Введение в функциональное программирование. Часть 2." Финансовый университет при Правительстве РФ, лектор <NAME>  Map / Filter / Reduce ![](MFR_emj.png)  Map Встроенная функция map() позволяет применить функцию к каждому элементу последовательности. Функция имеет следующий формат: `mар(, [, ... , ])`Функция возвращает объект, nоддерживающий итерацию, а не список. ![](map_.jpg)<jupyter_code>squared = lambda x: x**2
list(range(10))
m1 = map(squared, range(10))
m1
list(m1)
ls0 = list(zip(range(10), range(0, 100, 10)))
ls0
list(map(sum, ls0))
# альтернативное решение - генератор списков:
[sum(i) for i in ls0]<jupyter_output><empty_output><jupyter_text>Функции `map()` можно передать несколько nоследовательностей. В этом случае в функцию обратного вызова будут передаваться сразу несколько элементов, расположенных в nоследовательностях на одинаковом смещении.  ![](map2_.jpg)<jupyter_code>ls1 = list(range(10))
ls2 = list(range(0, 100, 10))
ls3 = list(range(0, 1000, 100))
ls1, ls2, ls3
list(map(lambda x, y: x + y, ls1, ls2))
# аналог - генератор списков:
[x + y for x, y in zip(ls1, ls2)]
import operator as op
list(map(op.add, ls1, ls2))
list(map(lambda x, y, z: x + y + z, ls1, ls2, ls3))
list(map(sum, ls1, ls2, ls3))
sum(1, 2, 3)
sum((1, 2, 3))
def f(*a):
    return sum(a)
sum2 = lambda *a: sum(a)
sum2(1, 2, 3)
list(map(sum2, ls1, ls2, ls3, ls1))
# решение, которое может показаться подходящим, но выполняет другую задачу:
list(map(sum, [ls1, ls2, ls3]))
list(zip(ls1, ls2, ls3, ls1))
list(map(sum, zip(ls1, ls2, ls3, ls1)))
# что, если функция ожидает позиционные аргументы? Пример:
list(map(print, zip(ls1, ls2, ls3, ls1)))
list(map(print, ls1, ls2, ls3, ls1))
lsz = list(zip(ls1, ls2, ls3, ls1))
lsz
import itertools as itl
print(*lsz[0])
list(itl.starmap(print, lsz))<jupyter_output>0 0 0 0
1 10 100 1
2 20 200 2
3 30 300 3
4 40 400 4
5 50 500 5
6 60 600 6
7 70 700 7
8 80 800 8
9 90 900 9<jupyter_text>Filter Функция `filter()` nозволяет выполнить проверку элементов последовательности. Формат функции: `filtеr(, )`Если в nервом nараметре вместо названия функции указать значение `None`, то каждый элемент nоследовательности будет nроверен на соответствие значению `True`. Если элемент в логическом контексте возвращает значение `False`, то он не будет добавлен в возвращаемый результат. Функция возвращает объект, поддерживающий итерацию, а не список. ![](filter_.jpg)<jupyter_code>import random
random.seed(42)
lr1 = [random.randint(-100, 100) for _ in range(20)]
lr1
list(filter(lambda x: x % 3 == 0, lr1))
# аналог - генератор списков:
[i for i in lr1 if i % 3 == 0]
lr2 = [random.randint(-1, 1) for _ in range(20)]
lr2
# первый параметр None имеет особую семантику:
list(filter(None, lr2))
# последовательное примененеие преобразований:
list(map(op.abs, filter(lambda x: x % 3 == 0, range(-10,10))))
%%timeit
[i for i in lr1 if i % 3 == 0]
%%timeit
list(filter(lambda x: x%3 == 0, lr1))<jupyter_output>8.73 µs ± 2.36 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)<jupyter_text>Reduce<jupyter_code>import functools as ft
from functools import reduce<jupyter_output><empty_output><jupyter_text>`functools.reduce(funct, iterable[, initializer])`Вычисляет функцию от двух элементов последовательно для элементов последовательности слева направо таким образом, что результатом вычисления становится единственное значение, которое становится первым аргументом для следующей итерации применения `funct`.<jupyter_code># Пример: 
reduce(lambda x, y: x + y, [1, 2, 3, 4, 5]) # вычисляется как ((((1+2)+3)+4)+5)<jupyter_output><empty_output><jupyter_text>Левый аргумент функции `funct` (аргумента `reduce`) - это аккумулированное значение, правый аргумент - очередное значение из списка.Если передан необязательный аргумент `initializer`, то он используется в качестве левого аргумента при первом применении функции (исходного аккумулированного значения).Если `initializer` не перередан, а последовательность имеет только одно значение, то возвращается это значенние. ![](reduce_.jpg)<jupyter_code>ls4 = list(range(10, 20))
ls4
reduce(op.add, ls4)
reduce(op.add, ls4, 1000)
def add_verbose(x, y):
    print(f"add(x={x}, y={y}) -> {x+y}")
    return x + y
reduce(add_verbose, ls4)
reduce(add_verbose, ls4, 1000)
st = "This is a test".split()
st
reduce(lambda n, s: n + len(s), ['This', 'is', 'a', 'test'], 0) 
def f2_verbose(n, s):
    print(f'n: {n}, s: {s}, len(s): {len(s)}')
    return n + len(s)
reduce(f2_verbose, ['This', 'is', 'a', 'test'], 0) 
reduce(lambda n, s: n + len(s), "This is a test".split(), 0) 
reduce(lambda s1, s2: s1 + s2, "This is a test".split(), "")<jupyter_output><empty_output><jupyter_text>Итераторы Итерируемый тип данных - это такой тип, который может возвращать свои элементы по одному. Любой объект, имеющий метод `__iter__()`, или любая последовательность (то есть объект, имеющий метод `__getitem__()`, принимающий целочисленный аргумент со значением от 0 и выше), является итерируемым и может предоставлять итератор. Итератор - это объект, имеющий метод `__next__()`, который при каждом вызове возвращает очередной элемент и возбуждает исключение `StopIteration` после исчерпания всех элементов.<jupyter_code>ls5 = [1, 2, 5, 3, 4, 8]
product = 1 

itr = iter(ls5) # iter() вызывает ls5.__iter__() (ls5 - значение итерируемого типа, itr - итератор )
while True: 
    try: 
        i = next(itr) # next вызывает itr.__next__()
        product *= i
    except StopIteration: 
        break 
product
product = 1 

for i in ls5: 
    product *= i 
print(product) 
# другой способ:
reduce(op.mul, ls5)<jupyter_output><empty_output><jupyter_text>К итерируемым типам могут применяться встроенные функции: `all()`, `аnу()`, `len()`, `min()`, `max()`, `sum()`:<jupyter_code>len(ls5), min(ls5), max(ls5), sum(ls5)
ls6 = [i % 2 == 0 for i in ls5]
ls6
all(ls6), any(ls6)
reduce(op.and_, ls6)<jupyter_output><empty_output><jupyter_text>Модуль itertools<jupyter_code>import itertools as itl<jupyter_output><empty_output><jupyter_text>https://docs.python.org/3.6/library/itertools.htmlhttp://www.ilnurgi1.ru/docs/python/modules/itertools.html `itertools.islice(iterable, [start, ]stop[, step])`Создает итератор, воспроизводящий элементы, которые вернула бы операция извлечения среза `iterable[start:stop:step]`. Первые `start` элементов пропускаются и итерации прекращаются по достижении позиции, указанной в аргументе `stop`. В необязательном аргументе `step` передается шаг выборки элементов. В отличие от срезов, в аргументах `start`, `stop` и `step` не допускается использовать отрицательные значения. Если аргумент `start` опущен, итерации начинаются с 0. Если аргумент `step` опущен, по умолчанию используется шаг 1.<jupyter_code>itl.islice('ABCDEFG', 5)
list(itl.islice('ABCDEFG', 5))
list(itl.islice('ABCDEFG', 2, 5))
list(itl.islice('ABCDEFG', 2, 8, 2))<jupyter_output><empty_output><jupyter_text>Бесконечные итераторы `itertools.cycle(iterable)`Создает итератор, который в цикле многократно выполняет обход элементов в объекте `iterable`. За кулисами создает копию элементов в объекте `iterable`. Эта копия затем используется для многократного обхода элементов в цикле.<jupyter_code>list(itl.islice(itl.cycle('ABCD'), 10))<jupyter_output><empty_output><jupyter_text>`itertools.count(start=0, step=1)`Создает итератор, который воспроизводит упорядоченную и непрерывную последовательность целых чисел, начиная со `start`. Если аргумент `start` опущен, в качестве первого значения возвращается число 0. (Обратите внимание, что этот итератор не поддерживает длинные целые числа. По достижении значения `sys.maxint` счетчик переполнится и итератор продолжит воспроизводить значения, начиная с `-sys.maxint - 1`.)<jupyter_code>list(itl.islice(itl.count(7), 10))<jupyter_output><empty_output><jupyter_text>`itertools.repeat(object[, times])`Создает итератор, который многократно воспроизводит объект `object`. В необязательном аргументе `times` передается количество повторений. Если этот аргумент не задан, количество повторений будет бесконечным.<jupyter_code>list(itl.islice(itl.repeat('a'), 10))
['a']*10
list(itl.repeat('a', 10))<jupyter_output><empty_output><jupyter_text>Примеры использования других итераторов:<jupyter_code># accumulate - cоздает последовательность с накопленной суммой:
list(itl.accumulate([1, 2, 3, 4, 5, 6]))
list(itl.accumulate(itl.repeat(3, 10)))
# chain - создает итератор по цепочке элементов, склеенных из последовательности итерируемых объектов:
list(itl.chain([1, 2, 3], [4, 5, 6], [7, 8, 9]))
# chain.from_iterable - аналог chain(), но принимает одну последовательность,
# каждый элемент которой считается отдельной последовательностью:
list(itl.chain.from_iterable(['abc', 'def']))
# zip_longest - альтернатива zip, итерируемся до конца самой длинной последовательности:
list(itl.zip_longest('abcd', 'def'))
# обычный zip работает так:
list(zip('abcd', 'def'))
# производит фильтрацию последовательности, указанной в первом параметре:
list(itl.compress('ABCDEF', [1, 0, 1, 0, 1, 1]))
# возвращает объект-итератор, который выбрасывает элементы, предшествующие элементу,
# для которого функция, указанная в первом параметре, впервые вернет False:
list(itl.dropwhile(lambda x: x < 5, [1, 4, 6, 4, 1]))<jupyter_output><empty_output><jupyter_text>Комбинаторные итераторы:<jupyter_code># Декартово произведение:
print(list(itl.product('ABCD', repeat=2)))
# Перестановки:
print(list(itl.permutations('ABCD', 2)))
# Сочетания:
print(list(itl.combinations('ABCD', 2)))<jupyter_output>[('A', 'B'), ('A', 'C'), ('A', 'D'), ('B', 'C'), ('B', 'D'), ('C', 'D')]<jupyter_text>Функции-генераторы Функция-генератор, или метод-генератор - это функция, или метод, содержащая выражение `yield`. В результате обращения к функции-генератору возвращается итератор. Значения из итератора извлекаются по одному, с помощью его метода `__next__()`. При каждом вызове метода `__next__()` он возвращает результат вычисления выражения `yield`. (Если выражение отсутствует, возвращается значение `None`.) Когда функция-генератор  завершается или выполняет инструкцию `return`, возбуждается  исключение `StopIteration`. На практике очень редко приходится вызывать метод `__next__()` или обрабатывать исключение `StopIteration`. Обычно функция-генератор используется в качестве итерируемого объекта.<jupyter_code># Создает и возвращает список 
def letter_range_l(a, z): 
    result = [] 
    while ord(a) < ord(z): 
        result.append(a) 
        a = chr(ord(a) + 1) 
    return result 
letter_range_l('a','o')
for l in letter_range_l('a', 'o'):
    print(l, end=', ')
# Возвращает каждое значение по требованию 
def letter_range_g(a, z): 
    while ord(a) < ord(z): 
        yield a 
        a = chr(ord(a) + 1) 
for l in letter_range_g('a', 'o'):
    print(l, end=', ')
list(letter_range_g('a', 'o'))
g1 = letter_range_g('a', 'c')
g1
next(g1)
next(g1)
next(g1)
list(letter_range_g('a', 'o'))<jupyter_output><empty_output><jupyter_text>Кроме функций-генераторов cуществует возможность создавать еще и __выражения-генераторы__.  Синтаксически они очень похожи на генераторы списков, единственное отличие состоит в том, что они заключаются не в квадратные скобки, а в круглые. `(expression for item in iterable)` `(expression for item in iterable if condition)`При передаче выражений-генераторов в качестве аргументов функции круглые скобки могут опускаться.<jupyter_code># функция-генератор, возвращающая из словаря пары ключ-значение в порядке убывания ключа
def items_in_key_order(d): 
    for key in sorted(d, reverse=True): 
        yield key, d[key] 
d1 = dict(zip(['ac','vg','ddxf','c','ff', 'bfafakl'], range(6)))
d1
list(items_in_key_order(d1))
[(key, d1[key]) for key in sorted(d1, reverse=True)]
go = ((key, d1[key]) for key in sorted(d1, reverse=True))
go
list(go)
list(((key, d1[key]) for key in sorted(d1, reverse=True)))
# При передаче выражений-генераторов в качестве аргументов функции круглые скобки можно опустить:
list((key, d1[key]) for key in sorted(d1, reverse=True))
items_in_key_order(d1)
g2 = ((key, d1[key]) for key in sorted(d1, reverse=True))
g2
next(g2)
next(g2)<jupyter_output><empty_output><jupyter_text>Благодаря отложенным вычислениям в генераторах (функциях и выражениях) можно экономить ресурсы и создавать генераторы бесконечных последовательностей.<jupyter_code># бесконечный генератор четвертей:
def quarters(next_quarter=0.0): 
    while True: 
        yield next_quarter 
        next_quarter += 0.25 
result = [] 
for x in quarters(): 
    result.append(x) 
    if x >= 5.0: 
        break 
result
list(itl.islice(quarters(10.0), 30))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12889911897769665, 'fraction_numerical': 0.029923009762679576, 'mean_word_length': 5.976744186046512, 'pattern_counts': {'":': 0, '<': 74, '<?xml version=': 0, '>': 73, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '18', 'dir': 'jupyter-structured-clean-dedup', 'id': '606899', 'license': 'WTFPL', 'n_tokens_mistral': 5432, 'n_tokens_neox': 5807, 'n_words': 1331, 'path': 'Course I/Алгоритмы Python/Part2/лекции/lec13_fp2/lec13fp2_v6.ipynb', 'repo_name': 'GeorgiyDemo/FA', 'size': '12606'}