Portada de libro electrónico gratuitaPython desde cero con mini-retos: aprende programando (sin teoría eterna)

Python desde cero con mini-retos: aprende programando (sin teoría eterna)

Nuevo curso

12 páginas

Ejercicios guiados y soluciones comentadas para consolidación

Capítulo 12

Tiempo estimado de lectura: 8 minutos

+ Ejercicio

Cómo usar este capítulo (modo “entrenamiento”)

Este capítulo es una sesión de consolidación: vas a resolver ejercicios guiados con soluciones comentadas. La idea no es aprender “cosas nuevas”, sino reforzar tu criterio para elegir estructuras, detectar errores típicos y escribir código más claro. Trabaja así: (1) lee el enunciado, (2) intenta 10–15 minutos, (3) compara con la solución, (4) reescribe tu versión aplicando 1 mejora concreta (nombres, validaciones, estructura, pruebas rápidas).

Reglas rápidas para consolidar

  • Primero haz que funcione, luego hazlo legible.
  • Divide el problema: entrada → proceso → salida.
  • Prueba con casos normales y casos borde (vacío, cero, negativos, repetidos).
  • Si te atoras, imprime estados intermedios (debug) y reduce el caso.

Ejercicio 1: Analizador de texto (frecuencias y limpieza)

Objetivo: dado un texto, contar palabras y devolver el “top N” de palabras más frecuentes. Consolidar: limpieza básica, conteo, ordenación y presentación.

Guía paso a paso

  • Normaliza el texto: minúsculas.
  • Elimina signos comunes reemplazándolos por espacios.
  • Separa en palabras y filtra vacíos.
  • Cuenta ocurrencias en un diccionario.
  • Ordena por frecuencia (descendente) y luego alfabéticamente (para desempates).
def top_palabras(texto, n=5):
    # 1) Normalización
    texto = texto.lower()

    # 2) Limpieza simple (sin librerías externas)
    for ch in [",", ".", ";", ":", "!", "?", "\n", "\t", "\"", "(", ")"]:
        texto = texto.replace(ch, " ")

    # 3) Tokenización
    palabras = [p for p in texto.split(" ") if p]

    # 4) Conteo
    conteo = {}
    for p in palabras:
        conteo[p] = conteo.get(p, 0) + 1

    # 5) Ordenación: primero por -frecuencia, luego por palabra
    ordenadas = sorted(conteo.items(), key=lambda item: (-item[1], item[0]))

    return ordenadas[:n]

texto = "Hola hola, mundo. Mundo! Python, python; PYTHON?"
print(top_palabras(texto, n=3))
# Esperado: [('python', 3), ('hola', 2), ('mundo', 2)]

Comentarios de consolidación

  • Desempates: si dos palabras tienen misma frecuencia, ordenar por palabra hace el resultado estable y predecible.
  • Limpieza: esta limpieza es “suficiente” para práctica; si quisieras robustez real, usarías expresiones regulares, pero aquí el foco es el razonamiento.
  • Pruebas rápidas: prueba con texto vacío, con solo signos, y con n mayor que el número de palabras distintas.

Ejercicio 2: Ranking de estudiantes (promedio, estado y orden)

Objetivo: a partir de una lista de estudiantes con notas, calcular promedio, asignar estado (Aprobado/Reprobado) y ordenar por promedio. Consolidar: transformación de datos, cálculo, ordenación y formato.

Enunciado

Tienes una lista de diccionarios con nombre y notas. Crea una nueva lista con nombre, promedio (2 decimales) y estado (Aprobado si promedio >= 60). Ordena de mayor a menor promedio.

estudiantes = [
    {"nombre": "Ana", "notas": [70, 80, 90]},
    {"nombre": "Luis", "notas": [50, 40, 60]},
    {"nombre": "Marta", "notas": [100, 90, 95]},
]

Solución comentada

def promedio(notas):
    # Evita división por cero si llegara una lista vacía
    if not notas:
        return 0.0
    return sum(notas) / len(notas)

def construir_ranking(estudiantes):
    ranking = []

    for e in estudiantes:
        prom = promedio(e.get("notas", []))
        prom_red = round(prom, 2)
        estado = "Aprobado" if prom_red >= 60 else "Reprobado"

        ranking.append({
            "nombre": e.get("nombre", "(sin nombre)"),
            "promedio": prom_red,
            "estado": estado
        })

    # Ordena por promedio desc, y por nombre asc para desempate
    ranking.sort(key=lambda r: (-r["promedio"], r["nombre"]))
    return ranking

ranking = construir_ranking(estudiantes)
for r in ranking:
    print(r["nombre"], r["promedio"], r["estado"])

Errores típicos que este ejercicio corrige

  • Calcular promedio con len(notas) sin validar lista vacía.
  • Modificar la lista original cuando querías una nueva (aquí construimos ranking aparte).
  • Ordenar solo por promedio y obtener resultados “raros” cuando hay empates (por eso el segundo criterio).

Ejercicio 3: Normalizador de inventario (consolidar datos repetidos)

Objetivo: tienes movimientos de inventario con productos repetidos. Debes consolidar cantidades por producto y devolver un resumen ordenado. Consolidar: acumulación, claves, y salida ordenada.

Continúa en nuestra aplicación.

Podrás escuchar el audiolibro con la pantalla apagada, recibir un certificado gratuito para este curso y además tener acceso a otros 5.000 cursos online gratuitos.

O continúa leyendo más abajo...
Download App

Descargar la aplicación

Enunciado

Dada una lista de tuplas (producto, cantidad), suma cantidades por producto. Si un producto queda con cantidad 0, no lo incluyas en el resultado final.

movimientos = [
    ("teclado", 3),
    ("mouse", 5),
    ("teclado", -1),
    ("monitor", 2),
    ("mouse", -5),
]

Solución comentada

def consolidar_inventario(movimientos):
    stock = {}

    for producto, cantidad in movimientos:
        # Acumula por clave
        stock[producto] = stock.get(producto, 0) + cantidad

    # Filtra ceros y ordena por nombre
    resumen = [(p, c) for p, c in stock.items() if c != 0]
    resumen.sort(key=lambda item: item[0])
    return resumen

print(consolidar_inventario(movimientos))
# Esperado: [('monitor', 2), ('teclado', 2)]

Variación para practicar (sin ver solución)

  • Ordena por cantidad descendente y, si empatan, por nombre ascendente.
  • Valida que cantidad sea numérica; si no, ignora el movimiento.

Ejercicio 4: Validador de contraseñas (reglas y reporte de fallos)

Objetivo: validar una contraseña con reglas y devolver un reporte claro de qué falló. Consolidar: condiciones, acumulación de errores, y funciones pequeñas.

Reglas

  • Mínimo 8 caracteres.
  • Al menos 1 mayúscula.
  • Al menos 1 minúscula.
  • Al menos 1 dígito.
  • Al menos 1 símbolo de esta lista: !@#$%

Solución comentada

def validar_password(pwd):
    errores = []

    if len(pwd) < 8:
        errores.append("Debe tener al menos 8 caracteres")

    if not any(c.isupper() for c in pwd):
        errores.append("Debe incluir una mayúscula")

    if not any(c.islower() for c in pwd):
        errores.append("Debe incluir una minúscula")

    if not any(c.isdigit() for c in pwd):
        errores.append("Debe incluir un dígito")

    simbolos = set("!@#$%")
    if not any(c in simbolos for c in pwd):
        errores.append("Debe incluir un símbolo (!@#$%)")

    return {
        "ok": len(errores) == 0,
        "errores": errores
    }

pruebas = ["abc", "abcdefgh", "Abcdefgh", "Abcdefg1", "Abcdefg1!"]
for p in pruebas:
    r = validar_password(p)
    print(p, r["ok"], r["errores"]) 

Qué estás consolidando aquí

  • Reporte útil: no solo “true/false”, sino lista de fallos para corregir rápido.
  • Lectura clara: cada regla es una condición independiente; fácil de mantener.
  • Pruebas: la lista pruebas es un mini set de casos progresivos.

Ejercicio 5: Mini-pipeline de datos (filtrar, transformar, resumir)

Objetivo: simular un flujo típico: filtrar registros inválidos, transformar campos y generar un resumen. Consolidar: pensamiento por etapas y control de casos borde.

Enunciado

Tienes ventas como diccionarios: producto, precio, cantidad. Debes: (1) ignorar ventas con cantidad <= 0 o precio < 0, (2) calcular total por venta, (3) sumar totales por producto, (4) devolver el top 3 productos por ingresos.

ventas = [
    {"producto": "libro", "precio": 10.0, "cantidad": 3},
    {"producto": "lapiz", "precio": 1.5, "cantidad": 10},
    {"producto": "libro", "precio": 10.0, "cantidad": 0},
    {"producto": "cuaderno", "precio": 5.0, "cantidad": 4},
    {"producto": "lapiz", "precio": 1.5, "cantidad": 2},
]

Solución comentada

def top_ingresos_por_producto(ventas, top_n=3):
    ingresos = {}

    for v in ventas:
        producto = v.get("producto")
        precio = v.get("precio")
        cantidad = v.get("cantidad")

        # 1) Filtrado de inválidos
        if producto is None:
            continue
        if not isinstance(precio, (int, float)) or not isinstance(cantidad, (int, float)):
            continue
        if precio < 0 or cantidad <= 0:
            continue

        # 2) Transformación
        total = precio * cantidad

        # 3) Resumen por producto
        ingresos[producto] = ingresos.get(producto, 0.0) + total

    # 4) Top N
    ordenados = sorted(ingresos.items(), key=lambda item: (-item[1], item[0]))
    return ordenados[:top_n]

print(top_ingresos_por_producto(ventas, top_n=3))
# Esperado: [('libro', 30.0), ('cuaderno', 20.0), ('lapiz', 18.0)]

Checklist de depuración para este tipo de ejercicios

  • Imprime una venta y verifica tipos reales antes de operar.
  • Confirma que el filtrado no esté eliminando registros válidos.
  • Verifica acumulación: ¿se está sobrescribiendo en vez de sumar?
  • Revisa el criterio de orden: -item[1] para descendente.

Ejercicio 6: Refactor guiado (de “funciona” a “mantenible”)

Objetivo: tomar un código que funciona pero es difícil de leer, y mejorarlo sin cambiar el resultado. Consolidar: nombres, separación de responsabilidades y pruebas rápidas.

Código inicial (intencionalmente mejorable)

def f(a):
    r = []
    for x in a:
        if x != "":
            r.append(x.strip().lower())
    d = {}
    for y in r:
        if y in d:
            d[y] += 1
        else:
            d[y] = 1
    return sorted(d.items(), key=lambda t: -t[1])

Refactor paso a paso (misma lógica, mejor estructura)

  • Renombra variables para expresar intención.
  • Extrae pasos: normalizar → contar → ordenar.
  • Agrega desempate y casos borde.
def normalizar_items(items):
    normalizados = []
    for item in items:
        if not item:
            continue
        normalizados.append(item.strip().lower())
    return normalizados

def contar_frecuencias(items):
    frec = {}
    for item in items:
        frec[item] = frec.get(item, 0) + 1
    return frec

def ordenar_por_frecuencia(frecuencias):
    return sorted(frecuencias.items(), key=lambda par: (-par[1], par[0]))

def ranking_items(items):
    items_norm = normalizar_items(items)
    frec = contar_frecuencias(items_norm)
    return ordenar_por_frecuencia(frec)

print(ranking_items(["  A ", "a", "B", "", "b", "b"]))
# Esperado: [('b', 3), ('a', 2)]

Qué ganaste con el refactor

  • Funciones pequeñas: más fáciles de probar y reutilizar.
  • Lectura lineal: se entiende el “pipeline” de datos.
  • Menos riesgo de romper algo: cada parte tiene una responsabilidad.

Mini-rúbrica de autoevaluación (para cualquier ejercicio)

  • Correctitud: ¿pasa tus casos normales y borde?
  • Claridad: ¿un tercero entiende el código en 30 segundos?
  • Robustez: ¿maneja entradas vacías o inválidas sin explotar?
  • Estructura: ¿hay funciones pequeñas y nombres descriptivos?
  • Pruebas rápidas: ¿dejaste 3–5 casos de prueba listos?

Ahora responde el ejercicio sobre el contenido:

En un ejercicio que ordena resultados por frecuencia, que criterio ayuda a que el resultado sea estable y predecible cuando hay empates?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Usar un segundo criterio de orden (la clave en ascendente) cuando la frecuencia empata hace que el orden final sea consistente y fácil de comparar entre ejecuciones.

Siguiente capítulo

Arrow Right Icon
Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.