{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "35e7641e",
   "metadata": {},
   "source": [
    "# Словари #"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aa115439",
   "metadata": {},
   "source": [
    "На этой лекции мы поговорим про словари.\n",
    "\n",
    "Словари являются важнейшей структурой данных в Python'е и они позволяют хранить данные в формате ключ-значение.\n",
    "\n",
    "Чтобы определить словарь, нужно использовать литерал фигурные скобки или просто вызвать `dict`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "647047ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "empty_dict = {}\n",
    "empty_dict = dict()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b793bf48",
   "metadata": {},
   "source": [
    "Если мы хотим определить словарь, в котором уже есть какие-то данные, мы просто пишем наши ключ-значение через двоеточие. Например, словарь collections_map содержит отображение и строк, и списки в листы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "ab12602e",
   "metadata": {},
   "outputs": [],
   "source": [
    "collections_map = {\n",
    "    \"mutable\": [\"list\", \"set\", \"dict\"],\n",
    "    \"immutable\": [\"tuple\", \"frozenset\"]\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8f0a511f",
   "metadata": {},
   "source": [
    "Чем хороши словари? Они позволяют получить значение по ключу за константное время, очень быстро. Это достигается с помощью алгоритма хеширования. Например, мы можем получить `immutable` лист."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ce41b9bf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['tuple', 'frozenset']\n"
     ]
    }
   ],
   "source": [
    "print(collections_map[\"immutable\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "46286967",
   "metadata": {},
   "source": [
    "Если мы попытаемся получить доступ по ключу, которого не существует, у нас Python выдаст нам ошибку KeyError, потому что такого ключа очевидно нет."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "bfbe0151",
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'irresistible'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyError\u001b[0m                                  Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-4-c3adb9d5d5cd>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mcollections_map\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"irresistible\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mKeyError\u001b[0m: 'irresistible'"
     ]
    }
   ],
   "source": [
    "print(collections_map[\"irresistible\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f9f60bb7",
   "metadata": {},
   "source": [
    "Однако, часто бывает полезно попробовать взять значение по ключу и в случае неудачи вернуть какое-то дефолтное значение. Для этого есть встроенный метод `get` у словаря, который делает именно это. Например, мы попытались взять ключи `irresistible` и вернули `not found`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "975284c8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "not found\n"
     ]
    }
   ],
   "source": [
    "print(collections_map.get(\"irresistible\", \"not found\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ead7601e",
   "metadata": {},
   "source": [
    "Чтобы проверить, содержится ли ключ в словаре, мы используем уже знакомые вам операторы."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "faa26fae",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "\"mutable\" in collections_map"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b3a915d7",
   "metadata": {},
   "source": [
    "Так как словарь является изменяемой структурой данных, мы можем добавлять и удалять элементы из него. Например, мы можем определить словарь `beatles_map`, который содержит знаменитых музыкантов и их инструменты, и добавить в него Ринго с ударными, просто используя доступ по ключу."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "0f6226ae",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Paul': 'Bass', 'John': 'Guitar', 'George': 'Guitar'}\n"
     ]
    }
   ],
   "source": [
    "beatles_map = {\n",
    "    \"Paul\": \"Bass\",\n",
    "    \"John\": \"Guitar\",\n",
    "    \"George\": \"Guitar\",\n",
    "}\n",
    "\n",
    "print(beatles_map)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0a54daf3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Paul': 'Bass', 'John': 'Guitar', 'George': 'Guitar', 'Ringo': 'Drums'}\n"
     ]
    }
   ],
   "source": [
    "beatles_map[\"Ringo\"] = \"Drums\"\n",
    "\n",
    "print(beatles_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d010fb3",
   "metadata": {},
   "source": [
    "Чтобы удалить ключ и значение из словаря, можно использовать уже знакомый вам оператор del, который удаляет в данном случае Джона из нашего словаря."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "230b9d49",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Paul': 'Bass', 'George': 'Guitar', 'Ringo': 'Drums'}\n"
     ]
    }
   ],
   "source": [
    "del beatles_map[\"John\"]\n",
    "\n",
    "print(beatles_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d2a6238",
   "metadata": {},
   "source": [
    "Также, чтобы добавить какой-то ключ-значение в словарь, можно использовать встроенный метод `update`, который принимает словарь и апдейтит существующий словарь."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d2ca1637",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Paul': 'Bass', 'George': 'Guitar', 'Ringo': 'Drums', 'John': 'Guitar'}\n"
     ]
    }
   ],
   "source": [
    "beatles_map.update({\n",
    "    \"John\": \"Guitar\"\n",
    "})\n",
    "\n",
    "print(beatles_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ffbbe1b1",
   "metadata": {},
   "source": [
    "Чтобы удалить ключ-значение из словаря и вернуть значение, можно использовать метод pop, и в данном случае мы удаляем Ринго, и нам возвращаются его ударные."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "c68f6183",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Drums\n",
      "{'Paul': 'Bass', 'George': 'Guitar', 'John': 'Guitar'}\n"
     ]
    }
   ],
   "source": [
    "print(beatles_map.pop(\"Ringo\"))\n",
    "\n",
    "print(beatles_map)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "64580aff",
   "metadata": {},
   "source": [
    "Часто бывает необходимо не только попробовать проверить, существует ли ключ в словаре, но и в случае неудачи добавить эту новую пару ключ-значение. Для этого есть метод `setdefault`, и давайте посмотрим, как он работает. Мы объявляем `unknown_dict`, который абсолютно пуст, и используя метод `setdefault` пытаемся взять какой-то ключ `key` и в случае неудачи добавим туда `default`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "403cdacb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "default\n"
     ]
    }
   ],
   "source": [
    "unknown_dict = {}\n",
    "\n",
    "print(unknown_dict.setdefault(\"key\", \"default\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "f8dbee49",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'key': 'default'}"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "unknown_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8fc6bb60",
   "metadata": {},
   "source": [
    "Происходит именно это, у нас в нашем пустом словаре получилось отображение `key`, `default` и `default` вернулся к нам в результате вызова метода `setdefault`.\n",
    "\n",
    "Если мы попытаемся вызвать `setdefault` и в качестве дефолтного значения передаём `new_default`, у нас вернётся значение, которое уже лежит в словаре, значение `default`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "9b0fa584",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "default\n"
     ]
    }
   ],
   "source": [
    "print(unknown_dict.setdefault(\"key\", \"new_default\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c5cf8c7",
   "metadata": {},
   "source": [
    "Словари, как и все коллекции, поддерживают протокол итерации, поэтому мы можем итерироваться по словарю, например, с помощью метода `for`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "5acefef3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'mutable': ['list', 'set', 'dict'], 'immutable': ['tuple', 'frozenset']}\n"
     ]
    }
   ],
   "source": [
    "print(collections_map)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "b4c7bb66",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mutable\n",
      "immutable\n"
     ]
    }
   ],
   "source": [
    "for key in collections_map:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74c5246c",
   "metadata": {},
   "source": [
    "Обратите внимание, мы итерируемся по ключам словаря. Можем вывести собственно ключи, которые содержатся в нашем словаре. \n",
    "\n",
    "Если нам нужно итерироваться не по ключам, как по умолчанию, а по ключам и значениям сразу можно использовать метод словаря `items`, который возвращает ключи и значения. Например, можно выводить в нашем `collections_map` как ключи, так и значения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "342baa0c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mutable: ['list', 'set', 'dict']\n",
      "immutable: ['tuple', 'frozenset']\n"
     ]
    }
   ],
   "source": [
    "for key, value in collections_map.items():\n",
    "    print(f\"{key}: {value}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "87b8d55f",
   "metadata": {},
   "source": [
    "Если нужно итерироваться по значениям, используйте логично метод `values`, который возвращает именно значения. Также существует симметричный метод `keys`, который возвращает оператор ключей."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "56344c1b",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['list', 'set', 'dict']\n",
      "['tuple', 'frozenset']\n"
     ]
    }
   ],
   "source": [
    "for value in collections_map.values():\n",
    "    print(value)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "3299f648",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "mutable\n",
      "immutable\n"
     ]
    }
   ],
   "source": [
    "for key in collections_map.keys():\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d96b364e",
   "metadata": {},
   "source": [
    "Важная особенность словарей в Python'е - они содержат ключи и значения в неупорядоченном виде, то есть вы не можете гарантировать, что ключи, например, отсортированы, или хранятся в том порядке, в каком вы их туда добавили. Однако, в Python'е существует `OrderedDict`, это тип, который содержится в модуле `collections`, который гарантирует вам, что ключи содержатся именно в том порядке, в каком вы их добавили в словарь. Например, мы можем вывести пару, число и строка, именно в том порядке, в каком мы их туда добавили."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "b7ce5f64",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n",
      "6\n",
      "7\n",
      "8\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "from collections import OrderedDict\n",
    "\n",
    "ordered = OrderedDict()\n",
    "\n",
    "for number in range(10):\n",
    "    ordered[number] = str(number)\n",
    "    \n",
    "for key in ordered:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e4e659e",
   "metadata": {},
   "source": [
    "По секрету с версии Python 3.8 порядок гарантирован."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "b885b0f1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0\n",
      "1\n",
      "2\n",
      "3\n",
      "4\n",
      "5\n",
      "6\n",
      "7\n",
      "8\n",
      "9\n"
     ]
    }
   ],
   "source": [
    "ordered = dict()\n",
    "\n",
    "for number in range(10):\n",
    "    ordered[number] = str(number)\n",
    "    \n",
    "for key in ordered:\n",
    "    print(key)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb29decb",
   "metadata": {},
   "source": [
    "Итак, на этой лекции мы обсудили важнейшую структуру данных - словари, которые содержат в неупорядоченном виде набор пар ключ-значение. Вы можете изменять эту структуру данных, потому что она является изменяемой. Словари позволяют вам получать значение по ключу очень быстро за константное время и точно также очень быстро проверять вхождение какого-то ключа в словарь. Это достигается с помощью алгоритмов хеширования. Давайте попробуем решить задачку на словари."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
