Estructuras de datos
Al completar este módulo serás capaz de:
- Trabajar con listas y tuplas
- Usar conjuntos para operaciones de teoría de conjuntos
- Crear y manipular diccionarios
- Implementar pilas y colas
- Elegir la estructura de datos adecuada para cada problema
¿Por qué necesitamos diferentes estructuras?
Imagina que estás organizando tu cocina. No guardarías todo en un mismo cajón, ¿verdad? Los cubiertos van en un organizador con divisiones, las especias en un estante donde puedas ver las etiquetas, los alimentos frescos en la nevera… Cada tipo de contenedor está diseñado para un propósito específico.
En programación ocurre exactamente lo mismo. Los datos que manejamos tienen diferentes características y necesidades:
- A veces necesitas mantener un orden específico (como los pasos de una receta).
- Otras veces quieres buscar algo rápidamente por su nombre (como en una agenda).
- En ocasiones solo te importa saber si algo existe o no, sin duplicados (como una lista de invitados).
Python te ofrece diferentes “contenedores” para cada situación. Elegir el correcto hará tu código más eficiente y fácil de entender. Vamos a conocerlos uno por uno.
Listas
Piensa en una lista de la compra o en tu playlist de música favorita. Ambas tienen algo en común: el orden importa y puedes modificarlas (añadir canciones, tachar productos, reordenar…).
En Python, una lista funciona exactamente así: es una colección ordenada donde puedes añadir, quitar o modificar elementos cuando quieras.
1# Crear listas
2numeros = [1, 2, 3, 4, 5]
3frutas = ["manzana", "banana", "cereza"]
4mixta = [1, "dos", 3.0, True, None]
5vacia = []Acceso a elementos
Una de las ventajas de las listas es que puedes acceder a cualquier elemento por su posición. Es como decir “dame la canción número 3 de mi playlist”.
1frutas = ["manzana", "banana", "cereza", "durazno"]
2
3# Índices positivos (desde el inicio)
4print(frutas[0]) # manzana
5print(frutas[1]) # banana
6
7# Índices negativos (desde el final)
8print(frutas[-1]) # durazno (último)
9print(frutas[-2]) # cereza (penúltimo)
10
11# Slicing (sublistas)
12print(frutas[1:3]) # ['banana', 'cereza']
13print(frutas[:2]) # ['manzana', 'banana']
14print(frutas[2:]) # ['cereza', 'durazno']
15print(frutas[::2]) # ['manzana', 'cereza'] (saltar de 2 en 2)Métodos útiles de listas
Las listas vienen con un montón de herramientas incorporadas. Piensa en ellas como los controles de tu reproductor de música: puedes añadir canciones, eliminarlas, buscar una específica o reordenar la lista.
1lista = [1, 2, 3]
2
3# append: añade al final
4lista.append(4)
5print(lista) # [1, 2, 3, 4]
6
7# insert: añade en posición específica
8lista.insert(0, 0)
9print(lista) # [0, 1, 2, 3, 4]
10
11# extend: añade múltiples elementos
12lista.extend([5, 6])
13print(lista) # [0, 1, 2, 3, 4, 5, 6] 1lista = [1, 2, 3, 4, 5, 3]
2
3# remove: elimina la primera ocurrencia del valor
4lista.remove(3)
5print(lista) # [1, 2, 4, 5, 3]
6
7# pop: elimina y devuelve elemento por índice
8ultimo = lista.pop() # sin argumento: último
9print(ultimo) # 3
10print(lista) # [1, 2, 4, 5]
11
12primero = lista.pop(0) # índice 0: primero
13print(primero) # 1
14
15# clear: elimina todos los elementos
16lista.clear()
17print(lista) # [] 1lista = [10, 20, 30, 40, 30]
2
3# index: encuentra la posición del elemento
4pos = lista.index(30)
5print(pos) # 2 (primera ocurrencia)
6
7# count: cuenta las ocurrencias
8veces = lista.count(30)
9print(veces) # 2
10
11# in: verifica si existe
12print(20 in lista) # True
13print(50 in lista) # False 1numeros = [3, 1, 4, 1, 5, 9, 2, 6]
2
3# sort: ordena la lista (modifica la original)
4numeros.sort()
5print(numeros) # [1, 1, 2, 3, 4, 5, 6, 9]
6
7# reverse: invierte el orden
8numeros.reverse()
9print(numeros) # [9, 6, 5, 4, 3, 2, 1, 1]
10
11# sorted: devuelve una NUEVA lista ordenada
12original = [3, 1, 4, 1, 5]
13ordenada = sorted(original)
14print(original) # [3, 1, 4, 1, 5] (sin modificar)
15print(ordenada) # [1, 1, 3, 4, 5]Comprensión de listas
Aquí viene uno de los superpoderes de Python. Imagina que quieres crear una lista con los cuadrados de los números del 0 al 9. Podrías hacerlo con un bucle for, pero Python te ofrece una forma mucho más elegante y concisa: la comprensión de listas.
Es como decirle a Python: “Dame una lista con X para cada elemento en Y”. Veamos la diferencia:
1# Forma tradicional
2cuadrados = []
3for x in range(10):
4 cuadrados.append(x ** 2)
5
6# Comprensión de lista (equivalente)
7cuadrados = [x ** 2 for x in range(10)]
8print(cuadrados) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
9
10# Con condición
11pares = [x for x in range(20) if x % 2 == 0]
12print(pares) # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
13
14# Transformación
15palabras = ["hola", "mundo", "python"]
16mayusculas = [p.upper() for p in palabras]
17print(mayusculas) # ['HOLA', 'MUNDO', 'PYTHON']Tuplas
Imagina que guardas las coordenadas GPS de tu casa: latitud 40.4168, longitud -3.7038. Estos números representan una ubicación fija. No tendría sentido que “accidentalmente” cambiaras la latitud mientras tu programa se ejecuta, ¿verdad?
Para estos casos existen las tuplas: son como listas, pero una vez creadas, no se pueden modificar. Esta característica, lejos de ser una limitación, es una ventaja: te garantiza que ciertos datos permanecerán intactos durante toda la ejecución de tu programa.
1# Crear tuplas
2coordenadas = (10, 20)
3colores = ("rojo", "verde", "azul")
4mixta = (1, "dos", 3.0)
5un_elemento = (42,) # La coma es importante
6
7# Las tuplas son inmutables
8# coordenadas[0] = 5 # Error: TypeError
9
10# Desempaquetado
11x, y = coordenadas
12print(f"x={x}, y={y}") # x=10, y=20
13
14# Útil para intercambiar valores
15a, b = 1, 2
16a, b = b, a
17print(a, b) # 2 1- Usa listas cuando necesites modificar los elementos
- Usa tuplas para datos que no deben cambiar (coordenadas, colores RGB, etc.)
- Las tuplas son ligeramente más eficientes en memoria y velocidad
Conjuntos
Imagina que estás organizando una fiesta y haces una lista de invitados. Si alguien te dice “invita a María” dos veces, no la vas a escribir dos veces, ¿verdad? Solo necesitas saber que María está invitada, punto.
Los conjuntos funcionan exactamente así: son colecciones donde no puede haber duplicados. Además, no les importa el orden (¿qué más da si María está primera o última en la lista de invitados?).
Pero lo realmente poderoso de los conjuntos son las operaciones entre ellos. Imagina que tienes la lista de alumnos que estudian Python y la lista de los que estudian Java. Con conjuntos puedes responder fácilmente: ¿Quién estudia ambos? ¿Quién estudia solo Python?
1# Crear conjuntos
2numeros = {1, 2, 3, 4, 5}
3letras = set("hello") # {'h', 'e', 'l', 'o'} - sin duplicados
4vacio = set() # {} crearía un diccionario vacío
5
6# Los conjuntos no tienen orden ni duplicados
7numeros = {3, 1, 4, 1, 5, 9, 2, 6, 5}
8print(numeros) # {1, 2, 3, 4, 5, 6, 9} - orden arbitrario, sin duplicadosOperaciones con conjuntos
Aquí es donde los conjuntos brillan. Siguiendo el ejemplo de los estudiantes:
1python = {"Ana", "Luis", "María", "Carlos"}
2java = {"Luis", "Pedro", "María", "Juan"}
3
4# Unión: elementos en cualquiera de los dos
5print(python | java)
6# {'Ana', 'Luis', 'María', 'Carlos', 'Pedro', 'Juan'}
7
8# Intersección: elementos en ambos
9print(python & java)
10# {'Luis', 'María'}
11
12# Diferencia: en python pero no en java
13print(python - java)
14# {'Ana', 'Carlos'}
15
16# Diferencia simétrica: en uno u otro, pero no ambos
17print(python ^ java)
18# {'Ana', 'Carlos', 'Pedro', 'Juan'}Métodos de conjuntos
1s = {1, 2, 3}
2
3# add: añadir un elemento
4s.add(4)
5print(s) # {1, 2, 3, 4}
6
7# remove: eliminar (error si no existe)
8s.remove(2)
9print(s) # {1, 3, 4}
10
11# discard: eliminar (sin error si no existe)
12s.discard(10) # No hace nada
13
14# Verificar pertenencia (muy rápido)
15print(3 in s) # True1lista_con_duplicados = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
2sin_duplicados = list(set(lista_con_duplicados))
3print(sin_duplicados) # [1, 2, 3, 4]Diccionarios
Piensa en tu agenda de contactos del teléfono. Cuando quieres llamar a alguien, no recorres todos los contactos uno por uno hasta encontrarlo. Simplemente buscas por el nombre y ¡ahí está el número! El nombre es la clave y el teléfono es el valor.
Los diccionarios de Python funcionan exactamente así: almacenan pares de clave → valor. Esto los hace increíblemente rápidos para buscar información: no importa si tienes 10 o 10 millones de contactos, encontrar uno por su nombre es prácticamente instantáneo.
1# Crear diccionarios
2persona = {
3 "nombre": "Ana",
4 "edad": 25,
5 "ciudad": "Madrid"
6}
7
8# Acceder a valores
9print(persona["nombre"]) # Ana
10print(persona.get("edad")) # 25
11print(persona.get("pais", "Desconocido")) # Desconocido (valor por defecto)
12
13# Modificar valores
14persona["edad"] = 26
15persona["pais"] = "España" # Añadir nueva clave
16
17# Eliminar
18del persona["ciudad"]
19valor = persona.pop("pais") # Elimina y devuelveIterar sobre diccionarios
A veces necesitas recorrer todos los elementos de un diccionario. Python te da varias formas de hacerlo según lo que necesites:
1persona = {"nombre": "Ana", "edad": 25, "ciudad": "Madrid"}
2
3# Solo claves
4for clave in persona:
5 print(clave)
6
7# Solo valores
8for valor in persona.values():
9 print(valor)
10
11# Claves y valores
12for clave, valor in persona.items():
13 print(f"{clave}: {valor}")Comprensión de diccionarios
1# Crear diccionario de cuadrados
2cuadrados = {x: x**2 for x in range(6)}
3print(cuadrados) # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
4
5# Invertir un diccionario
6original = {"a": 1, "b": 2, "c": 3}
7invertido = {v: k for k, v in original.items()}
8print(invertido) # {1: 'a', 2: 'b', 3: 'c'}Pilas y colas
Estas dos estructuras son fundamentales en programación y las usamos constantemente sin darnos cuenta. La diferencia entre ellas es simplemente el orden en que salen los elementos.
Pilas (Stack) - LIFO
Imagina una pila de platos recién lavados. Cuando pones un plato limpio, lo colocas arriba de la pila. Cuando necesitas uno, coges el de arriba (el último que pusiste). Esto se llama LIFO: Last In, First Out (último en entrar, primero en salir).
graph LR
subgraph Pila["Pila de elementos"]
direction TB
A["tercero - tope (último en entrar)"]
B["segundo"]
C["primero - fondo"]
end
E["append() agrega al final"] --> A
A --> D["pop() devuelve 'tercero'"]¿Dónde se usan las pilas en la vida real? El botón “atrás” de tu navegador es una pila: cada página que visitas se apila, y al pulsar atrás, sacas la última.
1# Usar una lista como pila
2pila = []
3
4# push: añadir al tope
5pila.append("primero")
6pila.append("segundo")
7pila.append("tercero")
8print(pila) # ['primero', 'segundo', 'tercero']
9
10# pop: sacar del tope
11elemento = pila.pop()
12print(elemento) # tercero
13print(pila) # ['primero', 'segundo']
14
15# peek: ver el tope sin eliminarlo
16if pila:
17 tope = pila[-1]
18 print(tope) # segundoColas (Queue) - FIFO
Ahora imagina la cola del supermercado. El primero que llega es el primero que paga y se va. Esto es FIFO: First In, First Out (primero en entrar, primero en salir).
graph LR
subgraph Cola["Cola de elementos"]
C["tercero"] --> B["segundo"] --> A["primero"]
end
A --> D["sale primero"]
E["entra nuevo"] --> CLas colas se usan en todas partes: la cola de impresión (los documentos se imprimen en el orden en que los mandas), las peticiones a un servidor web, los mensajes en una aplicación de chat…
1from collections import deque
2
3# Crear una cola eficiente
4cola = deque()
5
6# enqueue: añadir al final
7cola.append("primero")
8cola.append("segundo")
9cola.append("tercero")
10print(cola) # deque(['primero', 'segundo', 'tercero'])
11
12# dequeue: sacar del frente
13elemento = cola.popleft()
14print(elemento) # primero
15print(cola) # deque(['segundo', 'tercero'])Aunque podríamos usar lista.pop(0) para colas, deque es mucho más eficiente. pop(0) tiene complejidad O(n) porque debe mover todos los elementos, mientras que popleft() es O(1).
¿Cuál estructura elegir?
Después de ver tantas opciones, es normal preguntarse: “¿Cuándo uso cada una?” Aquí tienes una guía rápida:
| Si necesitas… | Usa… | Ejemplo |
|---|---|---|
| Colección ordenada que puedas modificar | Lista | Playlist, lista de tareas |
| Datos que no deben cambiar | Tupla | Coordenadas, fechas, colores RGB |
| Elementos únicos, sin duplicados | Conjunto | Lista de invitados, tags |
| Buscar valores por una clave | Diccionario | Agenda, configuración, caché |
| Último en entrar, primero en salir | Pila (lista) | Historial de navegador, deshacer |
| Primero en entrar, primero en salir | Cola (deque) | Cola de impresión, mensajes |
Cuando tengas dudas, empieza con una lista. Es la estructura más versátil. Solo cambia a otra cuando tengas una razón específica: necesitas claves (diccionario), garantizar unicidad (conjunto), o inmutabilidad (tupla).
Ejercicios prácticos
Dada una lista, elimina los duplicados pero mantén el orden original de aparición.
1entrada = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
2# Salida esperada: [3, 1, 4, 5, 9, 2, 6]Dado un texto, cuenta cuántas veces aparece cada palabra.
1texto = "el gato y el perro y el pájaro"
2# Salida esperada: {'el': 3, 'gato': 1, 'y': 2, 'perro': 1, 'pájaro': 1}Usa una pila para verificar si los paréntesis están balanceados.
1"((()))" # True
2"(()" # False
3")(" # False