{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Listas, tuplas y diccionarios\n",
    "\n",
    "Guardar muchas cosas juntas, las rebanadas, y la copia que no se copió, que es el error más traicionero del principio.\n",
    "\n",
    "Cuaderno de práctica del capítulo 4 de **Python desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/python-desde-cero/listas-y-diccionarios/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Esta celda baja el ayudante que corrige tus ejercicios. Después, en cada\n",
    "ejercicio que se pueda corregir solo, vas a ver `%%revisa` arriba de la celda:\n",
    "escribe tu respuesta debajo, ejecuta, y te digo si te salió 💛"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"V2ViIFRlbGVmb25vIDU=\",\n",
    "    2: \"WydUaWVuZGEnLCAnV2hhdHNBcHAnLCAnTWFya2V0cGxhY2UnXQ==\",\n",
    "    3: \"WzI0ODAuODQsIDEyMDAuMCwgNTI0LjldCjQ4MC4zNw==\",\n",
    "    4: \"WydBYmFycm90ZXMnLCAnQmViaWRhcycsICdTbmFja3MnLCAnTGltcGllemEnXQpbJ0FiYXJyb3RlcycsICdCZWJpZGFzJywgJ1NuYWNrcyddClRydWUgRmFsc2U=\",\n",
    "    5: \"Uy81MjQuOTA=\",\n",
    "    6: \"eydXZWInOiAzLCAnVGllbmRhJzogMiwgJ1doYXRzQXBwJzogMX0=\",\n",
    "    7: \"Q3VzY28gMjQ4MC44NA==\",\n",
    "    8: \"WydDdXNjbycsICdQaXVyYSdd\",\n",
    "    9: \"WydDdXNjbycsICdMaW1hJywgJ1BpdXJhJ10KWydMaW1hJywgJ0N1c2NvJywgJ1BpdXJhJ10=\",\n",
    "    10: \"VHlwZUVycm9yOiAndHVwbGUnIG9iamVjdCBkb2VzIG5vdCBzdXBwb3J0IGl0ZW0gYXNzaWdubWVudA==\",\n",
    "    11: \"Y2xpZW50ZXMgZGlzdGludG9zIGVuIGVsIHRyaW1lc3RyZTogNwpjb21wcmFyb24gbG9zIHRyZXMgbWVzZXMgICAgICAgICAgOiBbJ0MwMDQ1J10KcGVyZGlkb3MgZGUgZW5lcm8gYSBmZWJyZXJvICAgICAgIDogWydDMDExMScsICdDMDI2MCddCm51ZXZvcyBlbiBtYXJ6byAgICAgICAgICAgICAgICAgICA6IFsnQzA5OTknXQpyZXRlbmNpb24gZW5lcm8gLT4gZmVicmVybyAgICAgICAgOiA1MCAl\",\n",
    "    12: \"Q291bnRlcih7J0FiYXJyb3Rlcyc6IDIsICdCZWJpZGFzJzogMiwgJ0xpbXBpZXphJzogMX0pCmVsIHBvZGlvICAgICA6IFsoJ0FiYXJyb3RlcycsIDIpLCAoJ0JlYmlkYXMnLCAyKV0KY3VhbnRhcyBoYXkgIDogMyBjYXRlZ29yaWFzIGRpc3RpbnRhcwp0b3RhbCB2ZW50YXMgOiA1CnVuYSBxdWUgbm8gZXN0YTogMA==\",\n",
    "    13: \"QXJlcXVpcGEgICAxIHZlbnRhcyAgdG90YWwgUy8gICAxNTQuODMgIHByb21lZGlvIFMvICAxNTQuODMgIG1heW9yIFMvICAxNTQuODMKQ3VzY28gICAgICAxIHZlbnRhcyAgdG90YWwgUy8gICA1MjQuOTAgIHByb21lZGlvIFMvICA1MjQuOTAgIG1heW9yIFMvICA1MjQuOTAKTGltYSAgICAgICAzIHZlbnRhcyAgdG90YWwgUy8gIDM4NTEuMjEgIHByb21lZGlvIFMvIDEyODMuNzQgIG1heW9yIFMvIDI0ODAuODQ=\",\n",
    "    14: \"Y2l1ZGFkIEEtWiwgbW9udG8gZGUgbWF5b3IgYSBtZW5vcjoKICBBcmVxdWlwYSAgICAgMTU0LjgzCiAgQ3VzY28gICAgICAgIDUyNC45MAogIExpbWEgICAgICAgIDI0ODAuODQKICBMaW1hICAgICAgICAgODkwLjAwCiAgTGltYSAgICAgICAgIDQ4MC4zNwp5IGFob3JhIGNpdWRhZCBaLUEsIG1vbnRvIGRlIG1lbm9yIGEgbWF5b3I6CiAgTGltYSAgICAgICAgIDQ4MC4zNwogIExpbWEgICAgICAgICA4OTAuMDAKICBMaW1hICAgICAgICAyNDgwLjg0CiAgQ3VzY28gICAgICAgIDUyNC45MAogIEFyZXF1aXBhICAgICAxNTQuODM=\",\n",
    "}, lenguaje=\"python\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Hola! Ahora guardamos muchas cosas juntas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "En el capítulo 3 cada variable guardaba un solo valor. Eso alcanza para\n",
    "aprender y no alcanza para trabajar 🙂\n",
    "\n",
    "En un archivo real tienes tres mil ventas, no una. Así que necesitas\n",
    "estructuras que guarden muchas cosas, y en Python son tres las que vas a usar\n",
    "todo el tiempo.\n",
    "\n",
    "Antes de la tabla, piensa en el último archivo que abriste: **¿qué guardaba cada fila, una cosa sola o varias con nombre?** Según cómo contestes eso, la estructura que necesitas ya está elegida 🙂"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "| Estructura | Se escribe | Se puede cambiar | Para qué |\n",
    "|---|---|---|---|\n",
    "| **Lista** | `[1, 2, 3]` | Sí | Lo que sea que tenga orden y vaya cambiando |\n",
    "| **Tupla** | `(1, 2, 3)` | No | Cosas que no deben cambiar: coordenadas, una fila fija |\n",
    "| **Diccionario** | `{'ciudad': 'Lima'}` | Sí | Datos con nombre. Es como llega cualquier API |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Listas: lo que más vas a usar"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ciudades = ['Lima', 'Arequipa', 'Cusco', 'Piura', 'Trujillo']\n",
    "\n",
    "print(ciudades[0])\n",
    "print(ciudades[2])\n",
    "print(len(ciudades))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se cuenta desde cero. Ya lo vimos en el capítulo 2 con el\n",
    "`IndexError`, y te lo repito porque es la fuente número uno de\n",
    "errores por uno de diferencia.\n",
    "\n",
    "Ahora el atajo que te evita hacer cuentas: **los índices\n",
    "negativos**."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ciudades[-1])     # la ultima, siempre\n",
    "print(ciudades[-2])     # la penultima"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Nunca más `ciudades[len(ciudades) - 1]`. Yo escribí esa\n",
    "monstruosidad durante meses antes de que alguien me enseñara el `-1` 😅\n",
    "\n",
    "### Rebanadas, que es como se corta una lista\n",
    "\n",
    "Los dos puntos dentro de los corchetes sacan un pedazo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ciudades[0:3])     # de la 0 hasta ANTES de la 3\n",
    "print(ciudades[:3])      # lo mismo, el 0 se sobreentiende\n",
    "print(ciudades[2:])      # de la 2 hasta el final\n",
    "print(ciudades[-2:])     # las dos ultimas\n",
    "print(ciudades[::2])     # de dos en dos"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**El límite de la derecha no se incluye.** Eso confunde al\n",
    "principio y tiene una gracia: `ciudades[0:3]` te da exactamente 3\n",
    "elementos, y `ciudades[:3] + ciudades[3:]` te devuelve la lista\n",
    "entera sin repetir ni perder nada.\n",
    "\n",
    "Y una que parece un truco de fiesta y sirve de verdad:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(ciudades[::-1])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Modificar una lista"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ventas = [480.37, 524.90, 154.83]\n",
    "\n",
    "ventas.append(2480.84)          # agrega al final\n",
    "ventas.insert(0, 100.0)         # inserta en una posicion\n",
    "print(ventas)\n",
    "\n",
    "ultimo = ventas.pop()           # saca el ultimo Y te lo devuelve\n",
    "print(ultimo, ventas)\n",
    "\n",
    "ventas.remove(154.83)           # quita por valor, no por posicion\n",
    "print(ventas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ojo con `remove`: quita **la primera aparición** del\n",
    "valor, no todas. Y si el valor no está, revienta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    ventas.remove(999)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "ValueError: list.remove(x): x not in list\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Ordenar, y el error que da la primera vez\n",
    "\n",
    "Hay dos formas de ordenar y confundirlas te deja con una variable vacía."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = [480.37, 154.83, 2480.84, 524.90]\n",
    "\n",
    "ordenados = sorted(montos)          # devuelve una lista nueva\n",
    "print(ordenados)\n",
    "print(montos)                       # la original no se toco"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos.sort()                       # ordena la original y devuelve None\n",
    "print(montos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y aquí el error clásico, que quiero que veas de frente:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "resultado = montos.sort()\n",
    "print(resultado)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Sí: `None`. Porque `.sort()` ordena la lista en el sitio\n",
    "y no devuelve nada. Si guardas su resultado, guardas la nada 🙃\n",
    "\n",
    "La regla para acordarte: **`sorted()` te da una lista nueva,\n",
    "`.sort()` modifica la que ya tienes**. Cuando dudes, usa\n",
    "`sorted()`, que es más difícil de romper."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(sorted(montos, reverse=True))\n",
    "print(sorted(['Cusco', 'arequipa', 'Lima'], key=str.lower))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `key=str.lower` es lo que evita que las mayúsculas se ordenen\n",
    "antes que todas las minúsculas, que es lo que pasa por defecto."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La copia que no se copió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Presta atención a esto, que es el error más traicionero del principio y no\n",
    "falla: te da un resultado equivocado sin decir nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "original = [1, 2, 3]\n",
    "copia = original          # esto NO es una copia\n",
    "\n",
    "copia.append(4)\n",
    "print('copia   :', copia)\n",
    "print('original:', original)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se modificaron las dos 😱\n",
    "\n",
    "Acuérdate del capítulo 3: una variable es una etiqueta, no una caja. Con\n",
    "`copia = original` le pegaste una segunda etiqueta a la\n",
    "**misma** lista. No hay dos listas, hay una con dos nombres.\n",
    "\n",
    "Se comprueba así:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(copia is original)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para copiar de verdad hay tres formas, y las tres valen:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "original = [1, 2, 3]\n",
    "\n",
    "copia_a = original.copy()\n",
    "copia_b = list(original)\n",
    "copia_c = original[:]        # una rebanada completa\n",
    "\n",
    "copia_a.append(99)\n",
    "print(original, copia_a)\n",
    "print(copia_a is original)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con esto te ahorro una tarde de \"pero si yo no toqué esa variable\". A mí me\n",
    "pasó con un dataframe y perdí medio día buscando el fantasma 👻"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Tuplas: las que no se pueden cambiar"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Una tupla es una lista que no se puede modificar. Se escribe con paréntesis."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    coordenada = (-12.0464, -77.0428)      # Lima\n",
    "    print(coordenada[0])\n",
    "\n",
    "    coordenada[0] = 0\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "TypeError: 'tuple' object does not support item assignment\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Y para qué querría algo que no puedo cambiar? Por tres razones muy\n",
    "concretas:\n",
    "\n",
    "- 🔒 Dice \"esto no se toca\". Un par de coordenadas, un rango de fechas fijo.\n",
    "\n",
    "- 🗝️ Puede ser clave de un diccionario. Una lista no puede.\n",
    "\n",
    "- 📦 Es lo que devuelve una función cuando devuelve varias cosas.\n",
    "\n",
    "Ese último punto lo vas a ver todo el tiempo:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def minimo_y_maximo(numeros):\n",
    "    return min(numeros), max(numeros)\n",
    "\n",
    "bajo, alto = minimo_y_maximo([480.37, 154.83, 2480.84])\n",
    "print(bajo, alto)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso de repartir una tupla en varias variables se llama desempaquetado y es\n",
    "puro Python. Lo vamos a usar mucho en el capítulo 6."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Diccionarios: datos con nombre"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Aquí está la estructura que más te va a servir en datos, porque es como llega\n",
    "la información del mundo real."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "venta = {\n",
    "    'cliente': 'C0045',\n",
    "    'ciudad': 'Trujillo',\n",
    "    'canal': 'Marketplace',\n",
    "    'monto': 480.37,\n",
    "    'unidades': 10,\n",
    "}\n",
    "\n",
    "print(venta['ciudad'])\n",
    "print(venta['monto'] * 1.18)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya no dependes de recordar que la ciudad estaba en la posición 1. Pides por\n",
    "nombre, que es como piensa una persona.\n",
    "\n",
    "### get, que evita que reviente"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    print(venta['descuento'])\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "KeyError: 'descuento'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con corchetes, si la clave no existe, se cae. Con `.get()` te\n",
    "devuelve `None`, o lo que tú digas:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(venta.get('descuento'))\n",
    "print(venta.get('descuento', 0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese segundo caso es oro puro cuando procesas datos que a veces traen un campo\n",
    "y a veces no, que es siempre 🌸\n",
    "\n",
    "### Recorrer un diccionario"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for clave in venta:\n",
    "    print(clave)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for clave, valor in venta.items():\n",
    "    print(f'{clave:10} {valor}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`.items()` es el que vas a usar el 90% de las veces. Los otros dos\n",
    "son `.keys()` y `.values()`, y se llaman igual de fácil."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que de verdad te va a llegar: una lista de diccionarios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Junta las dos estructuras y tienes la forma en que viene cualquier respuesta\n",
    "de una API, cualquier JSON, cualquier exportación de un sistema."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ventas = [\n",
    "    {'ciudad': 'Lima', 'canal': 'Web', 'monto': 480.37},\n",
    "    {'ciudad': 'Cusco', 'canal': 'Tienda', 'monto': 154.83},\n",
    "    {'ciudad': 'Lima', 'canal': 'WhatsApp', 'monto': 2480.84},\n",
    "    {'ciudad': 'Piura', 'canal': 'Web', 'monto': 524.90},\n",
    "]\n",
    "\n",
    "total = 0\n",
    "for v in ventas:\n",
    "    total = total + v['monto']\n",
    "\n",
    "print(f'Total: S/{total:,.2f}')\n",
    "print(f'Ticket promedio: S/{total / len(ventas):,.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y ahora agrupemos por ciudad, que es la operación que más veces vas a hacer\n",
    "en tu vida con datos:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_ciudad = {}\n",
    "\n",
    "for v in ventas:\n",
    "    ciudad = v['ciudad']\n",
    "    por_ciudad[ciudad] = por_ciudad.get(ciudad, 0) + v['monto']\n",
    "\n",
    "print(por_ciudad)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Guárdate ese patrón, que es un clásico: `diccionario.get(clave, 0) +\n",
    "algo`. La primera vez que aparece una ciudad, `.get` devuelve 0\n",
    "y arranca la suma; las siguientes, sigue sumando.\n",
    "\n",
    "En el capítulo 12 esto mismo se escribe así: `df.groupby('ciudad')\n",
    "['monto'].sum()`. Una línea. Pero quería que vieras primero lo que hay\n",
    "por debajo, porque el día que `groupby` haga algo raro vas a saber\n",
    "qué está pasando 💜"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprensiones de lista"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es la forma corta de crear una lista a partir de otra, y es tan de Python\n",
    "que se nota cuando alguien no la usa."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "montos = [480.37, 154.83, 2480.84, 524.90]\n",
    "\n",
    "# La forma larga\n",
    "con_igv = []\n",
    "for m in montos:\n",
    "    con_igv.append(round(m * 1.18, 2))\n",
    "\n",
    "# La forma de Python\n",
    "con_igv_corto = [round(m * 1.18, 2) for m in montos]\n",
    "\n",
    "print(con_igv)\n",
    "print(con_igv == con_igv_corto)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Se lee de izquierda a derecha casi como una frase: \"redondea eme por 1,18,\n",
    "para cada eme en montos\".\n",
    "\n",
    "Y se le puede poner una condición al final:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "grandes = [m for m in montos if m > 500]\n",
    "print(grandes)\n",
    "\n",
    "ciudades = ['Lima', 'cusco', 'AREQUIPA']\n",
    "print([c.title() for c in ciudades])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un consejo de alguien que se pasó de lista: **si tu comprensión no cabe\n",
    "en dos líneas, escríbela como bucle normal**. La gracia es que se lea\n",
    "mejor, no que sea más corta."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conjuntos: para preguntar quién está y quién no"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Hay una cuarta estructura que casi nunca sale en los tutoriales y que resuelve\n",
    "en una línea preguntas que con listas ocupan diez 🎯\n",
    "\n",
    "Un conjunto es una bolsa **sin repetidos y sin orden**. Se escribe\n",
    "con llaves, como el diccionario, pero sin dos puntos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "enero = ['C0045', 'C0325', 'C0260', 'C0111', 'C0045']\n",
    "febrero = ['C0325', 'C0777', 'C0045', 'C0888']\n",
    "\n",
    "clientes_enero = set(enero)\n",
    "clientes_febrero = set(febrero)\n",
    "\n",
    "print('compras de enero      :', len(enero))\n",
    "print('clientes distintos    :', len(clientes_enero))\n",
    "print('compraron los dos meses:', sorted(clientes_enero & clientes_febrero))\n",
    "print('solo enero, se fueron  :', sorted(clientes_enero - clientes_febrero))\n",
    "print('solo febrero, nuevos   :', sorted(clientes_febrero - clientes_enero))\n",
    "print('distintos en total     :', len(clientes_enero | clientes_febrero))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es un análisis de retención entero en seis líneas 💜\n",
    "\n",
    "- 🤝 `&` es **los que están en los dos**: se\n",
    "quedaron.\n",
    "\n",
    "- ➖ `-` es **los que están en el primero y no en el\n",
    "segundo**: se fueron. Ojo al orden, que aquí sí importa.\n",
    "\n",
    "- ➕ `|` es **todos, sin repetir**.\n",
    "\n",
    "Y la otra cosa para la que sirven: preguntar si algo está dentro. En una lista\n",
    "larga, Python va mirando uno por uno; en un conjunto va directo, sin recorrer\n",
    "nada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print('C0045 compro en enero:', 'C0045' in clientes_enero)\n",
    "print('C0999 compro en enero:', 'C0999' in clientes_enero)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Con cinco clientes da igual. Con doscientos mil, la lista tarda y el conjunto\n",
    "no. Si vas a preguntar `in` muchas veces sobre lo mismo, conviértelo a\n",
    "conjunto una vez y pregunta ahí 🏃‍♀️\n",
    "\n",
    "Lo que un conjunto no tiene es orden, y eso es literal:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    clientes_enero[0]\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "TypeError: 'set' object is not subscriptable\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "No hay primero. No hay posición. Si necesitas verlos ordenados, pásalo por\n",
    "`sorted()` como hice arriba, y si necesitas conservar el orden en que\n",
    "llegaron, entonces lo que querías era una lista 📋"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Contar y agrupar sin escribir el bucle a mano"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "El ejercicio de contar por categoría lo hiciste con un diccionario y un\n",
    "`get`. Funciona perfecto y hay dos herramientas que lo dejan en una\n",
    "línea, las dos en el módulo `collections` 🧰"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from collections import Counter, defaultdict\n",
    "\n",
    "movimientos = [\n",
    "    {'ciudad': 'Lima', 'categoria': 'Abarrotes', 'monto': 480.37},\n",
    "    {'ciudad': 'Arequipa', 'categoria': 'Bebidas', 'monto': 154.83},\n",
    "    {'ciudad': 'Lima', 'categoria': 'Abarrotes', 'monto': 2480.84},\n",
    "    {'ciudad': 'Cusco', 'categoria': 'Limpieza', 'monto': 524.90},\n",
    "    {'ciudad': 'Lima', 'categoria': 'Bebidas', 'monto': 890.00},\n",
    "]\n",
    "\n",
    "cuenta = Counter(v['ciudad'] for v in movimientos)\n",
    "\n",
    "print(cuenta)\n",
    "print('la que mas vende:', cuenta.most_common(1))\n",
    "print('cuantas de Piura:', cuenta['Piura'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Tres regalos ahí 🎁\n",
    "\n",
    "`Counter` cuenta lo que le pases y ya viene ordenado de mayor a\n",
    "menor. `most_common(3)` te da el podio sin ordenar nada tú. Y una\n",
    "clave que no existe devuelve **cero** en vez de reventar, que es\n",
    "exactamente lo que quieres al contar.\n",
    "\n",
    "Para agrupar, en vez de contar, está el otro:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "por_ciudad = defaultdict(list)\n",
    "\n",
    "for v in movimientos:\n",
    "    por_ciudad[v['ciudad']].append(v['monto'])\n",
    "\n",
    "for ciudad, montos in por_ciudad.items():\n",
    "    print(f'{ciudad:<10} {len(montos)} ventas, S/{sum(montos):>8.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La gracia está en el `defaultdict(list)`: cuando le pides una clave\n",
    "que no existe, **se la inventa vacía** en vez de fallar. Con un\n",
    "diccionario normal, la primera venta de Lima revienta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Esto revienta a propósito.** Se ejecuta dentro de un `try` para que puedas seguir con \"ejecutar todo\" y aun así ver la queja."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    sin_default = {}\n",
    "    sin_default['Lima'].append(480.37)\n",
    "except Exception as e:\n",
    "    print(f'{type(e).__name__}: {e}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y la queja que tiene que salir es esta:\n",
    "\n",
    "```\n",
    "KeyError: 'Lima'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Por eso el patrón de siempre es `if ciudad not in d: d[ciudad] = []`\n",
    "antes de cada `append`. Con `defaultdict` esa línea\n",
    "desaparece y el bucle se lee de un vistazo 🌸"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ordenar por dos cosas a la vez"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ya ordenaste una lista de números. Con una lista de diccionarios hay que\n",
    "decirle **por qué campo**, y ahí entra `key`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ordenadas = sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto']))\n",
    "\n",
    "for v in ordenadas:\n",
    "    print(f\"{v['ciudad']:<10} {v['categoria']:<12} {v['monto']:>8.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ciudad en orden alfabético, y dentro de cada ciudad los montos de mayor a\n",
    "menor 📊\n",
    "\n",
    "El truco está en esa **tupla** que devuelve el\n",
    "`lambda`. Python compara tuplas de izquierda a derecha: primero mira\n",
    "la ciudad, y solo si empatan pasa al segundo elemento. Puedes poner tres o cuatro\n",
    "criterios, y se comparan en ese orden.\n",
    "\n",
    "Y el signo menos delante del monto es el truco para invertir **solo ese\n",
    "criterio**. Con `reverse=True` le darías la vuelta a los dos, y\n",
    "las ciudades te saldrían de la Z a la A. El menos solo funciona con números; para\n",
    "invertir un texto hay que ordenar dos veces, primero por el criterio de dentro y\n",
    "después por el de fuera."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Primero, último y cuántos\n",
    "\n",
    "Con una lista de cinco canales, imprime el primero, el\n",
    "último y cuántos hay, sin contar a mano."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Las tres del medio\n",
    "\n",
    "De esa misma lista, saca solo los elementos 2, 3 y 4 con una\n",
    "rebanada."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. El top 3 de montos\n",
    "\n",
    "De una lista de montos, saca los tres más altos ordenados de\n",
    "mayor a menor, sin modificar la lista original."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Demuestra el aliasing\n",
    "\n",
    "Crea una lista, hazle una copia falsa y una de verdad, y\n",
    "comprueba con `is` cuál es cuál."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Un diccionario que no revienta\n",
    "\n",
    "Con un diccionario de una venta que no trae la clave\n",
    "`descuento`, calcula el monto final asumiendo 0 si no está."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Contar por categoría\n",
    "\n",
    "Con una lista de canales repetidos, cuenta cuántas veces\n",
    "aparece cada uno."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. El máximo con nombre\n",
    "\n",
    "De una lista de diccionarios de ventas, encuentra la de\n",
    "mayor monto e imprime su ciudad."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 8. Comprensión con condición\n",
    "\n",
    "De la lista de ventas anterior, arma una lista solo con las\n",
    "ciudades cuyo monto pase los S/500."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 8\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 9. Quitar duplicados conservando el orden\n",
    "\n",
    "De una lista con ciudades repetidas, saca las únicas. Hazlo\n",
    "de dos formas y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 9\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 10. La tupla que no se deja\n",
    "\n",
    "Crea una tupla con un rango de fechas e intenta cambiar el\n",
    "primer valor. Después haz lo mismo con una lista y compara."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 10\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 11. Quién se fue y quién llegó\n",
    "\n",
    "Con tres meses de códigos de cliente, di cuántos se\n",
    "mantuvieron, cuántos se perdieron y cuántos entraron nuevos."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 11\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 12. El podio, sin ordenar nada\n",
    "\n",
    "Cuenta las ventas por categoría y saca las dos más\n",
    "frecuentes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 12\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 13. Agrupar montos por ciudad\n",
    "\n",
    "Junta los montos de cada ciudad y saca el total, el promedio\n",
    "y el máximo de cada una."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 13\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 14. Un ranking con dos criterios\n",
    "\n",
    "Ordena las ventas por ciudad y, dentro de cada ciudad, de\n",
    "mayor a menor monto. Después dale la vuelta al criterio de fuera."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 14\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quitar duplicados y quedarte con uno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esta es la primera trampa de listas con la que se topa todo el mundo, y no avisa de ninguna manera: no revienta, no imprime nada raro, y la lista sale más corta como esperabas 🫥"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "Limpias duplicados recorriendo la lista y quitando lo que sobra. Corre, no da error, y la lista sale más corta.\n",
    "\n",
    "```\n",
    "ciudades = ['lima', 'lima', 'cusco']\n",
    "\n",
    "for c in ciudades:\n",
    "    if c == 'lima':\n",
    "        ciudades.remove(c)\n",
    "\n",
    "print(ciudades)   # ['lima', 'cusco']\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Haces `b = a` con dos listas, cambias b y a también cambió. ¿Por qué?\n",
    "\n",
    "a) Porque las dos etiquetas apuntan a la misma lista\n",
    "\n",
    "b) Porque las listas se copian solas\n",
    "\n",
    "c) Porque hay que usar tuplas para que no cambien\n",
    "\n",
    "d) Porque b quedó dentro de a"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 🔢 Se cuenta desde 0, y `[-1]` es siempre el último.\n",
    "\n",
    "- ✂️ En una rebanada, el límite de la derecha no entra.\n",
    "\n",
    "- ⚠️ `copia = original` no copia nada: usa `.copy()`.\n",
    "\n",
    "- 🔀 `sorted()` devuelve una lista nueva; `.sort()`\n",
    "devuelve `None`.\n",
    "\n",
    "- 🗂️ Una lista de diccionarios es como llega el mundo real, y agrupar con\n",
    "`.get(clave, 0)` es el patrón que después hace\n",
    "`groupby`.\n",
    "\n",
    "Y si de todo el capítulo te llevas una sola frase, que sea esta:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un diccionario no es una lista con nombres. Es la respuesta a \"y esto de\n",
    "quién es\"."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Y si un diccionario te suena a \"una fila con los nombres de las\n",
    "columnas\", vas bien: es exactamente lo que devuelve una consulta, y de eso\n",
    "va el [libro de SQL](https://missyera.com/guias/sql-desde-cero/) 🗃️\n",
    "\n",
    "En el capítulo 5 le enseñamos a Python a decidir y a repetir: `if`,\n",
    "`for` y `while`. Ahí es donde esto deja de ser una\n",
    "calculadora y empieza a ser un programa.\n",
    "\n",
    "Que tengas un hermoso día! 🌟"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Preguntas frecuentes"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "¿Qué son las listas en Python?Una secuencia ordenada de cosas que se puede modificar. Se escribe entre corchetes y el primer elemento es el cero.\n",
    "\n",
    "¿Qué son los diccionarios en Python?Pares de clave y valor. En vez de pedir el elemento tres pides el elemento \"ciudad\", que es como llegan casi todos los datos de una API.\n",
    "\n",
    "¿Cuál es la diferencia entre lista y tupla?La tupla no se puede modificar después de creada. Se usa para cosas que no deberían cambiar, como unas coordenadas.\n",
    "\n",
    "¿Cómo copio una lista sin que se copie sola?Con lista.copy() o con una rebanada completa. Si escribes b = a, las dos apuntan a la misma lista y modificar una modifica la otra, que es el error que más desconcierta al principio."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 4 de **Python desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/python-desde-cero/listas-y-diccionarios/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
