Apuntes DAM
Volver al inicio

Corrector de exámenes tipo test

Ejercicio de PythonDifícilUnos 60 minutos

Corrige los exámenes tipo test de una clase con la plantilla de respuestas: penalización por fallo, notas sobre 10, clasificación, media, aprobados, la pregunta más fallada y las que nadie acertó. Listas, cadenas, ordenación y validación de datos.

  • Listas
  • Cadenas
  • enumerate y zip
  • sort con key
  • f-strings
  • Validación de la entrada

Enunciado

Una profesora pasa sus exámenes tipo test a un formulario y obtiene un fichero de texto con las respuestas de cada alumno. Quiere un programa que los corrija, ordene las notas y le diga qué preguntas han salido peor, para repasarlas en clase.

Cada pregunta tiene cuatro opciones (A, B, C y D) y el alumno puede dejarla en blanco. Para que no compense contestar al azar, cada fallo resta un tercio de acierto: tres fallos anulan una pregunta bien contestada. Las preguntas en blanco no suman ni restan, y la puntuación de un examen nunca puede ser negativa.

Algunas líneas del fichero llegan mal (sin el punto y coma, con respuestas de más o de menos o con letras que no existen): el programa debe avisar de cada una y corregir las demás.

Qué tiene que hacer el programa

  1. La primera línea es la plantilla: CLAVE seguido de las respuestas correctas, por ejemplo CLAVE ABCDA. El número de preguntas N es la longitud de la clave.
  2. Cada línea siguiente es un examen: nombre;respuestas, con una letra (A, B, C o D) o un guion (en blanco) por pregunta. Se ignoran las líneas vacías y los espacios al principio y al final de cada parte.
  3. Si una línea no tiene ;, escribe Aviso: la línea L no tiene el formato nombre;respuestas, donde L es su número de línea (la de la clave es la 1). Si las respuestas no tienen exactamente N caracteres o alguno no es A, B, C, D ni guion, escribe Aviso: las respuestas de nombre no son válidas y no se corrigen. En los dos casos ese examen no cuenta.
  4. Puntuación de un examen: aciertos − fallos ÷ 3, con un mínimo de 0. La nota es esa puntuación × 10 ÷ N.
  5. Escribe Corrección de N preguntas y una línea por alumno, de mayor a menor nota (a igual nota, por orden alfabético): la posición en 2 caracteres alineada a la derecha, un punto y un espacio, el nombre en 22 caracteres alineado a la izquierda y la nota con dos decimales y coma, en 6 caracteres a la derecha.
  6. Después: Media: X,XX, Aprobados: A de T (aprueba quien tiene un 5 o más), Pregunta más fallada: P (F fallos) (la de más respuestas incorrectas; en blanco no cuenta como fallo; si hay empate, la de número más bajo; «fallo» en singular si es 1; ninguna si nadie ha fallado nada) y Sin aciertos: … con los números de las preguntas que nadie ha acertado separados por coma y espacio, o ninguna.
  7. Si no hay ningún examen válido, después de los avisos escribe solo No hay exámenes que corregir.

Entrada

Línea 1: CLAVE respuestas. Resto de líneas: nombre;respuestas (puede haber líneas vacías o mal escritas).

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

Cinco exámenes de diez preguntas

Entrada

CLAVE ABCDABCDAB
Lucía Gómez;ABCDABCDAB
Mario Vega;ABCDABCD--
Nerea Paz;ABCAABCDBB
Olga Ruiz;DCBADCBADC
Pedro Sanz;-----ABCDA

Salida por consola

Corrección de 10 preguntas
 1. Lucía Gómez            10,00
 2. Mario Vega              8,00
 3. Nerea Paz               7,33
 4. Olga Ruiz               0,00
 5. Pedro Sanz              0,00
Media: 5,07
Aprobados: 3 de 5
Pregunta más fallada: 9 (3 fallos)
Sin aciertos: ninguna

Con líneas mal escritas

Entrada

CLAVE CCBA

Ana Ruiz;CCBB
Bruno Díaz;CCB
Carla Mena;CCXA
sin punto y coma
Diego Sola;CA-B
Elena Toro;C-BC

Salida por consola

Aviso: las respuestas de Bruno Díaz no son válidas y no se corrigen
Aviso: las respuestas de Carla Mena no son válidas y no se corrigen
Aviso: la línea 6 no tiene el formato nombre;respuestas
Corrección de 4 preguntas
 1. Ana Ruiz                6,67
 2. Elena Toro              4,17
 3. Diego Sola              0,83
Media: 3,89
Aprobados: 1 de 3
Pregunta más fallada: 4 (3 fallos)
Sin aciertos: 4

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. Leer la clave y recorrer los exámenes

Lee toda la entrada con sys.stdin.read().split("\n"). La clave es la segunda palabra de la primera línea. Recorre el resto con enumerate(lineas[1:], start=2) para tener a mano el número de línea que piden los avisos.

2. Validar antes de corregir

Primero comprueba que haya ;; después separa nombre y respuestas con split(";", 1) y comprueba la longitud y las letras. any(r not in "ABCD-" for r in respuestas) dice si hay algún carácter que no vale.

python
if len(respuestas) != n or any(r not in "ABCD-" for r in respuestas):
    print(f"Aviso: las respuestas de {nombre} no son válidas y no se corrigen")
    continue
3. Corregir pregunta a pregunta

zip(respuestas, clave) recorre a la vez la respuesta del alumno y la correcta de cada pregunta, y enumerate te da su índice para ir sumando los fallos y los aciertos de cada pregunta en dos listas de N contadores.

4. Ordenar y formatear

sort(key=lambda a: (-a[1], a[0])) ordena por nota de mayor a menor y, a igualdad, por nombre. En los f-strings, {pos:>2} alinea a la derecha en 2 caracteres y {nombre:<22} a la izquierda en 22.

5. Las estadísticas

Para la pregunta más fallada, max(range(n), key=lambda i: fallos[i]) devuelve el índice con más fallos y, en caso de empate, el primero. Las preguntas sin aciertos son los índices cuyo contador de aciertos vale 0; recuerda sumar 1 para mostrar el número de pregunta.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.

🐍PythonCorrector de exámenes tipo testDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
CLAVE ABCDABCDAB
Lucía Gómez;ABCDABCDAB
Mario Vega;ABCDABCD--
Nerea Paz;ABCAABCDBB
Olga Ruiz;DCBADCBADC
Pedro Sanz;-----ABCDA
Salida esperada
Corrección de 10 preguntas
 1. Lucía Gómez            10,00
 2. Mario Vega              8,00
 3. Nerea Paz               7,33
 4. Olga Ruiz               0,00
 5. Pedro Sanz              0,00
Media: 5,07
Aprobados: 3 de 5
Pregunta más fallada: 9 (3 fallos)
Sin aciertos: ninguna
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
python
1import sys
2
3
4def formato(numero):
5    """Dos decimales con coma, como se escriben las notas en España."""
6    return f"{numero:.2f}".replace(".", ",")
7
8
9def main():
10    lineas = sys.stdin.read().split("\n")
11    clave = lineas[0].split()[1]
12    n = len(clave)
13    alumnos = []                  # (nombre, nota) de cada examen válido
14    fallos = [0] * n              # fallos de cada pregunta
15    aciertos_pregunta = [0] * n   # aciertos de cada pregunta
16
17    for num, linea in enumerate(lineas[1:], start=2):
18        linea = linea.strip()
19        if not linea:
20            continue
21        if ";" not in linea:
22            print(f"Aviso: la línea {num} no tiene el formato nombre;respuestas")
23            continue
24        nombre, respuestas = (parte.strip() for parte in linea.split(";", 1))
25        if len(respuestas) != n or any(r not in "ABCD-" for r in respuestas):
26            print(f"Aviso: las respuestas de {nombre} no son válidas y no se corrigen")
27            continue
28        aciertos = mal = 0
29        for i, (respuesta, correcta) in enumerate(zip(respuestas, clave)):
30            if respuesta == "-":
31                continue
32            if respuesta == correcta:
33                aciertos += 1
34                aciertos_pregunta[i] += 1
35            else:
36                mal += 1
37                fallos[i] += 1
38        # Tres fallos restan un acierto, pero la puntuación nunca baja de 0
39        puntos = max(0, aciertos - mal / 3)
40        alumnos.append((nombre, puntos * 10 / n))
41
42    if not alumnos:
43        print("No hay exámenes que corregir.")
44        return
45
46    alumnos.sort(key=lambda a: (-a[1], a[0]))
47    print(f"Corrección de {n} preguntas")
48    for pos, (nombre, nota) in enumerate(alumnos, start=1):
49        print(f"{pos:>2}. {nombre:<22}{formato(nota):>6}")
50
51    media = sum(nota for _, nota in alumnos) / len(alumnos)
52    aprobados = sum(1 for _, nota in alumnos if nota >= 5)
53    print(f"Media: {formato(media)}")
54    print(f"Aprobados: {aprobados} de {len(alumnos)}")
55
56    # max devuelve el primero de los empatados: la pregunta de número más bajo
57    peor = max(range(n), key=lambda i: fallos[i])
58    if fallos[peor] == 0:
59        print("Pregunta más fallada: ninguna")
60    else:
61        print(f"Pregunta más fallada: {peor + 1} ({fallos[peor]} {'fallo' if fallos[peor] == 1 else 'fallos'})")
62    sin_aciertos = [str(i + 1) for i in range(n) if aciertos_pregunta[i] == 0]
63    print("Sin aciertos: " + (", ".join(sin_aciertos) if sin_aciertos else "ninguna"))
64
65
66main()

El programa recorre la entrada una sola vez y, por cada examen, actualiza tres cosas: la nota del alumno y los contadores de aciertos y fallos de cada pregunta. Así todas las estadísticas finales salen sin volver a leer nada.

La validación se hace antes de tocar ningún contador: un examen mal escrito no debe afectar a la estadística de las preguntas.

La nota se calcula con la fórmula de la penalización (aciertos - mal / 3) y max(0, …) impide que sea negativa. Ordenar por la tupla (-nota, nombre) resuelve el orden descendente y los empates en una sola línea.

La conversión a coma decimal se concentra en una función, formato, para no repetirla en cada print.

Para ir más allá

  • Admite exámenes con distinto número de opciones por pregunta, indicadas en la línea de la clave.
  • Muestra, para cada pregunta, el porcentaje de alumnos que eligió cada opción.
  • Guarda las notas en un fichero CSV con csv.writer, listo para abrirlo en una hoja de cálculo.

Dónde se explica