Nivel: Intermedio
Tema: Diccionarios, collections.Counter, claridad vs. concisión, validación de entradas, tests con pytest
Objetivo: Contar ocurrencias de elementos en una lista y comparar una implementación manual con una solución usando Counter, entendiendo cuándo conviene cada enfoque.

Enunciado
Crea una función llamada contar_ocurrencias(elementos) que:
- Reciba un valor
elementos. - Si
elementosesNone, devuelva un diccionario vacío{}. - Si
elementosno es una lista (list), lanceTypeError. - Devuelva un diccionario donde:
- las claves sean los elementos de la lista,
- los valores sean el número de veces que aparece cada elemento.
- Asume que los elementos son hashables (por ejemplo,
int,str,tuple).
Luego, implementa una segunda versión usando collections.Counter y compara resultados.
Ejemplos
contar_ocurrencias([1, 2, 2, 3, 1])→{1: 2, 2: 2, 3: 1}contar_ocurrencias(["a", "b", "a"])→{"a": 2, "b": 1}contar_ocurrencias([])→{}contar_ocurrencias(None)→{}contar_ocurrencias("abc")→ TypeError
Pistas
- Implementación manual: usa un diccionario y suma 1 por cada aparición.
- Con
Counter: importa desdecollectionsy pásale la lista. - Ambas deben producir el mismo resultado para los mismos datos.
Solución explicada (paso a paso)
Enfoque A — Implementación manual
- Validamos
Noney tipo. - Recorremos la lista.
- Incrementamos el contador por clave.
Enfoque B — collections.Counter
- Validamos
Noney tipo. - Creamos un
Counter(elementos). - Convertimos a
dictpara devolver un diccionario estándar.
Comparativa:
- Manual: más verboso, excelente para aprender.
Counter: más conciso, estándar y probado para producción.
Python
from collections import Counterdef contar_ocurrencias(elementos: list | None) -> dict: """ Cuenta ocurrencias de elementos en una lista (implementación manual). Reglas: - None -> {} - No list -> TypeError """ if elementos is None: return {} if not isinstance(elementos, list): raise TypeError("El parámetro 'elementos' debe ser una lista o None.") conteo = {} for elem in elementos: conteo[elem] = conteo.get(elem, 0) + 1 return conteodef contar_ocurrencias_counter(elementos: list | None) -> dict: """ Cuenta ocurrencias usando collections.Counter. Reglas: - None -> {} - No list -> TypeError """ if elementos is None: return {} if not isinstance(elementos, list): raise TypeError("El parámetro 'elementos' debe ser una lista o None.") return dict(Counter(elementos))
Python
import pytestfrom reto_17_contar_ocurrencias import ( contar_ocurrencias, contar_ocurrencias_counter,)pytest.mark.parametrize( "entrada,esperado", [ ([1, 2, 2, 3, 1], {1: 2, 2: 2, 3: 1}), (["a", "b", "a"], {"a": 2, "b": 1}), ([], {}), (None, {}), ],)def test_ambas_implementaciones_iguales(entrada, esperado): assert contar_ocurrencias(entrada) == esperado assert contar_ocurrencias_counter(entrada) == esperadodef test_tipo_incorrecto_lanza_typeerror(): with pytest.raises(TypeError): contar_ocurrencias("abc") with pytest.raises(TypeError): contar_ocurrencias_counter("abc")
Ejecuta:
pytest -q
Variantes para subir de nivel (opcional)
- Frecuencia de palabras (tokenizando strings)
- Top-N elementos más frecuentes
- Comparar rendimiento (manual vs.
Counter) con listas grandes - Soportar normalización (minúsculas, trimming)
Lo que aprendiste
- Dos maneras de resolver el mismo problema
- Cuándo preferir claridad vs. concisión
- Uso práctico de
collections.Counter - Tests parametrizados con
pytest
Accede al código completo y a los tests en GitHub para ejecutar y modificar la solución localmente.
Continúa con Reto #18 — Ordenar una lista de diccionarios por múltiples campos para profundizar en ordenación avanzada y claves personalizadas.