Qué Hace Útil a una Lista
Listas: Tu Primera Estructura de Datos
Una lista (llamada matriz en algunos lenguajes) almacena elementos en un orden específico. Puedes:
- Acceder a cualquier elemento por su posición: names[0] obtiene el primer elemento. O(1) — instantáneo.
- Agregar elementos al final: names.append("Zoe"). O(1) — instantáneo.
- Recorrer cada elemento en orden: for name in names. O(N) — visita cada elemento una vez.
Las listas preservan el orden en que pones las cosas. El primer elemento se mantiene primero. El último se mantiene último.
# Una lista de mascotas
pets = ["gato", "perro", "gato", "pez", "perro"]
print(pets[0]) # "gato" — O(1) instantáneo
print(pets[3]) # "pez" — O(1) instantáneo
print(len(pets)) # 5 — duplicados mantenidos
Observa: "gato" aparece dos veces. "perro" aparece dos veces. Las listas permiten duplicados. Almacenan exactamente lo que les das, en el orden en que lo diste.
Pero las listas tienen una debilidad. Mira qué sucede cuando preguntas: ¿está "pez" en esta lista?
# Verificación de membresía — ¿está "pez" en la lista?
if "pez" in pets: # escanea ["gato", "perro", "gato", "pez"...] uno por uno
print("¡encontrado!") # tuvo que verificar 4 elementos antes de encontrarlo
La computadora verifica "gato" — no. "perro" — no. "gato" — no. "pez" — ¡sí! Escaneó 4 elementos para encontrar la respuesta. Con 1,000 elementos, podría escanear los 1,000. Esa verificación de membresía cuesta O(N).
Predice el Costo del Escaneo
Tienes una lista de 500 nombres de estudiantes en orden aleatorio.
Quieres verificar si "Zara" aparece en la lista.
students = ["Alex", "María", ..., "Zara", ...] # 500 nombres
if "Zara" in students:
print("inscrito")
Cómo Funcionan Diferentemente los Conjuntos
Conjuntos: Construidos para Preguntas de Membresía
Un conjunto almacena elementos únicos usando una técnica llamada hashing (distribución). Cuando agregas un elemento, la computadora calcula un hash (una huella digital numérica) que le dice exactamente dónde almacenar ese elemento.
Cuando verificas la membresía, la computadora calcula el mismo hash & salta directamente al lugar correcto. Sin escaneo.
# Un conjunto de mascotas
pets = {"gato", "perro", "gato", "pez", "perro"}
print(pets) # {"gato", "perro", "pez"} — duplicados removidos
print(len(pets)) # 3 — solo elementos únicos
Observa tres diferencias con las listas:
1. Sin duplicados. "gato" apareció dos veces en la entrada pero el conjunto mantiene solo una copia.
2. Sin orden garantizado. El conjunto podría imprimir elementos en cualquier arreglo.
3. Sin acceso por índice. No puedes escribir pets[0] — los conjuntos no tienen posiciones.
El compromiso: pierdes orden & duplicados. Ganas prueba de membresía O(1) — verificar si un elemento existe toma el mismo tiempo ya sea que el conjunto tenga 5 elementos o 5 millones.
# Verificación de membresía — ¿está "pez" en el conjunto?
if "pez" in pets: # hash("pez") → salta a bucket → encontrado
print("¡encontrado!") # verificó exactamente 1 lugar, no 4
Membresía: Conjunto vs Lista
Tienes 500 nombres de estudiantes almacenados en un conjunto en lugar de una lista.
students = {"Alex", "María", ..., "Zara", ...} # 500 nombres
if "Zara" in students:
print("inscrito")
Comparación Lado a Lado
Hoja de Trucos de Operaciones
Usa una lista cuando necesites:
- Elementos en un orden específico (una lista de reproducción, una receta, una cola)
- Acceso por posición (items[3])
- Duplicados preservados ("gato" aparece 3 veces = 3 gatos)
Usa un conjunto cuando necesites:
- Verificaciones de membresía rápidas ("¿he visto esto antes?")
- Remoción automática de duplicados
- Sin preocupación por el orden
Usa ambos cuando necesites orden & búsqueda rápida:
seen = set() # Verificaciones de membresía O(1)
result = [] # preserva el orden de inserción
for item in data:
if item not in seen: # Verificación O(1)
seen.add(item) # O(1) agregar
result.append(item) # O(1) agregar al final
# Total: O(N) — un pase, cada paso O(1)
Este patrón "conjunto + lista" te da lo mejor de ambos: resultados ordenados con detección de duplicados rápida.
Elige la Estructura Correcta
Tres problemas. Para cada uno, decide: ¿lista, conjunto, o ambos?
1. Necesitas almacenar una lista de reproducción de canciones en el orden en que el usuario las agregó.
2. Necesitas verificar rápidamente si un nombre de usuario ya ha sido tomado.
3. Necesitas eliminar correos electrónicos duplicados de una lista de correo pero mantenerlos en el orden en que se registraron.
El Problema de Deduplicación
Problema: Eliminar Duplicados, Mantener Orden
Recibes una lista de registros de correo electrónico. Algunas personas se registraron dos veces. Necesitas enviar un correo por persona, en el orden en que se registraron primero.
Intento 1: solo lista (lento)
signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
unique = []
for email in signups:
if email not in unique: # Escaneo O(N) de lista única
unique.append(email)
# ¡Funciona! Pero: Verificación O(N) × N elementos = O(N²)
Con 100 registros, esto hace ~5,000 verificaciones. Con 10,000 registros: ~50,000,000 verificaciones.
Intento 2: conjunto + lista (rápido)
signups = ["alice@a.com", "bob@b.com", "alice@a.com", "carol@c.com"]
seen = set() # Verificaciones de membresía O(1)
unique = []
for email in signups:
if email not in seen: # Búsqueda de hash O(1)
seen.add(email) # O(1) agregar al conjunto
unique.append(email) # O(1) agregar a la lista
# Verificación O(1) × N elementos = O(N)
Mismo resultado. Mismo orden. Pero: 10,000 registros ahora requiere ~10,000 verificaciones en lugar de ~50,000,000.
Calcula la Aceleración
La versión solo lista se ejecuta en O(N²). La versión conjunto+lista se ejecuta en O(N).
Tienes 10,000 registros de correo electrónico para deduplicar.
Encontrando Elementos Comunes
Problema: Encuentra Elementos en Ambas Listas
Tienes dos listas de clases. Necesitas encontrar estudiantes inscritos en ambas clases.
Enfoque lento: bucles anidados O(N × M)
class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b = ["Bob", "Diana", "Eve", "Frank"]
both = []
for student in class_a: # N iteraciones
if student in class_b: # M escaneos cada vez
both.append(student)
# O(N × M) — cada estudiante escaneado contra todos de class_b
Enfoque rápido: convierte una lista a conjunto O(N + M)
class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b) # O(M) para construir
both = []
for student in class_a: # N iteraciones
if student in class_b_set: # O(1) cada vez
both.append(student)
# O(N + M) — construye el conjunto una vez, verifica N veces en O(1) cada una
O incluso más simple usando la intersección de conjuntos integrada de Python:
both = set(class_a) & set(class_b) # O(N + M)
El operador & encuentra todos los elementos que aparecen en ambos conjuntos.
Reescribe para Velocidad
Una escuela tiene dos clubes con 200 miembros cada uno. Este código encuentra estudiantes en ambos clubes:
chess_club = [...] # 200 nombres
art_club = [...] # 200 nombres
both_clubs = []
for student in chess_club:
if student in art_club:
both_clubs.append(student)
El Marco de Decisión
Tu Marco de Decisión de Estructura de Datos
Cada vez que escribas código que almacena una colección de elementos, pregunta:
1. ¿Necesito elementos en orden? → lista
2. ¿Necesito verificaciones de membresía rápidas? → conjunto
3. ¿Necesito tanto orden como verificaciones rápidas? → conjunto + lista juntos
4. ¿Necesito duplicados? → lista (los conjuntos eliminan duplicados)
---
La idea clave de esta lección: el mismo programa, resolviendo el mismo problema, puede ejecutarse 1,000× a 1,000,000× más rápido solo eligiendo la estructura de datos correcta. Sin trucos inteligentes. Sin matemáticas complicadas. Solo preguntando: ¿qué operaciones hace mi código más a menudo? Luego elige la estructura donde esas operaciones cuestan O(1) en lugar de O(N).
Esta lección cubrió listas & conjuntos. Existen otras estructuras de datos (diccionarios, árboles, pilas) que resuelven otros problemas. El marco de decisión sigue siendo el mismo: entiende tus operaciones, luego elige la estructura que las hace económicas.
---
¿Quieres profundizar? Nuestra lección Big O cubre tasas de crecimiento & cálculos de escalado en detalle. Ve Big O: ¿Qué Tan Rápido Es Lo Suficientemente Rápido?. Nuestro curso unhamming cubre el defecto del mundo real (MOAD-0001) donde este arreglo exacto de lista a conjunto produjo una aceleración 1,000× en software de producción. Ve El Capítulo Faltante: Complejidad Algorítmica.
Depura Este Código
Un estudiante escribió este código para contar cuántas palabras únicas aparecen en un libro:
words = book.split() # lista de todas las palabras
unique_count = 0
checked = []
for word in words: # N palabras
if word not in checked: # escanea lista checked
checked.append(word)
unique_count += 1
print(unique_count)
El libro tiene 100,000 palabras.