Práctica 1 | P2 GIR Saltearse al contenido

Práctica 1

A continuación se encuentra el enunciado de la práctica 1. Lee cuidadosamente el enunciado y sigue las instrucciones. Se recomienda consultar el apartado de Teoría para tratar de resolver dudas de concepto. También puedes consultar el enunciado de la práctica 0, donde puedes encontrar algún ejemplo sobre vectores del Standard Template Library (STL) que podría serte útil en esta práctica.

1 Cambios

1.1 Versión 1.0.0

  • Enunciado original.

1.1 Versión 1.0.1

  • Se ha añadido lo siguiente: en la función filter tienes que comprobar que no se añadan índices repetidos en el resultado de la función. Si hay índices repetidos dentro del vector student_idxs y del vector subject_idxs, se ignorarán los duplicados y se utilizarán solo los índices la primera vez que aparecen en los vectores.
  • Aclaración: El orden de los índices recibidos en la función filter debe respetarse en el resultado final.

2 Archivos de partida.

  • Aquí se encuentran los archivos de partida. Descárgalos y utilizalos como base para resolver la práctica.

3 Objetivos

  • Adquirir práctica y destreza con las características de C++ que lo hacen un mejor C.
  • Profundizar en los conocimientos aprendidos en Programación I.
  • Hacer uso de las herramientas estudiadas en la práctica 0.

4 Enunciado

Esta práctica consta de un ejercicio en el que trabajarás con una matriz de datos que contendrá una lista de alumnos y sus notas en varias asignaturas. Las filas se corresponderán con los estudiantes, mientras que las columnas representarán las asignaturas. Es decir, si tenemos 3 estudiantes y 5 asignaturas, data dentro del registro Evaluations será un puntero que apunte a un array de 3 elementos (los 3 estudiantes), y a su vez cada uno de estos estudiantes será otro puntero que apuntará a la lista de notas por asignatura, en este caso a un array de 5 notas. Emplearemos los siguientes tipos de datos y constantes que se encuentran en p1.h:

/** El tipo de los elementos de la matriz de bajo nivel. */
using etype_t = uint32_t;
/// El tipo de la fila de la matriz de bajo nivel
using student_t = etype_t*;
//! El tipo matriz de bajo nivel.
using matrix_t = student_t*;
// Estructura que contiene los datos de la matriz de evaluaciones
struct Evaluations {
uint32_t students;
uint32_t subjects;
matrix_t data;
};
// Constante que representa un dato de tipo Evaluations que no tiene información
// (con la matriz nula y número de estudiantes y de asignaturas con el valor 0)
const Evaluations empty_evaluations = {0, 0, nullptr};

4.1 Funciones de gestión de los datos.

Para poder implementar las funciones, se recomienda seguir el mismo orden en el que se encuentran en el fichero p1.h. Si te fijas, las primeras funciones que aparecen son funciones básicas para trabajar con el registro Evaluations: para crear una nueva variable (mreserve), para liberar la memoria reservada una vez que quiera destruir dicha variable (mfree), para mostrar la información que contiene (mshow) y para modificar la nota de un alumno en una determinada asignatura (modify_evaluaton). Vamos a ver en detalle primero estas funciones y más adelante veremos las restantes.

/* Funciones para crear un registro de Evaluations utilizando memoria dinámica
para construir los datos de acuerdo a la cantidad de estudiantes y asignaturas.
Inicializa las notas con el valor del parámetro v, cuyo valor por defecto es 0.
*/
Evaluations mreserve(uint32_t students, uint32_t subjects, etype_t v = 0);
// Libera la memoria creada con memoria dinámica
void mfree(Evaluations& e);
//Visualiza en pantalla los datos de las notas en forma de matriz. Las filas son los estudiantes y las columnas las asignaturas.
void mshow(const Evaluations& e);
// Función para modificar la evaluación de un alumno en una determinada asignatura
void modify_evaluation(Evaluations& e, uint32_t student_idx, uint32_t subject_idx, etype_t new_value);

Veamos lo que tienen que hacer estas funciones:

  1. Evaluations mreserve(uint32_t students, uint32_t subjects, etype_t v = 0); :

    • Crea y devuelve un registro de tipo Evaluations que contiene una matriz con las notas de los estudiantes en las diferentes asignaturas.

    • data contendrá una matriz con un número de filas igual al número de estudiantes (students) y un número de columnas igual al número de asignaturas (subjects). Fíjate que data es de tipo matrix_t, que a su vez es de tipo student_t*. Es decir, data realmente será un puntero que apuntará a un array de tipo student_t, y a su vez, cada uno de los elementos de ese array será otro puntero que apuntará a un array de etype_t, que contendrá la lista de notas del estudiante para todas las asignaturas.

    • Para rellenar los datos de data, necesitarás trabajar con memoria dinámica, utilizando el operador new.

    • Si el numero de estudiantes o de asignaturas solicitado es 0 entonces no debes reservar memoria devuelve empty_evaluations (definido en p1.h). Ten en cuenta que si por ejemplo tienes students = 5 y subjects = 0, se devolverá empty_evaluations ya que no se ha podido generar la matriz data. En este caso, tanto students como subjects dentro del nuevo registro creado tendrán el valor 0, tal y como aparece en empty_evaluations.

    • La función inicializará todas las evaluaciones de la matriz con el valor proporcionado por el parámetro v, que es opcional y su valor por defecto es 0 (esto ya está preparado en p1.h). Debes comprobar que v tiene un valor entre 0 y 10 (ambos incluidos). Si v no tiene un valor correcto, entonces se devolverá empty_evaluations.

      • Nota: El valor por defecto indica que un parámetro es opcional a la hora de invocar a la función. Si se invoca la función sin incluir dicho parámetro, dentro de la función ese parámetro tendrá el valor por defecto (en este caso el valor 0).
  2. void mfree(Evaluations& e); :

    • Libera la memoria dinámica de la matriz data detro del parámetro que se recibe por referencia e. Cuando termine la función, e debe de ser como empty_evaluations, es decir, data tendrá el valor nullptr y los atributos students y subjects tendrán el valor 0.
  3. void mshow(const Evaluations e); :

    • Muestra la matriz data que contiene e por pantalla como tienes en los ejemplos más abajo. Si la matriz está vacía, no se mostrará nada.
  4. void modify_evaluation(Evaluations& e, uint32_t student_idx, uint32_t subject_idx, etype_t new_evaluation);:

    • Modifica la nota de un alumno student_idx en una asignatura específica subject_idx. Estos dos parámetros contienen la posición donde se encuentra el estudiante y la asignatura dentro de la matriz. La primera posición será la 0.
    • Si se reciben índices que no existen dentro de la matriz o un valor en new_evaluation que no está entre 0 y 10, no se debe realizar ninguna modificación ni mostrar ningún mensaje en pantalla.

4.2 Funciones de cálculo.

Se nos pide implementar tres funciones que utilizarán los datos de la matriz para calcular promedios y también para filtrar los datos seleccionando unos pocos. En esta parte de la práctica, trabajarás con vectores.

Al igual que con las funciones anteriores, en p1.h tienes el prototipo de las funciones que tienes que implementar:

// Función que filtra las evaluaciones creando un nuevo registro con una copia de las evaluaciones para determinados estudiantes y asignaturas.
Evaluations filter(const Evaluations& e, const std::vector<uint32_t> &student_idxs, const std::vector<uint32_t> &subject_idxs);
// Función que devuelve el promedio de las evaluaciones por asignatura en forma de vector.
std::vector<etype_t> average_per_subject(const Evaluations& e);
// Función que devuelve el promedio de las evaluaciones por estudiante en forma de vector.
std::vector<etype_t> average_per_student(const Evaluations& e);

Y su funcionamiento es el siguiente:

  1. Evaluations filter(const Evaluations& e, const std::vector<uint32_t> &student_idxs, const std::vector<uint32_t> &subject_idxs); :
    • Esta función filtrará los datos de la matriz para seleccionar solo los datos de algunos estudiantes y asignaturas indicados por los parámetros de la función.

    • El objetivo de esta función es construir un nuevo registro Evaluations a partir de los datos del parámetro e, y de los vectores student_idxs y subject_idxs, que representan los índices de los estudiantes y de las asignaturas de los cuales se quiere obtener la información de su evaluación. La idea es que la función devuelva un registro nuevo que contendrá únicamente los datos de los estudiantes en las posiciones indicadas por student_idxs y de las asignaturas en las posiciones indicadas por subject_idxs.

    • Si se da el caso de que todos los índices de asignaturas y estudiantes existen dentro del parámetro e, entonces la nueva matriz tendrá un número de filas equivalente al tamaño de student_idxs y un número de columnas equivalente al tamaño de subject_idxs.

    • Si hay algún índice de estudiantes o de asignaturas en student_idxs o subject_idxs que no exista dentro de e, se ignorará ese índice y se pasará al siguiente.

    • Si se da el caso de que alguno de los vectores student_idxs o subject_idxs no contiene ningún índice válido, entonces se asume que el nuevo registro no se puede rellenar, por lo que el contenido del registro estará vacío. Esto quiere decir que la matriz data no tendrá datos (tendrá el valor nullptr) y el número de estudiantes students y de asignaturas (subjects) será 0. Para hacer esto, puedes devolver directamente empty_evaluations, definido en p1.h.

      • Si tras comprobarlo, se obtienen 0 estudiantes o 0 asignaturas, entonces se devolverá empty_evaluations.
    • La primera posición será la 0 en los índices recibidos por parámetro.

    • CAMBIO 1.0.1: Si la función filter recibe índices repetidos, se deben ignorar los duplicados y sólo utilizar el índice la primera vez que aparece en el vector. Esto aplica tanto a student_idxs como a subject_idxs. Hay muchas formas de hacer esto, pero aquí tienes una pista de una posible solución:

      • Pista: puedes declarar un nuevo vector (inicialmente vacío) donde anotar las posiciones que se van procesando. Si lo haces así, puedes crear una función simple que compruebe si existe o no un índice dentro de ese vector. En caso de que no exista, significa que no se ha encontrado antes el mismo índice ya que no se ha procesado hasta el momento, y por lo tanto se puede procesar y añadir al vector de índices ya procesados. Si lo haces así, recuerda no mezclar índices de asignaturas con índices de estudiantes.
    • CAMBIO 1.0.1: El orden de los índices de los vectores debe respetarse tal y como vienen en los vectores recibidos por parámetro.

    • Recuerda que los estudiantes se representan por filas y las asignaturas por columnas dentro de data.

    • Veamos un ejemplo (tienes más en mainp1.cc):

Supongamos este ejemplo con 3 estudiantes y 4 asignaturas:

[ 5 3 6 8 ]
[ 7 10 8 9 ]
[ 3 8 1 4 ]

Si usamos la función filter pidiendo los estudiantes 0 y 2 (en el vector student_idxs) y las asignaturas 1 y 3 (en el vector subjects_idxs), el resultado sería:

[ 3 8 ]
[ 8 4 ]

Si usamos la función filter pidiendo los estudiantes 0, 2 y 21 (en el vector student_idxs) y las asignaturas 1, 3 y 122 (en el vector subjects_idxs), el resultado sería exactamente el mismo:

[ 3 8 ]
[ 8 4 ]

Si usamos filter pidiendo el estudiante 21 (en el vector student_idxs) y las asignaturas 1 y 3 (en el vector subjects_idxs), el resultado sería empty_evaluations porque no hay ningún índice correcto de estudiantes.

Si usamos filter pidiendo un estudiante válido (por ejemplo el 2) y los índices de las asignaturas no son válidos (por ejemplo 200, 300 y 400), el resultado será de nuevo empty_evaluations porque no hay ningún índice correcto de asignaturas.

Del mismo modo, si ninguno de los índices de estudiantes y asignaturas es correcto, (por ejemplo el estudiante 20 y las asignaturas 200, 300 y 400), el resultado será también empty_evaluations.

CAMBIO 1.0.1 (ejemplos a partir de aquí): Por otro lado, si tenemos índices repetidos, se ignorarán los duplicados, utilizando solo su primera instancia en los vectores de índices. Por ejemplo, en el caso anterior:

[ 5 3 6 8 ]
[ 7 10 8 9 ]
[ 3 8 1 4 ]

Si utilizamos filter indicando en student_idxs los índices 0, 1 y 1, y en subjects_idxs tenemos todas las asignaturas (0, 1, 2 y 3), entonces el resultado sería el siguiente:

[ 5 3 6 8 ]
[ 7 10 8 9 ]

Fíjate que las evaluaciones del alumno en la posición 1 solo aparecen una vez, aunque se haya añadido dos veces al vector student_idxs el índice 1. Esto mismo deberá ocurrir con las asignaturas. En este caso, el atributo students tendrá el valor 2, mientras que subjects tendrá el valor 4 dentro de la variable de tipo Evaluations que se obtiene.

Por supuesto, si el orden de los índices es diferente, se tendrá en cuenta en el resultado final. Por ejemplo, suponiendo que student_idxs recibe 1, 0 y 1 (en este orden), el resultado será:

[ 7 10 8 9 ]
[ 5 3 6 8 ]

Fíjate que las evaluaciones del alumno en la posición 1 solo aparecen una vez, aunque se haya añadido dos veces al vector student_idxs el índice 1. Esto mismo deberá ocurrir con las asignaturas.

  1. std::vector<etype_t> average_per_subject(const Evaluations& e); :
    • Calculará el promedio de las notas de los estudiantes por cada asignatura, devolviendo un vector de tamaño igual al número de asignaturas que contiene cada el promedio de cada asignatura por separado.
    • Si la evaluación no tiene estudiantes o asignaturas, devolverá un vector vacío.

En este ejemplo con 3 estudiantes y 4 asignaturas:

[ 5 3 6 8 ]
[ 7 10 8 9 ]
[ 3 8 1 9 ]

El resultado de average_per_subject sería:

[ 5 7 5 8 ]

Fíjate que al ser el tipo de dato using etype_t = uint32_t, el resultado no tendrá valor decimal y se truncará (no se aproximará). En el ejemplo, en la última asignatura (última columna) el resultado sería 8.67, pero por el tipo de dato con el que trabajamos, se queda en 8,

  1. std::vector<etype_t> average_per_student(const Evaluations& e); :
    • Calculará el promedio de las notas de todas las asignaturas por cada estudiante, devolviendo un vector de tamaño igual al número de estudiantes que contiene el promedio de todas las notas de cada estudiante calculado por separado.
    • Si la evaluación no tiene estudiantes o asignaturas, devolverá un vector vacío.

En este ejemplo con 3 estudiantes y 4 asignaturas:

[ 5 3 6 8 ]
[ 7 10 8 9 ]
[ 3 8 1 4 ]

El resultado de average_per_student sería:

[ 5 8 4 ]

4.3 Ejemplo de mainp1.cc.

Tienes un programa de ejemplo en mainp1.cc y su salida debería ser:

Matriz de evaluaciones completa:
[ 7 5 9 6 ]
[ 4 8 6 7 ]
[ 9 6 5 8 ]
Promedios por asignatura:
Asignatura 0: 6
Asignatura 1: 6
Asignatura 2: 6
Asignatura 3: 7
Promedios por estudiante:
Estudiante 0: 6
Estudiante 1: 6
Estudiante 2: 7
Matriz filtrada (estudiantes 0 y 2, asignaturas 1 y 3):
[ 5 6 ]
[ 6 8 ]

No te conformes con el main que dispones en los archivos de partida. Asegúrate de probar tu código tanto como puedas. Puedes modificar la función main del fichero mainp1.cc tanto como necesites para incluir todas las pruebas que consideres oportunas.

Consejo: cuando pruebes tu código, diseña las pruebas sin mirar tu implementación. Diseñalas solo mirando la especificación del enunciado.

5 División del código fuente en archivos.

  • Tendremos un único archivo de cabecera ( p1.h ) con los prototipos de las funciones además de los #include necesarios para que no de errores de compilación.

  • Este archivo hará uso de la guarda para protegerlo de su inclusión más de una vez que hemos visto en el tema-1.

  • Tendremos un único archivo de implementación ( p1.cc, trabajamos en C++) con la implementación de todas las funciones además de los #include necesarios para que no de errores de compilación (también incluirá a p1.h). Este archivo no contendrá ningún programa principal (main) pero sí podrá contener las funciones auxiliares que necesites.

  • Para poder probar tus funciones puedes crearte la función main que quieras en otro archivo, p.e. mainp1.cc (el cual incluirá a p1.h) y compilarlo y enlazarlo con el anterior así: g++ -g -Wall --std=c++17 mainp1.cc p1.cc -o p1. No es necesario entregarlo, si se hace no pasa nada.

  • También puedes emplear el archivo Makefile para compilar y enlazar. Recuerda que lo tienes en la carpeta de la práctica. Para utilizarlo, puedes ejecutar en la terminal:

    • make para compilar y enlazar, creando el ejecutable.
    • make run para ejecutar tu programa después de haberlo compilado previamente.
    • make tgz para generar el archivo comprimido que deberás entregar.
    • make clean para borrar los archivos compilados.
  • Todos los archivos `.h' y `.cc' se encontrarán dentro de una carpeta llamada irp2-p1.

6 Entrega. Requisitos técnicos.

  • Requisitos que tiene que cumplir este trabajo práctico para considerarse válido y ser evaluado (si no se cumple alguno de los requisitos la calificación será cero):

  • El archivo entregado se llama irp2-p1.tgz (todo en minúsculas), no es necesario entregar ningún programa principal, sólo son necesarios tus archivos `.h' y `.cc' en una carpeta llamada irp2-p1. A su vez, esta carpeta estará comprimida en el fichero llamado irp2-p1.tgz. Este archivo lo puedes crear así en la terminal:

    tar cfz irp2-p1.tgz irp2-p1

  • Una vez comprimido, ábrelo y asegúrate de que tiene la estructura que se pide.

  • También puedes emplear el archivo Makefile que tienes en la carpeta de la práctica para generar el fichero de la entrega tal y como hemos visto en clase de prácticas: make tgz.

  • Al descomprimir el archivo irp2-p1.tgz se crea un directorio de nombre irp2-p1 (todo en minúsculas).

  • Dentro del directorio irp2-p1 estará al menos el archivo p1.cc (todo en minúsculas).

    Recuerda que el fichero ‘p1.hno se debe modificar, de lo contrario tus ficheros ‘.cc’ no compilarán con el corrector. No es necesario entregarlo, si se hace no pasa nada.

  • Las clases, métodos y funciones implementados se llaman como se indica en el enunciado (respetando en todo caso el uso de mayúsculas y minúsculas). También es imprescindible respetar estrictamente los textos y los formatos de salida que se indican en este enunciado.

  • Al principio de todos los ficheros fuente (`.h’ y `.cc’) entregados y escritos por ti se debe incluir un comentario con el nombre y el NIF (o equivalente) de la persona que entrega la práctica, como en el siguiente ejemplo:

    // NIF: 12345678-Z
    // NOMBRE: GARCIA PEREZ, LAURA
  • Un error de compilación/enlace implicará un cero en esa parte de la práctica.

  • Se utilizará valgrind para comprobar que no haya fugas de memoria en tu programa. Puedes instalarlo y probarlo antes de realizar la entrega para asegurarte de corregir posibles errores relacionados con la gestión de la memoria.

  • Lugar y fecha de entrega : Recuerda que las entregas se realizan siempre en (y sólo en) https://pracdlsi.dlsi.ua.es en las fechas y condiciones allí publicadas. Puedes entregar la práctica tantas veces como quieras, sólo se corregirá la última entrega (las anteriores no se borran). El usuario y contraseña para entregar prácticas es el mismo que se utiliza en UACloud.

7 Detección de plagios/copias.

  • IMPORTANTE:
    • Cada práctica debe ser un trabajo original de la persona que la entrega.

    • En caso de detectarse indicios de copia de una o más prácticas se tomarán las medidas disciplinarias correspondientes, informando a las direcciones de Departamento y de la EPS por si hubiera lugar a otras medidas disciplinarias adicionales.