Estructuras de datos

Objetivos del módulo

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
¿Cuándo usar listas vs tuplas?
  • 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 duplicados

Operaciones 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)  # True
Uso práctico: eliminar duplicados
1lista_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 devuelve

Iterar 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)  # segundo

Colas (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"] --> C

Las 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'])
¿Por qué usar deque?

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
Consejo práctico

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

Ejercicio 1: Eliminar duplicados manteniendo orden

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]
 1def eliminar_duplicados(lista):
 2    vistos = set()
 3    resultado = []
 4    for elemento in lista:
 5        if elemento not in vistos:
 6            vistos.add(elemento)
 7            resultado.append(elemento)
 8    return resultado
 9
10entrada = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
11print(eliminar_duplicados(entrada))  # [3, 1, 4, 5, 9, 2, 6]
Ejercicio 2: Contar frecuencia de palabras

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}
1def contar_palabras(texto):
2    palabras = texto.lower().split()
3    frecuencia = {}
4    for palabra in palabras:
5        frecuencia[palabra] = frecuencia.get(palabra, 0) + 1
6    return frecuencia
7
8texto = "el gato y el perro y el pájaro"
9print(contar_palabras(texto))
Ejercicio 3: Paréntesis balanceados

Usa una pila para verificar si los paréntesis están balanceados.

1"((()))"  # True
2"(()"     # False
3")("      # False
 1def parentesis_balanceados(s):
 2    pila = []
 3    for char in s:
 4        if char == '(':
 5            pila.append(char)
 6        elif char == ')':
 7            if not pila:
 8                return False
 9            pila.pop()
10    return len(pila) == 0
11
12print(parentesis_balanceados("((()))"))  # True
13print(parentesis_balanceados("(()"))     # False
14print(parentesis_balanceados(")("))      # False

Quiz

🎮 Quiz: Estructuras de datos

0 / 0
Cargando preguntas...

Anterior: Entorno Virtual Siguiente: Cadenas y fechas