Datos anidados



# ─────────────────────────────────────────────
# DICCIONARIOS CON LISTAS DENTRO
# ─────────────────────────────────────────────
# Un valor de un diccionario puede ser cualquier tipo:
# un número, una cadena, una lista, otro diccionario…

alumno = {
    'nombre': 'Ana',
    'email':  'ana@ana.com',
    'notas':  [6, 7, 5, 8]      # ← el valor es una lista
}

# Para acceder a la lista usamos la clave 'notas'
# y luego la recorremos con un for como cualquier otra lista
print(f"El alumno {alumno['nombre']} tiene las siguientes notas:")
for nota in alumno['notas']:
    print(nota)

# 💡 ALTERNATIVA: calcular la media al vuelo
notas = alumno['notas']
print(f"Media: {sum(notas) / len(notas):.1f}")  # :.1f → un decimal


# ─────────────────────────────────────────────
# DICCIONARIOS ANIDADOS (diccionario dentro de diccionario)
# ─────────────────────────────────────────────
# Un valor también puede ser otro diccionario.
# Para acceder a sus datos encadenamos corchetes: d['clave1']['clave2']

cliente = {
    'nombre': 'Ana',
    'email':  'ana@gmail.com',
    'direccion': {                  # ← valor que es otro diccionario
        'calle':   'Agla',
        'numero':  6,
        'cp':      '08001',
        'ciudad':  'Barcelona'
    }
}

# Acceso en cadena: primero entramos en 'direccion', luego en 'calle' y 'numero'
print(f"El cliente {cliente['nombre']} vive en "
      f"{cliente['direccion']['calle']} número {cliente['direccion']['numero']}")
# → El cliente Ana vive en Agla número 6

# 💡 ALTERNATIVA: extraer el sub-diccionario en una variable auxiliar
#    para no repetir cliente['direccion'] varias veces
dir = cliente['direccion']
print(f"{dir['calle']}, {dir['numero']}, {dir['cp']} {dir['ciudad']}")


# ─────────────────────────────────────────────
# LISTA DE DICCIONARIOS
# ─────────────────────────────────────────────
# Patrón muy habitual: una lista donde cada elemento
# es un diccionario que representa un "registro" (alumno, producto, usuario…)

alumnos = [
    {'nombre': 'Ana', 'email': 'ana@gmail.com'},
    {'nombre': 'Pep', 'email': 'pep@pepe.com'},
    {'nombre': 'Iu',  'email': 'iu@iu.com'}
]

# Recorrer la lista y acceder a una clave de cada diccionario
for alumno in alumnos:
    print(alumno['nombre'])    # → Ana / Pep / Iu

# Índice negativo -1: último elemento de la lista
print(alumnos[-1]['nombre'])   # → Iu

# 💡 ALTERNATIVA: buscar un alumno concreto con next()
ana = next((a for a in alumnos if a['nombre'] == 'Ana'), None)
print(ana)   # → {'nombre': 'Ana', 'email': 'ana@gmail.com'}
#  · next() devuelve el primer resultado que cumpla la condición
#  · el None al final evita un error si no se encuentra ninguno

# 💡 ALTERNATIVA: filtrar varios con comprensión de lista
emails = [a['email'] for a in alumnos]
print(emails)   # → ['ana@gmail.com', 'pep@pepe.com', 'iu@iu.com']


# ─────────────────────────────────────────────
# LISTAS DE LISTAS (matriz / cuadrícula)
# ─────────────────────────────────────────────
# Una lista puede contener otras listas → se comporta como una tabla 2D.
# Se accede con dos índices:  matriz[fila][columna]

cuadrado = [
    [1, 2, 3],   # fila 0
    [4, 5, 6],   # fila 1
    [7, 8, 9]    # fila 2
]

# Recorrer con for anidado: el primer for va fila a fila,
# el segundo va elemento a elemento dentro de cada fila
for linea in cuadrado:
    print(linea)           # imprime la fila entera como lista
    for numero in linea:
        print(numero)      # imprime cada número por separado

# Acceso directo a un elemento: [fila][columna]
print(cuadrado[1][1])   # → 5  (fila 1, columna 1 → el centro del cuadrado)
print(cuadrado[0][2])   # → 3  (fila 0, columna 2)
print(cuadrado[2][0])   # → 7  (fila 2, columna 0)

# 💡 ALTERNATIVA: imprimir el cuadrado de forma más visual
for fila in cuadrado:
    print(' '.join(str(n) for n in fila))
# → 1 2 3
# → 4 5 6
# → 7 8 9


# ─────────────────────────────────────────────
# ESTRUCTURA DE DATOS COMPLEJA (caso real)
# ─────────────────────────────────────────────
# En proyectos reales anidamos todos estos conceptos:
# diccionarios, listas y sub-diccionarios a varios niveles.
# La clave es ir "entrando" nivel a nivel con corchetes.

empresa = {
    'nombre': 'TechCorp',
    'departamentos': {                          # nivel 1: dict de departamentos
        'Desarrollo': {
            'jefe': 'María Ruiz',
            'presupuesto': 150000,
            'empleados': [                      # nivel 2: lista de 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],
                },
            ],
        },
        'Marketing': {
            'jefe': 'Pedro Sanz',
            'presupuesto': 80000,
            '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],
                },
            ],
        },
    }
}

# ── Acceso profundo paso a paso ──────────────────────────────────
# Vamos entrando nivel a nivel hasta llegar a lo que queremos:
#
#  empresa
#    └─ ['departamentos']          → dict de departamentos
#         └─ ['Desarrollo']        → dict del departamento
#              └─ ['empleados']    → lista de empleados
#                   └─ [0]         → primer empleado (Ana García)
#                        └─ ['notas_evaluacion']  → [8, 9, 8, 9]

notas = empresa['departamentos']['Desarrollo']['empleados'][0]['notas_evaluacion']
media = sum(notas) / len(notas)
print(media)   # → 8.5


# 💡 ALTERNATIVAS Y USOS PRÁCTICOS sobre la estructura 'empresa'

# ── Listar todos los empleados de todos los departamentos
for nombre_depto, depto in empresa['departamentos'].items():
    print(f"\n── {nombre_depto} (jefe: {depto['jefe']}) ──")
    for emp in depto['empleados']:
        print(f"  {emp['id']} | {emp['nombre']} | {emp['salario']}€")

# ── Calcular la media de evaluación de cada empleado
for nombre_depto, depto in empresa['departamentos'].items():
    for emp in depto['empleados']:
        notas = emp['notas_evaluacion']
        media = sum(notas) / len(notas)
        print(f"{emp['nombre']}: media {media:.1f}")

# ── Acceso seguro con get() cuando no sabemos si la clave existe
salario = empresa['departamentos']['Desarrollo']['empleados'][0].get('bonus', 0)
print(f"Bonus: {salario}€")   # → Bonus: 0€  (no existe la clave, devuelve 0)
# ─────────────────────────────────────────────
# FUNCIONES QUE BUSCAN DENTRO DE ESTRUCTURAS ANIDADAS
# ─────────────────────────────────────────────
# Estas funciones reciben el diccionario 'empresa' del ejercicio anterior
# y devuelven una lista con los empleados que coinciden con la búsqueda.
# Si no encuentran nada, devuelven una lista vacía [].


# ─────────────────────────────────────────────
# BUSCAR EMPLEADOS POR NOMBRE
# ─────────────────────────────────────────────

def buscar_empleado(empresa, nombre):
    resultado = []                              # lista donde iremos guardando los encontrados

    departamentos = empresa['departamentos']    # extraemos el dict de departamentos

    for departamento in departamentos.values():  # recorremos cada departamento
        for empleado in departamento['empleados']:  # recorremos cada empleado del departamento

            # 'in' sobre una cadena comprueba si nombre está CONTENIDO en empleado['nombre']
            # Por eso 'ía' encuentra 'Ana García' y 'María Ruiz'
            # Es sensible a mayúsculas: 'ana' NO encontraría 'Ana'
            if nombre in empleado['nombre']:
                resultado.append(empleado)      # añadimos el dict completo del empleado

    return resultado    # devuelve [] si no encontró nadie, o una lista de coincidencias


# Encuentra a Ana García y María Ruiz porque ambas contienen 'ía'
print(buscar_empleado(empresa, 'ía'))

# Devuelve [] porque no existe ningún 'Carlos González' (hay 'Carlos López')
print(buscar_empleado(empresa, 'Carlos González'))


# 💡 ALTERNATIVA: búsqueda sin distinguir mayúsculas/minúsculas
#    Convirtiendo ambas cadenas a minúsculas antes de comparar
def buscar_empleado_v2(empresa, nombre):
    resultado = []
    for departamento in empresa['departamentos'].values():
        for empleado in departamento['empleados']:
            if nombre.lower() in empleado['nombre'].lower():  # .lower() → todo minúsculas
                resultado.append(empleado)
    return resultado

# Ahora 'ana' encuentra 'Ana García', 'ANA' también, etc.
print(buscar_empleado_v2(empresa, 'ana'))


# 💡 ALTERNATIVA: devolver solo el dato que interesa, no el dict entero
def buscar_nombres(empresa, nombre):
    return [
        emp['nombre']                                  # solo el nombre
        for depto in empresa['departamentos'].values()
        for emp in depto['empleados']
        if nombre.lower() in emp['nombre'].lower()     # búsqueda insensible
    ]
# Comprensión de lista con dos for anidados: más compacta, misma lógica


# ─────────────────────────────────────────────
# BUSCAR EMPLEADOS POR HABILIDAD
# ─────────────────────────────────────────────

def buscar_empleado_habilidades(empresa, habilidad):
    resultado = []

    departamentos = empresa['departamentos']

    for departamento in departamentos.values():
        for empleado in departamento['empleados']:

            # Aquí 'in' comprueba si habilidad es un elemento de la LISTA
            # empleado['habilidades'] es ['Python', 'Django', 'SQL'] etc.
            # Esto busca coincidencia EXACTA: 'React' sí, 'react' NO
            if habilidad in empleado['habilidades']:
                resultado.append(empleado)

    return resultado


# Encuentra a Carlos López porque tiene 'React' en su lista de habilidades
print(buscar_empleado_habilidades(empresa, 'React'))

# Devuelve [] porque nadie tiene 'C#' en sus habilidades
print(buscar_empleado_habilidades(empresa, 'C#'))


# 💡 ALTERNATIVA: búsqueda insensible a mayúsculas en la lista de habilidades
def buscar_empleado_habilidades_v2(empresa, habilidad):
    resultado = []
    for departamento in empresa['departamentos'].values():
        for empleado in departamento['empleados']:
            # Convertimos cada habilidad a minúsculas antes de comparar
            habilidades_lower = [h.lower() for h in empleado['habilidades']]
            if habilidad.lower() in habilidades_lower:
                resultado.append(empleado)
    return resultado

print(buscar_empleado_habilidades_v2(empresa, 'python'))  # Encuentra a Ana García


# 💡 ALTERNATIVA: buscar empleados que tengan VARIAS habilidades a la vez
def buscar_por_varias_habilidades(empresa, habilidades_buscadas):
    resultado = []
    for departamento in empresa['departamentos'].values():
        for empleado in departamento['empleados']:
            # set y operador <= comprueban si un conjunto está contenido en otro
            # {'Python','SQL'} <= {'Python','Django','SQL'}  → True
            if set(habilidades_buscadas) <= set(empleado['habilidades']):
                resultado.append(empleado['nombre'])
    return resultado

print(buscar_por_varias_habilidades(empresa, ['Python', 'SQL']))
# → ['Ana García']


# &#x1f4a1; ALTERNATIVA: función genérica que busca por CUALQUIER campo
def buscar_por_campo(empresa, campo, valor):
    resultado = []
    for departamento in empresa['departamentos'].values():
        for empleado in departamento['empleados']:
            if empleado.get(campo) == valor:   # get() evita error si el campo no existe
                resultado.append(empleado['nombre'])
    return resultado

print(buscar_por_campo(empresa, 'salario', 32000))  # → ['Carlos López']

Patrón general: «recorrer y filtrar»

def buscar_X(estructura, criterio):
    resultado = []                          # 1. empezamos con lista vacía

    for nivel1 in estructura.values():      # 2. recorremos el nivel 1
        for item in nivel1['coleccion']:    # 3. recorremos el nivel 2

            if criterio cumple condicion:   # 4. comprobamos el criterio
                resultado.append(item)      # 5. guardamos el encontrado

    return resultado  

Publicado por

Juan Pablo Fuentes

Formador de programación y bases de datos