# Функциональное программирование

### Введение

Функциональное программирование — это такая методика написания программ, когда **в центре внимания находятся функции**. Функции могут присваиваться переменным, они могут передаваться в другие функции и порождать новые функции. Это можно назвать **стилем** или **парадигмой** программирования

В этой парадигме широко используются **функции высшего порядка** и **анонимные функции**

**Функции высшего порядка (ФВП)** — это функции, которые принимают в качестве аргумента другие функции и/или возвращают функции в качестве результата. Мы коснулись их в прошлом уроке, а в этой главе познакомимся поближе

**Анонимные функции** (lambda-функции) — безымянные функции

### Анонимные функции (lambda)

**Вспомним:**&#x20;

* Функции — это небольшие подпрограммы внутри основного кода. При запуске они могут взять какие-то стартовые **параметры**, обработать их, получить свой результат и **отдать этот результат** в основной код
* Чтобы вызвать функцию, нужно указать её имя, но бывают такие функции, у которых нет имени, но их всё равно можно вызывать

*Пример обычной функции:*

```python
def add_func(x, y):
    result = x + y
    return result
print(add_func(2,3))
```

* У функции есть имя, тело функции и есть результат, который она возвращает. Чтобы вызвать эту функцию, мы указываем имя  и аргументы, которые нужно обработать.
* Мы можем значительно усложнить функцию, для этого добавить в неё нужные инструкции

**Лямбда-функции** выглядят по-другому — они не имеют имени, но у них есть ключевое слово **lambda**.

* **lambda** *аргументы*: *выражение (значение функции)*

```python
result = lambda x, y: x + y
print(result(2, 3))
```

* лямбда-функция **всегда возвращает значение**
* она должна быть **записана в одну строку**
* внутри функций нет присваивания и сложной логики — это всегда **простое выражение**

#### Зачем нужны лямбда-функции?

* Самое частое применение — в качестве аргументов в других функциях
* ***Пример:***

{% code overflow="wrap" %}

```python
def filter_numbers(numbers, filter_func):
    result = []
    for num in numbers:
        if filter_func(num):
            result.append(num)
    return result


numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_numbers = filter_numbers(numbers, lambda x: x % 2 == 0) # передаём анонимную  функцию в качестве аргумента
print(filtered_numbers)
# [2, 4, 6, 8, 10]
```

{% endcode %}

* Пойдём дальше — мы можем вместо `filter_numbers` тоже использовать лямбда-функцию:

{% code overflow="wrap" %}

```python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
filtered_numbers = lambda lst: [num for num in lst if num % 2 == 0]
# здесь анонимная функция принимает lst и формирует список при помощи спискового включения
print(filtered_numbers(numbers))
# [2, 4, 6, 8, 10]
```

{% endcode %}

* Технически, можно обойтись без лямбда-функций. Но иногда с ними получается удобнее

***Практика:***

* Напишите четыре анонимные функции. Они должны:
  * Возводить переданное число в квадрат
  * Умножать два переданных числа
  * Возвращать бОльшее из двух переданных чисел
  * Возвращать первую букву переданного слова
* Выведите на экран результат работы этих функций

### Встроенные ФВП: map() filter() reduce()

#### map()

* Допустим, мы хотим применить к каждому элементу списка какую-то функцию или операцию. Мы можем сделать при помощи цикла for:

```python
numbers = [1, 2, 3, 4, 5]
new_numbers = []
for num in numbers:
    squared = num ** 2
    subtracted = squared - 10
    new_numbers.append(subtracted)

print(new_numbers)
# [-9, -6, -1, 6, 15]
```

* Можем определить две функции, одна из которых принимает последовательность и функцию, а вторая является той функцией, которую нужно применить:

```python
def func_1(numbers, func):
    res = []
    for num in numbers:
        res.append(func(num))
    return res
    
def func_2(number):
    number **= 2
    number -= 10
    return number
    
numbers = [1, 2, 3, 4, 5]
print(func_1(numbers, func_2))
# [-9, -6, -1, 6, 15]
```

* Есть третий способ — использовать встроенную функцию высшего порядка **map()**
* Она принимает в качестве аргументов функцию и последовательность. И применяет переданную функцию к каждому элементу последовательности&#x20;

{% code overflow="wrap" %}

```python
def func_2(number):
    number **= 2
    number -= 10
    return number

numbers = [1, 2, 3, 4, 5]
print(list(map(func_2, numbers))) # преобразуем в list, т.к. map возращает объект типа map
# [-9, -6, -1, 6, 15]
```

{% endcode %}

* Часто вместе в качестве аргументов для ФВП используют анонимные функции:

```python
numbers = [1, 2, 3, 4, 5]
print(list(map(lambda n: n ** 2 - 10, numbers)))
# [-9, -6, -1, 6, 15]
```

#### filter()

```python
filter(function or None, iterable) -> filter object
```

* Как мы видим из сигнатуры, функция принимает функцию или None и итерируемую последовательность. Возвращает объект `filter`

```python
numbers = [2, 3, 8, 15, 34, 42]
def is_even(number):
    return number % 2 == 0 

filter(is_even, numbers)
list(filter(is_even, numbers))
# [2, 8, 34, 42]
```

* `filter()` принимает функцию, которая возвращает булево значение
* Попробуйте передать в качестве аргумента вместо функции значение None. Проанализируйте результат

#### reduce()

* Для того, чтобы воспользоваться функцией `reduce()`нам понадобится импортировать её из  модуля `functools`

```python
from functools import reduce

reduce(function, iterable[, initializer]) -> value
```

* Функция `reduce()` кумулятивно применяет функцию `function` к элементам итерируемой `iterable` последовательности, сводя её к единственному значению
* По сути результат работы `reduce()` это аккумулятор

```python
reduce(lambda x, y: x + y, [1, 2, 3, 4, 5])
# 15
items = [10, 20, 30, 40, 50]
sum_all = reduce(lambda x, y: x + y, items)
sum_all
# 150
items = [1, 24, 17, 14, 9, 32, 2]
all_max = reduce(lambda a, b: a if (a > b) else b, items)
all_max
# 32
```

* Необязательный аргумент `initializer` — это значение аккумулятора по умолчанию, с которого начать работу. Если последовательность пустая, аккумулятор примет значение `initializer`. Если последовательность непустая, то `initializer` станет начальным значением аккумулятора
* Попробуйте применить аргумент `initializer` к примерам выше. Проанализируйте результат

#### Особенности объектов map, filter, reduce

* Результат работы функций `map() filter() reduce()` это объекты `map filter reduce` соответственно. Это не готовые последовательности, а **итераторы** — интерфейсы для взаимодействия с последовательностями, но не сами последовательности.&#x20;
* Мы можем использовать их как `iterable` объекты, но не можем увидеть всю последовательность. &#x20;
* Сама последовательность *скрыта* и данные предоставляются "по запросу" — то есть только тогда, когда это потребуется
* Это позволяет эффективнее использовать память, чем при хранении, например, списка
* Если нам нужна сама последовательность, результат работы `map() filter() reduce()` нужно преобразовать в неё

```python
c = reduce(lambda x, y: x + y, map(lambda x: x * 2, [1, 2, 3, 4, 5]))
# будет работать, т.к. объект map является iterable
res = map(lambda x: x * 2, [1, 2, 3, 4, 5])
res
# map object
list(res)
# [2, 4, 6, 8, 10]
```

***Практика:***

**Задание с числами:**

* Входные данные: список чисел.
* Используйте `map()`, чтобы возвести каждое число в квадрат.
* Затем к получившейся последовательности примените `filter()`, чтобы отфильтровать только нечётные числа.
* И к получившейся в результате последней операции последовательности примените `reduce()`, чтобы сложить оставшиеся числа в списке.

**Задание с текстом:**

* Входные данные: список строк. Также как в прошлом задании, последовательно примените функции к списку строк.
  * Используйте `map()`, чтобы преобразовать каждую строку в верхний регистр.
  * Используйте `filter()`, чтобы отфильтровать строки, которые содержат определенную подстроку
  * Используйте `reduce()`, чтобы объединить отфильтрованные строки в одну строку через запятую.
  * Верните результат.
* Для тестирования используйте следующие данные:

```python
['I love Python', 'You love Python', 'We love Python', "I don't love anything"]
Подстрока: 'PYTHON'
```

### Некоторые полезные функции

#### zip()

* Функция `zip()` — объединяет отдельные элементы нескольких последовательностей в кортежи. Возвращает объект `zip`

```python
zip(последовательность, последовательность, ...)
```

* Примеры использования:

```python
x = 'абв'
y = 'эюя'
zipped = zip(x, y)
list(zipped)
# [('а', 'э'), ('б', 'ю'), ('в', 'я')]
```

```python
names = ["Alice", "Bob", "Charlie"]
scores = [85, 92, 78]

zipped = zip(names, scores)

for item in zipped:
    print(item)
    
# ('Alice', 85)
# ('Bob', 92)
# ('Charlie', 78)
```

* Попробуйте применить zip() к последовательностям разной длины. Проанализируйте результат
* `zip()` часто используется для создания словарей. В главе, посвященной словарям, мы узнаем, как это работает

#### enumerate()

* Функция `enumerate` возвращает индекс элемента и сам элемент последовательности в качестве кортежа. Вот общий формат функции `enumerate`:

```python
enumerate(последовательность)
```

* Пример использования:

```python
letters = enumerate(['а','б','в'])
list(letters)
[(0, 'а'), (1, 'б'), (2, 'в')]
```

* `enumerate()` часто используют в цикле `for`, когда нам нужен и элемент и его индекс:

```python
names = ['Alice', 'Bob', 'John', 'Elena']
for i, name in enumerate(names):
    print(f'{name} имеет индекс {i}')
```

***Практика:***

* Напишите функцию, которая принимает на вход список чисел и возвращает новый список, состоящий из чисел, имеющих четный индекс. Используйте `enumerate()`


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://timosii.gitbook.io/py_tutorial/funkcii/funkcionalnoe-programmirovanie.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
