{
 "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 soluciones 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",
    "Este es el cuaderno de **soluciones**. Trae el código de cada ejercicio, la\n",
    "explicación de la trampa y la respuesta del quiz. Si vienes del cuaderno de\n",
    "práctica sin haberlo intentado, vuelve 🙂"
   ]
  },
  {
   "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": [
    "canales = ['Web', 'Tienda', 'WhatsApp', 'Marketplace', 'Telefono']\n",
    "\n",
    "print(canales[0], canales[-1], len(canales))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Web Telefono 5\n",
    "```"
   ]
  },
  {
   "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": [
    "print(canales[1:4])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Tienda', 'WhatsApp', 'Marketplace']\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Recuerda que el límite derecho no entra: `1:4` te da las posiciones\n",
    "1, 2 y 3."
   ]
  },
  {
   "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": [
    "montos = [480.37, 154.83, 2480.84, 524.90, 95.5, 1200.0]\n",
    "\n",
    "top = sorted(montos, reverse=True)[:3]\n",
    "print(top)\n",
    "print(montos[0])          # la original intacta"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "[2480.84, 1200.0, 524.9]\n",
    "480.37\n",
    "```"
   ]
  },
  {
   "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": [
    "pedido = ['Abarrotes', 'Bebidas', 'Snacks']\n",
    "falsa = pedido\n",
    "buena = pedido.copy()\n",
    "\n",
    "falsa.append('Limpieza')\n",
    "print(pedido)\n",
    "print(buena)\n",
    "print(falsa is pedido, buena is pedido)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Abarrotes', 'Bebidas', 'Snacks', 'Limpieza']\n",
    "['Abarrotes', 'Bebidas', 'Snacks']\n",
    "True False\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`is` pregunta si son el mismo objeto; `==` pregunta si\n",
    "tienen el mismo contenido. Dos listas distintas con lo mismo dentro dan\n",
    "`True` con `==` y `False` con `is`."
   ]
  },
  {
   "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": [
    "venta = {'cliente': 'C0325', 'monto': 524.90}\n",
    "\n",
    "descuento = venta.get('descuento', 0)\n",
    "print(f'S/{venta[\"monto\"] * (1 - descuento):.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "S/524.90\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Fíjate en las comillas dobles dentro del f-string que abre con comillas\n",
    "simples. Si usas las mismas por fuera y por dentro, Python se pierde."
   ]
  },
  {
   "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": [
    "canales = ['Web', 'Tienda', 'Web', 'WhatsApp', 'Web', 'Tienda']\n",
    "\n",
    "conteo = {}\n",
    "for c in canales:\n",
    "    conteo[c] = conteo.get(c, 0) + 1\n",
    "\n",
    "print(conteo)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "{'Web': 3, 'Tienda': 2, 'WhatsApp': 1}\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Es el mismo patrón de la suma por ciudad, pero sumando 1 en vez del monto.\n",
    "Cuando lo veas escrito así vas a reconocer `value_counts()` de pandas\n",
    "sin que nadie te lo explique."
   ]
  },
  {
   "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": [
    "ventas = [\n",
    "    {'ciudad': 'Lima', 'monto': 480.37},\n",
    "    {'ciudad': 'Cusco', 'monto': 2480.84},\n",
    "    {'ciudad': 'Piura', 'monto': 524.90},\n",
    "]\n",
    "\n",
    "mayor = max(ventas, key=lambda v: v['monto'])\n",
    "print(mayor['ciudad'], mayor['monto'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Cusco 2480.84\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `lambda` es una función chiquita sin nombre que dice \"para\n",
    "comparar, mira el monto\". Lo vemos con calma en el capítulo 6, tranqui."
   ]
  },
  {
   "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": [
    "print([v['ciudad'] for v in ventas if v['monto'] > 500])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Cusco', 'Piura']\n",
    "```"
   ]
  },
  {
   "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": [
    "ciudades = ['Lima', 'Cusco', 'Lima', 'Piura', 'Cusco', 'Lima']\n",
    "\n",
    "print(sorted(set(ciudades)))\n",
    "\n",
    "vistas = []\n",
    "for c in ciudades:\n",
    "    if c not in vistas:\n",
    "        vistas.append(c)\n",
    "print(vistas)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "['Cusco', 'Lima', 'Piura']\n",
    "['Lima', 'Cusco', 'Piura']\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`set()` es rapidísimo y pierde el orden original; el bucle lo\n",
    "conserva. Cuál usar depende de si el orden significa algo, y muchas veces\n",
    "significa."
   ]
  },
  {
   "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": [
    "rango_tupla = ('2026-01-01', '2026-12-31')\n",
    "rango_lista = ['2026-01-01', '2026-12-31']\n",
    "\n",
    "rango_lista[0] = '2026-02-01'\n",
    "print(rango_lista)\n",
    "\n",
    "rango_tupla[0] = '2026-02-01'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "TypeError: 'tuple' object does not support item assignment\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "La tupla te protege de un cambio accidental. Cuando algo no debe moverse en\n",
    "todo el programa, esa protección vale más que la flexibilidad."
   ]
  },
  {
   "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": [
    "meses = {\n",
    "    'enero': {'C0045', 'C0325', 'C0260', 'C0111'},\n",
    "    'febrero': {'C0325', 'C0045', 'C0777', 'C0888'},\n",
    "    'marzo': {'C0045', 'C0888', 'C0999'},\n",
    "}\n",
    "\n",
    "fieles = meses['enero'] & meses['febrero'] & meses['marzo']\n",
    "alguna_vez = meses['enero'] | meses['febrero'] | meses['marzo']\n",
    "\n",
    "print('clientes distintos en el trimestre:', len(alguna_vez))\n",
    "print('compraron los tres meses          :', sorted(fieles))\n",
    "print('perdidos de enero a febrero       :',\n",
    "      sorted(meses['enero'] - meses['febrero']))\n",
    "print('nuevos en marzo                   :',\n",
    "      sorted(meses['marzo'] - meses['enero'] - meses['febrero']))\n",
    "print('retencion enero -> febrero        :',\n",
    "      round(len(meses['enero'] & meses['febrero']) / len(meses['enero']) * 100), '%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "clientes distintos en el trimestre: 7\n",
    "compraron los tres meses          : ['C0045']\n",
    "perdidos de enero a febrero       : ['C0111', 'C0260']\n",
    "nuevos en marzo                   : ['C0999']\n",
    "retencion enero -> febrero        : 50 %\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un cliente de cuatro aguantó los tres meses, y la retención del primer mes al\n",
    "segundo es del 50% 📉\n",
    "\n",
    "Con datos de verdad esto es el análisis de cohortes entero, y sale de tres\n",
    "conjuntos y cuatro operaciones. Ni una librería."
   ]
  },
  {
   "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": [
    "categorias = Counter(v['categoria'] for v in movimientos)\n",
    "\n",
    "print(categorias)\n",
    "print('el podio     :', categorias.most_common(2))\n",
    "print('cuantas hay  :', len(categorias), 'categorias distintas')\n",
    "print('total ventas :', sum(categorias.values()))\n",
    "print('una que no esta:', categorias['Congelados'])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Counter({'Abarrotes': 2, 'Bebidas': 2, 'Limpieza': 1})\n",
    "el podio     : [('Abarrotes', 2), ('Bebidas', 2)]\n",
    "cuantas hay  : 3 categorias distintas\n",
    "total ventas : 5\n",
    "una que no esta: 0\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese último cero es la razón por la que uso `Counter` y no un\n",
    "diccionario normal 🛡️\n",
    "\n",
    "Preguntar por una categoría que no vino este mes devuelve cero, que es la\n",
    "respuesta correcta. Un diccionario normal te lanzaría un `KeyError` y\n",
    "te tumbaría el reporte por una categoría que simplemente no vendió."
   ]
  },
  {
   "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": [
    "agrupado = defaultdict(list)\n",
    "for v in movimientos:\n",
    "    agrupado[v['ciudad']].append(v['monto'])\n",
    "\n",
    "for ciudad in sorted(agrupado):\n",
    "    montos = agrupado[ciudad]\n",
    "    print(f'{ciudad:<10} {len(montos)} ventas  '\n",
    "          f'total S/{sum(montos):>9.2f}  '\n",
    "          f'promedio S/{sum(montos) / len(montos):>8.2f}  '\n",
    "          f'mayor S/{max(montos):>8.2f}')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "Arequipa   1 ventas  total S/   154.83  promedio S/  154.83  mayor S/  154.83\n",
    "Cusco      1 ventas  total S/   524.90  promedio S/  524.90  mayor S/  524.90\n",
    "Lima       3 ventas  total S/  3851.21  promedio S/ 1283.74  mayor S/ 2480.84\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ese `sorted(agrupado)` del bucle ordena las claves alfabéticamente,\n",
    "para que la tabla salga siempre igual. Sin él salen en el orden en que\n",
    "aparecieron, que depende del archivo y cambia cada mes 🔀\n",
    "\n",
    "Y esto es, literalmente, lo que hace `groupby` de pandas. Cuando\n",
    "llegues a ese capítulo vas a saber qué está pasando por debajo."
   ]
  },
  {
   "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": [
    "print('ciudad A-Z, monto de mayor a menor:')\n",
    "for v in sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto'])):\n",
    "    print(f\"  {v['ciudad']:<10} {v['monto']:>8.2f}\")\n",
    "\n",
    "print('y ahora ciudad Z-A, monto de menor a mayor:')\n",
    "for v in sorted(movimientos, key=lambda v: (v['ciudad'], -v['monto']), reverse=True):\n",
    "    print(f\"  {v['ciudad']:<10} {v['monto']:>8.2f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "```\n",
    "ciudad A-Z, monto de mayor a menor:\n",
    "  Arequipa     154.83\n",
    "  Cusco        524.90\n",
    "  Lima        2480.84\n",
    "  Lima         890.00\n",
    "  Lima         480.37\n",
    "y ahora ciudad Z-A, monto de menor a mayor:\n",
    "  Lima         480.37\n",
    "  Lima         890.00\n",
    "  Lima        2480.84\n",
    "  Cusco        524.90\n",
    "  Arequipa     154.83\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Mira lo que hizo el `reverse=True`: le dio la vuelta a\n",
    "**los dos** criterios 🔄\n",
    "\n",
    "Las ciudades pasaron de la Z a la A, pero los montos, que iban de mayor a\n",
    "menor por el signo menos, ahora van de menor a mayor. El `reverse` no\n",
    "distingue: invierte la comparación entera.\n",
    "\n",
    "Por eso el signo menos es mejor herramienta cuando quieres criterios en\n",
    "direcciones distintas. Es más quisquilloso de escribir y te deja controlar cada\n",
    "uno por separado 🎛️"
   ]
  },
  {
   "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**\n",
    "\n",
    "Sobrevivió una Lima 👻 Al quitar la primera, todo lo de detrás se corre un sitio hacia la izquierda, pero el bucle ya va por la posición 1. Se salta la segunda Lima sin mirarla.\n",
    "\n",
    "Y esto es lo peligroso: la lista *sí* se acortó, así que parece que funcionó. Con duplicados sueltos acierta, con duplicados pegados falla, y nadie revisa un limpiador de duplicados que devuelve menos filas de las que entraron.\n",
    "\n",
    "Nunca se modifica una lista mientras se recorre. Se construye una nueva con una comprensión (`[c for c in ciudades if c != 'lima']`), que además se lee mejor y es más rápida."
   ]
  },
  {
   "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\n",
    "\n",
    "---\n",
    "\n",
    "**La correcta es la a.**\n",
    "\n",
    "*b)* Al revés: lo que no se copió es justamente el problema.\n",
    "\n",
    "*c)* Una tupla evitaría el cambio, pero no explica lo que acaba de pasar.\n",
    "\n",
    "*d)* No hay una dentro de otra: hay una sola lista con dos nombres.\n",
    "\n",
    "El igual no copia, pega otra etiqueta."
   ]
  },
  {
   "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
}
