Opciones de anotación

Completado

Hay algunas opciones que son comunes a todas las anotaciones. En el último módulo, aprendió las opciones success_message y failure_message. En este módulo, analizaremos tres opciones más que se pueden aplicar a las anotaciones y cómo se pueden usar.

name

La opción name se usa para agrupar diferentes instancias de clases de anotación que representan la misma anotación. Esta opción se usa para evitar que los mensajes se muestren varias veces cuando no es necesario. Veamos este ejemplo: la función maximum que se muestra a continuación simplemente llama a la función max de Python para comprobar que el alumno ha identificado correctamente el máximo, pero se imprime un mensaje de resultado correcto para cada entrada en la que se prueba la función.

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
>>> for test_list in test_lists:
...     maximum(test_list, track=True)
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: True
MESSAGES:
  - Found the max!
  - Found the max!
  - Found the max!
  - Found the max!

El problema con esta función es simple: la anotación que se crea en cada prueba está comprobando básicamente lo mismo: si el alumno devolvió el valor correcto. Tener el mismo mensaje impreso varias veces hace que parezca que las anotaciones están probando condiciones diferentes y sobrecarga el informe generado por PyBryt. Podemos contraer todos estos mensajes juntos asignando un nombre a la anotación creada en la función maximum:

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             name="list-maximum",
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
>>> for test_list in test_lists:
...     maximum(test_list, track=True)
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: True
MESSAGES:
  - Found the max!

Ahora, podemos ver que el mensaje solo se imprime una vez.

Cuando PyBryt contrae las anotaciones en un único mensaje, solo muestra el mensaje correcto si se cumplen todas las anotaciones del grupo de nombres. Si se produce un error en alguna prueba del grupo, se mostrará el mensaje de error. Vamos a introducir un error en maximum para mostrarlo:

>>> def maximum(l):
...     if len(l) % 2 == 0:
...         m = min(l)
...     else:
...         m = max(l)
...     return m
>>> with pybryt.check(max_ref):
...     for test_list in test_lists:
...         maximum(test_list)
REFERENCE: maximum
SATISFIED: False
MESSAGES:
  - Did not find the max

limit

La opción limit permite controlar cuántas copias de anotaciones con nombre se incluyen en la implementación de referencia. Esta opción ayuda a los casos en los que las funciones que generan las anotaciones se reutilizan muchas veces a lo largo de una asignación. Los casos en los que unas pocas pruebas iniciales son suficientes para comprobar la validez de la implementación al reducir el tamaño de la propia implementación de referencia.

Lo vamos a ilustrar con la función maximum. Aquí, usaremos una implementación similar a la de referencia anterior, pero estableceremos limit en cinco anotaciones y lo probaremos en varias listas de entrada.

>>> max_ref = []
>>> def maximum(l, track=False):
...     m = max(l)
...     if track:
...         max_ref.append(pybryt.Value(
...             m,
...             name="list-maximum",
...             limit=5,
...             success_message="Found the max!", 
...             failure_message="Did not find the max",
...         ))
...     return m
>>> for _ in range(1000):
...     test_list = np.random.normal(size=100)
...     maximum(test_list, track=True)
>>> print(f"Annotations created: {len(max_ref)}")
>>> max_ref = pybryt.ReferenceImplementation("maximum", max_ref)
>>> print(f"Annotations in reference: {len(max_ref.annotations)}")
Annotations created: 1000
Annotations in reference: 5

Como puede ver, la longitud de max_ref.annotations es 5 aunque se incluyeron 1000 anotaciones en la lista que se pasa al constructor.

group

La opción group es parecida a la opción name en que se usa para agrupar anotaciones, pero estas anotaciones no representan necesariamente la "misma anotación", sino que se agrupan en fragmentos significativos para que determinadas partes de las referencias se puedan comprobar de una en una en lugar de todas a la vez. Esta opción puede ser útil para construir asignaciones con varias preguntas en PyBryt.

Por ejemplo, considere una asignación sencilla que pide a los alumnos que implementen una función mean y median. Puede dividirlo en dos preguntas como se muestra a continuación:

# Question 1
mean_ref = []

def mean(l, track=False):
    size = len(l)
    if track:
        mean_ref.append(pybryt.Value(
            size,
            name="len",
            group="mean",
            success_message="Determined the length of the list",
        ))

    m = sum(l) / size
    if track:
        mean_ref.append(pybryt.Value(
            m,
            name="mean",
            group="mean",
            success_message="Calculated the correct mean of the list",
            failure_message="Did not find the correct mean of the list",
        ))

    return m

# Question 2
median_ref = []

def median(l, track=True):
    sorted_l = sorted(l)
    if track:
        median_ref.append(pybryt.Value(
            sorted_l,
            name="sorted",
            group="median",
            success_message="Sorted the list",
        ))
    
    size = len(l)
    if track:
        mean_ref.append(pybryt.Value(
            size,
            name="len",
            group="median",
            success_message="Determined the length of the list",
        ))

    middle = size // 2
    is_set_size_even = size % 2 == 0

    if is_set_size_even:
        m = (sorted_l[middle - 1] + sorted_l[middle]) / 2
    else:
        m = sorted_l[middle]

    if track:
        mean_ref.append(pybryt.Value(
            m,
            name="mean",
            group="mean",
            success_message="Calculated the correct mean of the list",
            failure_message="Did not find the correct mean of the list",
        ))

    return m

test_lists = [[1, 2, 3], [-1, 0, 1], [10, -4, 2, 0], [1]]
for test_list in test_lists:
    mean(test_list, track=True)
    median(test_list, track=True)

assignment_ref = pybryt.ReferenceImplementation("mean-median", [*mean_ref, *median_ref])

Con una referencia generada como la del ejemplo anterior, podemos ofrecer a los alumnos la oportunidad de comprobar su trabajo en cada pregunta antes de pasar a la siguiente, diciéndole a PyBryt qué grupo de anotaciones debe tenerse en cuenta:

>>> with pybryt.check(assignment_ref, group="mean"):
...     for test_list in test_lists:
...         mean(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Calculated the correct mean of the list
>>> with pybryt.check(assignment_ref, group="median"):
...     for test_list in test_lists:
...         median(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Sorted the list
>>> with pybryt.check(assignment_ref):
...     for test_list in test_lists:
...         mean(test_list)
...         median(test_list)
REFERENCE: mean-median
SATISFIED: True
MESSAGES:
  - Determined the length of the list
  - Calculated the correct mean of the list
  - Sorted the list

Comprobar los conocimientos

1.

¿Cuál de las siguientes llamadas limita a cinco el número de veces que se hace un seguimiento de la variable lst?

2.

¿Cuál es el objetivo de la opción group?

3.

Supongamos que creamos una referencia con el siguiente código:

for i in range(10):
    pybryt.Value(foo(i), name="foo", limit=5)
    pybryt.Value(bar(i), group="foo")
    pybryt.Value(baz(i), name="baz")

¿Cuántas anotaciones hay en la referencia?