Anotaciones relacionales

Completado

El tipo más básico de anotación en PyBryt es la anotación value, que declara la presencia de algún valor en la superficie de memoria del alumno. Sin embargo, estas anotaciones no cubren todos los casos. ¿Qué ocurre si desea comprobar dos representaciones posibles de algún valor o desea examinar el orden de los valores? Aquí es donde entran en juego las anotaciones relacionales.

Las anotaciones relacionales definen algún tipo de relación entre otras anotaciones. Se puede decir que las anotaciones relacionales son anotaciones que funcionan en otras anotaciones y declaran condiciones sobre cómo se cumplen esas anotaciones. En el último módulo, aprendió sobre las anotaciones de colección y cómo usarlas para imponer el orden de las anotaciones. Las anotaciones relacionales son similares y, de hecho, se puede decir que las colecciones son un tipo de anotaciones relacionales.

Actualmente, PyBryt admite dos tipos de anotaciones relacionales: las anotaciones temporales y las anotaciones lógicas.

Anotaciones temporales

Las anotaciones temporales describen una relación temporal entre diferentes anotaciones. Las anotaciones se cumplen al cumplirse todas sus anotaciones secundarias; y las marcas de tiempo de los valores que cumplen esas anotaciones se producen en un orden determinado.

PyBryt solo tiene un tipo de anotación temporal, BeforeAnnotation, que declara que las marcas de tiempo que cumplen sus anotaciones secundarias se producen en orden no decreciente. Se pueden crear instancias de estas anotaciones directamente usando el constructor como con cualquier otra anotación, pero todas las anotaciones tienen también un método before y after que se puede usar para construir las anotaciones de una manera más semántica:

>>> a1 = pybryt.Value(1)
>>> a2 = pybryt.Value(2)
>>> a1.before(a2), a1.after(a2)
(pybryt.BeforeAnnotation, pybryt.BeforeAnnotation)

Como se puede ver, Annotation.before y Annotation.after devuelven BeforeAnnotation, pero el orden de las anotaciones se invierte en la anotación devuelta por after.

Al crear una anotación relacional, puede actualizar los campos de cada una de las opciones de la anotación según sea necesario, o bien pasar las opciones como argumentos de palabras clave al método before o after:

a1_before_a2 = a1.before(
    a2,
    success_message="a1 is before a2",
    failure_message="a1 is not before a2",
)

# or:
a1_before_a2 = a1.before(a2)
a1_before_a2.success_message = "a1 is before a2"
a1_before_a2.failure_message = "a1 is not before a2"

Con una superficie de memoria ficticia, podemos ver cómo se cumple BeforeAnnotation. En el siguiente ejemplo, crearemos este tipo de superficie con el método pybryt.MemoryFootprint.from_values, que acepta valores y marcas de tiempo alternos:

pybryt.MemoryFootprint.from_values(val1, ts1, val2, ts2, val3, ts3, ...)

Observe cómo cambia el resultado de la anotación relacional a medida que cambiamos los valores de la superficie y sus marcas de tiempo.

>>> ref = pybryt.ReferenceImplementation("temporal-annotations", [a1_before_a2])
>>> # the values in the correct order
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1, 2, 2))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: True
MESSAGES:
  - a1 is before a2
>>> # put both values at the same timestamp
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1, 2, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: True
MESSAGES:
  - a1 is before a2
>>> # put the timestamp of 1 after the timestamp of 2
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 2, 2, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: False
MESSAGES:
  - a1 is not before a2
>>> # don't satisfy the second annotation
>>> res = ref.run(pybryt.MemoryFootprint.from_values(1, 1))
>>> print(pybryt.generate_report(res))
REFERENCE: temporal-annotations
SATISFIED: False
MESSAGES:
  - a1 is not before a2

Anotaciones lógicas

Las anotaciones lógicas no se ocupan de la temporalidad de cuándo se cumplen las anotaciones, sino que actúan en función de si se cumplen o no las anotaciones. Declaran condiciones sobre si se cumplen las anotaciones secundarias, lo que permite construir una lógica booleana compleja dentro de las referencias para permitir que varias rutas de acceso lleguen a la misma solución.

Para crear una anotación lógica, use los operadores lógicos bit a bit de Python en cualquier anotación:

>>> a1 & a2, a1 | a2, a1 ^ a2
(pybryt.AndAnnotation, pybryt.OrAnnotation, pybryt.XorAnnotation)

Para crear condiciones que impliquen más de dos anotaciones, puede encadenar los operadores o crear instancias de las anotaciones directamente con sus anotaciones secundarias. De forma parecida a las anotaciones temporales, las opciones de las anotaciones lógicas se pueden establecer actualizando el atributo correspondiente en el objeto de anotación.

a3 = pybryt.Value(3)

all_anns = a1 & a2 & a3
all_anns.success_message = "Found a1, a2, and a3"
all_anns.failure_message = "Did not find a1, a2, and a3"

any_anns = a1 | a2 | a3
any_anns.success_message = "Found a1, a2, or a3"
any_anns.failure_message = "Did not find a1, a2, or a3"

one_ann = a1 ^ a2 ^ a3
one_ann.success_message = "Found exactly of a1, a2, or a3"
one_ann.failure_message = "Did not find exactly one of a1, a2, or a3"

PyBryt también admite el operador no (~) para crear anotaciones que solo se cumplan cuando su anotación secundaria sea no se cumple. Por ejemplo, estas anotaciones se pueden usar para enviar un mensaje a los alumnos si hay un valor determinado en su superficie de memoria que no debería estar ahí:

not_lst = ~pybryt.Value(lst)
not_lst.failure_message = "Found an incorrect value in your submission; " + \
    "please double-check your implementation."

La anotación anterior proporciona un mensaje a los alumnos si encuentra el valor de lst en la superficie de memoria. Se podría lograr el mismo efecto al establecer success_message en el constructor pybryt.Value.

Comprobar los conocimientos

1.

¿Cuál de estas expresiones crea una anotación para declarar que se cumplen a1 o a2?

2.

¿Cuál de estas llamadas crea una anotación para declarar que v2 viene inmediatamente después de v1? Es decir, las marcas de tiempo de v1 y v2 no pueden ser las mismas.

3.

Supongamos que creamos una anotación ann con:

a1, a2, a3 = pybryt.Value(1), pybryt.Value(2), pybryt.Value(3)
ann = (a1 | a2) ^ a3

¿Cuál de los siguientes conjuntos, si se convierte en una superficie de memoria, cumpliría ann?