{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<p>\n",
    "<font size='5' face='Georgia, Arial'>IIC2233 Apunte Programación Avanzada</font><br>\n",
    "<font size='1'>&copy; 2015 Karim Pichara - Christian Pieringer. Todos los derechos reservados.</font>\n",
    "<br>\n",
    "<font size='1'>Modificado desde 2018-1 al 2025-2 por Equipo Docente IIC2233</font>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tabla de contenidos\n",
    "\n",
    "1. [Diccionarios](#Diccionarios)\n",
    "    1. [Diccionarios en Python](#Diccionarios-en-Python)\n",
    "    2. [Eficiencia de diccionarios](#Eficiencia-de-diccionarios)\n",
    "        1. [Llaves permitidas en diccionarios](#Llaves-permitidas-en-diccionarios)\n",
    "        2. [Valores permitidos en diccionarios](#Valores-permitidos-en-diccionarios)\n",
    "    3. [Métodos útiles](#Métodos-útiles)\n",
    "    4. [Diccionarios por comprensión](#Diccionarios-por-comprensión)\n",
    "    5. [Aplicaciones](#Aplicaciones)\n",
    "2. [Defaultdicts](#Defaultdicts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "# Diccionarios\n",
    "\n",
    "Imaginemos que estamos escribiendo un programa para manejar dinero, y necesitamos saber qué moneda usa el usuario dado el país en que se encuentra. Una posible solución es escribir una función que reciba el nombre del país y que en una trama de `if - elif - else` retorne la moneda correspondiente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_moneda(pais):\n",
    "    if pais == \"Chile\":\n",
    "        return \"Peso\"\n",
    "    elif pais == \"Perú\":\n",
    "        return \"Soles\"\n",
    "    elif pais == \"España\" or pais == \"Holanda\":\n",
    "        return \"Euro\"\n",
    "    elif pais == \"Brasil\":\n",
    "        return \"Real\""
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No obstante, esta solución es larga, tediosa de escribir, y muy difícil de mantener. Tampoco nos permite extender o actualizar la información en tiempo de ejecución, con datos que podemos leer desde un archivo o desde un servicio web. Si nos fijamos, lo único que estamos haciendo es asociar un valor que conocemos de antemano con otro valor. Esto es un comportamiento muy común de un programa, y es lo que hacen precisamente los **diccionarios**.\n",
    "\n",
    "Un **diccionario** es una estructura de datos no secuencial y **mutable** que permite asociar pares de elementos mediante la relación **llave-valor**. Al diccionario se le consulta por una **llave** y retorna su **valor** asociado. La **llave** y el **valor** (*key-value*) pueden tener distinto tipo en un diccionario. A este tipo de estructura también se le conoce como estructura de \"mapeo\" (*mapping*), porque asocian o *mapean* un valor a otro. Estructuras similares existen en muchos otros lenguajes de programación con otros nombres como \"tablas de hash\" o \"*hash maps*\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el ejemplo de las monedas, los países serían las llaves por las que consultamos, y los valores serían las distintas monedas.\n",
    "\n",
    "![](img/hash-table.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Diccionarios en Python\n",
    "\n",
    "\n",
    "En Python, los [*diccionarios*](https://docs.python.org/3/library/stdtypes.html#dict) están implementados por la clase `dict`. La notación para describir un diccionario es mediante llaves (`{}`), y cada par llave-valor se asocia con `:` como muestra el ejemplo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "monedas = {\n",
    "    \"Chile\": \"Peso\",\n",
    "    \"Perú\": \"Soles\",\n",
    "    \"España\": \"Euro\", \n",
    "    \"Holanda\": \"Euro\",\n",
    "    \"Brasil\": \"Real\"\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para acceder al valor asociado a una llave se usan los corchetes (`[]`), mediante la instrucción:\n",
    "\n",
    "`diccionario[nombre_llave]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Peso\n"
     ]
    }
   ],
   "source": [
    "print(monedas[\"Chile\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si se intenta consultar por una llave que no existe, obtenemos un error de tipo `KeyError` indicando que no existe una llave asociada a ese valor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "'Argentina'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mKeyError\u001b[39m                                  Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[4]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[38;5;28mprint\u001b[39m(\u001b[43mmonedas\u001b[49m\u001b[43m[\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43mArgentina\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m]\u001b[49m)\n",
      "\u001b[31mKeyError\u001b[39m: 'Argentina'"
     ]
    }
   ],
   "source": [
    "print(monedas[\"Argentina\"])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Otra manera de acceder al valor asociado a una llave consiste en utilizar el método [`get`](https://docs.python.org/3/library/stdtypes.html#dict.get) que posee la clase diccionario. Este método requiere dos parámetros: la llave buscada y un valor en caso de que la llave no exista. En caso de que no se ingrese el segundo parámetro, el valor retornado sera `None`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Peso\n",
      "Soles\n",
      "No tiene moneda\n",
      "False\n",
      "None\n"
     ]
    }
   ],
   "source": [
    "print(monedas.get('Chile', 'No tiene moneda'))\n",
    "print(monedas.get('Perú', 0))\n",
    "print(monedas.get('Argentina', 'No tiene moneda'))\n",
    "print(monedas.get('Colombia', False))\n",
    "print(monedas.get('Australia'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Como los diccionarios son **mutables**, si se asigna un valor a una llave existen dos comportamientos posibles. Si la llave no existe, ésta se crea y se le asigna un valor."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Chile': 'Peso', 'Perú': 'Soles', 'España': 'Euro', 'Holanda': 'Euro', 'Brasil': 'Real', 'Vaticano': 'Lira'}\n"
     ]
    }
   ],
   "source": [
    "monedas[\"Vaticano\"] = \"Lira\"\n",
    "print(monedas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si la llave ya existe, se actualiza con el nuevo valor:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'Chile': 'Peso', 'Perú': 'Soles', 'España': 'Euro', 'Holanda': 'Euro', 'Brasil': 'Real', 'Vaticano': 'Euro'}\n"
     ]
    }
   ],
   "source": [
    "monedas[\"Vaticano\"] = \"Euro\"\n",
    "print(monedas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A diferencia de otros lenguajes como C# o Java, en Python no es necesario que las llaves sean todas del mismo tipo. Tampoco es necesario que los valores sean todos del mismo tipo."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'Chile': 'Peso',\n",
       " 'Perú': 'Soles',\n",
       " 'España': 'Euro',\n",
       " 'Holanda': 'Euro',\n",
       " 'Brasil': 'Real',\n",
       " 'Vaticano': 'Euro',\n",
       " 3.14: ('Peso', 'Dolar')}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "monedas[3.14] = (\"Peso\", \"Dolar\")\n",
    "monedas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se puede eliminar ítems del diccionario utilizando la sentencia `del` como:\n",
    "\n",
    "`del diccionario[nombre_llave]`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'Chile': 'Peso',\n",
       " 'Perú': 'Soles',\n",
       " 'España': 'Euro',\n",
       " 'Holanda': 'Euro',\n",
       " 'Brasil': 'Real',\n",
       " 'Vaticano': 'Euro'}"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "del monedas[3.14]\n",
    "monedas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso si, es necesario que la llave exista, de lo contrario, obtenemos un error de tipo `KeyError` indicando que no existe la llave que queremos eliminar."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "ename": "KeyError",
     "evalue": "3.14",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mKeyError\u001b[39m                                  Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[10]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m \u001b[38;5;28;01mdel\u001b[39;00m \u001b[43mmonedas\u001b[49m\u001b[43m[\u001b[49m\u001b[32;43m3.14\u001b[39;49m\u001b[43m]\u001b[49m\n\u001b[32m      2\u001b[39m monedas\n",
      "\u001b[31mKeyError\u001b[39m: 3.14"
     ]
    }
   ],
   "source": [
    "del monedas[3.14]\n",
    "monedas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se puede comprobar la existencia de una llave en el diccionario utilizando la sentencia `in`. El comportamiento por defecto al utilizar sentencias sobre el diccionario es operar sobre los valores de las llaves. En el caso de `in`, devuelve `True` si la llave requerida existe dentro de las llaves en el diccionario."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n",
      "False\n",
      "False\n",
      "False\n"
     ]
    }
   ],
   "source": [
    "print('Chile' in monedas)\n",
    "print('Argentina' in monedas)\n",
    "print('Peso' in monedas)\n",
    "print('Euro' in monedas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Eficiencia de diccionarios\n",
    "\n",
    "Dada una llave `llave`, **acceder a su valor asociado `dict[llave]`** es una operación **muy eficiente** que toma tiempo constante, es decir, **no depende de la cantidad de elementos que tenga el diccionario**. En contraste, esta estructura no está diseñada para buscar (en forma eficiente) una llave a partir de su valor, por lo que sería necesario recorrer todas las llaves creadas para encontrarla, como uno haría con una lista convencional, lo cual implica que ese tiempo de búsqueda es **proporcional al tamaño del diccionario**.\n",
    "\n",
    "Esta eficiencia de acceso mediante llaves viene con un costo, no todo objeto puede ser usado como llave de diccionario en Python:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Llaves permitidas en diccionarios\n",
    "\n",
    "Los diccionarios están implementados a partir una estructura llamada *tabla de hash*. En esta estructura, existe una función matemática que se le aplica a la llave para saber en qué lugar debe guardar un determinado valor. Esa función se llama *función de hash*.\n",
    "\n",
    "No todo elemento puede ser usado como llave en un diccionario. El primer requisito es que las llaves **deben ser únicas** (no pueden repetirse), de lo contrario podría pasar que dos valores queden asociados a la misma llave. El segundo requisito es que la llave debe ser [*hasheable*](https://docs.python.org/3/glossary.html#term-hashable), es decir, que se le pueda entregar a la *función de hash*.\n",
    "\n",
    "Un objeto es _hasheable_ si:\n",
    "1. Implementa el método [`__hash__`](https://docs.python.org/3/reference/datamodel.html#object.__hash__). Este método retorna un entero, y sirve de entrada para la función de *hash* de la *tabla de hash*.\n",
    "2. El valor que retorna `__hash__` **no cambia** durante el ciclo de vida del objeto.\n",
    "3. Implementa el método [`__eq__`](https://docs.python.org/3/reference/datamodel.html#object.__eq__). Este método compara dos objetos y retorna `True` si éstos deben ser considerados iguales.\n",
    "4. Si dos objetos son iguales según el método `__eq__`, entonces el valor que retorna `__hash__` debe ser el mismo. No es necesario que esto se cumpla al revés. \n",
    "\n",
    "En particular, todos los *built-ins* de Python que son **inmutables** son *hasheables*. Esto significa que tipos como `int`, `str` o `tuple` se pueden usar como llave en un diccionario. Por el contrario, todos los *built-ins* mutables no son *hasheables*, por lo que tipos como `list` no pueden ser ocupados como llave.\n",
    "\n",
    "En el ejemplo de abajo, inicializamos dos *strings* iguales. Primero, vemos que su valor de *hash* es igual. También vemos que son iguales. Recordemos que el hecho de que dos objetos sean iguales **no significa** que sean el mismo, esto lo podemos ver usando la sentencia `is` que retorna `True` sólo si dos variables apuntan al mismo objeto."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hash de hello_1: -3187774577748240740\n",
      "Hash de hello_2: -3187774577748240740\n",
      "¿Son iguales? True\n",
      "¿Son el mismo objeto? False\n"
     ]
    }
   ],
   "source": [
    "hello_1 = \"Hello world\"\n",
    "hello_2 = \"Hello world\"\n",
    "\n",
    "print(f\"Hash de hello_1: {hash(hello_1)}\")\n",
    "print(f\"Hash de hello_2: {hash(hello_2)}\")\n",
    "print(f\"¿Son iguales? {hello_1 == hello_2}\")\n",
    "print(f\"¿Son el mismo objeto? {hello_1 is hello_2}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\\* Notar que una tupla es *hasheable* sólo si todos los valores que contiene son *hasheables*. Por ejemplo, una tupla que contiene una lista en su interior no es *hasheable*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "unhashable type: 'list'",
     "output_type": "error",
     "traceback": [
      "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
      "\u001b[31mTypeError\u001b[39m                                 Traceback (most recent call last)",
      "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[13]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m      1\u001b[39m tupla = (\u001b[32m0\u001b[39m, \u001b[32m1\u001b[39m, [\u001b[32m2\u001b[39m, \u001b[32m3\u001b[39m])\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[38;5;28;43mhash\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mtupla\u001b[49m\u001b[43m)\u001b[49m\n",
      "\u001b[31mTypeError\u001b[39m: unhashable type: 'list'"
     ]
    }
   ],
   "source": [
    "tupla = (0, 1, [2, 3])\n",
    "hash(tupla)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Las instancias de clases creadas por el usuario son *hasheables* por defecto, puesto que el método `__hash__` retorna un valor único y fijo para cada instancia. Además, el método `__eq__` devuelve `True` sólo si corresponde exactamente al mismo objeto, por lo que no necesitamos que el valor de `__hash__` sea igual al de ningún otro objeto.\n",
    "\n",
    "Podemos ver que los objetos de clases personalizadas son *hasheables* incluso si almacenan valores no *hasheables* en alguno de sus atributos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hash de dummy_0: 127707834667\n",
      "Hash de dummy_1: 127707834655\n",
      "Hash de dummy_2: 127707835192\n"
     ]
    }
   ],
   "source": [
    "class Dummy:\n",
    "\n",
    "    def __init__(self, x, y):\n",
    "        self.x = x\n",
    "        self.y = y\n",
    "\n",
    "\n",
    "dummy_0 = Dummy(4, 5)\n",
    "dummy_1 = Dummy(0, [1, 2])\n",
    "dummy_2 = Dummy(\"Hello\", [\"World\", \"Hold on\"])\n",
    "\n",
    "print(f\"Hash de dummy_0: {hash(dummy_0)}\")\n",
    "print(f\"Hash de dummy_1: {hash(dummy_1)}\")\n",
    "print(f\"Hash de dummy_2: {hash(dummy_2)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto ocurre porque, en instancias, el valor del *hash* **no depende** de los valores de los atributos de la instancia."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hash de dummy_1: 127707834655\n"
     ]
    }
   ],
   "source": [
    "dummy_1.x = 33\n",
    "print(f\"Hash de dummy_1: {hash(dummy_1)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por último, los valores del *hash* para cada objeto son únicos (aunque los valores de los atributos sean iguales)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Hash de dummy_3: 127707834379\n"
     ]
    }
   ],
   "source": [
    "dummy_3 = Dummy(\"Hello\", [\"World\", \"Hold on\"])\n",
    "print(f\"Hash de dummy_3: {hash(dummy_3)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Valores permitidos en diccionarios\n",
    "\n",
    "Para el caso de los valores que puede contener un diccionario, no hay restricciones. Los valores pueden ser mutables o inmutables, *hasheables* o no, e incluso pueden incluir otros diccionarios. Como en Python todo es un objeto, se pueden incluso guardar funciones y clases."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Métodos útiles\n",
    "\n",
    "Tres métodos útiles que existen en los diccionarios son: \n",
    "\n",
    "1. `keys()`: permite obtener todas las llaves del diccionario.\n",
    "2. `values()`: permite obtener todos los valores del diccionario.\n",
    "3. `items()`: permite obtener cada **par** 'llave-valor\" que tiene el diccionario. Cada par es una tupla de la forma `(llave, valor)`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "dict_keys(['Chile', 'Perú', 'España', 'Holanda', 'Brasil', 'Vaticano'])\n",
      "dict_values(['Peso', 'Soles', 'Euro', 'Euro', 'Real', 'Euro'])\n",
      "dict_items([('Chile', 'Peso'), ('Perú', 'Soles'), ('España', 'Euro'), ('Holanda', 'Euro'), ('Brasil', 'Real'), ('Vaticano', 'Euro')])\n"
     ]
    }
   ],
   "source": [
    "print(monedas.keys()) # todas las llaves\n",
    "print(monedas.values()) # todos los valores\n",
    "print(monedas.items()) # tuplas de pares llave-valor"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Estos métodos son prácticos y útiles durante la iteración sobre diccionarios."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Las llaves en el diccionario son las siguientes:\n",
      "Chile\n",
      "Perú\n",
      "España\n",
      "Holanda\n",
      "Brasil\n",
      "Vaticano\n"
     ]
    }
   ],
   "source": [
    "print('Las llaves en el diccionario son las siguientes:')\n",
    "\n",
    "for m in monedas.keys():\n",
    "    print(f'{m}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Las llaves en el diccionario son las siguientes:\n",
      "Chile\n",
      "Perú\n",
      "España\n",
      "Holanda\n",
      "Brasil\n",
      "Vaticano\n"
     ]
    }
   ],
   "source": [
    "## También se puede hacer sin usar .keys().\n",
    "## Si no se especifica nada, se recorren las llaves.\n",
    "\n",
    "print('Las llaves en el diccionario son las siguientes:')\n",
    "\n",
    "for m in monedas:\n",
    "    print(f'{m}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Los valores en el diccionario:\n",
      "Peso\n",
      "Soles\n",
      "Euro\n",
      "Euro\n",
      "Real\n",
      "Euro\n"
     ]
    }
   ],
   "source": [
    "print('Los valores en el diccionario:')\n",
    "\n",
    "for v in monedas.values():\n",
    "    print(f'{v}')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Los pares en el diccionario:\n",
      "La moneda de Chile es Peso\n",
      "La moneda de Perú es Soles\n",
      "La moneda de España es Euro\n",
      "La moneda de Holanda es Euro\n",
      "La moneda de Brasil es Real\n",
      "La moneda de Vaticano es Euro\n"
     ]
    }
   ],
   "source": [
    "print('Los pares en el diccionario:')\n",
    "\n",
    "for k, v in monedas.items():\n",
    "    print(f'La moneda de {k} es {v}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Diccionarios por comprensión\n",
    "\n",
    "De forma similar a otras estructuras, en Python es posible definir diccionarios por comprensión. Esto permite escribir y crear diccionarios a partir de un concepto estructurado. Permite también el uso de condiciones dentro de éste."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9, 'J': 10, 'K': 11, 'L': 12, 'M': 13, 'N': 14, 'O': 15, 'P': 16, 'Q': 17, 'R': 18, 'S': 19, 'T': 20, 'U': 21, 'V': 22, 'W': 23, 'X': 24, 'Y': 25, 'Z': 26}\n",
      "{'A': 1, 'E': 5, 'I': 9, 'O': 15, 'U': 21}\n"
     ]
    }
   ],
   "source": [
    "from string import ascii_lowercase as letras\n",
    "\n",
    "\n",
    "# Diccionario por comprensión\n",
    "numero_por_letra = {letras[i].upper(): i + 1 for i in range(len(letras))}\n",
    "print(numero_por_letra)\n",
    "\n",
    "numero_por_vocales = {\n",
    "    letras[i].upper(): i + 1 for i in range(len(letras))\n",
    "    if letras[i].upper() in \"AEIOU\"\n",
    "}\n",
    "print(numero_por_vocales)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Aplicaciones\n",
    "\n",
    "Una aplicación usual de diccionarios es realizar conteos de frecuencia. En el siguiente ejemplo, el código cuenta cuántas veces aparece cada vocal en un *string*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'u': 1, 'e': 2, 'a': 3, 'i': 6, 'o': 3}\n"
     ]
    }
   ],
   "source": [
    "msg = 'supercalifragilisticoespialidoso'\n",
    "\n",
    "# Crea un diccionario vacío para contabilizar las letras\n",
    "vocales = dict()\n",
    "\n",
    "for letra in msg:\n",
    "    # Revisa si la letra es una vocal\n",
    "    if letra not in 'aeiou':\n",
    "        continue\n",
    "\n",
    "    # Revisa si letra existe en el diccionario, si no la crea en 0\n",
    "    if letra not in vocales:\n",
    "        vocales[letra] = 0\n",
    "\n",
    "    vocales[letra] += 1  # si ya existe, agrega una cuenta mas\n",
    "\n",
    "print(vocales)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Defaultdicts\n",
    "\n",
    "Los [`defaultdicts`](https://docs.python.org/3/library/collections.html#collections.defaultdict) son diccionarios que nos permiten asignar un valor por defecto a cada *key* con la que se llama el diccionario. Esto nos ahorra el problema de tener que escribir código preocupándonos de los casos en que el valor que se intenta obtener el diccionario no existe. \n",
    "\n",
    "Los `defaultdicts` reciben una función (o un *callable*) que debe devolver el valor que se asignará por defecto. Esta función *no debe recibir parámetros*, puede realizar cualquier acción, y puede devolver cualquier objeto, el cual será asignado como valor para el respectivo *key* en el diccionario.\n",
    " \n",
    "Por ejemplo, podemos rehacer el ejemplo de contar las vocales de un *string*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "defaultdict(<class 'int'>, {'u': 1, 'e': 2, 'a': 3, 'i': 6, 'o': 3})\n"
     ]
    }
   ],
   "source": [
    "from collections import defaultdict\n",
    "\n",
    "\n",
    "msg = 'supercalifragilisticoespialidoso'\n",
    "\n",
    "# Crea un defaultdict vacío.\n",
    "vocales = defaultdict(int)\n",
    "\n",
    "# Pasamos int como callable. El callable se va a llamar sin parámetros\n",
    "# cada vez que se consulte por una key que no existe.\n",
    "# En este caso, int() devolverá el valor por defecto de este tipo (0)\n",
    "\n",
    "for letra in msg:\n",
    "    if letra not in 'aeiou':  # Revisa si la letra es una vocal\n",
    "        continue\n",
    "\n",
    "    vocales[letra] += 1  # si ya existe, agrega una cuenta mas\n",
    "\n",
    "print(vocales)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "También podemos entregar una función que nosotros definamos. En este ejemplo, cada vez que una *key* no exista le asociará un número *random*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "defaultdict(<function funcion_default at 0x000001DBC006F920>, {})\n",
      "defaultdict(<function funcion_default at 0x000001DBC006F920>, {'A': 0.5424979051531029})\n",
      "defaultdict(<function funcion_default at 0x000001DBC006F920>, {'A': 0.5424979051531029, 'B': 0.12288696691384415})\n"
     ]
    }
   ],
   "source": [
    "from random import random\n",
    "\n",
    "\n",
    "def funcion_default():\n",
    "    return random()\n",
    "\n",
    "\n",
    "diccionario = defaultdict(funcion_default)\n",
    "\n",
    "print(diccionario)\n",
    "diccionario['A']\n",
    "print(diccionario)\n",
    "diccionario['B']\n",
    "print(diccionario)"
   ]
  }
 ],
 "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.12.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
