¿Qué Hace que una Lista sea Útil
Listas: Tu Primera Estructura de Datos
Una lista (llamada matriz en algunos idiomas) 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.
- Añadir 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 conservan el orden en que colocas las cosas. El primer elemento sigue siendo el primero. El último sigue siendo el último.
# Una lista de mascotas
pets = ["cat", "dog", "cat", "fish", "dog"]
print(pets[0]) # "cat" — O(1) instantáneo
print(pets[3]) # "fish" — O(1) instantáneo
print(len(pets)) # 5 — se mantienen los duplicados
Nota: "cat" aparece dos veces. "dog" aparece dos veces. Las listas permiten duplicados. Almacenan exactamente lo que les das, en el orden en que se lo diste.
Pero las listas tienen una debilidad. Mira qué sucede cuando preguntas: ¿Está "fish" en esta lista?
# Verificación de pertenencia — ¿Está "fish" en la lista?
if "fish" in pets: # escanea ["cat", "dog", "cat", "fish"...] uno por uno
print("encontrado!") # tuvo que comprobar 4 elementos antes de encontrarlo
El ordenador verifica "cat" — no. "dog" — no. "cat" — no. "fish" — sí. Tuvo que escanear 4 elementos para obtener la respuesta. Con 1.000 elementos, podría escanear todos los 1.000. Esa verificación de pertenencia cuesta O(N).
Predice el Costo de Escaneo
Tienes una lista de 500 nombres de estudiantes en orden aleatorio.
Quieres verificar si "Zara" aparece en la lista.
students = ["Alex", "Maria", ..., "Zara", ...] # 500 nombres
if "Zara" in students:
print("inscrito")
Cómo Funcionan los Conjuntos Diferentemente
Conjuntos: Diseñados para Preguntas de Membresía
Un conjunto almacena elementos únicos utilizando una técnica llamada hasheo. Cuando agregas un elemento, la computadora calcula un hash (una huella numérica) que le dice exactamente dónde almacenar ese elemento.
Cuando verificas la membresía, la computadora calcula el mismo hash y salta directamente al lugar correcto. Sin escaneo.
# Un conjunto de mascotas
mascotas = {"gato", "perro", "gato", "pez", "perro"}
print(mascotas) # {"gato", "perro", "pez"} — eliminados duplicados
print(len(mascotas)) # 3 — solo elementos únicos
Toma nota de tres diferencias de las listas:
1. Sin duplicados. "gato" apareció dos veces en la entrada, pero el conjunto guarda solo una copia.
2. Sin orden garantizado. El conjunto puede imprimir elementos en cualquier disposición.
3. Sin acceso a índice. No se puede escribir mascotas[0] — los conjuntos no tienen posiciones.
El trueque: pierdes el orden y los duplicados. Ganas de O(1) pruebas de membresía — 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 — ¿"pez" está en el conjunto?
if "pez" in mascotas: # hash("pez") → salta al bote → encontrado
print("encontrado!") # verificó exactamente 1 lugar, no 4
Conjunto vs Lista de Membresía
Tienes 500 nombres de estudiantes almacenados en un conjunto en lugar de una lista.
estudiantes = {"Alex", "Maria", ..., "Zara", ...} # 500 nombres
si "Zara" en estudiantes:
imprimir("inscrito")
Comparación Codo a Codo
Hoja de Honorarios de Operaciones
Utiliza una lista cuando necesitas:
- Elementos en un orden específico (una lista de reproducción, una receta, una cola)
- Acceso por posición (items[3])
- Duplicados conservados ("gato" aparece 3 veces = 3 gatos)
Utiliza un conjunto cuando necesitas:
- Pruebas rápidas de pertenencia ("¿He visto esto antes?")
- Eliminación automática de duplicados
- Sin preocupación por el orden
Utiliza ambos cuando necesitas orden Y búsqueda rápida:
vistos = conjunto() # O(1) pruebas de pertenencia
resultado = [] # conserva el orden de inserción
para elemento in datos:
si elemento no en vistos: # O(1) verificación
vistos.agregar(elemento) # O(1) agregar
resultado.append(elemento) # O(1) agregar
# Total: O(N) - una pasada, cada paso O(1)
Este "conjunto + lista" patrón te da lo mejor de ambos: resultados ordenados con detección rápida de duplicados.
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 los correos electrónicos duplicados de una lista de correos, 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 electrónico por persona, en el orden en que se registraron por primera vez.
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: # O(N) escaneo de la lista única
unique.append(email)
# Funciona! Pero: O(N) verificació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()
unique = []
for email in signups:
if email not in seen: # O(1) búsqueda por hash
seen.add(email) # O(1) agregar a conjunto
unique.append(email) # O(1) agregar a lista
# O(1) verificación × N elementos = O(N)
Mismo resultado. Mismo orden. Pero: 10,000 registros ahora requieren ~10,000 verificaciones en lugar de ~50,000,000.
Calcular la Aceleración
La versión solo con lista se ejecuta en O(N²). La versión set+list se ejecuta en O(N).
You have 10,000 email signups to deduplicate.
Encontrar elementos comunes
Problema: encontrar elementos en ambas listas
Tienes dos listas de clases. Necesitas encontrar a los 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: convertir una lista a un conjunto O(N + M)
class_a = ["Alice", "Bob", "Carol", "Diana"]
class_b_set = set(class_b) # O(M) para crear
both = []
for student in class_a: # N iteraciones
if student in class_b_set: # O(1) cada vez
both.append(student)
# O(N + M) - crear el conjunto una vez, revisar N veces en O(1) cada vez
O incluso más simple usando la intersección de conjuntos de Python incorporado:
both = set(class_a) & set(class_b) # O(N + M)
El operador & encuentra todos los elementos que aparecen en ambos conjuntos.
Escribir para la velocidad
Una escuela tiene dos clubs con 200 miembros cada uno. Este código encuentra a los estudiantes en ambos clubs:
club_de_ajedrez = [...] # 200 nombres
club_de_artes = [...] # 200 nombres
clubes_comunes = []
for estudiante in club_de_ajedrez:
if estudiante in club_de_artes:
clubes_comunes.append(estudiante)
El Marco de Decisiones
Tu Marco de Decisiones de Estructuras de Datos
Cada vez que escribes código que almacena una colección de elementos, pregúntate:
1. ¿Necesito elementos en orden? → lista
2. ¿Necesito verificaciones de pertenencia rápidas? → conjunto
3. ¿Necesito ambos, orden y verificaciones rápidas? → conjunto + lista juntas
4. ¿Necesito elementos duplicados? → lista (los conjuntos eliminan duplicados)
---
La clave de esta lección: el mismo programa, resolviendo el mismo problema, puede ejecutarse 1,000× a 1,000,000× más rápido simplemente eligiendo la estructura de datos adecuada. Sin trucos ingeniosos. Sin matemáticas complicadas. Solo preguntar: ¿Qué operaciones realiza mi código con más frecuencia? Luego, elegir la estructura en la que esos costos O(1) en lugar de O(N).
Esta lección cubrió listas y conjuntos. Existen otras estructuras de datos (diccionarios, árboles, pilas) que resuelven otros problemas. El marco de toma de decisiones sigue siendo el mismo: entiende tus operaciones, luego elija la estructura que las haga baratas.
---
¿Quieres ir más allá? Nuestra lección sobre la notación O aborda los tipos de crecimiento y los cálculos de escalabilidad en detalle. Nuestro curso de unhamming cubre el defecto real (MOAD-0001) en el que esta solución exacta de lista a conjunto produjo una aceleración de velocidad de 1,000× en software de producción. Vea [El capítulo perdido: Complejidad algorítmica](/en/un/unhamming_algorithmic_complexity).
Depurá 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 la lista de verificado
checked.append(word)
unique_count += 1
print(unique_count)
El libro tiene 100,000 palabras.