{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- dom:TITLE: Типы коллекций -->\n",
    "# Типы коллекций\n",
    "<!-- dom:AUTHOR: С.В. Лемешевский Email:sergey.lemeshevsky@gmail.com at Институт математики НАН Беларуси -->\n",
    "<!-- Author: -->  \n",
    "**С.В. Лемешевский** (email: `sergey.lemeshevsky@gmail.com`), Институт математики НАН Беларуси\n",
    "\n",
    "Date: **Mar 8, 2020**\n",
    "\n",
    "<!-- Common Mako variable and functions -->\n",
    "<!-- -*- coding: utf-8 -*- -->\n",
    "\n",
    "\n",
    "\n",
    "Рассматриваются кортежи и списки, а также новые типы коллекций, включая словари и множества.\n",
    "\n",
    "# Последовательности\n",
    "<div id=\"collections:seq\"></div>\n",
    "\n",
    "Последовательности – это один из типов данных, поддерживающих\n",
    "оператор проверки на вхождение (`in`), функцию определения размера\n",
    "(`len()`), оператор извлечения срезов (`[]`) и возможность выполнения\n",
    "итераций. В языке Python имеется пять встроенных типов\n",
    "последовательностей: `bytearray`, `bytes`, `list`, `str` и\n",
    "`tuple`. Ряд дополнительных типов последовательностей реализован в\n",
    "стандартной библиотеке; наиболее примечательным \n",
    "из них является тип `collections.namedtuple`. При выполнении итераций\n",
    "все эти последовательности гарантируют строго определенный порядок\n",
    "следования элементов.\n",
    "\n",
    "Строки мы уже рассматривали выше, а в этом разделе познакомимся с\n",
    "кортежами, именованными кортежами и списками.\n",
    "\n",
    "## Кортежи\n",
    "<div id=\"collections:seq:tuples\"></div>\n",
    "\n",
    "Кортеж – это упорядоченная последовательность из нуля или более ссылок\n",
    "на объекты. Кортежи поддерживают тот же синтаксис получения срезов,\n",
    "что и строки. Это упрощает извлечение элементов из кортежа. Подобно\n",
    "строкам, кортежи относятся к категории неизменяемых объектов, поэтому\n",
    "мы не можем замещать или удалять какие-либо их элементы. Если нам\n",
    "необходимо иметь возможность изменять упорядоченную\n",
    "последовательность, то вместо кортежей можно просто использовать\n",
    "списки или, если в программе уже используется кортеж, который\n",
    "нежелательно модифицировать, можно преобразовать кортеж в список с\n",
    "помощью функции преобразования `list()` и затем изменять полученный\n",
    "список.\n",
    "\n",
    "Тип данных `tuple` может вызываться как функция `tuple()` – без\n",
    "аргументов она возвращает пустой кортеж, с аргументом типа tuple\n",
    "возвращает поверхностную копию аргумента; в случае, если аргумент\n",
    "имеет другой тип, выполняется попытка преобразовать его в объект\n",
    "типа `tuple`. Эта функция принимает не более одного аргумента. Кроме\n",
    "того, кортежи могут создаваться без использования функции\n",
    "`tuple()`. Пустой кортеж создается с помощью пары пустых круглых\n",
    "скобок `()`, а кортеж, состоящий из одного или более элементов, может быть\n",
    "создан с помощью запятых. Иногда кортежи приходится заключать в\n",
    "круглые скобки, чтобы избежать синтаксической\n",
    "неоднозначности. Например, чтобы передать кортеж `1, 2, 3` в функцию,\n",
    "необходимо использовать такую форму записи: `function((1, 2, 3))`.\n",
    "\n",
    "<!-- dom:FIGURE: [fig-collections/seq_1.png, width=800 frac=1.0] Позиции элементов в кортеже <div id=\"collections:seq:fig:1\"></div> -->\n",
    "<!-- begin figure -->\n",
    "<div id=\"collections:seq:fig:1\"></div>\n",
    "![Позиции элементов в кортеже](fig-collections/seq_1.png)<!-- end figure -->\n",
    "\n",
    "\n",
    "На рис. [collections:seq:fig:1](#collections:seq:fig:1) показан кортеж\n",
    "`t = \"venus\", – 28, \"green\", \"21\", 19.74` и индексы элементов внутри кортежа. Строки\n",
    "индексируются точно так же, но, если в строках каждой позиции\n",
    "соответствует единственный символ, то в кортежах каждой позиции\n",
    "соответствует единственная ссылка на объект.\n",
    "\n",
    "Кортежи предоставляют всего два метода: `t.count(x)`, который\n",
    "возвращает количество объектов `x` в кортеже `t`, и `t.index(x)`,\n",
    "который возвращает индекс самого первого (слева) вхождения объекта `x`\n",
    "в кортеж `t` или возбуждает исключение `ValueError`, если объект `x`\n",
    "отсутствует в кортеже. (Эти методы имеются также и у списков.)\n",
    "\n",
    "Кроме того, кортежи могут использоваться с оператором `+`\n",
    "(конкатенации), `*` (дублирования) и `[]` (получения среза), а\n",
    "операторы `in` и `not in` могут применяться для проверки на\n",
    "вхождение. Можно использовать также комбинированные операторы\n",
    "присваивания `+=` и `*=`. Несмотря на то, что кортежи являются\n",
    "неизменяемыми объектами, при выполнении этих операторов интерпретатор\n",
    "Python создает за кулисами новый кортеж с результатом операции и\n",
    "присваивает ссылку на него объекту, расположенному слева от оператора,\n",
    "то есть используется тот же самый прием, что и со строками. Кортежи\n",
    "могут сравниваться с помощью стандартных операторов сравнения (`<`,\n",
    "`<=`, `==`, `!=`, `>=`, `>`), при этом сравнивание производится\n",
    "поэлементно (и рекурсивно, при наличии вложенных элементов, таких как\n",
    "кортежи в кортежах).\n",
    "\n",
    "Рассмотрим несколько примеров получения срезов, начав с извлечения\n",
    "единственного элемента и группы элементов:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "hair = \"black\", \"brown\", \"blonde\", \"red\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "hair[2]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "hair[-3:] # то же, что и hair[1:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Эта операция выполняется точно так же, как и в случае со строками,\n",
    "списками или любыми другими последовательностями."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "hair[:2], \"gray\", hair[2:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь мы попытались создать новый кортеж из 5 элементов, но в\n",
    "результате получили кортеж с тремя элементами, содержащий два\n",
    "двухэлементных кортежа. Это произошло потому, что мы применили\n",
    "оператор запятой к трем элементам (кортеж, строка и кортеж). Чтобы\n",
    "получить единый кортеж со всеми этими элементами, необходимо выполнить\n",
    "конкатенацию кортежей:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "hair[:2] + (\"gray\",) + hair[2:]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чтобы создать кортеж из одного элемента, необходимо поставить запятую,\n",
    "но если запятую просто добавить, будет получено исключение `TypeError`\n",
    "(так как интерпретатор будет думать, что выполняется конкатенация\n",
    "строки и кортежа), поэтому необходимо использовать запятую и круглые\n",
    "скобки.\n",
    "\n",
    "Коллекции допускают возможность вложения с любой глубиной\n",
    "вложенности. Оператор извлечения срезов `[]` может применяться для\n",
    "доступа к вложенным коллекциям столько раз, сколько это будет\n",
    "необходимо. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "things = (1, -7.5, (\"pea\", (5, \"Xyz\"), \"queue\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "things[2][1][1][2]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Кортежи могут хранить элементы любых типов, включая другие коллекции,\n",
    "такие как кортежи и списки, так как на самом деле кортежи хранят\n",
    "ссылки на объекты. Использование сложных, вложенных структур данных,\n",
    "таких, как показано ниже, легко может создавать путаницу. Одно из\n",
    "решений этой проблемы состоит в том, чтобы давать значениям индексов\n",
    "осмысленные имена. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "MANUFACTURER, MODEL, SEATING = (0, 1, 2)\n",
    "MINIMUM, MAXIMUM = (0, 1)\n",
    "aircraft = (\"Airbus\", \"A320-200\", (100, 220))\n",
    "aircraft[SEATING][MAXIMUM]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В первых двух строках вышеприведенного фрагмента мы выполнили\n",
    "присваивание кортежам. Когда справа от оператора присваивания\n",
    "указывается последовательность (в данном случае – это кортежи),\n",
    "а слева указан кортеж, мы говорим, что последовательность справа\n",
    "*распаковывается*. Операция распаковывания последовательностей\n",
    "может использоваться для организации обмена значений между\n",
    "переменными, например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "a, b = (b, a)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Именованные кортежы\n",
    "<div id=\"collections:seq:namedtuple\"></div>\n",
    "\n",
    "Именованные кортежи ведут себя точно так же, как и обычные кортежи, и\n",
    "не уступают им в производительности. Отличаются они возможностью\n",
    "ссылаться на элементы кортежа не только по числовому индексу, но и по\n",
    "имени, что в свою очередь позволяет создавать сложные агрегаты из\n",
    "элементов данных.\n",
    "\n",
    "В модуле `collections` имеется функция `namedtuple()`. Эта функция\n",
    "используется для создания собственных типов кортежей. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import collections\n",
    "Sale = collections.namedtuple(\"Sale\", \"productid customerid date quantity price\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Первый аргумент функции `collections.namedtuple()` – это имя\n",
    "создаваемого кортежа. Второй аргумент – это строка имен, разделенных\n",
    "пробелами, для каждого элемента, который будет присутствовать в этом\n",
    "кортеже. Первый аргумент и имена во втором аргументе должны быть\n",
    "допустимыми идентификаторами языка Python. Функция возвращает класс\n",
    "(тип данных), который может использоваться для создания именованных\n",
    "кортежей. Так, в примере выше мы можем интерпретировать имя `Sale` как\n",
    "имя любого другого класса (такого как `tuple`) в языке Python и\n",
    "создавать объекты типа `Sale`. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "sales = []\n",
    "sales.append(Sale(432, 921, \"2008-09-14\", 3, 7.99))\n",
    "sales.append(Sale(419, 874, \"2008-09-15\", 1, 18.49))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В этом примере мы создали список из двух элементов типа `Sale`, то\n",
    "есть из двух именованных кортежей. Мы можем обращаться к элементам\n",
    "таких кортежей по их индексам – например, обратиться к элементу\n",
    "`price` в первом элементе списка `sales` можно с помощью выражения\n",
    "`sales[0][-1]` (вернет значение `7.99`) – или по именам, которые\n",
    "делают программный код более удобочитаемым:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "total = 0\n",
    "for sale in sales:\n",
    "    total += sale.quantity*sale.price\n",
    "print(\"Total {0:.2f}\".format(total))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Очень часто простоту и удобство, которые предоставляют именованные\n",
    "кортежи, можно обратить на пользу делу. Например, ниже приводится\n",
    "версия примера `aircraft` из предыдущего подраздела, имеющая более\n",
    "аккуратный вид:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "Aircraft = collections.namedtuple(\"Aircraft\", \"manufacturer model seating\")\n",
    "Seating = collections.namedtuple(\"Seating\", \"minimum maximum\")\n",
    "aircraft = Aircraft(\"Airbus\", \"A320-200\", Seating(100, 220))\n",
    "aircraft.seating.maximum"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Списки\n",
    "<div id=\"collections:seq:lists\"></div>\n",
    "\n",
    "Список – это упорядоченная последовательность из нуля\n",
    "или более ссылок на объекты. Списки поддерживают тот\n",
    "же синтаксис получения срезов, что и строки с кортежами. Это упрощает\n",
    "извлечение элементов из списка. В отличие от строк и кортежей списки\n",
    "относятся к категории изменяемых объектов, поэтому мы можем замещать\n",
    "или удалять любые их элементы. Кроме того, существует возможность\n",
    "вставлять, замещать и удалять целые срезы списков.\n",
    "\n",
    "Тип данных `list` может вызываться как функция `list()` –\n",
    "без аргументов она возвращает пустой список, с аргументом типа `list`\n",
    "возвращает поверхностную копию аргумента; в случае, если аргумент\n",
    "имеет другой тип, выполняется попытка преобразовать его в объект типа\n",
    "`list`. Эта функция принимает не более одного аргумента. Кроме того,\n",
    "списки могут создаваться без использования функции `list()`. Пустой\n",
    "список создается с помощью пары пустых квадратных скобок `[]`, а список,\n",
    "состоящий из одного или более элементов, может быть создан с помощью\n",
    "последовательности элементов, разделенных запятыми, заключенной в\n",
    "квадратные скобки. Другой способ создания списков заключается в\n",
    "использовании генераторов списков – эта тема будет рассматриваться\n",
    "ниже в этом подразделе.\n",
    "\n",
    "Поскольку все элементы списка в действительности являются ссылками на\n",
    "объекты, списки, как и кортежи, могут хранить элементы любых типов\n",
    "данных, включая коллекции, такие как списки и кортежи. Списки могут\n",
    "сравниваться с помощью стандартных операторов сравнения (`<`, `<=`,\n",
    "`==`, `!=`, `>=`, `>`), при этом сравнивание производится поэлементно\n",
    "(и рекурсивно, при наличии вложенных элементов, таких как списки или\n",
    "кортежи в списках).\n",
    "\n",
    "В результате выполнения операции присваивания"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "L = [ – 17.5, \"kilo\", 49, \"V\", [\"ram\", 5, \"echo\"], 7]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "мы получим список, как показано на рис. [collections:seq:fig:2](#collections:seq:fig:2)\n",
    "\n",
    "<!-- dom:FIGURE: [fig-collections/seq_2.png, width=800 frac=1.0] Позиции элементов в списке <div id=\"collections:seq:fig:2\"></div> -->\n",
    "<!-- begin figure -->\n",
    "<div id=\"collections:seq:fig:2\"></div>\n",
    "![Позиции элементов в списке](fig-collections/seq_2.png)<!-- end figure -->\n",
    "\n",
    "\n",
    "К спискам, таким как `L`, мы можем применять оператор извлечения\n",
    "среза, повторяя его столько раз, сколько потребуется для доступа к\n",
    "элементам в списке, как показано ниже:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "L[0] == L[-6] == -17.5\n",
    "L[1] == L[-5] == 'kilo'\n",
    "L[1][0] == L[-5][0] == 'k'\n",
    "L[4][2] == L[4][-1] == L[-2][2] == L[-2][-1] == 'echo'\n",
    "L[4][2][1] == L[4][2][-3] == L[-2][-1][1] == L[-2][-1][-3] == 'c'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Списки, как и кортежи, могут вкладываться друг в друга; допускают\n",
    "выполнение итераций по их элементам и извлечение срезов. Все примеры с\n",
    "кортежами, которые приводились в предыдущем подразделе, будут работать\n",
    "точно так же, если вместо кортежей в них будут использованы\n",
    "списки. Списки поддерживают операторы проверки на вхождение `in` и\n",
    "`not in`, оператор конкатенации `+`, оператор расширения `+=` (то есть\n",
    "добавляет операнд справа в конец списка) и операторы дублирования `*`\n",
    "и `*=`. Списки могут также использоваться в качестве аргументов\n",
    "функции `len()`.\n",
    "\n",
    "Кроме того, списки предоставляют методы, перечисленные в\n",
    "табл. [collections:seq:tbl:1](#collections:seq:tbl:1) \n",
    "\n",
    "\n",
    "## Таблица 1 : Методы списков <div id=\"collections:seq:tbl:1\"></div>\n",
    "\n",
    "\n",
    "<table border=\"1\">\n",
    "<thead>\n",
    "<tr><th align=\"left\">        Синтаксис         </th> <th align=\"left\">                                                                                Описание                                                                               </th> </tr>\n",
    "</thead>\n",
    "<tbody>\n",
    "<tr><td align=\"left\">   <code>L.append(x)</code>                 </td> <td align=\"left\">   Добавляет элемент <code>x</code> в конец списка <code>L</code>                                                                                                                                   </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.count(x)</code>                  </td> <td align=\"left\">   Возвращает число вхождений элемента <code>x</code> в список <code>L</code>                                                                                                                       </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.extend(m)</code> или <code>L += m</code>    </td> <td align=\"left\">   Добавляет в конец списка L все элементы итерируемого объекта <code>m</code>                                                                                                           </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.index(x, start, end)</code>      </td> <td align=\"left\">   Возвращает индекс самого первого (слева) вхождения элемента <code>x</code> в список <code>L</code> (или в срез <code>start:end</code> списка <code>L</code>); в противном случае возбуждает исключение <code>ValueError</code>    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.insert(i, x)</code>              </td> <td align=\"left\">   Вставляет элемент <code>x</code> в список <code>L</code> в позицию <code>int i</code>                                                                                                                       </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.pop()</code>                     </td> <td align=\"left\">   Удаляет самый последний элемент из списка <code>L</code> и возвращает его в качестве результата                                                                                       </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.pop(i)</code>                    </td> <td align=\"left\">   Удаляет из списка <code>L</code> элемент с индексом <code>int i</code> и возвращает его в качестве результата                                                                                    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>L.remove(x)</code>                 </td> <td align=\"left\">   Удаляет самый первый (слева) найденный элемент <code>x</code> из списка <code>L</code> или возбуждает исключение <code>ValueError</code>, если элемент <code>x</code>не будет найден <code>                                 </td> </tr>\n",
    "<tr><td align=\"left\">   </code>L.reverse()<code>                 </td> <td align=\"left\">   Переставляет в памяти элементы списка в обратном порядке                                                                                                                   </td> </tr>\n",
    "<tr><td align=\"left\">   </code>L.sort()<code>                    </td> <td align=\"left\">   Сортирует список в памяти. Этот метод принимает те же необязательные аргументы </code>key<code> и </code>reverse<code> что и встроенная функция </code>sorted()`                                       </td> </tr>\n",
    "</tbody>\n",
    "</table>\n",
    "\n",
    "\n",
    "Несмотря на то, что для доступа к элементам списка можно использовать\n",
    "оператор извлечения среза, тем не менее в некоторых ситуациях бывает\n",
    "необходимо одновременно извлечь две или более частей списка. \n",
    "Сделать это можно с помощью операции распаковывания\n",
    "последовательности. Любой итерируемый объект (списки, кортежи и\n",
    "другие) может быть распакован с помощью оператора распаковывания\n",
    "«звездочка» (`*`). Когда слева от оператора присваивания указывается\n",
    "две или более переменных, одна из которых предваряется символом `*`,\n",
    "каждой переменной присваивается по одному элементу списка, а\n",
    "переменной со звездочкой присваивается оставшаяся часть списка. Ниже\n",
    "приводится несколько примеров выполнения распаковывания списков:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "first, *rest = [9, 2, -4, 8, 7]\n",
    "first, rest"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "first, *mid, last = \"Charles Philip Arthur George Windsor\".split()\n",
    "first, mid, last"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "*directories, executable = \"/usr/local/bin/gvim\".split(\"/\")\n",
    "directories, executable"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда используется оператор распаковывания последовательности,\n",
    "как в данном примере, выражение `*rest` и подобные ему называются\n",
    "*выражениями со звездочкой*.\n",
    "\n",
    "В языке Python имеется также похожее понятие *аргументов со звездочкой*.\n",
    "Например, допустим, что имеется следующая функция, принимающая три\n",
    "аргумента:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def product(a, b, c):\n",
    "    return a * b * c"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "тогда мы можем вызывать эту функцию с тремя аргументами или\n",
    "использовать аргументы со звездочкой:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "product(2, 3, 5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "L = [2, 3, 5]\n",
    "product(*L)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "product(2, *L[1:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В первом примере функция вызывается, как обычно, с тремя\n",
    "аргументами. Во втором вызове использован аргумент со звездочкой; в\n",
    "этом случае список из трех элементов распаковывается оператором `*`,\n",
    "так что функция получает столько аргументов, сколько ей\n",
    "требуется. Того же эффекта можно было бы добиться при использовании\n",
    "кортежа с тремя элементами. В третьем вызове функции первый аргумент\n",
    "передается традиционным способом, а другие два – посредством\n",
    "применения операции распаковывания двухэлементного среза списка `L`. \n",
    "\n",
    "Имеется возможность выполнять итерации по элементам списка с помощью\n",
    "конструкции `for item in L:`. Если в цикле потребуется изменять\n",
    "элементы списка, то можно использовать следующий прием:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for i in range(len(L)):\n",
    "    L[i] = process(L[i])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> **Замечание.**\n",
    ">\n",
    "> Встроенная функция `range()` возвращает целочисленный\n",
    "> итератор. С одним целочисленным аргументом, `n`, итератор `range()`\n",
    "> возвращает последовательность чисел $0$, $1$, ..., $n-1$.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "Этот прием можно использовать для увеличения всех элементов в списке\n",
    "целых чисел. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for i in range(len(numbers)):\n",
    "    numbers[i] += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Генераторы списков\n",
    "\n",
    "<div id=\"collections:seq:lists:gen\"></div>\n",
    "\n",
    "Небольшие списки часто создаются как литералы, но длинные списки\n",
    "обычно создаются программным способом. Списки целых чисел могут\n",
    "создаваться с помощью выражения `list(range(n))`; когда необходим\n",
    "итератор целых чисел, достаточно функции `range()`; а для создания\n",
    "списков других типов часто используется оператор цикла `for ... in`.\n",
    "Предположим, например, что нам требуется получить список високосных\n",
    "годов в определенном диапазоне. Для начала мы могли бы использовать\n",
    "такой цикл:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "leaps = []\n",
    "for year in range(1900, 1940):\n",
    "    if (year % 4 == 0 and year % 100 != 0) or (year % 400 == 0):\n",
    "        leaps.append(year)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда функции `range()` передаются два целочисленных\n",
    "аргумента `n` и `m`, итератор возвращает последовательность целых\n",
    "чисел `n`, `n+1`, ..., `m–1`. \n",
    "\n",
    "Конечно, если диапазон известен заранее, можно было\n",
    "бы использовать литерал списка, например,\n",
    "`leaps = [1904, 1908, 1912, 1916, 1920, 1924, 1928, 1932, 1936]`.\n",
    "\n",
    "*Генератор списков* – это выражение и цикл с дополнительным условием,\n",
    "заключенное в квадратные скобки, в котором цикл используется для\n",
    "создания элементов списка, а условие используется для исключения\n",
    "нежелательных элементов. В простейшем виде генератор списков\n",
    "записывается, как показано ниже:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        [item for item in iterable]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Это выражение вернет список всех элементов объекта `iterable` и\n",
    "семантически ничем не отличается от выражения\n",
    "`list(iterable)`. Интересными генераторы списков делают две\n",
    "особенности – они могут использоваться как выражения и они допускают\n",
    "включение условной инструкции, вследствие чего мы получаем две\n",
    "типичные синтаксические конструкции использования генераторов списков:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        [expression for item in iterable]\n",
    "        [expression for item in iterable if condition]\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вторая форма записи эквивалентна циклу:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        temp = []\n",
    "        for item in iterable:\n",
    "            if condition:\n",
    "               temp.append(expression)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обычно выражение `expression` является либо самим элементом `item`,\n",
    "либо некоторым выражением с его участием. Конечно, генератору\n",
    "списков не требуется временная переменная `temp[]`, которая необходима\n",
    "в версии с циклом `for ... in`.\n",
    "\n",
    "Теперь можно переписать программный код создания списка високосных\n",
    "годов с использованием генератора списка. Мы сделаем это в три\n",
    "этапа. Сначала создадим список, содержащий все годы в указанном\n",
    "диапазоне:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "leaps = [y for y in range(1900, 1940)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "То же самое можно было бы сделать с помощью выражения\n",
    "`leaps = list(range(1900, 1940))`. Теперь добавим простое условие,\n",
    "которое будет оставлять в списке только каждый четвертый год:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "leaps = [y for y in range(1900, 1940) if y % 4 == 0]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И, наконец, получаем окончательную версию:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "leaps = [y for y in range(1900, 1940)\n",
    "         if (y % 4 == 0 and y % 100 != 0) or (y % 400 == 0)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Использование генератора списков в данном случае позволило уменьшить\n",
    "объем программного кода с четырех строк до двух – не так много, но в\n",
    "крупных проектах суммарная экономия может оказаться весьма\n",
    "существенной. \n",
    "\n",
    "Так как генераторы списков воспроизводят списки, то есть итерируемые\n",
    "объекты, и сами генераторы списков используют итерируемые объекты,\n",
    "имеется возможность вкладывать генераторы списков друг \n",
    "в друга. Это эквивалентно вложению циклов `for ... in`. Например, если\n",
    "бы нам потребовалось сгенерировать список всех возможных кодов одежды\n",
    "для разных полов, разных размеров и расцветок, но исключая одежду для\n",
    "полных женщин, нужды и чаянья которых индустрия моды нередко\n",
    "игнорирует, мы могли бы использовать вложенные циклы\n",
    "`for ... in`, как показано ниже:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "codes = []\n",
    "for sex in \"MF\":\n",
    "    for size in \"SMLX\":\n",
    "    \tif sex == \"F\" and size\n",
    "\t   continue\n",
    "        for color in \"BGW\":\n",
    "\t   codes.append(sex + size + color)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Этот фрагмент воспроизводит список, содержащий 21 элемент – `['MSB',\n",
    "'MSG', ..., 'FLW']`. Тот же самый список можно создать парой строк,\n",
    "если воспользоваться генераторами списков:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "codes = [s + z + c for s in \"MF\" for z in \"SMLX\" for c in \"BGW\"\n",
    "         if not (s == \"F\" and z == \"X\")]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь каждый элемент списка воспроизводится выражением `s + z + c`.\n",
    "Кроме того, в генераторе списков несколько иначе построена логика\n",
    "обхода нежелательной комбинации пол/размер – проверка выполняется в\n",
    "самом внутреннем цикле, тогда как в версии с циклами `for ... in` эта\n",
    "проверка выполняется в среднем цикле. Любой генератор списков можно\n",
    "переписать, используя один или более циклов `for ... in`. \n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"seq\" -->\n",
    "<!-- End: -->\n",
    "# Множества\n",
    "<div id=\"collections:sets\"></div>\n",
    "\n",
    "Тип `set` – это разновидность коллекций, которая поддерживает оператор\n",
    "проверки на вхождение `in`, функцию `len()` и относится к разряду\n",
    "итерируемых объектов. Кроме того, множества предоставляют метод \n",
    "`set.isdisjoint()` и поддерживают операторы сравнения и битовые\n",
    "операторы (которые в контексте множеств используются для получения \n",
    "объединения, пересечения и т. д.). В языке Python имеется два\n",
    "встроенных типа множеств: изменяемый тип `set` и неизменяемый\n",
    "`frozenset`. При переборе элементов множества элементы могут следовать\n",
    "в произвольном порядке. \n",
    "\n",
    "В состав множеств могут включаться только *хешируемые*\n",
    "объекты. Хешируемые объекты – это объекты, имеющие специальный метод \n",
    "`__hash__()`, на протяжении всего жизненного цикла объекта всегда\n",
    "возвращающий одно и то же значение, которые могут участвовать в\n",
    "операциях сравнения на равенство посредством специального метода \n",
    "`__eq__()`. (Специальные методы – это методы, имена которых начинаются\n",
    "и оканчиваются двумя символами подчеркивания).\n",
    "\n",
    "Все встроенные неизменяемые типы данных, такие как `float`,\n",
    "`frozenset`, `int`, `str` и `tuple`, являются хешируемыми объектами и\n",
    "могут добавляться во множества. Встроенные изменяемые типы данных,\n",
    "такие как `dict`, `list` и `set`, не являются хешируемыми объектами,\n",
    "так как значение хеша в каждом конкретном случае зависит от\n",
    "содержащихся в объекте элементов, поэтому они не могут добавляться в\n",
    "множества.\n",
    "\n",
    "Множества могут сравниваться между собой с использованием стандартных\n",
    "операторов сравнения (`<`, `<=`, `==`, `!=`, `>=`, `>`). Обратите\n",
    "внимание:  операторы `==` и `!=` имеют обычный смысл, и сравнение\n",
    "выполняется путем поэлементного сравнения (или рекурсивно при наличии\n",
    "таких вложенных элементов, как кортежи и фиксированные множества\n",
    "(`frozenset`)), но остальные операторы сравнения выполняют сравнение\n",
    "подмножеств и надмножеств, как вскоре будет показано. \n",
    "\n",
    "## Тип `set`\n",
    "<div id=\"collections:sets:set\"></div>\n",
    "\n",
    "Тип `set` – это неупорядоченная коллекция из нуля или более ссылок на\n",
    "объекты, указывающих на хешируемые объекты. Множества относятся к\n",
    "категории изменяемых типов, поэтому легко можно добавлять и удалять их\n",
    "элементы, но, так как они являются неупорядоченными коллекциями, к ним\n",
    "не применимо понятие индекса и не применима операция извлечения\n",
    "среза. На рис. [collections:sets:fig:1](#collections:sets:fig:1) иллюстрируется множество,\n",
    "созданное следующим фрагментом программного кода:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "S = {7, \"veil\", 0, -29, (\"x\", 11), \"sun\", frozenset({8, 4, 7}), 913}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- dom:FIGURE: [fig-collections/set_1.png, width=400 frac=1.0] Множество – это неупорядоченная коллекция уникальных элементов <div id=\"collections:sets:fig:1\"></div> -->\n",
    "<!-- begin figure -->\n",
    "<div id=\"collections:sets:fig:1\"></div>\n",
    "![Множество – это неупорядоченная коллекция уникальных элементов](fig-collections/set_1.png)<!-- end figure -->\n",
    "\n",
    "\n",
    "Тип данных `set` может вызываться как функция `set()` –\n",
    "без аргументов она возвращает пустое множество; с аргументом типа\n",
    "`set` возвращает поверхностную копию аргумента; в случае, если\n",
    "аргумент имеет другой тип, выполняется попытка преобразовать его в\n",
    "объект типа `set`. Эта функция принимает не более одного\n",
    "аргумента. Кроме того, непустые множества могут создаваться без\n",
    "использования функции `set()`, а пустые множества могут создаваться\n",
    "только с помощью функции `set()` – их нельзя создать с помощью пары\n",
    "пустых скобок. Множество, состоящее из одного или более элементов,\n",
    "может быть создано с помощью последовательности элементов, разделенных\n",
    "запятыми, заключенной в фигурные скобки. Другой способ создания\n",
    "множеств заключается в использовании генераторов множеств. Множества\n",
    "всегда содержат уникальные элементы – добавление повторяющихся\n",
    "элементов возможно, но не имеет смысла. Например, следующие три\n",
    "множества являются эквивалентными: `set(\"apple\")`, `set(\"aple\")` и\n",
    "`{'e', 'p', 'a', 'l'}`. Благодаря этой их особенности множества часто\n",
    "используются для устранения повторяющихся значений. Например, если\n",
    "предположить, что `x` – это список строк, то после выполнения\n",
    "инструкции `x = list(set(x))` в списке останутся только уникальные\n",
    "строки, причем располагаться они могут в произвольном порядке.\n",
    "\n",
    "<!-- dom:FIGURE: [fig-collections/set_2.png, width=800 frac=1.0] Стандартные операторы множеств <div id=\"collections:sets:fig:2\"></div> -->\n",
    "<!-- begin figure -->\n",
    "<div id=\"collections:sets:fig:2\"></div>\n",
    "![Стандартные операторы множеств](fig-collections/set_2.png)<!-- end figure -->\n",
    "\n",
    "\n",
    "Множества поддерживают встроенную функцию `len()` и быструю проверку\n",
    "на вхождение с помощью операторов `in` и `not in`. Они также предоставляют\n",
    "типичный набор операторов, как показано на рис. . \n",
    "Полный перечень методов и операторов, применимых к множествам,\n",
    "приводится в табл. [collections:sets:tbl:1](#collections:sets:tbl:1). Все методы семейства\n",
    "«update» (`set.update()`, `set.intersection_update()` и т. д.) могут принимать в качестве\n",
    "аргумента любые итерируемые объекты, но эквивалентные им\n",
    "комбинированные операторы присваивания (`|=`, `&=` и т. д.) требуют, чтобы\n",
    "оба операнда были множествами. \n",
    "\n",
    "\n",
    "## Таблица 2 : Методы и операторы множеств <div id=\"collections:sets:tbl:1\"></div>\n",
    "\n",
    "\n",
    "<table border=\"1\">\n",
    "<thead>\n",
    "<tr><th align=\"left\">                   Синтаксис                   </th> <th align=\"left\">                                                                                                       Описание                                                                                                      </th> </tr>\n",
    "</thead>\n",
    "<tbody>\n",
    "<tr><td align=\"left\">   <code>s.add(x)</code>                                         </td> <td align=\"left\">   Добавляет элементы <code>x</code> во множество <code>s</code>, если они отсутствуют в <code>s</code>                                                                                                                                                      </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.clear()</code>                                        </td> <td align=\"left\">   Удаляет все элементы из множества <code>s</code>                                                                                                                                                                                    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.difference(t)</code>или <code>s-t</code>                         </td> <td align=\"left\">   Возвращает новое множество включающее элементы множества <code>s</code>, которые отсутствуют в множестве <code>t</code>                                                                                                                        </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.difference_update(t)</code> или <code>s-=t</code>                </td> <td align=\"left\">   Удаляет из множества <code>s</code> все элементы, присутствующие в множестве <code>t</code>                                                                                                                                                    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.discard(x)</code>                                     </td> <td align=\"left\">   Удаляет элемент <code>x</code> из множества <code>s</code>, если он присутствует в множестве <code>s</code>                                                                                                                                               </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.intersection(t)</code> или <code>s & t</code>                    </td> <td align=\"left\">   Возвращает новое множество, включающее элементы, присутствующие одновременно в множествах <code>s</code> и <code>t</code>                                                                                                                      </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.intersection_update(t)</code> или <code>s &= t</code>            </td> <td align=\"left\">   Оставляет во множестве <code>s</code> пересечение множеств <code>s</code> и <code>t</code>                                                                                                                                                                </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.isdisjoint(t)</code>                                  </td> <td align=\"left\">   Возвращает <code>True</code>, если множества <code>s</code> и <code>t</code> не имеют общих элементов                                                                                                                                                     </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.issubset(t)</code> или <code>s <= t</code>                       </td> <td align=\"left\">   Возвращает <code>True</code>, если множество <code>s</code> эквивалентно множеству <code>t</code> или является его подмножеством; чтобы проверить, является ли множество <code>s</code> только подмножеством множества <code>t</code>, следует использовать проверку <code>s < t</code>    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.issuperset(t)</code> или <code>s >= t</code>                     </td> <td align=\"left\">   Возвращает <code>True</code>, если множество <code>s</code> эквивалентно множеству <code>t</code> или является его надмножеством; чтобы проверить, является ли множество <code>s</code> только надмножеством множества <code>t</code>, следует использовать проверку <code>s > t</code>    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.pop()</code>                                          </td> <td align=\"left\">   Возвращает и удаляет случайный элемент множества <code>s</code> или возбуждает исключение <code>KeyError</code>, если <code>s</code> – это пустое множество                                                                                               </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.remove(x)</code>                                      </td> <td align=\"left\">   Удаляет элемент <code>x</code> из множества <code>s</code> или возбуждает исключение <code>KeyError</code>, если элемент <code>x</code> отсутствует в множестве <code>s</code>                                                                                                  </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.symmetric_difference(t)</code> или <code>s ^ t</code>            </td> <td align=\"left\">   Возвращает новое множество, включающее все элементы, присутствующие в множествах <code>s</code> и <code>t</code>, за исключением элементов, присутствующих в обоих множествах одновременно                                                     </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.symmetric_difference_update(t)</code> или <code>s ^= t</code>    </td> <td align=\"left\">   Возвращает в множестве <code>s</code> результат строгой дизъюнкции множеств <code>s</code> и <code>t</code>                                                                                                                                               </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.union(t)</code> или <code>s | t</code>                           </td> <td align=\"left\">   Возвращает новое множество, включающее все элементы множества <code>s</code> и все элементы множества <code>t</code>, отсутствующие в множестве <code>s</code>                                                                                            </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s.update(t)</code> или <code>s |= t</code>                         </td> <td align=\"left\">   Добавляет во множество <code>s</code> все элементы множества <code>t</code>, отсутствующие в множестве <code>s</code>                                                                                                                                     </td> </tr>\n",
    "</tbody>\n",
    "</table>\n",
    "\n",
    "\n",
    "### Генераторы множеств\n",
    "\n",
    "<div id=\"collections:sets:gen\"></div>\n",
    "\n",
    "В дополнение к возможности создавать множества с помощью функции\n",
    "`set()` или литералов, существует возможность создавать множества с\n",
    "помощью *генераторов множеств*. Генератор множества – это выражение и\n",
    "цикл с необязательным условием, заключенные в фигурные скобки. Подобно\n",
    "генераторам списков, генераторы множеств поддерживают две формы\n",
    "записи:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        {expression for item in iterable}\n",
    "        {expression for item in iterable if condition}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Мы могли бы использовать генераторы множеств для фильтрации\n",
    "нежелательных элементов (когда порядок следования элементов не имеет\n",
    "значения), как показано ниже:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "html = {x for x in files if x.lower().endswith((\".htm\", \".html\"))}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если предположить, что `files` – это список имен файлов, то данный\n",
    "генератор множества создает множество `html`, в котором хранятся\n",
    "только имена файлов с расширениями `.htm` и `.html`, независимо от\n",
    "регистра символов.\n",
    "\n",
    "Как и в случае с генераторами списков, в генераторах множеств\n",
    "используются итерируемые объеты, которые в свою очередь могут быть\n",
    "генераторами множеств (или генераторами любого другого типа), что\n",
    "позволяет создавать весьма замысловатые генераторы множеств.\n",
    "\n",
    "## Тип `frozenset`\n",
    "<div id=\"collections:sets:frozenset\"></div>\n",
    "\n",
    "Фиксированное множество (`frozenset`) – это множество, которое после\n",
    "создания невозможно изменить. Хотя при этом мы, конечно, можем\n",
    "повторно связать переменную, которая ссылалась на фиксированное\n",
    "множество, с чем-то другим. Фиксированные множества могут создаваться\n",
    "только в результате обращения к имени типа `frozenset` как к\n",
    "функции. При вызове `frozenset()` без аргументов возвращается пустое\n",
    "фиксированное множество; с аргументом типа `frozenset` возвращается\n",
    "поверхностная копия аргумента; если аргумент имеет другой тип,\n",
    "выполняется попытка преобразовать его в объект типа `frozenset`. Эта\n",
    "функция принимает не более одного аргумента.\n",
    "\n",
    "Поскольку фиксированные множества относятся к категории неизменяемых\n",
    "объектов, они поддерживают только те методы и операторы, которые\n",
    "воспроизводят результат, не оказывая воздействия на фиксированное\n",
    "множество или на множества, к которым они применяются.\n",
    "\n",
    "Поскольку фиксированные множества относятся к категории неизменяемых\n",
    "объектов, они поддерживают только те методы и операторы, которые\n",
    "воспроизводят результат, не оказывая воздействия на фиксированное\n",
    "множество или на множества, к которым они применяются. \n",
    "В табл. [collections:sets:tbl:1](#collections:sets:tbl:1) перечислены все методы множеств из\n",
    "которых фиксированными множествами поддерживаются: `frozenset.copy()`,\n",
    "`frozenset.difference()` (`-`), `frozenset.intersection()` (`&`),\n",
    "`frozenset.isdis-joint()`, `frozenset.issubset()` (`<=` и `<` для\n",
    "выявления подмножеств), `frozenset.issuperset()` (`>=` и `>` для\n",
    "выявления надмножеств), `frozenset.union()` (`|`) и\n",
    "`frozenset.symmetric_difference()` (`^`).\n",
    "\n",
    "Если двухместный оператор применяется ко множеству и фиксированному\n",
    "множеству, тип результата будет совпадать с типом операнда, стоящего\n",
    "слева от оператора. То есть если предположить, что `f` – это\n",
    "фиксированное множество, а `s` – это обычное множество, то выражение\n",
    "`f & s` вернет объект типа frozenset, а выражение `s & f` – объект\n",
    "типа `set`. В случае операторов `==` и `!=` порядок операндов не имеет\n",
    "значения, и выражение `f == s` вернет `True`, только если оба\n",
    "множества содержат одни и те же элементы.\n",
    "\n",
    "Другое следствие неизменности фиксированных множеств заключается в\n",
    "том, что они соответствуют критерию хеширования, предъявляемому к\n",
    "элементам множеств, и потому множества и фиксированные множества могут\n",
    "содержать другие фиксированные множества.\n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"sets\" -->\n",
    "<!-- End: -->\n",
    "# Отображения\n",
    "<div id=\"collections:maps\"></div>\n",
    "\n",
    "Отображениями называются типы данных, поддерживающие оператор проверки\n",
    "на вхождение (`in`), функцию `len()` и возможность обхода элементов в\n",
    "цикле. Отображения – это коллекции пар элементов «ключ-значение»,\n",
    "которые предоставляют методы доступа к элементам и их ключам и\n",
    "значениям. При выполнении итераций порядок следования элементов\n",
    "отображений может быть произвольным. В языке Python имеется два типа\n",
    "отображений: встроенный тип `dict` и тип `collections.defaultdict`,\n",
    "определяемый в стандартной библиотеке. Мы будем использовать термин\n",
    "словарь для ссылки на любой из этих типов, когда различия между ними\n",
    "не будут иметь никакого значения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "import collections.defaultdict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В качестве ключей словарей могут использоваться только хешируемые\n",
    "объекты, поэтому в качестве ключей словаря такие неизменяемые типы,\n",
    "как `float`, `frozenset`, `int`, `str` и `tuple`, использовать\n",
    "допускается, а изменяемые типы, такие как `dict`, `list` и `set`, –\n",
    "нет. С другой стороны, каждому ключу соответствует некоторое значение,\n",
    "которое может быть ссылкой на объект любого типа, включая числа,\n",
    "строки, списки, множества, словари, функции и т. д.\n",
    "\n",
    "Словари могут сравниваться с помощью стандартных операторов сравнения\n",
    "(`<`, `<=`, `==`, `!=`, `>=`, `>`), при этом сравнивание производится\n",
    "поэлементно (и рекурсивно, при наличии вложенных элементов, таких как\n",
    "кортежи или словари в словарях). Пожалуй, единственными операторами\n",
    "сравнения, применение которых к словарям имеет смысл, являются\n",
    "операторы `==` и `!=`.\n",
    "\n",
    "## Словари\n",
    "<div id=\"collections:maps:dict\"></div>\n",
    "\n",
    "Тип `dict` – это неупорядоченная коллекция из нуля или более пар\n",
    "«ключ-значение», в которых в качестве ключей могут использоваться\n",
    "ссылки на хешируемые объекты, а в качестве значений – ссылки на\n",
    "объекты любого типа. Словари относятся к категории изменяемых типов,\n",
    "поэтому легко можно добавлять и удалять их элементы, но так как они\n",
    "являются неупорядоченными коллекциями, к ним не применимо понятие\n",
    "индекса и не применима операция извлечения среза.\n",
    "\n",
    "Тип данных `dict` может вызываться как функция `dict()` – без\n",
    "аргументов она возвращает пустой словарь; если в качестве аргумента\n",
    "передается отображение, возвращается словарь, основанный на этом\n",
    "отображении: например, с аргументом типа `dict` возвращается\n",
    "поверхностная копия словаря. Существует возможность передавать в\n",
    "качестве аргумента последовательности, если каждый элемент\n",
    "последовательности в свою очередь является последовательностью из двух\n",
    "объектов, первый из которых используется в качестве ключа, а второй –\n",
    "в качестве значения. Как вариант, для создания словарей, в которых \n",
    "ключи являются допустимыми идентификаторами языка Python, можно\n",
    "использовать именованные аргументы; тогда имена аргументов будут\n",
    "играть роль ключей, а значения аргументов – роль значений\n",
    "ключей. Кроме того, словари могут создаваться с помощью фигурных\n",
    "скобок – пустые скобки `{}` создадут пустой словарь. Непустые фигурные\n",
    "скобки должны содержать один или более элементов, разделенных\n",
    "запятыми, каждый из которых состоит из ключа, символа двоеточия и\n",
    "значения. Еще один способ создания словарей заключается в\n",
    "использовании генераторов словарей – эта тема будет рассматриваться\n",
    "ниже, в соответствующем подразделе.\n",
    "\n",
    "Ниже приводятся несколько способов создания словарей – все они создают\n",
    "один и тот же словарь:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "d1 = dict({\"id\": 1948, \"name\": \"Washer\", \"size\": 3})\n",
    "d2 = dict(id=1948, name=\"Washer\", size=3)\n",
    "d3 = dict([(\"id\", 1948), (\"name\", \"Washer\"), (\"size\", 3)])\n",
    "d4 = dict(zip((\"id\", \"name\", \"size\"), (1948, \"Washer\", 3)))\n",
    "d5 = {\"id\": 1948, \"name\": \"Washer\", \"size\": 3}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<!-- dom:FIGURE: [fig-collections/map_1.png, width=800 frac=1.0] Словарь – это неупорядоченная коллекция элементов (ключ, значение) с уникальными ключами <div id=\"collections:maps:fig:1\"></div> -->\n",
    "<!-- begin figure -->\n",
    "<div id=\"collections:maps:fig:1\"></div>\n",
    "![Словарь – это неупорядоченная коллекция элементов (ключ, значение) с уникальными ключами](fig-collections/map_1.png)<!-- end figure -->\n",
    "\n",
    "\n",
    "На рис. [collections:maps:fig:1](#collections:maps:fig:1) демонстрируется словарь, созданный\n",
    "следующим фрагментом программного кода:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "d = {\"root\": 18, \"blue\": [75, \"R\", 2], 21: \"venus\", -14: None,\n",
    "     \"mars\": \"rover\", (4, 11): 18, 0: 45}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ключи словарей являются уникальными, поэтому если в словарь\n",
    "добавляется пара «ключ-значение» с ключом, который уже присутствует в\n",
    "словаре, в результате происходит замена значения существующего ключа\n",
    "новым значением. Для доступа к отдельным элементам используются\n",
    "квадратные скобки: например, выражение `d[\"root\"]` вернет `18`,\n",
    "выражение `d[21]` вернет строку `\"venus\"`, а выражение `d[91]` \n",
    "применительно к словарю, изображенному на\n",
    "рис. [collections:maps:fig:1](#collections:maps:fig:1), возбудит исключение `KeyError`. \n",
    "\n",
    "Квадратные скобки могут также использоваться для добавления и удаления\n",
    "элементов словаря. Чтобы добавить новый элемент, используется оператор\n",
    "`=`, например, `d[\"X\"] = 59`. Для удаления элементов используется\n",
    "инструкция `del`, например, инструкция `del d[\"mars\"]` удалит из \n",
    "словаря элемент с ключом `\"mars\"` или возбудит исключение `KeyError`,\n",
    "если элемент с указанным ключом отсутствует в словаре. Кроме того,\n",
    "элементы могут удаляться (и возвращаться вызывающей программе)\n",
    "методом `dict.pop()`.\n",
    "\n",
    "Словари поддерживают встроенную функцию `len()` и для ключей\n",
    "поддерживают возможность быстрой проверки на вхождение с помощью\n",
    "операторов `in` и `not in`. В табл. [collections:maps:tbl:1](#collections:maps:tbl:1)\n",
    "перечислены все методы словарей.\n",
    "\n",
    "Так как словари содержат пары «ключ-значение», у нас может возникнуть\n",
    "потребность обойти в цикле элементы словаря (ключ, значение) по\n",
    "значениям или по ключам. Например, ниже приводятся два эквивалентных\n",
    "способа обхода пар «ключ-значение»:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3\n",
    "9\n",
    " \n",
    "<\n",
    "<\n",
    "<\n",
    "!\n",
    "!\n",
    "C\n",
    "O\n",
    "D\n",
    "E\n",
    "_\n",
    "B\n",
    "L\n",
    "O\n",
    "C\n",
    "K\n",
    " \n",
    " \n",
    "p\n",
    "y\n",
    "c\n",
    "o\n",
    "d"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for key, value in d.items():\n",
    "    print(key, value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Таблица 3 : Методы словарей <div id=\"collections:maps:tbl:1\"></div>\n",
    "\n",
    "\n",
    "<table border=\"1\">\n",
    "<thead>\n",
    "<tr><th align=\"left\">     Синтаксис      </th> <th align=\"left\">                                                                                                                                                Описание                                                                                                                                                </th> </tr>\n",
    "</thead>\n",
    "<tbody>\n",
    "<tr><td align=\"left\">   <code>d.clear()</code>             </td> <td align=\"left\">   Удаляет все элементы из словаря <code>d</code>                                                                                                                                                                                                                                                                         </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.copy()</code>              </td> <td align=\"left\">   Возвращает поверхностную копию словаря <code>d</code>                                                                                                                                                                                                                                                                  </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.fromkeys(s, v)</code>      </td> <td align=\"left\">   Возвращает словарь типа <code>dict</code>, ключами которого являются элементы последовательности <code>s</code> значениями либо <code>None</code> либо <code>v</code>, если аргумент <code>v</code> определен                                                                                                                                                      </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.get(k)</code>              </td> <td align=\"left\">   Возвращает значение ключа <code>k</code> или <code>None</code>, если ключ <code>k</code> отсутствует в словаре                                                                                                                                                                                                                               </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.get(k, v)</code>           </td> <td align=\"left\">   Возвращает значение ключа <code>k</code> или <code>v</code>, если ключ <code>k</code> отсутствует в словаре                                                                                                                                                                                                                                  </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.items()</code>             </td> <td align=\"left\">   Возвращает представление всех пар (ключ, значение) в словаре <code>d</code>                                                                                                                                                                                                                                            </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.keys()</code>              </td> <td align=\"left\">   Возвращает представление всех ключей словаря <code>d</code>                                                                                                                                                                                                                                                            </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.pop(k)</code>              </td> <td align=\"left\">   Возвращает значение ключа <code>k</code> и удаляет из словаря элемент с ключом <code>k</code> или возбуждает исключение <code>KeyError</code>, если ключ <code>k</code> отсутствует в словаре                                                                                                                                                           </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.pop(k, v)</code>           </td> <td align=\"left\">   Возвращает значение ключа <code>k</code> и удаляет из словаря элемент с ключом <code>k</code> или возвращает значение <code>v</code>, если ключ <code>k</code> отсутствует в словаре                                                                                                                                                                    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.popitem()</code>           </td> <td align=\"left\">   Возвращает и удаляет произвольную пару (ключ, значение) из словаря <code>d</code> или возбуждает исключение <code>KeyError</code>, если словарь <code>d</code> пуст                                                                                                                                                                          </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.setdefault(k, v)</code>    </td> <td align=\"left\">   То же что и <code>dict.get()</code> за исключением того, что, если ключ <code>k</code> в словаре отсутствует, в словарь вставляется новый элемент с ключом <code>k</code> и со значением <code>None</code> или <code>v</code>, если аргумент <code>v</code> задан                                                                                                             </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.update(a)</code>           </td> <td align=\"left\">   Добавляет в словарь <code>d</code> пары (ключ, значение) из <code>a</code>, которые отсутствуют в словаре <code>d</code> а для каждого ключа который уже присутствует в словаре <code>d</code> выполняется замена соответствующим значением из <code>a</code>; a может быть словарем итерируемым объектом с парами (ключ значение) или именованными аргументами    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>d.values()</code>            </td> <td align=\"left\">   Возвращает представление всех значений в словаре <code>d</code>                                                                                                                                                                                                                                                        </td> </tr>\n",
    "</tbody>\n",
    "</table>\n",
    "\n",
    "\n",
    "\n",
    "Обход значений в словаре выполняется похожим способом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for value in d.values():\n",
    "    print(value)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для обхода ключей в словаре можно использовать метод `dict.keys()`\n",
    "или просто интерпретировать словарь как итерируемый объект и вы-\n",
    "полнить итерации по его ключам, как показано в следующих двух\n",
    "фрагментах:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for key in d:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for key in d.keys():\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если необходимо изменить значения в словаре, то можно выполнить\n",
    "обход ключей словаря в цикле и изменить значения, используя оператор\n",
    "квадратных скобок. Например, ниже показано, как можно было бы\n",
    "увеличить все значения в словаре `d`, если предполагать, что все\n",
    "значения являются числами:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "for key in d:\n",
    "    d[key] += 1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Методы `dict.items()`, `dict.keys()` и `dict.values()` возвращают\n",
    "представления словарей. Представление словаря – это в действительности\n",
    "итерируемый объект, доступный только для чтения и хранящий элементы,\n",
    "ключи или значения словаря в зависимости от того, какое представление\n",
    "было запрошено.\n",
    "\n",
    "### Генераторы словарей\n",
    "\n",
    "<div id=\"collections:maps:dict:gen\"></div>\n",
    "\n",
    "*Генератор словарей* – это выражение и цикл с необязательным условием,\n",
    "заключенное в фигурные скобки, очень напоминающее генератор\n",
    "множеств. Подобно генераторам списков и множеств, генераторы словарей\n",
    "поддерживают две формы записи:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        {keyexpression: valueexpression for key, value in iterable}\n",
    "        {keyexpression: valueexpression for key, value in iterable if condition}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ниже показано, как можно использовать генератор словарей для создания\n",
    "словаря, в котором каждый ключ является именем файла в текущем\n",
    "каталоге, а каждое значение – это размер файла в байтах:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        file_sizes = {name: os.path.getsize(name) for name in os.listdir(\".\")}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Функция `os.listdir()` из модуля os («operating system» – операционная\n",
    "система) возвращает список файлов и каталогов в указанном каталоге, но\n",
    "при этом в список никогда не включаются специальные имена каталогов\n",
    "«.» или «..». Функция os.path.getsize() возвращает размер заданного\n",
    "файла в байтах. Чтобы отфильтровать каталоги и другие элементы списка,\n",
    "не являющиеся файлами, можно добавить дополнительное условие:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        file_sizes = {name: os.path.getsize(name) for name in os.listdir(\".\")\n",
    "        \t      if os.path.isfile(name)}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Функция `os.path.isfile()` из модуля `os.path` возвращает `True`, если\n",
    "указанный путь соответствует файлу, и `False` – в противном случае, то\n",
    "есть для каталогов, ссылок и тому подобного.\n",
    "\n",
    "Генераторы словарей могут также использоваться для создания инвер-\n",
    "тированных словарей. Например, пусть имеется словарь `d`, тогда мы\n",
    "можем создать новый словарь, ключами которого будут значения словаря\n",
    "`d`, а значениями – ключи словаря `d`:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```Python\n",
    "        inverted_d = {v: k for k, v in d.items()}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Полученный словарь можно инвертировать обратно и получить\n",
    "первоначальный словарь – при условии, что все значения в\n",
    "первоначальном словаре были уникальными, однако инверсия будет терпеть\n",
    "неудачу, с возбуждением исключения `TypeError`, если какое-либо\n",
    "значение окажется не хешируемым.\n",
    "\n",
    "\n",
    "## Словари со значениями по умолчанию\n",
    "<div id=\"collections:maps:defaultdict\"></div>\n",
    "\n",
    "Словари со значениями по умолчанию – это обычные словари, они\n",
    "поддерживают те же самые методы и операторы, что и обычные\n",
    "словари. Единственное, что отличает такие словари от обычных словарей,\n",
    "– это способ обработки отсутствующих ключей, но во всех остальных\n",
    "отношениях они ничем не отличаются друг от друга. \n",
    "\n",
    "При обращении к несуществующему («отсутствующему») ключу слова-\n",
    "ря возбуждается исключение `KeyError`. Это очень удобно, так как\n",
    "нередко для нас бывает желательно знать об отсутствии ключа, который,\n",
    "согласно нашим предположениям, может присутствовать. Но в некоторых\n",
    "случаях бывает необходимо, чтобы в словаре присутствовали все ключи,\n",
    "которые мы используем, даже если это означает, что элемент с заданным\n",
    "ключом добавляется в словарь в момент первого обращения к нему.\n",
    "\n",
    "Например, допустим, что имеется словарь `d`, который не имеет элемента\n",
    "с ключом m, тогда выражение `x = d[m]` возбудит исключение\n",
    "`KeyError`. Если `d` – это словарь со значениями по умолчанию,\n",
    "созданный соответствующим способом, а элемент с ключом m принадлежит\n",
    "такому словарю, то при обращении к нему будет возвращено\n",
    "соответствующее значение, как и в случае с обычным словарем. Но если в\n",
    "словаре со значениями по умолчанию отсутствует ключ m, то будет создан\n",
    "новый элемент словаря с ключом m и со значением по умолчанию, и будет\n",
    "возвращено значение этого, вновь созданного элемента. \n",
    "\n",
    "\n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"maps\" -->\n",
    "<!-- End: -->\n",
    "# Обход в цикле и копирование коллекций\n",
    "<div id=\"collections:iterandcopy\"></div>\n",
    "\n",
    "После того как будет создана коллекция элементов данных, вполне\n",
    "естественно возникает желание обойти все элементы, содержащиеся в\n",
    "ней. Еще одна часто выполняемая операция – копирование коллекций. Из-за\n",
    "того, что в языке Python повсеместно используются ссылки на объекты\n",
    "(ради повышения эффективности), существуют некоторые особенности,\n",
    "связанные с копированием.\n",
    "\n",
    "В этом разделе сначала мы рассмотрим итераторы языка Python, а затем\n",
    "принципы копирования коллекций.\n",
    "\n",
    "## Итераторы, функции и операторы для работы с итерируемыми объектами\n",
    "<div id=\"collections:iterandcopy:iterator\"></div>\n",
    "\n",
    "*Итерируемый* тип данных – это такой тип, который может возвращать\n",
    "свои элементы по одному. Любой объект, имеющий метод `__iter__()`, или\n",
    "любая последовательность (то есть объект, имеющий метод\n",
    "`__getitem__()`, принимающий целочисленный аргумент со значением от\n",
    "`0` и выше), является итерируемым и может предоставлять\n",
    "итератор. Итератор – это объект, имеющий метод `__next__()`, который\n",
    "при каждом вызове возвращает очередной элемент и возбуждает исключение\n",
    "`StopIteration` после исчерпания всех элементов. В\n",
    "табл. [collections:iterandcopy:iterator:tbl:1](#collections:iterandcopy:iterator:tbl:1) перечислены\n",
    "операторы и функции, которые могут применяться к итерируемым\n",
    "объектам. \n",
    "\n",
    "\n",
    "## Таблица 4 : Общие функции и операторы для работы с итерируемыми объектами <div id=\"collections:iterandcopy:iterator:tbl:1\"></div>\n",
    "\n",
    "<table border=\"1\">\n",
    "<thead>\n",
    "<tr><th align=\"left\">        Синтаксис         </th> <th align=\"left\">                                                                                                                                                             Описание                                                                                                                                                            </th> </tr>\n",
    "</thead>\n",
    "<tbody>\n",
    "<tr><td align=\"left\">   <code>s + t</code>                       </td> <td align=\"left\">   Возвращает конкатенацию последовательностей <code>s</code> и <code>t</code>                                                                                                                                                                                                                                                                                </td> </tr>\n",
    "<tr><td align=\"left\">   <code>s * n</code>                       </td> <td align=\"left\">   Возвращает конкатенацию из <code>int n</code> последовательностей <code>s</code>                                                                                                                                                                                                                                                                           </td> </tr>\n",
    "<tr><td align=\"left\">   <code>x in i</code>                      </td> <td align=\"left\">   Возвращает <code>True</code>, если элемент <code>x</code> присутствует в итерируемом объекте <code>i</code>, обратная проверка выполняется с помощью оператора <code>not in</code>                                                                                                                                                                                               </td> </tr>\n",
    "<tr><td align=\"left\">   <code>all(i)</code>                      </td> <td align=\"left\">   Возвращает <code>True</code>, если все элементы итерируемого объекта <code>i</code> в логическом контексте оцениваются как значение <code>True</code>                                                                                                                                                                                                                 </td> </tr>\n",
    "<tr><td align=\"left\">   <code>any(i)</code>                      </td> <td align=\"left\">   Возвращает <code>True</code>, если хотя бы один элемент итерируемого объекта <code>i</code> в логическом контексте оценивается как значение <code>True</code>                                                                                                                                                                                                         </td> </tr>\n",
    "<tr><td align=\"left\">   <code>enumerate (i,start)</code>         </td> <td align=\"left\">   Обычно используется в циклах <code>for ... in</code>, чтобы получить последовательность кортежей <code>(index, item)</code>, где значения индексов начинают отсчитывать от <code>0</code> или от значения <code>start</code>                                                                                                                                                     </td> </tr>\n",
    "<tr><td align=\"left\">   <code>len(x)</code>                      </td> <td align=\"left\">   Возвращает «длину» объекта <code>x</code>. Если <code>x</code> – коллекция, то возвращаемое число представляет количество элементов. Если <code>x</code> – строка, то возвращаемое число представляет количество символов                                                                                                                                             </td> </tr>\n",
    "<tr><td align=\"left\">   <code>max(i, key)</code>                 </td> <td align=\"left\">   Возвращает наибольший элемент в итерируемом объекте <code>i</code> или элемент с наибольшим значением <code>key(item)</code>, если функция <code>key</code> определена                                                                                                                                                                                                </td> </tr>\n",
    "<tr><td align=\"left\">   <code>min(i, key)</code>                 </td> <td align=\"left\">   Возвращает наименьший элемент в итерируемом объекте <code>i</code> или элемент с наименьшим значением <code>key(item)</code>, если функция <code>key</code> определена                                                                                                                                                                                                </td> </tr>\n",
    "<tr><td align=\"left\">   <code>range(start, stop, step)</code>    </td> <td align=\"left\">   Возвращает целочисленный итератор. С одним аргументом (<code>stop</code>) итератор представляет последовательность целых чисел от <code>0</code> до <code>stop-1</code>, с двумя аргументами (<code>start</code>, <code>stop</code>) – последовательность целых чисел от <code>start</code> до <code>stop-1</code>, с тремя аргументами – последовательность целых чисел от <code>start</code> до <code>stop-1</code> c шагом <code>step</code>    </td> </tr>\n",
    "<tr><td align=\"left\">   <code>reversed(i)</code>                 </td> <td align=\"left\">   Возвращает итератор, который будет возвращать элементы итератора <code>i</code> в обратном порядке                                                                                                                                                                                                                                              </td> </tr>\n",
    "<tr><td align=\"left\">   <code>sorted(i, key, reverse)</code>     </td> <td align=\"left\">   Возвращает список элементов итератора <code>i</code> в отсортированном порядке. Аргумент <code>key</code> используется для выполнения сортировки DSU (Decorate, Sort, Undecorate – декорирование, сортировка, обратное декорирование). Если аргумент <code>reverse</code> имеет значение <code>True</code>, сортировка выполняется в обратном порядке                            </td> </tr>\n",
    "<tr><td align=\"left\">   <code>sum(i, start)</code>               </td> <td align=\"left\">   Возвращает сумму элементов итерируемого объекта <code>i</code>, плюс ар гумент <code>start</code> (значение которого по умолчанию равно <code>0</code>). Объект <code>i</code> не должен содержать строк                                                                                                                                                                         </td> </tr>\n",
    "<tr><td align=\"left\">   <code>zip(i1, ..., iN)</code>            </td> <td align=\"left\">   Возвращает итератор кортежей, используя итераторы от <code>i1</code> до <code>iN</code>                                                                                                                                                                                                                                                                    </td> </tr>\n",
    "</tbody>\n",
    "</table>\n",
    "\n",
    "\n",
    "Порядок, в котором возвращаются элементы, зависит от итерируемого\n",
    "объекта. В случае списков и кортежей элементы обычно возвращаются\n",
    "в предопределенном порядке, начиная с первого элемента (находящегося в\n",
    "позиции с индексом `0`), но другие итераторы возвращают элементы в\n",
    "произвольном порядке – например, итераторы словарей и множеств.\n",
    "\n",
    "Встроенная функция `iter()` используется двумя совершенно различными\n",
    "способами. Применяемая к коллекции или к последовательности, она\n",
    "возвращает итератор для заданного объекта или возбуждает исключение\n",
    "`TypeError`, если объект не является итерируемым. Такой способ часто\n",
    "используется при работе с нестандартными типами коллекций \n",
    "и крайне редко – в других контекстах. Во втором варианте использования\n",
    "функции `iter()` ей передается вызываемый объект (функция или метод) и\n",
    "специальное значение. В этом случае полученная функция или метод\n",
    "вызывается на каждой итерации, а значение этой функции, если оно не\n",
    "равно специальному значению, возвращается вызывающей программе; в\n",
    "противном случае возбуждается исключение `StopIteration`.\n",
    "\n",
    "Когда в программе используется цикл `for item in iterable`,\n",
    "интерпретатор Python вызывает функцию `iter(iterable)`, чтобы получить\n",
    "итератор. После этого на каждой итерации вызывается метод `__next__()`\n",
    "итератора, чтобы получить очередной элемент, а когда возбуждается\n",
    "исключение `StopIteration`, оно перехватывается и цикл завершается.\n",
    "Другой способ получить очередной элемент итератора состоит в том,\n",
    "чтобы вызвать встроенную функцию `next()`. Ниже приводятся два\n",
    "эквивалентных фрагмента программного кода (оба они вычисляют\n",
    "произведение элементов списка), в одном из них используется цикл\n",
    "`for ... in`, а во втором явно используется итератор:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "product = 1\n",
    "for i in [1, 2, 4, 8]:\n",
    "    product *= i\n",
    "print(product)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "product = 1\n",
    "i = iter([1, 2, 4, 8])\n",
    "while True:\n",
    "    try:\n",
    "        product *= next(i)\n",
    "    except StopIteration:\n",
    "        break\n",
    "print(product)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Любой (конечный) итерируемый объект `i` может быть преобразован\n",
    "в кортеж вызовом функции `tuple(i)` или в список – вызовом функции\n",
    "`list(i)`.\n",
    "\n",
    "## Копирование коллекций\n",
    "<div id=\"collections:iterandcopy:copy\"></div>\n",
    "\n",
    "Поскольку в языке Python повсюду используются ссылки на объекты, когда\n",
    "выполняется оператор присваивания (`=`), никакого копирования данных\n",
    "на самом деле не происходит. Если справа от оператора находится\n",
    "литерал, например, строка или число, в операнд слева записывается\n",
    "ссылка, которая указывает на объект в памяти, хранящий значение\n",
    "литерала. Если справа находится ссылка на объект, в левый операнд\n",
    "записывается ссылка, указывающая на тот же самый объект, на который\n",
    "ссылается правый операнд. Вследствие этого операция присваивания\n",
    "обладает чрезвычайно высокой скоростью выполнения. \n",
    "\n",
    "Когда выполняется присваивание крупной коллекции, такой как\n",
    "длинный список, экономия времени становится более чем очевидной.\n",
    "Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "songs = [\"Because\", \"Boys\", \"Carol\"]\n",
    "beatles = songs\n",
    "beatles, songs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь была создана новая ссылка на объект (`beatles`), и обе ссылки\n",
    "указывают на один и тот же список – никакого копирования данных не\n",
    "производилось. \n",
    "\n",
    "Поскольку списки относятся к категории изменяемых объектов, мы\n",
    "можем вносить в них изменения. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "beatles[2] = \"Cayenne\"\n",
    "beatles, songs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Изменения были внесены с использованием переменной `beatles`, но это\n",
    "всего лишь ссылка, указывающая на тот же самый объект, что и ссылка\n",
    "songs. Поэтому любые изменения, произведенные с использованием одной\n",
    "ссылки, можно наблюдать с использованием другой ссылки. \n",
    "Часто это именно то, что нам требуется, поскольку копирование крупных\n",
    "коллекций может оказаться дорогостоящей операцией. Кроме того, это\n",
    "также означает, что имеется возможность передавать списки или другие\n",
    "изменяемые коллекции в виде аргументов функций, изменять эти коллекции\n",
    "в функциях и пребывать в уверенности, что изменения будут доступны\n",
    "после того, как функция вернет управление вызывающей программе. \n",
    "\n",
    "Однако в некоторых ситуациях действительно бывает необходимо создать\n",
    "отдельную копию коллекции (то есть создать другой изменяемый\n",
    "объект). В случае последовательностей, когда выполняется оператор\n",
    "извлечения среза, например, `songs[:2]`, полученный срез – это всегда \n",
    "независимая копия элементов. Поэтому скопировать последовательность\n",
    "целиком можно следующим способом:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "songs = [\"Because\", \"Boys\", \"Carol\"]\n",
    "beatles = songs[:]\n",
    "beatles[2] = \"Cayenne\"\n",
    "beatles, songs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В случае словарей и множеств копирование можно выполнить с помощью\n",
    "методов `dict.copy()` и `set.copy()`. Кроме того, в модуле copy\n",
    "имеется функция copy.copy(), которая возвращает копию заданного\n",
    "объекта. Другой способ копирования встроенных типов коллекций\n",
    "заключается в использовании имени типа как функции, которой в качестве\n",
    "аргумента передается копируемая коллекция. Например:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "copy_of_dict_d = dict(d)\n",
    "copy_of_list_L = list(L)\n",
    "copy_of_set_s = set(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обратите внимание, что все эти приемы копирования\n",
    "создают *поверхностные копии*, то есть копируются только ссылки на\n",
    "объекты, но не сами объекты. Для неизменяемых типов данных, таких как числа\n",
    "и строки, это равносильно копированию (за исключением более высокой\n",
    "эффективности), но для изменяемых типов данных, таких как вложенные\n",
    "коллекции, это означает, что ссылки в оригинальной коллекции и в копии\n",
    "будут указывать на одни и те же объекты. Эту особенность иллюстрирует\n",
    "следующий пример:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "x = [53, 68, [\"A\", \"B\", \"C\"]]\n",
    "y = x[:] # поверхностное копирование\n",
    "x, y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "y[1] = 40\n",
    "x[2][0] = 'Q'\n",
    "x, y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Когда выполняется поверхностное копирование списка `x`, копируется\n",
    "ссылка на вложенный список `[\"A\", \"B\", \"C\"]`. Это означает, что третий\n",
    "элемент в обоих списках, `x` и `y`, ссылается на один и тот же список,\n",
    "поэтому любые изменения, произведенные во вложенном списке, можно\n",
    "наблюдать с помощью любой из ссылок, `x` или `y`. Если действительно\n",
    "необходимо создать абсолютно независимую копию коллекции с\n",
    "произвольной глубиной вложенности, необходимо выполнить глубокое \n",
    "копирование:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import copy\n",
    "x = [53, 68, [\"A\", \"B\", \"C\"]]\n",
    "y = copy.deepcopy(x)\n",
    "y[1] = 40\n",
    "x[2][0] = 'Q'\n",
    "x, y"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь списки `x` и `y`, а также элементы, которые они содержат,\n",
    "полностью независимы. \n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"iterandcopy\" -->\n",
    "<!-- End: -->\n",
    "# Примеры\n",
    "<div id=\"collections:examples\"></div>\n",
    "\n",
    "Сейчас рассмотрим пару примеров, в которых используется многое из\n",
    "того, о чем рассказывалось в этой и в предыдущей главах.\n",
    "\n",
    "Первая программа имеет отношение к обработке текстовой\n",
    "информации. Вторая программа содержит примерно девяносто строк и\n",
    "предназначена для выполнения математических вычислений. Обе программы\n",
    "используют словари, списки, именованные кортежи и множества и обе\n",
    "широко используют метод `str.format()`, который был описан в предыдущей\n",
    "главе.\n",
    "\n",
    "## Генератор имен пользователей\n",
    "<div id=\"collections:examples:gu\"></div>\n",
    "\n",
    "Представьте, что мы выполняем настройку новой компьютерной системы и\n",
    "нам необходимо сгенерировать имена пользователей для всех служащих\n",
    "нашей компании. У нас имеется простой текстовый файл (в кодировке\n",
    "UTF-8), где каждая строка представляет собой запись из полей,\n",
    "разделенных двоеточиями. Каждая запись соответствует одному сотруднику\n",
    "компании и содержит следующие поля: уникальный идентификатор\n",
    "служащего, имя, отчество (это поле может быть пустым), фамилию и\n",
    "название отдела.\n",
    "\n",
    "Ниже в качестве примера приводятся несколько строк из файла\n",
    "[users.txt](src-collections/users.txt) :"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "        1080:Priscillia:Forbes:Shepard:Cleaning Services\n",
    "        4382:Devan::Fielder:Public Relations\n",
    "        6285:Grey::Collyer:Public Relations\n",
    "        6201:Kierah::Battaile:Catering\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Программа должна читать данные из файла, который указан в командной\n",
    "строке, извлекать отдельные поля из каждой строки (записи) и\n",
    "возвращать подходящие имена пользователей. Каждое имя пользователя\n",
    "должно быть уникальным и должно создаваться на основе имени\n",
    "сотрудника. Результаты должны выводиться на консоль в текстовом виде,\n",
    "отсортированными в алфавитном порядке по фамилии и имени, например:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "        Terminal> ./generate_usernames.py users.txt\n",
    "        Name                               ID   Username \n",
    "        -------------------------------- ------ ---------\n",
    "        Battaile, Kierah................ (6201) kbattail \n",
    "        Blakey, Kelci-Louise............ (0641) kblakey  \n",
    "        Blenkinsop, Keirien............. (4541) kblenkin \n",
    "        Clifford, Rebbekkah............. (6263) rcliffor \n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Каждая запись имеет точно пять полей, и хотя можно обращаться\n",
    "к ним по числовым индексам, тем не менее мы будем использовать\n",
    "осмысленные имена, чтобы сделать программный код более понятным:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ID, FORENAME, MIDDLENAME, SURNAME, DEPARTMENT = range(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В языке Python общепринято использовать только символы верхнего\n",
    "регистра для идентификаторов, которые будут играть роль констант.\n",
    "\n",
    "Нам также необходимо создать тип именованного кортежа, где будут\n",
    "храниться данные о текущем пользователе:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\n",
    "User = collections.namedtuple(\"User\",\n",
    "            \"username forename middlename surname id\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Основная логика программы сосредоточена в функции `main()`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def main():\n",
    "    if len(sys.argv) == 1 or sys.argv[1] in {\"-h\", \"--help\"}:\n",
    "        print(\"usage: {0} file1 [file2 [... fileN]]\".format(\n",
    "              sys.argv[0]))\n",
    "        sys.exit()\n",
    "\n",
    "    usernames = set()\n",
    "    users = {}\n",
    "    for filename in sys.argv[1:]:\n",
    "        with open(filename, encoding=\"utf8\") as file:\n",
    "            for line in file:\n",
    "                line = line.rstrip()\n",
    "                if line:\n",
    "                    user = process_line(line, usernames)\n",
    "                    users[(user.surname.lower(), user.forename.lower(),\n",
    "                            user.id)] = user\n",
    "    print_users(users)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Если пользователь не ввел в командной строке имя какого-нибудь\n",
    "файла или ввел параметр `-h` или `--help`, то программа просто выводит\n",
    "текст сообщения с инструкцией о порядке использования и завершает\n",
    "работу.\n",
    "\n",
    "Из каждой прочитанной строки удаляются любые завершающие пробельные\n",
    "символы (такие как `\\n`), и обработка строки продолжается, только если\n",
    "она не пустая. Это означает, что если в данных содержатся пустые\n",
    "строки, они будут просто проигнорированы.\n",
    "\n",
    "Все сгенерированные имена пользователей сохраняются в множестве\n",
    "`usernames`, чтобы гарантировать отсутствие повторяющихся имен\n",
    "пользователей. Сами данные сохраняются в словаре `users`. Информация о\n",
    "каждом пользователе сохраняется в виде элемента словаря, ключом\n",
    "которого является кортеж, содержащий фамилию сотрудника, его имя и\n",
    "идентификатор, а значением – именованный кортеж типа\n",
    "`User`. Использование кортежа, содержащего фамилию сотрудника, его имя \n",
    "и идентификатор, в качестве ключа обеспечивает возможность вызывать\n",
    "функцию `sorted()` для словаря и получать итерируемый объект, в\n",
    "котором элементы будут упорядочены в требуемом нам порядке (то есть\n",
    "фамилия, имя, идентификатор), избежав необходимости создавать функцию,\n",
    "которую пришлось бы передавать в качестве аргумента `key`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def process_line(line, usernames):\n",
    "    fields = line.split(\":\")\n",
    "    username = generate_username(fields, usernames)\n",
    "    user = User(username, fields[FORENAME], fields[MIDDLENAME],\n",
    "                fields[SURNAME], fields[ID])\n",
    "    return user"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Поскольку все записи имеют очень простой формат, и мы уже удалили\n",
    "из строки завершающие пробельные символы, извлечь отдельные поля можно\n",
    "простой разбивкой строки по двоеточиям. Мы передаем список полей и\n",
    "множество `usernames` в функцию `generate_username()` и затем \n",
    "создаем экземпляр именованного кортежа User, который возвращается\n",
    "вызывающей программе (функции `main()`), которая в свою очередь\n",
    "вставляет информацию о пользователе в словарь `users`, готовый для\n",
    "вывода на экран.\n",
    "\n",
    "Если бы мы не создали соответствующие константы для хранения индексов,\n",
    "мы могли бы использовать числовые индексы, как показано ниже:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "user = User(username, fields[1], fields[2], fields[3], fields[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Хотя такой программный код занимает меньше места, тем не менее\n",
    "это не самое лучшее решение. Во-первых, человеку, который будет\n",
    "сопровождать такой программный код, непонятно, какое поле какую\n",
    "информацию содержит, а, во-вторых, такой программный код чувствителен\n",
    "к изменениям в формате файла с данными – если изменится \n",
    "порядок или число полей в записи, этот программный код окажется\n",
    "неработоспособен. При использовании констант в случае изменения\n",
    "структуры записи нам достаточно будет изменить только значения\n",
    "констант, и программа сохранит свою работоспособность."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def generate_username(fields, usernames):\n",
    "    username = ((fields[FORENAME][0] + fields[MIDDLENAME][:1] +\n",
    "                 fields[SURNAME]).replace(\"-\", \"\").replace(\"'\", \"\"))\n",
    "    username = original_name = username[:8].lower()\n",
    "    count = 1\n",
    "    while username in usernames:\n",
    "        username = \"{0}{1}\".format(original_name, count)\n",
    "        count += 1\n",
    "    usernames.add(username)\n",
    "    return username"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При первой попытке имя пользователя создается путем конкатенации\n",
    "первого символа имени, первого символа отчества и фамилии целиком,\n",
    "после чего из полученной строки удаляются дефисы и апострофы. \n",
    "Выражение, извлекающее первый символ отчества, таит в себе одну\n",
    "хитрость. Если просто использовать обращение `fields[MIDDLENAME][0]`,\n",
    "то в случае отсутствия отчества будет возбуждено исключение\n",
    "`IndexError`. Но при использовании операции извлечения среза мы\n",
    "получаем либо первый символ отчества, либо пустую строку.\n",
    "\n",
    "Затем мы переводим все символы полученного имени пользователя\n",
    "в нижний регистр и ограничиваем его длину восемью символами. Если\n",
    "имя пользователя уже занято (то есть оно уже присутствует в множестве\n",
    "`usernames`), предпринимается попытка добавить в конец имени\n",
    "пользователя символ `\"1\"`, если это имя пользователя тоже занято, тогда\n",
    "предпринимается попытка добавить символ `\"2\"` и т. д., пока не будет\n",
    "получено незанятое имя пользователя. После этого имя пользователя\n",
    "добавляется в множество `usernames` и возвращается вызывающей\n",
    "программе."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def print_users(users):\n",
    "    namewidth = 32\n",
    "    usernamewidth = 9\n",
    "\n",
    "    print(\"{0:<{nw}} {1:^6} {2:{uw}}\".format(\n",
    "          \"Name\", \"ID\", \"Username\", nw=namewidth, uw=usernamewidth))\n",
    "    print(\"{0:-<{nw}} {0:-<6} {0:-<{uw}}\".format(\n",
    "          \"\", nw=namewidth, uw=usernamewidth))\n",
    "\n",
    "    for key in sorted(users):\n",
    "        user = users[key]\n",
    "        initial = \"\"\n",
    "        if user.middlename:\n",
    "            initial = \" \" + user.middlename[0]\n",
    "        name = \"{0.surname}, {0.forename}{1}\".format(user, initial)\n",
    "        print(\"{0:.<{nw}} ({1.id:4}) {1.username:{uw}}\".format(\n",
    "              name, user, nw=namewidth, uw=usernamewidth))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "После обработки всех записей вызывается функция `print_users()`,\n",
    "которой в качестве параметра передается словарь `users`.\n",
    "\n",
    "Первая инструкция `print()` выводит заголовки столбцов. Вторая\n",
    "инструкция `print()` выводит дефисы под каждым из заголовков. В этой\n",
    "второй инструкции метод `str.format()` используется довольно\n",
    "оригинальным образом. Для вывода ему определяется строка `\"\"`, то есть\n",
    "пустая строка; в результате при выводе пустой строки мы получаем\n",
    "строку из дефисов заданной ширины поля вывода.\n",
    "\n",
    "Затем мы используем цикл `for ... in` для вывода информации о каждом\n",
    "пользователе, извлекая ключи из отсортированного словаря. Для удобства\n",
    "мы создаем переменную user, чтобы не вводить каждый раз `users[key]` в\n",
    "оставшейся части функции. В цикле сначала вызывается метод\n",
    "`str.format()`, чтобы записать в переменную name фамилию сотрудника, имя\n",
    "и необязательный первый символ отчества. Обращение к элементам в\n",
    "именованном кортеже user производится по их именам. Собрав строку с\n",
    "именем пользователя, мы выводим информацию о пользователе, ограничивая\n",
    "ширину каждого столбца (имя сотрудника, идентификатор и имя\n",
    "пользователя) желаемыми значениями.\n",
    "\n",
    "Полный программный код программы находится в файле\n",
    "[generate_usernames.py](src-collections/generate_usernames.py)\n",
    "\n",
    "## Статистика\n",
    "<div id=\"collections:examples:stat\"></div>\n",
    "\n",
    "Предположим, что у нас имеется пакет файлов с данными, содержащих\n",
    "числовые результаты некоторой обработки, выполненной нами, и нам\n",
    "необходимо вычислить некоторые основные статистические характеристики,\n",
    "которые дадут нам возможность составить общую картину о полученных\n",
    "данных. В каждом файле находится обычный текст (в кодировке ASCII), с\n",
    "одним или более числами в каждой строке (разделенными пробельными\n",
    "символами).\n",
    "\n",
    "Ниже приводится пример информации, которую нам необходимо получить:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "        Terminal> python statistics.py statistics.dat \n",
    "        count     =    129\n",
    "        mean      =    170.49\n",
    "        median    =     51.00\n",
    "        mode      =     50.00\n",
    "        std. dev. =    269.50\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Здесь видно, что прочитано $129$ чисел, из которых наиболее часто\n",
    "встречается число $50$ со стандартным отклонением по выборке $269.50$.\n",
    "\n",
    "Сами статистические ххарактеристики хранятся в именованном кортеже\n",
    "`Statistics`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "Statistics = collections.namedtuple(\"Statistics\",\n",
    "                                    \"mean mode median std_dev\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Функция `main()` может служить схематическим отображением структуры\n",
    "программы:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def main():\n",
    "    if len(sys.argv) == 1 or sys.argv[1] in {\"-h\", \"--help\"}:\n",
    "        print(\"usage: {0} file1 [file2 [... fileN]]\".format(\n",
    "              sys.argv[0]))\n",
    "        sys.exit()\n",
    "\n",
    "    numbers = []\n",
    "    frequencies = collections.defaultdict(int)\n",
    "    for filename in sys.argv[1:]:\n",
    "        read_data(filename, numbers, frequencies)\n",
    "    if numbers:\n",
    "        statistics = calculate_statistics(numbers, frequencies)\n",
    "        print_results(len(numbers), statistics)\n",
    "    else:\n",
    "        print(\"no numbers found\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Все числа из всех файлов сохраняются в списке `numbers`. Для\n",
    "нахождения модальных («наиболее часто встречающихся») значений нам\n",
    "необходимо знать, сколько раз встречается каждое число, поэтому мы\n",
    "создаем словарь со значениями по умолчанию, используя функцию `int()` \n",
    "в качестве фабричной функции, где будут накапливаться счетчики.\n",
    "\n",
    "Затем выполняется обход списка файлов и производится чтение данных из\n",
    "них. В качестве дополнительных аргументов мы передаем функции\n",
    "`read_data()` список и словарь со значениями по умолчанию, чтобы она\n",
    "могла обновлять их. После чтения всех данных мы исходим из\n",
    "предположения, что некоторые числа были благополучно прочитаны, и\n",
    "вызываем функцию `calculate_statistics()`. Она возвращает именованный\n",
    "кортеж типа `Statistics`, который затем используется для вывода\n",
    "результатов."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def read_data(filename, numbers, frequencies):\n",
    "    with open(filename, encoding=\"ascii\") as file:\n",
    "        for lino, line in enumerate(file, start=1):\n",
    "            for x in line.split():\n",
    "                try:\n",
    "                    number = float(x)\n",
    "                    numbers.append(number)\n",
    "                    frequencies[number] += 1\n",
    "                except ValueError as err:\n",
    "                    print(\"{filename}:{lino}: skipping {x}: {err}\".format(\n",
    "                        **locals()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Каждая строка разбивается по пробельным символам, после чего\n",
    "производится попытка преобразовать каждый элемент в число типа\n",
    "`float`. Если преобразование удалось, следовательно, это либо целое\n",
    "число, либо число с плавающей точкой в десятичной или в\n",
    "экспоненциальной форме. Полученное число добавляется в список numbers\n",
    "и выполняется обновление словаря `frequencies` со значениями по\n",
    "умолчанию. (Если бы здесь использовался обычный словарь `dict`,\n",
    "программный код, выполняющий обновление словаря, мог бы выглядеть так:\n",
    "`frequencies[number] = frequencies.get(number, 0) + 1`.) Если\n",
    "преобразование потерпело неудачу, выводится номер строки (счет строк в\n",
    "текстовых файлах по традиции начинается с 1), текст, который программа\n",
    "пыталась преобразовать в число, и сообщение об ошибке, соответствующее\n",
    "исключению `ValueError`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def calculate_statistics(numbers, frequencies):\n",
    "    mean = sum(numbers) / len(numbers)\n",
    "    mode = calculate_mode(frequencies, 3)\n",
    "    median = calculate_median(numbers)\n",
    "    std_dev = calculate_std_dev(numbers, mean)\n",
    "    return Statistics(mean, mode, median, std_dev)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Эта функция используется для сбора всех статистических характеристик\n",
    "воедино. Поскольку среднее значение вычисляется очень просто, мы\n",
    "делаем это прямо здесь. Вычислением других статистических\n",
    "характеристик занимаются отдельные функции, и в заключение данная\n",
    "функция возвращает экземпляр именованного кортежа `Statistics`,\n",
    "содержащий четыре вычисленные статистические характеристики."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def calculate_mode(frequencies, maximum_modes):\n",
    "    highest_frequency = max(frequencies.values())\n",
    "    mode = [number for number, frequency in frequencies.items()\n",
    "            if frequency == highest_frequency]\n",
    "    if not (1 <= len(mode) <= maximum_modes):\n",
    "        mode = None\n",
    "    else:\n",
    "        mode.sort()\n",
    "    return mode"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В выборке может существовать сразу несколько значений, встречающихся\n",
    "наиболее часто, поэтому, помимо словаря `frequencies`, функции\n",
    "передается максимально допустимое число модальных значений. (Эта\n",
    "функция вызывается из `calculate_statistics()`, и при вызове задается\n",
    "максимальное число модальных значений, равное трем.)\n",
    "\n",
    "Функция `max()` используется для поиска наибольшего значения в словаре\n",
    "`frequencies`. Затем с помощью генератора списков создается список из\n",
    "значений, которые равны наивысшему значению. Поскольку числа могут\n",
    "быть с плавающей точкой, мы сравниваем абсолютное значение разницы\n",
    "(используя функцию `math.fabs()`, поскольку она лучше подходит для\n",
    "случаев сравнения малых величин, близких к порогу точности\n",
    "представления числовых значений в компьютере, чем `abs()`) с\n",
    "наименьшим значением, которое может быть представлено компьютером.\n",
    "\n",
    "Если число модальных значений равно 0 или больше максимального,\n",
    "то в качестве модального значения возвращается `None`; в противном \n",
    "случае возвращается сортированный список модальных значений."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def calculate_median(numbers):\n",
    "    numbers = sorted(numbers)\n",
    "    middle = len(numbers) // 2\n",
    "    median = numbers[middle]\n",
    "    if len(numbers) % 2 == 0:\n",
    "        median = (median + numbers[middle - 1]) / 2\n",
    "    return median"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Медиана* («среднее значение») – это значение, находящееся в середине упорядоченной\n",
    "выборки чисел, за исключением случая, когда в выборке присутствует\n",
    "четное число чисел, – тогда значение медианы определяется как среднее\n",
    "арифметическое значение двух чисел, находящихся в середине. \n",
    "\n",
    "Функция вычисления медианы сначала выполняет сортировку чисел\n",
    "по возрастанию. Затем посредством целочисленного деления определяется\n",
    "позиция середины выборки, откуда извлекается число и сохраняется как\n",
    "значение медианы. Если выборка содержит четное число значений, то\n",
    "значение медианы определяется как среднее арифметическое двух чисел,\n",
    "находящихся в середине."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def calculate_std_dev(numbers, mean):\n",
    "    total = 0\n",
    "    for number in numbers:\n",
    "        total += ((number - mean) ** 2)\n",
    "    variance = total / (len(numbers) - 1)\n",
    "    return math.sqrt(variance)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "*Стандартное отклонение* – это мера дисперсии; оно определяет, как \n",
    "сильно отклоняются значения в выборке от среднего значения. Вычисление\n",
    "стандартного отклонения в этой функции выполняется по формуле"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "$$\n",
    "\\frac{\\sqrt{\\sum(x + \\bar{x})^2}}{n-1},\n",
    "$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "где $x$ — очередное число, $\\bar{x}$ — среднее значение, а $n$ —\n",
    "количество чисел."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def print_results(count, statistics):\n",
    "    real = \"9.2f\"\n",
    "\n",
    "    if statistics.mode is None:\n",
    "        modeline = \"\"\n",
    "    elif len(statistics.mode) == 1:\n",
    "        modeline = \"mode      = {0:{fmt}}\\n\".format(statistics.mode[0], fmt=real)\n",
    "    else:\n",
    "        modeline = (\"mode      = [\" + \", \".join([\"{0:.2f}\".format(m)\n",
    "                    for m in statistics.mode]) + \"]\\n\")\n",
    "\n",
    "    print(\"\"\"\\\n",
    "count     = {0:6}\n",
    "mean      = {mean:{fmt}}\n",
    "median    = {median:{fmt}}\n",
    "{1}\\\n",
    "std. dev. = {std_dev:{fmt}}\"\"\".format(count, modeline, fmt=real, **statistics._asdict()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Большая часть этой функции связана с форматированием списка модальных\n",
    "значений в строку `modeline`. Если модальные значения отсутствуют, то\n",
    "строка `modeline` вообще не выводится. Если модальное значение\n",
    "единственное, список модальных значений содержит единственный элемент\n",
    "(`mode[0]`), который и выводится с той же строкой форматирования, что\n",
    "используется при выводе других статистических значений. Если имеется\n",
    "несколько модальных значений, они выводятся как список, в котором\n",
    "каждое значение форматируется отдельно. Делается это с помощью\n",
    "генератора списков, который позволяет получить список строк с\n",
    "модальными значениями; строки затем объединяются в единую строку, где\n",
    "отделяются друг от друга запятой с пробелом (`\", \"`). Последняя\n",
    "инструкция `print()` в самом конце получилась очень простой благодаря\n",
    "использованию именованного кортежа. Он позволяет обращаться к\n",
    "статистическим значениям в объекте `statistics`, используя не числовые\n",
    "индексы, а их имена, а благодаря строкам в тройных кавычках мы смогли\n",
    "отформатировать выводимый текст наглядным способом.\n",
    "\n",
    "В этой функции имеется одна особенность, о которой следует упомянуть\n",
    "отдельно. Строка с модальными значениями выводится с помощью элемента\n",
    "строки формата `{2}`, за которым следует символ обратного\n",
    "слеша. Символ обратного слеша экранирует символ перевода строки,\n",
    "поэтому если строка с модальными значениями пустая, то пустая строка\n",
    "выводиться не будет. Именно по этой причине мы вынуждены были добавить\n",
    "символ `\\n` в конец строки `modeline`, если она не пустая.\n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"examples\" -->\n",
    "<!-- End: -->\n",
    "# Упражнения\n",
    "<div id=\"collections:exercises\"></div>\n",
    "\n",
    "\n",
    "\n",
    "<!-- --- begin exercise --- -->\n",
    "\n",
    "## Модификация вывода в генераторе имен пользователей\n",
    "<div id=\"collections:exercises:\"></div>\n",
    "\n",
    "Модифицируйте программу `generate_usernames.py` так, чтобы в каждой\n",
    "строке она выводила информацию о двух пользователях, ограничив длину\n",
    "имени 17 символами; через каждые 64 строки программа должна выводить\n",
    "символ перевода формата и в начале каждой страницы она должна выводить\n",
    "заголовки столбцов. Ниже приводится пример того, как должен выглядеть\n",
    "вывод программы:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "        Name                ID   Username   Name                ID   Username \n",
    "        ----------------- ------ ---------  ----------------- ------ ---------\n",
    "        Warne, Brogan A.. (8985) bawarne    Wasling, Elisabet (1853) ewasling \n",
    "        Webber, Gretchen. (6427) gwebber    Weidenmeyer, Deva (6290) dweidenm \n",
    "        Wennerbom, Emma.. (8115) ewennerb   Wennerbom, Keigan (3617) kywenner\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Достаточно большой объем исходных данных вы найдете в файле\n",
    "[users2.txt](src-collections/users2.txt).\n",
    "\n",
    "<!-- --- begin hint in exercise --- -->\n",
    "\n",
    "**Подсказка.**\n",
    "Это достаточно сложно. Вам потребуется сохранить заголовки\n",
    "столбцов в переменных, чтобы потом их можно было использовать\n",
    "по мере необходимости, и изменить спецификаторы формата, чтобы\n",
    "обеспечить вывод более коротких имен. Один из способов обеспечить\n",
    "постраничный вывод заключается в том, чтобы сохранить все выводимые\n",
    "строки в списке, а затем выполнить обход списка, используя оператор\n",
    "извлечения среза с шагом для получения элементов слева и справа и\n",
    "применяя функцию `zip()` для их объединения.\n",
    "\n",
    "<!-- --- end hint in exercise --- -->\n",
    "Имя файла: `generate_usernames_ans.py`.\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- doconce-section-nickname: \"exercises\" -->\n",
    "<!-- End: -->\n",
    "\n",
    "\n",
    "<!-- Local Variables: -->\n",
    "<!-- doconce-chapter-nickname: \"collections\" -->\n",
    "<!-- End: -->\n",
    "<!-- --- end exercise --- -->"
   ]
  }
 ],
 "metadata": {},
 "nbformat": 4,
 "nbformat_minor": 2
}
