Los ejercicios 1-4 se hacen en un script suelto (
structures_lab.py); el 5-6, en tu TicketFlow. No mires solutions.md hasta entregar.
Preparación:
cd ~/dev/ticketflow && source .venv/bin/activateEjercicio 1 — La prueba de la lista vs el set
- Construye una lista de 100.000 IDs y mide:
import time, random
ids = list(range(100_000))
objetivo = random.sample(ids, 1000)
t0 = time.perf_counter()
encontrados = [x for x in objetivo if x in ids] # ¿O(?) por búsqueda
t1 = time.perf_counter()
print(f"lista: {t1 - t0:.4f}s")- Repite con
ids_set = set(ids). Anota ambos tiempos y la aceleración. - ¿Cuándo NO compensa el set? (pista: construir el set es O(n); ¿cuántas búsquedas necesitas para amortizarlo?)
Ejercicio 2 — Agrupar y contar (tu disponibilidad)
Dada esta lista de asientos simulados:
asientos = [
{"sector": "A", "fila": "1", "numero": n, "tipo": "VIP" if n < 5 else "GENERAL"}
for n in range(1, 51)
] + [
{"sector": "B", "fila": "1", "numero": n, "tipo": "GENERAL"}
for n in range(1, 31)
]- Agrupa por sector con
defaultdict(list)y devuelve{sector: cantidad}. - Cuenta por tipo con
Counter. - Calcula el asiento de menor número libre por sector... y luego ordena los sectores por "hueco más pequeño" (pista:
heapqomin+sorted; ¿cuál es más limpio aquí y por qué?)
Ejercicio 3 — Top-K de expiraciones
- Genera 10.000 reservas con
expires_ataleatorios. Conheapq, extrae las 10 que antes expiran en O(n log k). - Compara con
sorted(reservas, key=...)\[:10]y mide ambos. ¿Gana el heap? ¿Merece la diferencia la complejidad de lectura? - Ahora escribe la query SQL/Django equivalente (
Reservation.objects.filter(status=...).order_by("expires_at")\[:10]) y compara el enfoque: ¿dónde vive cada costo (memoria del proceso vs BD + red)?
Ejercicio 4 — El bloque contiguo (dos punteros)
Un usuario quiere 4 asientos juntos en la fila "A" del sector "B". Los asientos libres de la fila son: [3, 4, 5, 6, 12, 13, 20, 21, 22, 23, 24].
- Escribe una función
mejor_bloque(numeros_libres: list[int], k: int) -> list[int] | Noneque devuelva el primer bloque de k consecutivos (o None). Debe ser O(n) — dos punteros o un solo pase; prohibido el doble bucle. - Pruébala:
mejor_bloque([3,4,5,6,12,13,20,21,22,23,24], 4)→[20,21,22,23]ymejor_bloque([3,4,20], 3)→None. - ¿Qué cambia si "mejor" significa "el más centrado en la fila" y no "el primero"?
Ejercicio 5 — La complejidad oculta de tu API
Abre tu available_seats(event_id) (la escribiste en la 00b):
- Cuenta cuántas queries ejecuta con 3 eventos, 500 asientos cada uno. ¿Es 1, n o n²? (pista:
from django.db import connection; print(len(connection.queries))). - Si hiciera N+1, arréglalo con
select_related/prefetch_relatedo con la subconsulta que ya viste en las soluciones de la 00b. Mide antes y después. - La respuesta agrupa por sector (Ejercicio 2): ¿dónde sería más caro agrupar, en Python o en SQL (
GROUP BY)? Justifica con el tamaño esperado de la respuesta y el ancho de banda.
Ejercicio 6 — Análisis Big-O de tu serializer (lectura crítica)
Coge cualquier serializer de DRF de tu proyecto (o escribe uno que valide 10 campos):
- ¿Cuántas veces se valida cada campo? ¿Qué coste tiene
validate()global? - DRF hace
to_representationpor objeto: si un listado serializa 200 eventos y cada uno accede aevent.organizer.namesinselect_related, ¿qué complejidad de queries tiene la respuesta completa? - Escribe la regla que usarás desde hoy: "cada O(n) en memoria que acompaña a un acceso por objeto debe comprobarse contra ___".
Entrega
Pega resultados, mediciones y diffs en el chat. Con la corrección cerramos la 04 y pasamos a la Lección 05 — Linux y terminal: los procesos que acabas de multiplicar en Gunicorn, desde la consola que los opera.