Estructuras combinadas
Estructuras de Datos Combinadas y Anidadas en Python
Por qué combinar estructuras de datos
En la vida real los datos rara vez son simples. Un alumno no es solo un nombre, es un conjunto de datos relacionados: nombre, edad, lista de notas, diccionario de asignaturas, ciudad. Una empresa no es solo una lista de empleados, cada empleado tiene departamento, salario, habilidades y proyectos asignados.
Las estructuras simples por sí solas se quedan cortas para representar esta complejidad. Combinarlas permite modelar el mundo real de forma natural y eficiente.
# Datos simples — limitados
nombre = "Ana"
notas = [8.5, 9.0, 7.8]
# Datos combinados — representan la realidad
alumno = {
"nombre": "Ana",
"notas": [8.5, 9.0, 7.8], # lista dentro de diccionario
"asignaturas": { # diccionario dentro de diccionario
"Matemáticas": 9.0,
"Historia": 7.5
},
"habilidades": {"Python", "Excel"} # conjunto dentro de diccionario
}
1. Lista de diccionarios
Es la combinación más frecuente en Python. Representa una tabla de datos donde cada diccionario es una fila y sus claves son las columnas.
Cuándo usarla
Cuando tienes varios registros del mismo tipo: alumnos, productos, empleados, ventas.
alumnos = [
{"nombre": "Ana García", "edad": 22, "nota": 8.5, "ciudad": "Madrid"},
{"nombre": "Carlos López", "edad": 20, "nota": 6.0, "ciudad": "Barcelona"},
{"nombre": "María Ruiz", "edad": 21, "nota": 9.2, "ciudad": "Valencia"},
{"nombre": "Pedro Sanz", "edad": 23, "nota": 5.0, "ciudad": "Madrid"},
]
Cómo recorrerla
# Recorrer todos los registros
for alumno in alumnos:
print(f"{alumno['nombre']}: {alumno['nota']}")
# Acceder a un campo específico de todos
nombres = [a["nombre"] for a in alumnos]
print(nombres) # ['Ana García', 'Carlos López', 'María Ruiz', 'Pedro Sanz']
# Filtrar por condición
aprobados = [a for a in alumnos if a["nota"] >= 5]
print(len(aprobados)) # 4
# De Madrid
de_madrid = [a for a in alumnos if a["ciudad"] == "Madrid"]
for a in de_madrid:
print(a["nombre"]) # Ana García, Pedro Sanz
# Ordenar por nota descendente
por_nota = sorted(alumnos, key=lambda a: a["nota"], reverse=True)
for a in por_nota:
print(f"{a['nombre']:<15} {a['nota']}")
Buscar y modificar
# Buscar por nombre
def buscar_alumno(alumnos, nombre):
for alumno in alumnos:
if alumno["nombre"].lower() == nombre.lower():
return alumno
return None
encontrado = buscar_alumno(alumnos, "ana garcía")
if encontrado:
print(f"Encontrado: {encontrado}")
# Modificar un campo
for alumno in alumnos:
if alumno["nombre"] == "Carlos López":
alumno["nota"] = 7.0
break
# Añadir campo a todos
for alumno in alumnos:
alumno["aprobado"] = alumno["nota"] >= 5
Agregar datos
# Media de notas
media = sum(a["nota"] for a in alumnos) / len(alumnos)
print(f"Media de la clase: {media:.2f}")
# Agrupar por ciudad
from collections import defaultdict
por_ciudad = defaultdict(list)
for alumno in alumnos:
por_ciudad[alumno["ciudad"]].append(alumno["nombre"])
for ciudad, nombres in sorted(por_ciudad.items()):
print(f"{ciudad}: {', '.join(nombres)}")
# Barcelona: Carlos López
# Madrid: Ana García, Pedro Sanz
# Valencia: María Ruiz
2. Diccionario de listas
Agrupa varios valores bajo una misma clave. Útil para registros históricos, clasificaciones o cualquier relación uno-a-muchos.
Cuándo usarla
Cuando una entidad tiene múltiples valores del mismo tipo: un alumno con varias notas, una ciudad con varios habitantes, un mes con varias ventas.
notas_por_alumno = {
"Ana": [8.5, 9.0, 7.8, 9.2],
"Carlos": [4.8, 5.5, 6.0],
"María": [9.5, 8.7, 9.8, 9.1],
}
Cómo recorrerla
# Recorrer clave y lista
for alumno, notas in notas_por_alumno.items():
media = sum(notas) / len(notas)
print(f"{alumno:<10} notas: {notas} media: {media:.2f}")
# Acceder a las notas de uno en concreto
print(notas_por_alumno["Ana"]) # [8.5, 9.0, 7.8, 9.2]
print(notas_por_alumno["Ana"][0]) # 8.5 ← primera nota
print(notas_por_alumno["Ana"][-1]) # 9.2 ← última nota
# Añadir nota a un alumno
notas_por_alumno["Ana"].append(8.0)
# Añadir alumno nuevo
notas_por_alumno["Pedro"] = [7.5, 6.8]
# Media de cada alumno
medias = {
alumno: round(sum(notas) / len(notas), 2)
for alumno, notas in notas_por_alumno.items()
}
print(medias)
# {'Ana': 8.7, 'Carlos': 5.43, 'María': 9.28, 'Pedro': 7.15}
# Alumno con mejor media
mejor = max(medias, key=medias.get)
print(f"Mejor alumno: {mejor} ({medias[mejor]})")
3. Diccionario de diccionarios
Representa entidades con múltiples atributos identificadas por una clave. Es como una base de datos en miniatura.
Cuándo usarla
Cuando necesitas acceder rápidamente a un registro completo por su identificador y cada registro tiene varios campos.
empleados = {
"E001": {
"nombre": "Ana García",
"departamento": "Desarrollo",
"salario": 35000,
"habilidades": ["Python", "JavaScript", "SQL"],
},
"E002": {
"nombre": "Carlos López",
"departamento": "Marketing",
"salario": 28000,
"habilidades": ["Excel", "PowerBI", "Photoshop"],
},
"E003": {
"nombre": "María Ruiz",
"departamento": "Desarrollo",
"salario": 38000,
"habilidades": ["Python", "Django", "PostgreSQL"],
},
}
Cómo recorrerla
# Recorrer todos
for codigo, datos in empleados.items():
print(f"{codigo}: {datos['nombre']} — {datos['departamento']}")
# Acceder a un empleado por código
emp = empleados["E001"]
print(emp["nombre"]) # Ana García
print(emp["habilidades"][0]) # Python ← primer elemento de la lista interna
# Acceso directo encadenado
print(empleados["E003"]["salario"]) # 38000
# Filtrar por departamento
desarrollo = {
codigo: datos
for codigo, datos in empleados.items()
if datos["departamento"] == "Desarrollo"
}
for codigo, datos in desarrollo.items():
print(f" {datos['nombre']}")
# Ana García
# María Ruiz
# Modificar un campo
empleados["E001"]["salario"] = 37000
# Añadir campo a todos
for datos in empleados.values():
datos["activo"] = True
# Calcular salario medio
salario_medio = sum(e["salario"] for e in empleados.values()) / len(empleados)
print(f"Salario medio: {salario_medio:.2f} €")
4. Lista de listas — matrices
Representa datos en dos dimensiones: filas y columnas. Es la estructura natural para tablas, cuadrículas y matrices matemáticas.
Cuándo usarla
Para representar tableros de juego, matrices matemáticas, horarios, mapas de celdas o cualquier dato con estructura de fila×columna.
# Tabla de notas: filas=alumnos, columnas=asignaturas
tabla_notas = [
[8.5, 9.0, 7.8], # Ana
[4.8, 5.5, 6.0], # Carlos
[9.5, 8.7, 9.8], # María
]
asignaturas = ["Matemáticas", "Historia", "Física"]
alumnos = ["Ana", "Carlos", "María"]
Cómo recorrerla
# Recorrer toda la matriz
for i, fila in enumerate(tabla_notas):
for j, nota in enumerate(fila):
print(f"{alumnos[i]:<10} {asignaturas[j]:<15} {nota}")
# Acceder por fila y columna
print(tabla_notas[0][1]) # 9.0 ← Ana, Historia
print(tabla_notas[2][2]) # 9.8 ← María, Física
# Media por alumno (media de cada fila)
for i, fila in enumerate(tabla_notas):
media = sum(fila) / len(fila)
print(f"{alumnos[i]}: {media:.2f}")
# Media por asignatura (media de cada columna)
for j, asig in enumerate(asignaturas):
columna = [tabla_notas[i][j] for i in range(len(tabla_notas))]
media = sum(columna) / len(columna)
print(f"{asig}: {media:.2f}")
# Transponer la matriz (filas ↔ columnas)
transpuesta = [
[tabla_notas[i][j] for i in range(len(tabla_notas))]
for j in range(len(tabla_notas[0]))
]
5. Diccionario de conjuntos
Asocia una clave a un grupo de valores únicos. Ideal para relaciones muchos-a-muchos donde no importan los duplicados.
Cuándo usarla
Para almacenar etiquetas, permisos, categorías, amistades o cualquier relación donde el orden no importa y no puede haber repetidos.
# Asignaturas por alumno (sin duplicados, sin orden)
asignaturas_alumno = {
"Ana": {"Matemáticas", "Física", "Historia", "Lengua"},
"Carlos": {"Física", "Química", "Historia", "Arte"},
"María": {"Matemáticas", "Lengua", "Música", "Arte"},
}
Cómo recorrerla y operar
# Recorrer
for alumno, asigs in asignaturas_alumno.items():
print(f"{alumno}: {sorted(asigs)}")
# Asignaturas en común entre dos alumnos
comunes = asignaturas_alumno["Ana"] & asignaturas_alumno["Carlos"]
print(f"En común: {comunes}") # {'Física', 'Historia'}
# Todas las asignaturas que hay entre todos
todas = set()
for asigs in asignaturas_alumno.values():
todas |= asigs
print(f"Todas: {sorted(todas)}")
# Añadir asignatura a un alumno
asignaturas_alumno["Ana"].add("Música")
# Alumnos que tienen una asignatura concreta
def alumnos_en_asignatura(registro, asignatura):
return [alumno for alumno, asigs in registro.items()
if asignatura in asigs]
print(alumnos_en_asignatura(asignaturas_alumno, "Arte"))
# ['Carlos', 'María']
6. Estructura compleja — combinación de todo
En proyectos reales las estructuras se anidan a varios niveles. Este ejemplo modela una empresa con departamentos, empleados, proyectos y habilidades.
empresa = {
"nombre": "TechCorp",
"departamentos": {
"Desarrollo": {
"jefe": "María Ruiz",
"empleados": [
{
"id": "E001",
"nombre": "Ana García",
"salario": 35000,
"habilidades": {"Python", "Django", "SQL"},
"proyectos": ["WebApp", "API REST"],
"notas_evaluacion": [8, 9, 8, 9],
},
{
"id": "E002",
"nombre": "Carlos López",
"salario": 32000,
"habilidades": {"JavaScript", "React", "CSS"},
"proyectos": ["WebApp", "Dashboard"],
"notas_evaluacion": [7, 8, 7, 6],
},
],
"presupuesto": 150000,
},
"Marketing": {
"jefe": "Pedro Sanz",
"empleados": [
{
"id": "E003",
"nombre": "Lucía Mora",
"salario": 28000,
"habilidades": {"PowerBI", "Excel", "Photoshop"},
"proyectos": ["Campaña Q1", "Redes Sociales"],
"notas_evaluacion": [9, 9, 8, 9],
},
],
"presupuesto": 80000,
},
}
}
Cómo recorrer esta estructura compleja
# Recorrer todos los empleados de todos los departamentos
def todos_los_empleados(empresa):
empleados = []
for depto, datos in empresa["departamentos"].items():
for empleado in datos["empleados"]:
empleado_con_depto = dict(empleado) # copia
empleado_con_depto["departamento"] = depto
empleados.append(empleado_con_depto)
return empleados
for emp in todos_los_empleados(empresa):
media = sum(emp["notas_evaluacion"]) / len(emp["notas_evaluacion"])
print(f"{emp['nombre']:<15} [{emp['departamento']}] "
f"evaluación media: {media:.1f}")
# Encontrar empleados con una habilidad concreta
def buscar_por_habilidad(empresa, habilidad):
encontrados = []
for depto, datos in empresa["departamentos"].items():
for emp in datos["empleados"]:
if habilidad in emp["habilidades"]:
encontrados.append((emp["nombre"], depto))
return encontrados
pythonistas = buscar_por_habilidad(empresa, "Python")
print(f"Empleados con Python: {pythonistas}")
# [('Ana García', 'Desarrollo')]
# Calcular el coste total de salarios
def coste_total_salarios(empresa):
return sum(
emp["salario"]
for datos in empresa["departamentos"].values()
for emp in datos["empleados"]
)
print(f"Coste total: {coste_total_salarios(empresa):,} €")
# Todos los proyectos activos sin duplicados
def proyectos_activos(empresa):
return set(
proyecto
for datos in empresa["departamentos"].values()
for emp in datos["empleados"]
for proyecto in emp["proyectos"]
)
print(f"Proyectos activos: {proyectos_activos(empresa)}")
# {'WebApp', 'API REST', 'Dashboard', 'Campaña Q1', 'Redes Sociales'}
7. Técnicas para manejar estructuras anidadas
Acceso seguro con .get() en múltiples niveles
datos = {
"usuario": {
"perfil": {
"ciudad": "Madrid"
}
}
}
# Acceso directo — falla si algún nivel no existe
try:
print(datos["usuario"]["perfil"]["pais"]) # KeyError
except KeyError:
print("Clave no encontrada")
# Acceso seguro encadenando .get()
ciudad = datos.get("usuario", {}).get("perfil", {}).get("ciudad", "Desconocida")
pais = datos.get("usuario", {}).get("perfil", {}).get("pais", "Desconocido")
print(ciudad) # Madrid
print(pais) # Desconocido ← sin error
Función de acceso profundo genérica
def obtener_profundo(diccionario, *claves, defecto=None):
"""Accede a cualquier nivel de profundidad de forma segura."""
actual = diccionario
for clave in claves:
if isinstance(actual, dict):
actual = actual.get(clave, defecto)
elif isinstance(actual, list) and isinstance(clave, int):
actual = actual[clave] if 0 <= clave < len(actual) else defecto
else:
return defecto
return actual
# Uso
print(obtener_profundo(empresa, "departamentos", "Desarrollo", "jefe"))
# María Ruiz
print(obtener_profundo(empresa, "departamentos", "Desarrollo",
"empleados", 0, "nombre"))
# Ana García
print(obtener_profundo(empresa, "departamentos", "RRHH", "jefe",
defecto="Departamento no existe"))
# Departamento no existe
Aplanar estructuras anidadas
# Lista de listas → lista plana
def aplanar_lista(lista_anidada):
resultado = []
for elemento in lista_anidada:
if isinstance(elemento, list):
resultado.extend(aplanar_lista(elemento))
else:
resultado.append(elemento)
return resultado
anidada = [1, [2, 3], [4, [5, 6]], 7]
print(aplanar_lista(anidada)) # [1, 2, 3, 4, 5, 6, 7]
# Diccionario anidado → diccionario plano
def aplanar_dict(d, prefijo="", separador="."):
resultado = {}
for clave, valor in d.items():
clave_nueva = f"{prefijo}{separador}{clave}" if prefijo else clave
if isinstance(valor, dict):
resultado.update(aplanar_dict(valor, clave_nueva, separador))
else:
resultado[clave_nueva] = valor
return resultado
config = {
"base_datos": {
"host": "localhost",
"puerto": 5432,
"nombre": "midb"
},
"app": {
"debug": True,
"puerto": 8000
}
}
plano = aplanar_dict(config)
for k, v in plano.items():
print(f"{k}: {v}")
# base_datos.host: localhost
# base_datos.puerto: 5432
# base_datos.nombre: midb
# app.debug: True
# app.puerto: 8000
Transformar estructuras
# Lista de diccionarios → diccionario de diccionarios
alumnos_lista = [
{"id": "A001", "nombre": "Ana", "nota": 8.5},
{"id": "A002", "nombre": "Carlos", "nota": 6.0},
]
alumnos_dict = {a["id"]: a for a in alumnos_lista}
print(alumnos_dict["A001"]["nombre"]) # Ana
# Diccionario de diccionarios → lista de diccionarios
alumnos_lista_nueva = list(alumnos_dict.values())
# Diccionario de listas → lista de diccionarios
notas_por_alumno = {
"Ana": [8.5, 9.0, 7.8],
"Carlos": [6.0, 5.5, 7.0],
}
lista_alumnos = [
{"nombre": nombre, "notas": notas,
"media": round(sum(notas)/len(notas), 2)}
for nombre, notas in notas_por_alumno.items()
]
for a in lista_alumnos:
print(f"{a['nombre']}: {a['media']}")
8. Ejemplo completo — sistema académico
Este ejemplo integra todas las combinaciones vistas en un sistema real:
from collections import defaultdict
# ── Estructura de datos principal ──────────────────────
centro = {
"nombre": "IES Python",
"cursos": {
"1DAW": {
"tutor": "Prof. García",
"alumnos": [
{
"id": "A001",
"nombre": "Ana López",
"notas": {
"Programación": [8.5, 9.0, 8.8],
"Bases de Datos": [9.0, 8.5, 9.2],
"Sistemas": [7.5, 8.0, 7.8],
},
"habilidades": {"Python", "SQL", "Linux"},
},
{
"id": "A002",
"nombre": "Carlos Ruiz",
"notas": {
"Programación": [6.0, 5.5, 7.0],
"Bases de Datos": [4.8, 5.0, 5.5],
"Sistemas": [7.0, 6.5, 7.5],
},
"habilidades": {"HTML", "CSS"},
},
],
},
"2DAW": {
"tutor": "Prof. Martínez",
"alumnos": [
{
"id": "A003",
"nombre": "María Sanz",
"notas": {
"Desarrollo Web": [9.5, 9.0, 9.8],
"Despliegue": [8.5, 9.0, 8.8],
"Empresa": [8.0, 7.5, 8.5],
},
"habilidades": {"Python", "Django", "Docker", "SQL"},
},
],
},
}
}
# ── Funciones de análisis ───────────────────────────────
def media_alumno(alumno):
"""Calcula la media global de todas las notas de un alumno."""
todas = [
nota
for notas_asig in alumno["notas"].values()
for nota in notas_asig
]
return round(sum(todas) / len(todas), 2) if todas else 0
def informe_curso(centro, nombre_curso):
"""Genera el informe completo de un curso."""
curso = centro["cursos"].get(nombre_curso)
if not curso:
print(f"Curso {nombre_curso} no encontrado.")
return
print(f"\n{'='*55}")
print(f" CURSO: {nombre_curso} | Tutor: {curso['tutor']}")
print(f"{'='*55}")
medias = []
for alumno in curso["alumnos"]:
media = media_alumno(alumno)
medias.append((alumno["nombre"], media))
estado = "✓" if media >= 5 else "✗"
print(f"\n {estado} {alumno['nombre']} (media: {media})")
for asig, notas in alumno["notas"].items():
media_asig = round(sum(notas) / len(notas), 2)
print(f" {asig:<20} {notas} → {media_asig}")
print(f" Habilidades: {sorted(alumno['habilidades'])}")
print(f"\n {'─'*50}")
print(f" Media del curso: "
f"{sum(m for _, m in medias) / len(medias):.2f}")
mejor = max(medias, key=lambda x: x[1])
print(f" Mejor alumno: {mejor[0]} ({mejor[1]})")
def buscar_por_habilidad(centro, habilidad):
"""Encuentra todos los alumnos con una habilidad concreta."""
resultado = []
for curso_nombre, curso in centro["cursos"].items():
for alumno in curso["alumnos"]:
if habilidad.lower() in {h.lower() for h in alumno["habilidades"]}:
resultado.append({
"alumno": alumno["nombre"],
"curso": curso_nombre
})
return resultado
def estadisticas_globales(centro):
"""Estadísticas de todo el centro."""
todos_alumnos = [
alumno
for curso in centro["cursos"].values()
for alumno in curso["alumnos"]
]
todas_habilidades = set()
for alumno in todos_alumnos:
todas_habilidades |= alumno["habilidades"]
medias = [media_alumno(a) for a in todos_alumnos]
aprobados = sum(1 for m in medias if m >= 5)
print(f"\n{'='*45}")
print(f" ESTADÍSTICAS GLOBALES — {centro['nombre']}")
print(f"{'='*45}")
print(f" Total alumnos: {len(todos_alumnos)}")
print(f" Total cursos: {len(centro['cursos'])}")
print(f" Tasa de aprobados: {aprobados}/{len(todos_alumnos)}")
print(f" Media global: {sum(medias)/len(medias):.2f}")
print(f" Habilidades totales: {sorted(todas_habilidades)}")
# ── Ejecución ──────────────────────────────────────────
informe_curso(centro, "1DAW")
informe_curso(centro, "2DAW")
pythonistas = buscar_por_habilidad(centro, "python")
print(f"\nAlumnos con Python:")
for p in pythonistas:
print(f" {p['alumno']} ({p['curso']})")
estadisticas_globales(centro)
```
Salida:
```
=======================================================
CURSO: 1DAW | Tutor: Prof. García
=======================================================
✓ Ana López (media: 8.72)
Programación [8.5, 9.0, 8.8] → 8.77
Bases de Datos [9.0, 8.5, 9.2] → 8.9
Sistemas [7.5, 8.0, 7.8] → 7.77
Habilidades: ['Linux', 'Python', 'SQL']
✓ Carlos Ruiz (media: 6.09)
Programación [6.0, 5.5, 7.0] → 6.17
Bases de Datos [4.8, 5.0, 5.5] → 5.1
Sistemas [7.0, 6.5, 7.5] → 7.0
Habilidades: ['CSS', 'HTML']
──────────────────────────────────────────────────
Media del curso: 7.41
Mejor alumno: Ana López (8.72)
Alumnos con Python:
Ana López (1DAW)
María Sanz (2DAW)
=============================================
ESTADÍSTICAS GLOBALES — IES Python
=============================================
Total alumnos: 3
Total cursos: 2
Tasa de aprobados: 3/3
Media global: 8.03
Habilidades totales: ['CSS', 'Django', 'Docker', 'HTML', 'Linux', 'Python', 'SQL']
Resumen de cuándo usar cada combinación
| Combinación | Cuándo usarla | Ejemplo real |
|---|---|---|
| Lista de diccionarios | Varios registros del mismo tipo | Alumnos, productos, ventas |
| Diccionario de listas | Una entidad con múltiples valores históricos | Notas por alumno, ventas por mes |
| Diccionario de diccionarios | Entidades identificables con múltiples atributos | Empleados por ID, configuración |
| Lista de listas | Datos en cuadrícula o tabla | Matriz, horario, tablero |
| Diccionario de conjuntos | Relaciones muchos-a-muchos sin duplicados | Permisos, etiquetas, asignaturas |
| Anidamiento profundo | Modelos de datos complejos del mundo real | Empresa, sistema académico, API |