{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "(listcomp)=\n", "# Основы списковых включений\n", "\n", "Для создания списков есть очень мощный инструмент [list comprehensions](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions), который на русском иногда называют списковыми включениями.\n", "\n", "## Создание списка копий и копирование списков\n", "\n", "В самом простом своём варианте списковое включение позволяет копировать содержимое итерируемого объекта в новый список." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 2, 3]\n" ] } ], "source": [ "a_list = [1, 2, 3]\n", "a_copy = [x for x in a_list]\n", "print(a_copy)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Рассмотрим синтаксис:\n", "```python\n", "[x for x in iterable]\n", "```\n", "- `[]` квадратные скобочки означают, что содержимое будет списком;\n", "- `x for x in iterable` означает, что в список попадут элементы `x`, где `x` пробегает по `iterable`;\n", "\n", "То, что попадёт в список необязательно должно зависеть от `iterable`. Например, можно создать список одинаковых элементов." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 0, 0, 0, 0]\n" ] } ], "source": [ "zeros = [0 for i in range(5)]\n", "print(zeros)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Результат очень похож на выражение `[0] * 5`, но в данном случае выражение вычисляется заново на каждой итерации цикла. Т.е. можно действительно создать список разных списков." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[['Много разных списков'], [], [], [], []]\n" ] } ], "source": [ "many_empty_lists = [[] for i in range(5)]\n", "many_empty_lists[0].append(\"Много разных списков\")\n", "print(many_empty_lists)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[['Один список'], ['Один список'], ['Один список'], ['Один список'], ['Один список']]\n" ] } ], "source": [ "one_empty_list = [[]] * 5 \n", "one_empty_list[0].append(\"Один список\")\n", "print(one_empty_list)" ] }, { "cell_type": "markdown", "id": "12249855", "metadata": {}, "source": [ "Ещё это можно проиллюстрировать на примере с генерацией случайных чисел." ] }, { "cell_type": "code", "execution_count": 21, "id": "28bb1583", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.13918752202784812, 0.13918752202784812, 0.13918752202784812]\n", "[0.7732752554781216, 0.7653853834316299, 0.5661113682821938]\n" ] } ], "source": [ "from random import random\n", "\n", "print([random()] * 3) \n", "print([random() for _ in range(3)])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Аналог функции map\n", "\n", "В python есть встроенная функция [map](https://docs.python.org/3/library/functions.html#map), которая позволяет отображать функцию на элементы списка (или другого итерируемого объекта), т.е. применять какую-то функцию к всем элементам списка. \n", "\n", "```{note}\n", "Функция `map` делает вычисления лениво. Концепция ленивых вычислений будет обсуждаться при обсуждении генераторов и итераторов.\n", "Пока что надо знать, что, чтобы получить список, необходимо применить `list` к результату функции `map`.\n", "```" ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1.0, 2.0, 3.0, 4.0]\n", "[1.0, 4.0, 9.0, 16.0]\n" ] } ], "source": [ "import math\n", "\n", "array = [1, 4, 9, 16]\n", "array_1 = list(map(math.sqrt, array))\n", "print(array_1)\n", "\n", "def my_square(x):\n", " return x * x\n", "\n", "array_2 = list(map(my_square, array_1))\n", "print(array_2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Можно имитировать функцию `map` с помощью списковых включений." ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1.0, 2.0, 3.0, 4.0]\n", "[1.0, 4.0, 9.0, 16.0]\n" ] } ], "source": [ "array_1 = [math.sqrt(x) for x in array]\n", "array_2 = [my_square(x) for x in array_1]\n", "print(array_1)\n", "print(array_2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "При этом необязательно применять функцию. Допустимо любое выражение." ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1.0, 4.0, 9.0, 16.0]\n" ] } ], "source": [ "array_2 = [x * x for x in array_1]\n", "print(array_2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Т.е. теперь синтаксис такой:\n", "```python\n", " [expression(x) for x in iterable]\n", "```" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Аналог функции filter\n", "\n", "В python есть встроенная функция фильтрации [filter](https://docs.python.org/3/library/functions.html#filter), которая принимает на вход функцию `f` и итерируемый объект `iterable`. Она оставляет только те элементы `iterable`, на которых `f` возвращает `True`. Как и функция `map`, функция `filter` делает это лениво." ] }, { "cell_type": "code", "execution_count": 29, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[21, 39, 91, 45, 35, 88, 52, 42, 20, 63, 100, 26, 30, 44, 58, 30, 74, 96, 73, 75]\n", "[88, 52, 42, 20, 100, 26, 30, 44, 58, 30, 74, 96]\n", "[7744, 2704, 1764, 400, 10000, 676, 900, 1936, 3364, 900, 5476, 9216]\n" ] } ], "source": [ "from random import randint\n", "\n", "def is_even(x):\n", " # if x % 2:\n", " # return False\n", " # else:\n", " # return True\n", " return False if x % 2 else True # Тернарный оператор\n", "\n", "array = [randint(0, 100) for _ in range(20)]\n", "only_even = list(filter(is_even, array))\n", "only_even_squared = list(map(my_square, filter(is_even, array)))\n", "print(array)\n", "print(only_even)\n", "print(only_even_squared)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Того же самого можно добиться с помощью списковых включений." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[88, 52, 42, 20, 100, 26, 30, 44, 58, 30, 74, 96]\n", "[7744, 2704, 1764, 400, 10000, 676, 900, 1936, 3364, 900, 5476, 9216]\n" ] } ], "source": [ "only_even = [x for x in array if x % 2 == 0]\n", "only_even_squared = [x * x for x in array if x % 2 == 0]\n", "print(only_even)\n", "print(only_even_squared)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Т.е. теперь синтаксис такой:\n", "```python\n", " [expression(x) for x in iterable if condition(x)]\n", "```" ] }, { "cell_type": "markdown", "id": "1ebc77ef", "metadata": {}, "source": [ "## Аналогия с множествами из математики\n", "\n", "Иногда можно провести аналогию, между списковыми включениями и тем, как задаются множества в математике.\n", "\n", "В качестве примера, рассмотрим множество заданной в виде\n", "\n", "$$\n", "R = \\{x^2 | x\\vdots 2, |x| < 10, x\\in\\mathbb{N}\\} = \\{1, 9, 25, 49, 81\\}.\n", "$$\n", "\n", "В этом примере, новое множество задаётся в виде некоторого выражения над переменной, которая пробегает по другому множеству, что очень хорошо ложится на синтаксис списковых включений:" ] }, { "cell_type": "code", "execution_count": 7, "id": "283ab0a5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[81, 49, 25, 9, 1, 1, 9, 25, 49, 81]\n" ] } ], "source": [ "R = [x ** 2 for x in range(-9, 10) if x % 2]\n", "print(R)" ] }, { "cell_type": "markdown", "id": "11334132", "metadata": {}, "source": [ "Важная деталь, которую стоит подметить --- в математике множество не может содержать дубликатов, а список в python может. Благо, есть встроенный тип данных [set](https://docs.python.org/3/tutorial/datastructures.html#sets), который ведет себя куда более похоже на математическое множество и тоже поддерживает синтаксис включений:" ] }, { "cell_type": "code", "execution_count": 9, "id": "81bff514", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{1, 9, 81, 49, 25}\n" ] } ], "source": [ "R = {x ** 2 for x in range(-9, 10) if x % 2}\n", "print(R)" ] }, { "cell_type": "markdown", "id": "f70fb074", "metadata": {}, "source": [ "## \"Словарные включения\"\n", "\n", "Словари тоже поддерживают похожий синтаксис. Например, словарь, отображающий строчные буквы английского алфавита, на прописные можно записать следующим образом. " ] }, { "cell_type": "code", "execution_count": 11, "id": "2ff6c372", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z'}\n" ] } ], "source": [ "from string import ascii_lowercase\n", "\n", "d = {symbol: symbol.upper() for symbol in ascii_lowercase}\n", "print(d)" ] }, { "cell_type": "markdown", "id": "6dbeea7f", "metadata": {}, "source": [ "Используя функцию `zip` можно записать его иначе:" ] }, { "cell_type": "code", "execution_count": 12, "id": "23eda547", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D', 'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y', 'z': 'Z'}\n" ] } ], "source": [ "from string import ascii_lowercase, ascii_uppercase\n", "\n", "d = {s:S for s, S in zip(ascii_lowercase, ascii_uppercase)}\n", "print(d)" ] }, { "cell_type": "markdown", "id": "08010408", "metadata": {}, "source": [ "Все остальные элементы из списковых включений поддерживаются и словарными включениями. Сделаем отображение только согласных букв. " ] }, { "cell_type": "code", "execution_count": 15, "id": "8e873ad5", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'b': 'B', 'c': 'C', 'd': 'D', 'f': 'F', 'g': 'G', 'h': 'H', 'j': 'J', 'k': 'K', 'l': 'L', 'm': 'M', 'n': 'N', 'p': 'P', 'q': 'Q', 'r': 'R', 's': 'S', 't': 'T', 'v': 'V', 'w': 'W', 'x': 'X', 'z': 'Z'}\n" ] } ], "source": [ "from string import ascii_lowercase, ascii_uppercase\n", "\n", "vowels = [\"a\", \"e\", \"i\", \"o\", \"u\", \"y\"] \n", "d = d = {s:S for s, S in zip(ascii_lowercase, ascii_uppercase) if s not in vowels}\n", "print(d)" ] }, { "cell_type": "markdown", "id": "4ca6eaa5", "metadata": {}, "source": [ "В качестве аналога фильтрации, можно рассмотреть здесь применение множеств из `python`: оператор `-` для двух множеств возвращает их разницу. Мы можем получить согласные буквы, вычтя из всех букв гласные. " ] }, { "cell_type": "code", "execution_count": 18, "id": "0ba2d592", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'w', 'g', 'm', 'c', 'h', 'x', 'z', 'p', 't', 'r', 'l', 'f', 'b', 'v', 'k', 'q', 's', 'd', 'n', 'j'}\n" ] } ], "source": [ "print(set(ascii_lowercase) - set(vowels))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Больше возможностей\n", "\n", "В одном списковом включении может быть несколько циклов." ] }, { "cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "['a', 'aa', 'aaa', 'b', 'bb', 'bbb', 'c', 'cc', 'ccc']\n" ] } ], "source": [ "array = [symbol * n for symbol in [\"a\", \"b\", \"c\"] for n in [1, 2, 3]]\n", "print(array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Списковые включения могут быть вложенными друг в друга." ] }, { "cell_type": "code", "execution_count": 42, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[['a', 'b', 'c'], ['aa', 'bb', 'cc'], ['aaa', 'bbb', 'ccc']]\n" ] } ], "source": [ "array = [[symbol * n for symbol in [\"a\", \"b\", \"c\"]] for n in [1, 2, 3]]\n", "print(array)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Матрица" ] }, { "cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]]\n" ] } ], "source": [ "matrix = [[j * 4 + i for i in range(4)] for j in range(3)]\n", "print(matrix)" ] } ], "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.10" } }, "nbformat": 4, "nbformat_minor": 5 }