{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cb7efa50",
   "metadata": {},
   "source": [
    "[← Anterior](02_control_del_flujo.ipynb) · [Índice](README.md) · [Siguiente →](04_funciones.ipynb)\n",
    "\n",
    "# 03 · Estructuras de datos\n",
    "\n",
    "**Audiencia.** Personas que ya han visto algo de programación y quieren repasar Python de forma práctica.  \n",
    "**Prerrequisitos.** Variables, bucles y condicionales.  \n",
    "**Duración orientativa.** 60–90 minutos, incluida la práctica.\n",
    "\n",
    "## Objetivos de aprendizaje\n",
    "\n",
    "- Elegir entre listas, tuplas, diccionarios y conjuntos.\n",
    "- Consultar, actualizar, recorrer y comprender estructuras.\n",
    "- Usar desempaquetado, comprensiones y utilidades de `collections`.\n",
    "- Distinguir alias, copia superficial y copia profunda.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9eea1efc",
   "metadata": {},
   "source": [
    "## Itinerario\n",
    "\n",
    "1. Listas\n",
    "2. Tuplas\n",
    "3. Diccionarios\n",
    "4. Conjuntos\n",
    "5. Comprensiones\n",
    "6. Copias\n",
    "7. `collections`\n",
    "8. Práctica\n",
    "\n",
    "> **Cómo trabajar:** ejecuta las celdas en orden, predice el resultado antes de verlo y modifica los ejemplos. Cada bloque termina con un ejercicio, un punto de partida y una solución posible.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "08428fb8",
   "metadata": {},
   "source": [
    "## 1. Listas: secuencias mutables\n",
    "\n",
    "Una lista mantiene orden, acepta duplicados y se puede modificar. Los índices y *slices* funcionan como en las cadenas.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "ffff629f",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.876705Z",
     "iopub.status.busy": "2026-08-02T22:29:45.876574Z",
     "iopub.status.idle": "2026-08-02T22:29:45.880796Z",
     "shell.execute_reply": "2026-08-02T22:29:45.880480Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['Python', 'SQL', 'JavaScript', 'Rust']\n",
      "Primero: Python Último extraído: Go\n",
      "Fragmento: ['SQL', 'JavaScript']\n"
     ]
    }
   ],
   "source": [
    "lenguajes = [\"Python\", \"JavaScript\", \"Rust\"]\n",
    "lenguajes.append(\"Go\")\n",
    "lenguajes.insert(1, \"SQL\")\n",
    "ultimo = lenguajes.pop()\n",
    "\n",
    "print(lenguajes)\n",
    "print(\"Primero:\", lenguajes[0], \"Último extraído:\", ultimo)\n",
    "print(\"Fragmento:\", lenguajes[1:3])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eab35954",
   "metadata": {},
   "source": [
    "`sort()` modifica la lista y devuelve `None`; `sorted()` produce una lista nueva desde cualquier iterable. La función `key` define el criterio sin alterar los elementos.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "59a60408",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.882719Z",
     "iopub.status.busy": "2026-08-02T22:29:45.882568Z",
     "iopub.status.idle": "2026-08-02T22:29:45.884950Z",
     "shell.execute_reply": "2026-08-02T22:29:45.884657Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original: ['sol', 'universo', 'luz', 'planeta']\n",
      "Ordenada: ['sol', 'luz', 'planeta', 'universo']\n"
     ]
    }
   ],
   "source": [
    "palabras = [\"sol\", \"universo\", \"luz\", \"planeta\"]\n",
    "por_longitud = sorted(palabras, key=len)\n",
    "print(\"Original:\", palabras)\n",
    "print(\"Ordenada:\", por_longitud)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32fe1d22",
   "metadata": {},
   "source": [
    "## 2. Tuplas: registros inmutables\n",
    "\n",
    "Una tupla mantiene orden pero no se puede modificar. Es útil para coordenadas, valores de retorno múltiples y claves compuestas de diccionario.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "7c57a593",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.886578Z",
     "iopub.status.busy": "2026-08-02T22:29:45.886455Z",
     "iopub.status.idle": "2026-08-02T22:29:45.888883Z",
     "shell.execute_reply": "2026-08-02T22:29:45.888607Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "x=3, y=7\n",
      "Marta 31 Valencia\n"
     ]
    }
   ],
   "source": [
    "punto = (3, 7)\n",
    "x, y = punto\n",
    "print(f\"x={x}, y={y}\")\n",
    "\n",
    "persona = (\"Marta\", 31, \"Valencia\")\n",
    "nombre, edad, ciudad = persona\n",
    "print(nombre, edad, ciudad)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b3e4b16",
   "metadata": {},
   "source": [
    "El desempaquetado ampliado captura una parte variable con `*`. Una tupla de un elemento necesita coma: `(42,)`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "fd6b695e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.890531Z",
     "iopub.status.busy": "2026-08-02T22:29:45.890411Z",
     "iopub.status.idle": "2026-08-02T22:29:45.892376Z",
     "shell.execute_reply": "2026-08-02T22:29:45.892095Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "10 [20, 30, 40] 50\n",
      "tuple int\n"
     ]
    }
   ],
   "source": [
    "primero, *centro, ultimo = [10, 20, 30, 40, 50]\n",
    "print(primero, centro, ultimo)\n",
    "print(type((42,)).__name__, type((42)).__name__)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c23b74de",
   "metadata": {},
   "source": [
    "## 3. Diccionarios: pares clave–valor\n",
    "\n",
    "Un diccionario relaciona claves únicas con valores. Conserva el orden de inserción. Las claves deben ser *hashables* (por ejemplo, cadenas, números o tuplas inmutables).\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "510b2542",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.894119Z",
     "iopub.status.busy": "2026-08-02T22:29:45.893998Z",
     "iopub.status.idle": "2026-08-02T22:29:45.896229Z",
     "shell.execute_reply": "2026-08-02T22:29:45.895972Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Nora\n",
      "sin teléfono\n",
      "{'nombre': 'Nora', 'nivel': 3, 'activo': True, 'ciudad': 'Sevilla'}\n"
     ]
    }
   ],
   "source": [
    "usuario = {\"nombre\": \"Nora\", \"nivel\": 2, \"activo\": True}\n",
    "usuario[\"nivel\"] += 1\n",
    "usuario[\"ciudad\"] = \"Sevilla\"\n",
    "\n",
    "print(usuario[\"nombre\"])\n",
    "print(usuario.get(\"telefono\", \"sin teléfono\"))\n",
    "print(usuario)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5427c69f",
   "metadata": {},
   "source": [
    "Acceder con `diccionario[clave]` falla si no existe; `get` permite un valor predeterminado. Para recorrer, usa `items()` cuando necesites clave y valor.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "12f51793",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.897800Z",
     "iopub.status.busy": "2026-08-02T22:29:45.897685Z",
     "iopub.status.idle": "2026-08-02T22:29:45.899926Z",
     "shell.execute_reply": "2026-08-02T22:29:45.899695Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "teclado   4 → ok\n",
      "ratón     7 → ok\n",
      "monitor   2 → reponer\n"
     ]
    }
   ],
   "source": [
    "inventario = {\"teclado\": 4, \"ratón\": 7, \"monitor\": 2}\n",
    "\n",
    "for producto, unidades in inventario.items():\n",
    "    estado = \"reponer\" if unidades < 3 else \"ok\"\n",
    "    print(f\"{producto:8} {unidades:2} → {estado}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f6faafd",
   "metadata": {},
   "source": [
    "## 4. Conjuntos: elementos únicos\n",
    "\n",
    "Un `set` no conserva posiciones y elimina duplicados. Es ideal para pertenencia y operaciones matemáticas: unión `|`, intersección `&`, diferencia `-` y diferencia simétrica `^`.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "d8e95ff7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.901343Z",
     "iopub.status.busy": "2026-08-02T22:29:45.901240Z",
     "iopub.status.idle": "2026-08-02T22:29:45.903449Z",
     "shell.execute_reply": "2026-08-02T22:29:45.903206Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "En ambos: {'Eva', 'Omar'}\n",
      "En alguno: {'Sara', 'Luis', 'Eva', 'Omar', 'Ana'}\n",
      "Solo Python: {'Luis', 'Ana'}\n",
      "Personas únicas: 5\n"
     ]
    }
   ],
   "source": [
    "inscritos_python = {\"Ana\", \"Luis\", \"Eva\", \"Omar\"}\n",
    "inscritos_sql = {\"Eva\", \"Omar\", \"Sara\"}\n",
    "\n",
    "print(\"En ambos:\", inscritos_python & inscritos_sql)\n",
    "print(\"En alguno:\", inscritos_python | inscritos_sql)\n",
    "print(\"Solo Python:\", inscritos_python - inscritos_sql)\n",
    "print(\"Personas únicas:\", len(inscritos_python | inscritos_sql))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "534ec1b4",
   "metadata": {},
   "source": [
    "## 5. Comprensiones\n",
    "\n",
    "Las comprensiones construyen colecciones a partir de iterables. Son claras para una transformación y, como máximo, un filtro sencillo. Si la lógica crece, usa un bucle o una función.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5991f0c4",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.904815Z",
     "iopub.status.busy": "2026-08-02T22:29:45.904709Z",
     "iopub.status.idle": "2026-08-02T22:29:45.906939Z",
     "shell.execute_reply": "2026-08-02T22:29:45.906717Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1, 4, 9, 16, 25, 36, 49]\n",
      "{1: 'impar', 2: 'par', 3: 'impar', 4: 'par', 5: 'impar', 6: 'par', 7: 'impar'}\n",
      "{'G', 'A'}\n"
     ]
    }
   ],
   "source": [
    "numeros = range(1, 8)\n",
    "cuadrados = [n ** 2 for n in numeros]\n",
    "paridad = {n: \"par\" if n % 2 == 0 else \"impar\" for n in numeros}\n",
    "iniciales = {nombre[0] for nombre in [\"Ada\", \"Alan\", \"Grace\", \"Guido\"]}\n",
    "\n",
    "print(cuadrados)\n",
    "print(paridad)\n",
    "print(iniciales)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f956604d",
   "metadata": {},
   "source": [
    "## 6. Alias y copias\n",
    "\n",
    "La asignación de una colección mutable crea otro nombre para el mismo objeto. `list.copy()` hace una **copia superficial**: duplica el contenedor exterior, no los objetos anidados. `copy.deepcopy()` duplica recursivamente.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "e768de11",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.908349Z",
     "iopub.status.busy": "2026-08-02T22:29:45.908252Z",
     "iopub.status.idle": "2026-08-02T22:29:45.910376Z",
     "shell.execute_reply": "2026-08-02T22:29:45.910138Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Alias: [[1, 2, 99], [3, 4]]\n",
      "Superficial: [[1, 2, 99], [3, 4]]\n",
      "Profunda: [[1, 2], [3, 4]]\n"
     ]
    }
   ],
   "source": [
    "import copy\n",
    "\n",
    "original = [[1, 2], [3, 4]]\n",
    "alias = original\n",
    "superficial = original.copy()\n",
    "profunda = copy.deepcopy(original)\n",
    "\n",
    "original[0].append(99)\n",
    "print(\"Alias:\", alias)\n",
    "print(\"Superficial:\", superficial)\n",
    "print(\"Profunda:\", profunda)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "faa5fea9",
   "metadata": {},
   "source": [
    "## 7. Herramientas de `collections`\n",
    "\n",
    "La biblioteca estándar aporta estructuras especializadas: `Counter` cuenta, `defaultdict` crea valores por defecto y `deque` añade o extrae eficientemente por ambos extremos.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "a8a9db5e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.911669Z",
     "iopub.status.busy": "2026-08-02T22:29:45.911580Z",
     "iopub.status.idle": "2026-08-02T22:29:45.913830Z",
     "shell.execute_reply": "2026-08-02T22:29:45.913629Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('a', 5), ('b', 2), ('r', 2)]\n",
      "{'Lugo': ['Ana', 'Eva'], 'León': ['Leo']}\n",
      "primero ['segundo', 'tercero']\n"
     ]
    }
   ],
   "source": [
    "from collections import Counter, defaultdict, deque\n",
    "\n",
    "conteo = Counter(\"abracadabra\")\n",
    "grupos = defaultdict(list)\n",
    "for nombre, ciudad in [(\"Ana\", \"Lugo\"), (\"Leo\", \"León\"), (\"Eva\", \"Lugo\")]:\n",
    "    grupos[ciudad].append(nombre)\n",
    "\n",
    "cola = deque([\"primero\", \"segundo\"])\n",
    "cola.append(\"tercero\")\n",
    "\n",
    "print(conteo.most_common(3))\n",
    "print(dict(grupos))\n",
    "print(cola.popleft(), list(cola))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d2f194f",
   "metadata": {},
   "source": [
    "## Guía de elección\n",
    "\n",
    "| Necesidad | Estructura |\n",
    "|---|---|\n",
    "| Secuencia ordenada y modificable | `list` |\n",
    "| Registro ordenado e inmutable | `tuple` |\n",
    "| Relación clave–valor | `dict` |\n",
    "| Unicidad y pertenencia rápida | `set` |\n",
    "| Cola de doble extremo | `collections.deque` |\n",
    "\n",
    "**Error habitual:** usar una lista como valor predeterminado compartido o copiar una estructura anidada superficialmente sin advertirlo. Veremos el primer caso en funciones.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4911023b",
   "metadata": {},
   "source": [
    "## Práctica · Estadísticas de palabras\n",
    "\n",
    "Implementa `estadisticas(texto)`:\n",
    "\n",
    "1. Normaliza el texto a minúsculas y separa por espacios.\n",
    "2. Devuelve un diccionario con total de palabras, palabras únicas y la más común.\n",
    "3. Ignora comas y puntos sencillos.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "40d8ca6b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.915197Z",
     "iopub.status.busy": "2026-08-02T22:29:45.915089Z",
     "iopub.status.idle": "2026-08-02T22:29:45.916762Z",
     "shell.execute_reply": "2026-08-02T22:29:45.916538Z"
    },
    "tags": [
     "ejercicio"
    ]
   },
   "outputs": [],
   "source": [
    "def estadisticas(texto):\n",
    "    # TODO: limpia, cuenta y devuelve un diccionario.\n",
    "    pass\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "a6ac49b0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-08-02T22:29:45.917960Z",
     "iopub.status.busy": "2026-08-02T22:29:45.917886Z",
     "iopub.status.idle": "2026-08-02T22:29:45.920024Z",
     "shell.execute_reply": "2026-08-02T22:29:45.919809Z"
    },
    "tags": [
     "solucion"
    ]
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'total': 7, 'unicas': 5, 'mas_comun': ('python', 2)}\n"
     ]
    }
   ],
   "source": [
    "from collections import Counter\n",
    "\n",
    "def estadisticas(texto):\n",
    "    limpias = texto.lower().replace(\",\", \"\").replace(\".\", \"\").split()\n",
    "    frecuencias = Counter(limpias)\n",
    "    mas_comun = frecuencias.most_common(1)[0] if limpias else (None, 0)\n",
    "    return {\n",
    "        \"total\": len(limpias),\n",
    "        \"unicas\": len(set(limpias)),\n",
    "        \"mas_comun\": mas_comun,\n",
    "    }\n",
    "\n",
    "resultado = estadisticas(\"Python es claro, y Python es práctico.\")\n",
    "print(resultado)\n",
    "assert resultado == {\"total\": 7, \"unicas\": 5, \"mas_comun\": (\"python\", 2)}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75a61241",
   "metadata": {},
   "source": [
    "            ## Comprobación rápida\n",
    "\n",
    "            - [ ] Puedo justificar qué estructura usar para un problema.\n",
    "- [ ] Sé recorrer claves y valores sin manejar índices.\n",
    "- [ ] Comprendo la diferencia entre alias, copia superficial y profunda.\n",
    "\n",
    "            [← Anterior](02_control_del_flujo.ipynb) · [Volver al índice](README.md) · [Siguiente →](04_funciones.ipynb)\n"
   ]
  }
 ],
 "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.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
