SolveConPython

Python Reto #13 — Agrupar elementos por clave

Nivel: Intermedio
Tema: Diccionarios, listas de diccionarios, agrupación de datos, validación de entradas, tests con pytest
Objetivo: Agrupar elementos de una lista según una clave común, un patrón muy habitual en análisis y transformación de datos.

Reto #13 Agrupar elementos por clave
Reto #13 Agrupar elementos por clave

Enunciado

Crea una función llamada agrupar_por_clave(elementos, clave) que:

  1. Reciba una lista de diccionarios elementos y una cadena clave.
  2. Si elementos es None, devuelva un diccionario vacío {}.
  3. Si elementos no es una lista, lance TypeError.
  4. Si clave no es un str, lance TypeError.
  5. Devuelva un diccionario donde:
    • las claves sean los valores de clave en cada elemento,
    • los valores sean listas con los diccionarios que comparten esa clave.
  6. Ignore elementos que no contengan la clave indicada.

Ejemplos

datos = [
{"categoria": "A", "valor": 10},
{"categoria": "B", "valor": 20},
{"categoria": "A", "valor": 15},
]


agrupar_por_clave(datos, "categoria")
# {
# "A": [
# {"categoria": "A", "valor": 10},
# {"categoria": "A", "valor": 15}
# ],
# "B": [
# {"categoria": "B", "valor": 20}
# ]
# }

Casos borde:

  • agrupar_por_clave(None, "categoria"){}
  • agrupar_por_clave([], "categoria"){}
  • agrupar_por_clave(datos, "no_existe"){}

Pistas

  1. Usa un diccionario para acumular los grupos.
  2. Recorre la lista elemento por elemento.
  3. Comprueba si la clave existe antes de agrupar.
  4. Inicializa la lista del grupo si aún no existe.

Solución explicada (paso a paso)

  1. Si elementos es None, no hay datos → devolvemos {}.
  2. Validamos que elementos sea una lista y clave sea una cadena.
  3. Creamos un diccionario vacío para los grupos.
  4. Recorremos cada elemento:
    • si no es un diccionario o no contiene la clave, lo ignoramos,
    • obtenemos el valor asociado a la clave,
    • añadimos el elemento al grupo correspondiente.
  5. Devolvemos el diccionario de grupos.

Python
def agrupar_por_clave(elementos: list | None, clave: str) -> dict:
"""
Agrupa una lista de diccionarios por una clave común.
Reglas:
- None -> {}
- elementos debe ser list
- clave debe ser str
- Ignora elementos sin la clave
"""
if elementos is None:
return {}
if not isinstance(elementos, list):
raise TypeError("El parámetro 'elementos' debe ser una lista o None.")
if not isinstance(clave, str):
raise TypeError("El parámetro 'clave' debe ser una cadena (str).")
grupos = {}
for elemento in elementos:
if not isinstance(elemento, dict):
continue
if clave not in elemento:
continue
valor_clave = elemento[clave]
if valor_clave not in grupos:
grupos[valor_clave] = []
grupos[valor_clave].append(elemento)
return grupos

Python
import pytest
from reto_13_agrupar_por_clave import agrupar_por_clave
def test_agrupacion_basica():
datos = [
{"categoria": "A", "valor": 10},
{"categoria": "B", "valor": 20},
{"categoria": "A", "valor": 15},
]
resultado = agrupar_por_clave(datos, "categoria")
assert resultado["A"] == [
{"categoria": "A", "valor": 10},
{"categoria": "A", "valor": 15},
]
assert resultado["B"] == [
{"categoria": "B", "valor": 20},
]
def test_lista_vacia():
assert agrupar_por_clave([], "categoria") == {}
def test_none_devuelve_diccionario_vacio():
assert agrupar_por_clave(None, "categoria") == {}
def test_clave_no_existente():
datos = [{"a": 1}, {"b": 2}]
assert agrupar_por_clave(datos, "categoria") == {}
def test_tipo_incorrecto_elementos():
with pytest.raises(TypeError):
agrupar_por_clave("no_es_lista", "categoria")
def test_tipo_incorrecto_clave():
with pytest.raises(TypeError):
agrupar_por_clave([], 123)

Ejecuta:

  • pytest -q

Variantes para subir de nivel (opcional)

  1. Usar defaultdict(list) para simplificar el código
  2. Agrupar por múltiples claves
  3. Transformar el resultado (por ejemplo, contar elementos por grupo)
  4. Soportar claves anidadas ("usuario.id")

Lo que aprendiste

  • Un patrón clave en procesamiento de datos: group by
  • Manejo de listas de diccionarios
  • Validación defensiva en funciones intermedias
  • Tests para estructuras de datos compuestas

Accede al código completo y a los tests en GitHub para ejecutar y modificar la solución localmente.

Continúa con Reto #14 — Eliminar duplicados preservando el orden para seguir trabajando con patrones intermedios de listas y conjuntos.