{ "cells": [ { "cell_type": "markdown", "id": "fef8bd2f", "metadata": {}, "source": [ "# Срезы\n", "\n", "## Срезы встроенных коллекций python\n", "\n", "### Простые срезы\n", "\n", "Списки, строки, кортежи и другие последовательности `python` кроме обычной индексации поддерживают индексацию срезами ([slices](https://docs.python.org/3/library/functions.html#slice)). Рассмотрим на примере строк." ] }, { "cell_type": "code", "execution_count": 2, "id": "9c267d61", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'abcdefghijklmnopqrstuvwxyz'" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import string\n", "\n", "a = string.ascii_lowercase\n", "a" ] }, { "cell_type": "markdown", "id": "ae24fa69", "metadata": {}, "source": [ "Итак, у нас есть строка `a`, состоящая из строчных символов английского алфавита. \n", "\n", "Если мы хотим извлечь подстроку, начиная с позиции `start` включая и заканчивая позицией `stop` не включая, то применяется синтаксис \n", "```python\n", "sequence[start:stop]\n", "```\n", "Например, если нас часть строки с 1-го по 5-ый символ (нумерация с нуля), мы запишем" ] }, { "cell_type": "code", "execution_count": 4, "id": "9b792eb0", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "bcde\n" ] } ], "source": [ "print(a[1:5])" ] }, { "cell_type": "markdown", "id": "a14d6c20", "metadata": {}, "source": [ "Если нас интересует подстрока с самого начала до какого-то индекс `stop`, то допускается опускать первый параметр среза" ] }, { "cell_type": "code", "execution_count": 7, "id": "eb5fd435", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abc\n", "abc\n" ] } ], "source": [ "print(a[0:3])\n", "print(a[:3])" ] }, { "cell_type": "markdown", "id": "60acb9fd", "metadata": {}, "source": [ "Аналогично, и со вторым параметром: если на интересует подстрока до самого конца исходный строки, то можно опустить второй параметр." ] }, { "cell_type": "code", "execution_count": 8, "id": "3672b19c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "xyz\n", "xyz\n" ] } ], "source": [ "print(a[23:len(a)])\n", "print(a[23:])" ] }, { "cell_type": "markdown", "id": "497cea19", "metadata": {}, "source": [ "Если опустить оба параметра, то возвращается полная копия строки." ] }, { "cell_type": "code", "execution_count": 9, "id": "d434cd2e", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "abcdefghijklmnopqrstuvwxyz\n" ] } ], "source": [ "print(a[:])" ] }, { "cell_type": "markdown", "id": "d89b7d06", "metadata": {}, "source": [ "Если в срезе выйти за пределы последовательности, то никакой ошибки не будет. Результатом будет та часть строки, что пересекается с выбранным срезом, которая может оказаться пустой, если индексы среза не пересекаются с допустимыми индексами строки." ] }, { "cell_type": "code", "execution_count": 18, "id": "9c21d170", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "|abcdefghijklmnopqrstuvwxyz|\n", "||\n", "||\n" ] } ], "source": [ "# символы '|' по бокам, чтобы наглядно показать, что напечаталась пустая строка\n", "print(f\"|{a[-100:100]}|\")\n", "print(f\"|{a[90:100]}|\")\n", "print(f\"|{a[5:2]}|\")" ] }, { "cell_type": "markdown", "id": "ed78f41d", "metadata": {}, "source": [ "### Срезы с шагом\n", "\n", "Для разнообразия теперь будем брать срезы от списков." ] }, { "cell_type": "code", "execution_count": 36, "id": "d6e15c9d", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "b = list(range(10))\n", "print(b)" ] }, { "cell_type": "markdown", "id": "a9f0c255", "metadata": {}, "source": [ "Итак, у нас есть список `b` чисел от 0 до 9.\n", "\n", "Срезы с шагом позволяют извлекать элементы не подряд, а, например, с прореживанием. Для этого предыдущий синтаксис расширяется третьим параметром, который как раз и отвечает за шаг.\n", "```python\n", "sequence[start:stop:step]\n", "```\n", "\n", "Например, чтобы вырезать каждый второй элемент списка `b`, начиная с 1-го и заканчивая 8-м (не включая), необходимо записать." ] }, { "cell_type": "code", "execution_count": 37, "id": "74e00299", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1, 3, 5, 7]\n" ] } ], "source": [ "print(b[1:8:2])" ] }, { "cell_type": "markdown", "id": "c367193c", "metadata": {}, "source": [ "Как и прежде, первые два параметра можно опускать, если `start` и `stop` совпадают с началом и концом списка соответственно.\n", "\n", "Ниже берется каждый второй элемент всего списка. " ] }, { "cell_type": "code", "execution_count": 38, "id": "be24956c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 2, 4, 6, 8]\n" ] } ], "source": [ "print(b[::2])" ] }, { "cell_type": "markdown", "id": "c1cc9db8", "metadata": {}, "source": [ "Шаг может быть отрицательным, но тогда `start` должен быть больше `step`, чтобы вернулась не пустая подпоследовательность. При этом, как и до этого, `start` попадает в срез, а `stop` нет." ] }, { "cell_type": "code", "execution_count": 39, "id": "6db69b22", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[8, 7, 6, 5, 4, 3, 2]\n" ] } ], "source": [ "print(b[8:1:-1])" ] }, { "cell_type": "markdown", "id": "18703bf2", "metadata": {}, "source": [ "Получить последовательность в обратном порядке (обратить последовательность) можно синтаксисом\n", "```python\n", "sequence[::-1]\n", "```\n" ] }, { "cell_type": "code", "execution_count": 40, "id": "818d9316", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "zyxwvutsrqponmlkjihgfedcba\n", "[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]\n" ] } ], "source": [ "print(a[::-1])\n", "print(b[::-1])" ] }, { "cell_type": "markdown", "id": "0f6598be", "metadata": {}, "source": [ "### Присваивание по срезам\n", "\n", "Если последовательность изменяемая (список, например), то допускается присваивание по срезу. \n", "\n", "Если шаг не указан, то можно заменить элементы среза в списке элементами коллекции справа от оператора `=` вне зависимости от того, совпадает ли их количество или нет" ] }, { "cell_type": "code", "execution_count": 41, "id": "374f9f54", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "print(b)" ] }, { "cell_type": "markdown", "id": "6c3af999", "metadata": {}, "source": [ "Например, код в ячейке ниже заменяет первые два элемента списка `b` на новые два элемента (0 заменяется на 3.14, 1 заменяется на 42)." ] }, { "cell_type": "code", "execution_count": 42, "id": "4e22c92b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "b[:2] = [3.14, 42]\n", "print(b)" ] }, { "cell_type": "markdown", "id": "dbeeab82", "metadata": {}, "source": [ "Код в ячейке ниже заменяет первые два элемента (3.14 и 42 после предыдущей замены) на сразу три новых элемента, тем самым расширяя список." ] }, { "cell_type": "code", "execution_count": 43, "id": "de3eb409", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 3.14, 42, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "b[:2] = [0, 3.14, 42]\n", "print(b)" ] }, { "cell_type": "markdown", "id": "54af3991", "metadata": {}, "source": [ "Следующим синтаксисом можно удалить первых два элемента." ] }, { "cell_type": "code", "execution_count": 30, "id": "db6d74a8", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[42, 2, 3, 4, 5, 6, 7, 8, 9]\n" ] } ], "source": [ "b[:2] = []\n", "print(b)" ] }, { "cell_type": "markdown", "id": "84863064", "metadata": {}, "source": [ "Хотя для этого предпочтительнее воспользоваться синтаксисом \n", "```python\n", "del b[:2]\n", "```\n", "\n", "Если шаг указан, то количество элементов в коллекции справа от `=` должно совпадать с количеством элементов в срезе" ] }, { "cell_type": "code", "execution_count": 44, "id": "835e7959", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0, 2, 4, 6, 8]\n", "[1, 1, 3, 3, 5, 5, 7, 7, 9, 9]\n" ] }, { "ename": "ValueError", "evalue": "attempt to assign sequence of size 0 to extended slice of size 5", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mValueError\u001b[0m Traceback (most recent call last)", "\u001b[1;32mc:\\Users\\qujim\\Desktop\\python_lectures\\notebooks\\slices.ipynb Cell 32'\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[0;32m 4\u001b[0m b[::\u001b[39m2\u001b[39m] \u001b[39m=\u001b[39m b[\u001b[39m1\u001b[39m::\u001b[39m2\u001b[39m]\n\u001b[0;32m 5\u001b[0m \u001b[39mprint\u001b[39m(b)\n\u001b[1;32m----> 6\u001b[0m b[::\u001b[39m2\u001b[39m] \u001b[39m=\u001b[39m []\n", "\u001b[1;31mValueError\u001b[0m: attempt to assign sequence of size 0 to extended slice of size 5" ] } ], "source": [ "b = list(range(10))\n", "\n", "print(b[::2])\n", "b[::2] = b[1::2]\n", "print(b)\n", "b[::2] = []" ] }, { "cell_type": "markdown", "id": "684120b8", "metadata": {}, "source": [ "## Срезы массивов `NumPy`\n", "\n", "Срезы `NumPy` введут себя очень похоже с двумя основными отличиями ([документация NumPy про срезы](https://numpy.org/doc/stable/user/basics.indexing.html?highlight=slice#slicing-and-striding)):\n", "- при присваивании по срезу размер массива не может измениться вне зависимости от того, указан шаг или нет;\n", "- они допускают многомерные срезы;\n", "\n", "Если массив одномерный, то роль играет только первое отличие." ] }, { "cell_type": "code", "execution_count": 73, "id": "0be62213", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0 1 2 3 4 5 6 7 8 9]\n" ] } ], "source": [ "import numpy as np\n", "x = np.arange(10)\n", "print(x)" ] }, { "cell_type": "code", "execution_count": 74, "id": "0f275518", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0 2 4 6 8]\n" ] } ], "source": [ "print(x[::2])" ] }, { "cell_type": "code", "execution_count": 77, "id": "9c51ed8a", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[1 1 3 3 5 5 7 7 9 9]\n" ] } ], "source": [ "x[::2] = [1, 3, 5, 7, 9]\n", "print(x)" ] }, { "cell_type": "markdown", "id": "696561d9", "metadata": {}, "source": [ "Но если массив многомерный, то можно делать срезы сразу вдоль нескольких осей." ] }, { "cell_type": "code", "execution_count": 84, "id": "900c6002", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[[ 0 1 2 3 4 5 6 7 8 9]\n", " [10 11 12 13 14 15 16 17 18 19]\n", " [20 21 22 23 24 25 26 27 28 29]\n", " [30 31 32 33 34 35 36 37 38 39]\n", " [40 41 42 43 44 45 46 47 48 49]\n", " [50 51 52 53 54 55 56 57 58 59]\n", " [60 61 62 63 64 65 66 67 68 69]\n", " [70 71 72 73 74 75 76 77 78 79]\n", " [80 81 82 83 84 85 86 87 88 89]\n", " [90 91 92 93 94 95 96 97 98 99]]\n" ] } ], "source": [ "A = np.arange(100).reshape(10, 10)\n", "print(A)" ] }, { "cell_type": "code", "execution_count": 85, "id": "1004e888", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[22, 23],\n", " [32, 33]])" ] }, "execution_count": 85, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[2:4, 2:4]" ] }, { "cell_type": "code", "execution_count": 88, "id": "ef7777ed", "metadata": {}, "outputs": [ { "data": { "text/plain": [ "array([[ 0, 2, 4, 6, 8],\n", " [10, 12, 14, 16, 18],\n", " [20, 22, 24, 26, 28],\n", " [30, 32, 34, 36, 38],\n", " [40, 42, 44, 46, 48],\n", " [50, 52, 54, 56, 58],\n", " [60, 62, 64, 66, 68],\n", " [70, 72, 74, 76, 78],\n", " [80, 82, 84, 86, 88],\n", " [90, 92, 94, 96, 98]])" ] }, "execution_count": 88, "metadata": {}, "output_type": "execute_result" } ], "source": [ "A[:, ::2]" ] }, { "cell_type": "markdown", "id": "ee80710f", "metadata": {}, "source": [ "Процесс получения двухмерного среза можно мысленно представить в следующем виде:\n", "- срез, указанный до запятой, вырезает строки из массива;\n", "- срез, указанный после запятой, вырезает столбцы из массива;\n", "- результирующий срез представляет из себя все то, что располагается на пересечении этих строк и столбцов.\n", "\n", "Это позволяет получить `i`-ю строку выражением\n", "```python\n", "A[i, :]\n", "```\n", "а `j`-й столбец выражением\n", "```python\n", "A[:, j]\n", "```" ] }, { "cell_type": "code", "execution_count": 89, "id": "89cf478b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0 1 2 3 4 5 6 7 8 9]\n" ] } ], "source": [ "print(A[0, :])" ] }, { "cell_type": "code", "execution_count": 20, "id": "c2584497", "metadata": {}, "outputs": [ { "ename": "NameError", "evalue": "name 'A' is not defined", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)", "\u001b[1;32mc:\\Users\\qujim\\Desktop\\python_lectures\\notebooks\\slices.ipynb Cell 37'\u001b[0m in \u001b[0;36m\u001b[1;34m()\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[39mprint\u001b[39m(A[:, \u001b[39m0\u001b[39m])\n", "\u001b[1;31mNameError\u001b[0m: name 'A' is not defined" ] } ], "source": [ "print(A[:, 0])" ] }, { "cell_type": "markdown", "id": "5aec65b0", "metadata": {}, "source": [ "## Присваивание по срезу\n", "\n", "Срезы ссылаются на данные исходного массива. По ним можно производить присваивание.\n", "Например, можно целиком заменить столбец матрицы." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A[:, 0] = np.arange(-10, 0)\n", "print(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "В примере выше формы среза слева от \"`=`\" и массива справа от \"`=`\" совпадают. Это необязательное условие: работают правила *броадкастинга*. Например, прибавим к всем элемента первой строки 100, а каждый элемент последней строки сделаем равными 42." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A[0, :] += 100\n", "A[-1, ::2] = 42\n", "print(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Теперь возьмем все элементы на пересечении 4-6 строк и столбцов (включительно) и обнулим их." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "A[3:7, 3:7] = 0\n", "print(A)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Чуть более сложный пример. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "end = f\"\\n{'-'*80}\\n\"\n", "A[:, -3:] = np.array([1, 2, 3])\n", "print(A, end=end)\n", "A[::2, -3:] = np.array([4, 5, 6])\n", "print(A, end=end)\n", "A[-3:, :] = np.array([[7], [8], [9]])\n", "print(A)" ] } ], "metadata": { "interpreter": { "hash": "eea3ef54c04064aa17c056cc2578c78db8b44278034b77b7225a3166c34cea02" }, "kernelspec": { "display_name": "Python 3.8.10 ('venv': venv)", "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 }