{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "46044c32",
   "metadata": {},
   "source": [
    "# Функциональное программирование #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1d0b1e3",
   "metadata": {},
   "source": [
    "Мы с вами разобрали то, как работают и определяются функции в Python'e и настало время разобраться с таким сложным концептом как функциональное программирование.\n",
    "\n",
    "Что же это такое? Чтобы понять, что такое функциональное программирование, нужно понимать несколько особенностей.\n",
    "\n",
    "Во-первых, функции в Python'e - это такие же объекты, как и, например, строки, списки или классы.\n",
    "Их можно передавать в функции другие, их можно возвращать из функций, их можно создавать на лету, то есть это объекты первого класса.\n",
    "Например, мы можем определить функцию `caller`, которая принимает другую функцию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "dd050baa",
   "metadata": {},
   "outputs": [],
   "source": [
    "def caller(func, params):\n",
    "    return func(*params)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "60905ede",
   "metadata": {},
   "source": [
    "Функцию можно передавать в функцию, как я вам говорил. Она принимает другую функцию и какой-то список параметров, и, собственно, эта функция вызывается этим списком параметров с использованием звездочки, с которой мы только что с вами разобрались.\n",
    "\n",
    "Определим другую функцию — обычную `printer`, которая просто выводит какую-то строчку. Мы можем в наш `caller` передать функцию `printer` и вызвать её. Это может быть функция `printer` в нашем `caller`'е, а может быть какая-нибудь другая функция - нам неважно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "bcfb80f1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def printer(name, origin):\n",
    "    print(f\"I'm {name} of {origin}!\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "5c79161f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "I'm Moana of Motunui!\n"
     ]
    }
   ],
   "source": [
    "caller(printer, [\"Moana\", \"Motunui\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "29db28ad",
   "metadata": {},
   "source": [
    "Главное, чтобы мы передали туда функцию и список параметров. Итак, мы вызываем наш `caller` с функцией `printer` и передаем туда список параметров и у нас выводится строка.\n",
    "\n",
    "Собственно, главное, что нужно вынести из этого примера, что функции можно передавать другие функции. Это такие же объекты."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4aae0263",
   "metadata": {},
   "source": [
    "Ещё один важный момент. Функции можно не только передавать, их еще можно создавать внутри других функций. Например, мы можем определить функцию `get_multiplier` и внутри функции `get_multiplier` определить другую функцию, и эта функция будет `inner`. Она будет просто умножать два аргумента ей переданных."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "77503f82",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_multiplier():\n",
    "    def inner(a, b):\n",
    "        return a * b\n",
    "    \n",
    "    return inner"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "95090fff",
   "metadata": {},
   "source": [
    "Мы можем вызвать функцию `get_multiplier` и получить обычный `multiplier`, который умножает, например, 10 на 11, то есть мы определили функцию, которая возвращает другую функцию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "644b0061",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "110"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "multiplier = get_multiplier()\n",
    "multiplier(10, 11)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a1490a6",
   "metadata": {},
   "source": [
    "Пока ничего сложного. Обратите внимание, что именно `multiplier`'ы — это `inner`, потому что мы вернули новую функцию и `multiplier` перестал быть тем, каким мы его знали раньше. Он стал `inner`'ом. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "2978a144",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "inner\n"
     ]
    }
   ],
   "source": [
    "print(multiplier.__name__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "18d1ca79",
   "metadata": {},
   "source": [
    "Гораздо более интересный момент - это если мы наш `get_multiplier` что-нибудь передадим. Давайте попробуем определить функцию `inner`, которая будет принимать один аргумент и умножать она его будет всегда на то самое число, которое мы передали в `get_multiplier`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "3c7d8cef",
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_multiplier(number):\n",
    "    def inner(a):\n",
    "        return a * number\n",
    "    \n",
    "    return inner"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f00d88e1",
   "metadata": {},
   "source": [
    "Смотрите, мы передаем `get_multiplier` двойку и получаем функцию, которая всегда умножает переданный ей аргумент на двойку."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "06513593",
   "metadata": {},
   "outputs": [],
   "source": [
    "multiplier_by_2 = get_multiplier(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "840f794a",
   "metadata": {},
   "source": [
    "Например, мы передали в `multiplier_by_2` десятку и получили 20."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "256d8b08",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "20"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "multiplier_by_2(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e72a8eb0",
   "metadata": {},
   "source": [
    "Эта концепция называется \"замыканием\". В данном случае у нас используется `number` из скопа выше, из области видимости функции `get_multiplier` и это очень важный момент, который в дальнейшем будет использоваться, например, в декораторах.\n",
    "\n",
    "Часто бывает необходимо определить какие-то свои функции, которые принимают другие функции, но ещё чаще эти функции используют примерно одинаково, например, для того, чтобы применить какую-то функцию к набору элементов.\n",
    "  \n",
    "Существует несколько стандартных функций, которые вам позволяют в функциональном стиле работать.\n",
    "\n",
    "Одна из таких функций - это `map`. Функция `map` принимает функцию и какой-то итерабельный объект, то есть, например, список или что-то, по чему можно итерироваться, и применяет полученную функцию к этому итерабельному объекту.\n",
    "\n",
    "Например, мы можем определить функцию `squarify`, которая просто принимает число и возвращает вторую степень его, то есть возвращает квадрат."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "7460b616",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0, 1, 4, 9, 16]"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def squarify(a):\n",
    "    return a ** 2\n",
    "\n",
    "list(map(squarify, range(5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fded733",
   "metadata": {},
   "source": [
    "Функция `map` работает очень просто. Мы передаем ей функцию `squarify` и `range`, то есть итератор от ноля до четырех. Функция `map` бежит по этому итерабельному объекту, то есть по `rang`'у, и применяет `squarify` к элементу одному за другим. В данном случае у нас получается список от 0 до 16 квадратов. Обратите внимание, я вызываю функцию `list` вокруг `map`'а, потому что `map` по умолчанию возвращает `map object`. Это не список, а какой-то объект внутренний, которому самое главное, что нужно — итерироваться.\n",
    "\n",
    "Очень часто нам просто необходимо итерироваться по этому объекту, нам не важно конкретно, чтобы это был список. Собственно, что здесь происходит, можно посмотреть на чистом Python'e. У нас как бы в начале создается `squared_list`, и потом просто мы в цикле в этот `squared_list` записываем применение функции к текущему `number`'у.\n",
    "\n",
    "Однако, это делается всё довольно просто и коротко с помощью функции `map`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "726ace85",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16]\n"
     ]
    }
   ],
   "source": [
    "squared_list = []\n",
    "\n",
    "for number in range(5):\n",
    "    squared_list.append(squarify(number))\n",
    "    \n",
    "print(squared_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6518fd00",
   "metadata": {},
   "source": [
    "Ещё одна функция, которая часто используется в контексте функционального программирования, это функция `filter`. Функция `filter` вам позволяет фильтровать по какому-то предикату итерабельный объект.\n",
    "\n",
    "У вас есть какое-то условие, в данном случае — это функция, которая принимает число и проверяет, что это число больше ноля и возвращает `True` в этом случае, если это ноль или меньше, то возвращается `False`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "ee4c3b6f",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2]"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def is_positive(a):\n",
    "    return a > 0\n",
    "\n",
    "list(filter(is_positive, range(-2, 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4442abda",
   "metadata": {},
   "source": [
    "У нас `filter` принимает какую-то функцию и фильтрует все аргументы внутри итерабельного объекта по этому предикату. Например, мы можем оставить в нашем списке, или в нашем `rang`'е, в нашем итерабельном объекте только положительные числа. \n",
    "\n",
    "Собственно, на чистом Python'e это выглядело бы как-то так. У нас есть наш `positive_list` и мы в цикле просто проверяем, что если у нас функция возвращает `True`, то мы оставляем это значение, то есть прибавляем в `positive_list` это число. Иначе мы просто продолжаем итерацию и ничего не делаем. Можно убедиться, что выводится именно то же самое."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "ae7405d5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 2]\n"
     ]
    }
   ],
   "source": [
    "positive_list = []\n",
    "\n",
    "for number in range(-2, 3):\n",
    "    if is_positive(number):\n",
    "        positive_list.append(number)\n",
    "        \n",
    "print(positive_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5f9f452",
   "metadata": {},
   "source": [
    "`Filter` и `map` позволяет вам лаконично и просто записать стандартные какие-то паттерны программирования."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02a5ad50",
   "metadata": {},
   "source": [
    "Сейчас мы с вами разберем анонимные функции. Хотелось бы заметить, что несмотря на то, что `map` и `filter` очень мощны, не стоит злоупотреблять ими, потому что, если вы пишете довольно сложный код, который сложно разобрать, если вы вкладываете `map` в `map`, и `filter` в `filter`, пишете многострочные какие-то функциональные штуки, программистам сложно будет читать этот код. \n",
    "\n",
    "Чем сильнее, чем выше когнитивная нагрузка у программиста, тем хуже. Пожалуйста, не злоупотребляйте ими, хотя это довольно мощные конструкции.\n",
    "\n",
    "Итак, часто нам бывает необходимо определить какую-то функцию, например, `squarify` как мы определяли, но нам не нужно будет её применять в дальнейшем. Нам она интересна только в каком-то контексте. Например, возвести во вторую степень все элементы в этом списке, но мы не хотим использовать эту функцию потом. Нам на помощь в данном случае придут анонимные функции, так они называются в Python'e или `lambda` функции. `Lambda` позволяет вам определить функцию прям `in place`, то есть без оператора `def`, без литерала `def`. Используйте её прям на месте и забыли про нее потом. В данном случае мы делаем точно то же самое, что и в предыдущем примере, только определяем функцию не с помощью `def`'а, где-то вверху, а прямо на месте и пишем, что наша анонимная функция принимает один параметр и возводит его в квадрат. Всё."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "312fd40e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[0, 1, 4, 9, 16]"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(map(lambda x: x ** 2, range(5)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "01b7bc1c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "function"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(lambda x: x ** 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "5d3224c3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[1, 2]"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list(filter(lambda x: x > 0, range(-2, 3)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08af4100",
   "metadata": {},
   "source": [
    "Происходит то же самое, только у нас функция создается `in place`. Собственно, как видите, `map` точно так же принимает функцию и итерабельный объект. Ничего нового. `Lambda` — это действительно функция, но она анонимная. У нее нет имени, как видите, просто какая-то функция, принимающая один объект и возвращающая квадрат. То же самое можно сделать с `filter`'ом, и `filter` у нас примет функцию `lambda`, и `lambda` проверит, что число больше нуля."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f87bbbf",
   "metadata": {},
   "source": [
    "Давайте в качестве примера напишем функцию, которая превращает список чисел в список строк. Сделайте это самостоятельно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "0977cbb9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def stringify_list(num_list):\n",
    "    return list(map(str, num_list))\n",
    "\n",
    "stringify_list(range(10))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a27fac1",
   "metadata": {},
   "source": [
    "Отлично, скорее всего у вас всё получилось. Ничего сложного не должно быть. Чтобы превратить список чисел в список строк нам нужно использовать функцию `map`, как вы могли догадаться, и передать функцию `map`, класс `str`. При применении класса `str` к числу, у нас получается строка и именно это нам и нужно. Нам нужно пробежаться по списку и всё передать в строку, то есть передать в конструктор `str`. Мы можем вызвать нашу функцию в `range` и получить список строк от нуля до девяти."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a31320c1",
   "metadata": {},
   "source": [
    "Существует отличный модуль `functools`, который вам позволяет использовать функциональные особенности Python'а ещё лучше.\n",
    "\n",
    "Например, `functools` в последних версиях языка принесли функцию `reduce`, которая изначально была в общем скопе глобальных функций. Что делает `reduce`? `Reduce`, как вы уже могли догадаться, принимает функцию и итерабельный объект. Что она делает с ним? В данном случае у нас функция `multiply`, которая принимает два значения и перемножает их. Функция `reduce`, начиная слева направо, бежит и по цепочке применяет эту функцию."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "6007f8fc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "120"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from functools import reduce\n",
    "\n",
    "def multiply(a, b):\n",
    "    return a * b\n",
    "\n",
    "reduce(multiply, [1, 2, 3, 4, 5])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "28512684",
   "metadata": {},
   "source": [
    "Каким образом? В начале в функцию `multiply` попадают один и два, они перемножаются и запоминается двойка, у нас в памяти лежит двойка. Дальше, у нас эта двойка перемножается с тройкой. Таким образом у нас была двойка в памяти, берется следующий элемент, перемножается с тройкой. У нас уже шесть в памяти. Шесть в памяти умножается на четыре. У нас получается 24 и запоминается. 24 потом уже умножается на пять, получается 120, что мы получаем на входе. `Reduce` позволяет вам сжимать какие-то данные, применяя последовательно функцию и запоминая результат. То же самое можно сделать с помощью анонимных функций."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "634a1fde",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "24"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reduce(lambda x, y: x * y, range(1, 5))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fad411a",
   "metadata": {},
   "source": [
    "Да, но получилось не 120 — `range`, как вы могли догадаться, пятерку не включает."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "377ef58d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "120"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "reduce(lambda x, y: x * y, range(1, 6))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0b35a963",
   "metadata": {},
   "source": [
    "Также в `functools` есть отличный метод `partial`, который вам позволяет модифицировать немного поведение функций. Например, у нас есть функция `greeter`, которая просто приветствует человека с помощью какого-то приветствия `greeting`. Например, может поздороваться с Майклом. И мы можем определить какой-то новый набор функций, подменив по умолчанию определенные параметры. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "9dfec3a9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hi, brother!\n",
      "Hello, sir!\n"
     ]
    }
   ],
   "source": [
    "from functools import partial\n",
    "\n",
    "def greeter(person, greeting):\n",
    "    return f\"{greeting}, {person}!\"\n",
    "\n",
    "hier = partial(greeter, greeting=\"Hi\")\n",
    "helloer = partial(greeter, greeting=\"Hello\")\n",
    "\n",
    "print(hier(\"brother\"))\n",
    "print(helloer(\"sir\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "737347b9",
   "metadata": {},
   "source": [
    "Например, мы можем создать новую функцию `hier`, которая будет всегда приветствовать с помощью приветствия \"Hi\". Делается это с помощью функции `partial`, которая принимает, опять же, функцию и какой-то параметр, который нужно подменить. В данном случае у нас будет функция `hier` и функция `helloer`. `helloer` всегда приветствует с помощью \"Hallo\", а `hier` — всегда с помощью \"Hi\"."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9c07a270",
   "metadata": {},
   "source": [
    "Например, у нас формальное и неформальное приветствие. Таким образом, у нас была функция `greeter`, которая принимает `greeting` и `person`, а стала только функция `hier` и `helloer`, которая принимает только `person`. Например, мы можем поздороваться неформально со своим братом и с каким-то незнакомым мужчиной с помощью \"Hallo\". Функция `partial` позволяет вам подменять определенные аргументы и модифицировать поведение функций. Например, мы можем определить логер, который записывает информацию только в какой-то конкретный файл или любые действия на ваш вкус."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5fe2ecb1",
   "metadata": {},
   "source": [
    "Списочные выражения, очень важная концепция, которая используется практически повсеместно, потому, что позволяет вам упростить жизнь, и написать красивый код. Раньше, чтобы создать список, например, квадратов, мы с вами писали цикл. Создавали список и в цикле в этот список добавляли элементы — квадраты числа. Получалось довольно просто, однако нам необходимо определять список, определять цикл и в цикле ещё делать `append`.\n",
    "\n",
    "Это довольно многословно, в Python-е так не годится, и поэтому есть более простой, удобный метод написать то же самое. Этот метод называется \"списочное выражение\", или `list comprehensions`, которое выглядит так. У нас есть квадратные скобочки, и мы в квадратных скобочках пишем цикл. У нас цикл по `range`-у, мы берем `number`, этот `number` возводим в квадрат, и присваиваем его в список. Таким образом у нас список наполняется с помощью вот таких элементов. В цикле у нас добавляются квадраты числа и получается точно такой же результат — это называется \"списочное выражение\", оно работает немного быстрее, чем такой вот цикл и выглядит намного лаконичнее и короче. Поэтому практически всегда используются именно они, именно \"списочные выражения\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "606fba9b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "square_list = []\n",
    "\n",
    "for number in range(10):\n",
    "    square_list.append(number ** 2)\n",
    "    \n",
    "print(square_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "e6076d78",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]\n"
     ]
    }
   ],
   "source": [
    "square_list = [number ** 2 for number in range(10)]\n",
    "print(square_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2d240574",
   "metadata": {},
   "source": [
    "Точно так же можно написать списочное выражение с каким-то условием. А если нам нужны только четные числа в нашем списке, мы бы писали что-то в этом роде. У нас был бы цикл и условия, и мы только при выполнении условия бы добавляли в наш список элемент. Мы можем точно так же добавить условие в `list comprehensions`, и добавлять наш `num` только при выполнении условия остаток от деления равен нулю."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "fa856e67",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 2, 4, 6, 8]\n"
     ]
    }
   ],
   "source": [
    "even_list = []\n",
    "\n",
    "for number in range(10):\n",
    "    if number % 2 == 0:\n",
    "        even_list.append(number)\n",
    "        \n",
    "print(even_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "d417cb66",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 2, 4, 6, 8]\n"
     ]
    }
   ],
   "source": [
    "even_list = [num for num in range(10) if num % 2 == 0]\n",
    "print(even_list)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9625d939",
   "metadata": {},
   "source": [
    "Точно так же мы можем определять словари. Например, с помощью фигурных скобочек и двоеточия у нас соответственно в словаре, это ключ, и number в квадрате — это значение. Например, у нас строится отображение из числа в его квадрат. Мы можем определять точно так же с помощью `dict comprehensions` \"дикты\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "e584a051",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}\n"
     ]
    }
   ],
   "source": [
    "square_map = {number: number ** 2 for number in range(5)}\n",
    "print(square_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2edd7643",
   "metadata": {},
   "source": [
    "Мы можем делать с помощью обычных фигурных скобочек без двоеточий делать `set`-ы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "04d502db",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}\n"
     ]
    }
   ],
   "source": [
    "reminders_set = {num % 10 for num in range(100)}\n",
    "print(reminders_set)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2cbba536",
   "metadata": {},
   "source": [
    "Обратите внимание, это опять же довольно мощные конструкции, которые здорово и легко использовать, однако, например, `list comprehensions`-ы позволяют вам вкладывать циклы `for` друг в друга и строить какие-то более сложные вещи.\n",
    "\n",
    "Например, вы можете в условиях писать сложные предикаты. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e276ea97",
   "metadata": {},
   "source": [
    "Пожалуйста не делайте этого, держите ваш код простым и понятным. Несмотря на то, что функциональные особенности языка очень легко использовать и писать довольно большие многострочные выражения, лучше, чтобы ваш код был простым и понятным. Не злоупотребляйте этим. Ну и что интересно, если мы просто без скобочек напишем такое выражение, то у нас тоже что-то получится. И на самом деле это генератор. О генераторах мы поговорим позже. Пока просто знайте, что это какой-то объект, в котором, например, можно итерироваться. Но это не список, не `tuple`, и не `set`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "4d02e541",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'generator'>\n"
     ]
    }
   ],
   "source": [
    "print(type(number ** 2 for number in range(5)))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abb2a048",
   "metadata": {},
   "source": [
    "Ещё одна важная функция, которая часто рассматривается в контексте функционального программирования, это функция `zip`. Функция `zip` позволяет вам склеить два итерабельных объекта. В данном случае у нас есть `numList`, `squaredList`, представляющий собой квадраты чисел, и мы склеиваем их. И у нас первый элемент одного списка с первым элементом других списков попадает в один `tuple`. Происходит что-то в этом роде. Часто бывает функция эта полезна."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "67c6fbd2",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25), (6, 36)]"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "num_list = range(7)\n",
    "squared_list = [x ** 2 for x in num_list]\n",
    "\n",
    "list(zip(num_list, squared_list))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "53cb437e",
   "metadata": {},
   "source": [
    "Итак. Мы с вами разобрали такую страшную концепцию, как функциональное программирование, которая на самом деле проста.\n",
    "\n",
    "Мы с вами узнали, что функции - это объекты первого класса, их можно передавать внутри функций, возвращать из функций, создавать внутри функций, возвращать, то есть работать с ними как с обычными объектами; разобрали с вами классические функции `map`, `filter` и `reduce`; посмотрели на анонимные функции, которые можно создавать `in place` и разобрали списочные выражения, которые часто вам могут упростить жизнь для того, чтобы создавать списки, `set`-ы, `dict`-ы, или что-то в этом роде."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
