SolveConPython

Python Reto #17 — Contar ocurrencias con collections.Counter (comparativa)

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.

Reto #17 Contar ocurrencias con collections.Counter (comparativa)
Reto #17 Contar ocurrencias con collections.Counter (comparativa)

Enunciado

Crea una función llamada contar_ocurrencias(elementos) que:

  1. Reciba un valor elementos.
  2. Si elementos es None, devuelva un diccionario vacío {}.
  3. Si elementos no es una lista (list), lance TypeError.
  4. Devuelva un diccionario donde:
    • las claves sean los elementos de la lista,
    • los valores sean el número de veces que aparece cada elemento.
  5. 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

  1. Implementación manual: usa un diccionario y suma 1 por cada aparición.
  2. Con Counter: importa desde collections y pásale la lista.
  3. Ambas deben producir el mismo resultado para los mismos datos.

Solución explicada (paso a paso)

Enfoque A — Implementación manual

  1. Validamos None y tipo.
  2. Recorremos la lista.
  3. Incrementamos el contador por clave.

Enfoque B — collections.Counter

  1. Validamos None y tipo.
  2. Creamos un Counter(elementos).
  3. Convertimos a dict para 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 Counter
def 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 conteo
def 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 pytest
from 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) == esperado
def 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)

  1. Frecuencia de palabras (tokenizando strings)
  2. Top-N elementos más frecuentes
  3. Comparar rendimiento (manual vs. Counter) con listas grandes
  4. 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.