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 vectorstudent_idxs
y del vectorsubject_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 nivelusing student_t = etype_t*;//! El tipo matriz de bajo nivel.using matrix_t = student_t*;
// Estructura que contiene los datos de la matriz de evaluacionesstruct 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ámicavoid 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 asignaturavoid modify_evaluation(Evaluations& e, uint32_t student_idx, uint32_t subject_idx, etype_t new_value);
Veamos lo que tienen que hacer estas funciones:
-
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 quedata
es de tipomatrix_t
, que a su vez es de tipostudent_t*
. Es decir,data
realmente será un puntero que apuntará a un array de tipostudent_t
, y a su vez, cada uno de los elementos de ese array será otro puntero que apuntará a un array deetype_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 operadornew
. -
Si el numero de estudiantes o de asignaturas solicitado es
0
entonces no debes reservar memoria devuelveempty_evaluations
(definido enp1.h
). Ten en cuenta que si por ejemplo tienesstudents = 5
ysubjects = 0
, se devolveráempty_evaluations
ya que no se ha podido generar la matrizdata
. En este caso, tantostudents
comosubjects
dentro del nuevo registro creado tendrán el valor0
, tal y como aparece enempty_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 es0
(esto ya está preparado enp1.h
). Debes comprobar quev
tiene un valor entre0
y10
(ambos incluidos). Siv
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
).
- 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
-
-
void mfree(Evaluations& e);
:- Libera la memoria dinámica de la matriz
data
detro del parámetro que se recibe por referenciae
. Cuando termine la función,e
debe de ser comoempty_evaluations
, es decir,data
tendrá el valornullptr
y los atributosstudents
ysubjects
tendrán el valor0
.
- Libera la memoria dinámica de la matriz
-
void mshow(const Evaluations e);
:- Muestra la matriz
data
que contienee
por pantalla como tienes en los ejemplos más abajo. Si la matriz está vacía, no se mostrará nada.
- Muestra la matriz
-
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íficasubject_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á la0
. - Si se reciben índices que no existen dentro de la matriz o un valor en
new_evaluation
que no está entre0
y10
, no se debe realizar ninguna modificación ni mostrar ningún mensaje en pantalla.
- Modifica la nota de un alumno
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:
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ámetroe
, y de los vectoresstudent_idxs
ysubject_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 porstudent_idxs
y de las asignaturas en las posiciones indicadas porsubject_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 destudent_idxs
y un número de columnas equivalente al tamaño desubject_idxs
. -
Si hay algún índice de estudiantes o de asignaturas en
student_idxs
osubject_idxs
que no exista dentro dee
, se ignorará ese índice y se pasará al siguiente. -
Si se da el caso de que alguno de los vectores
student_idxs
osubject_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 matrizdata
no tendrá datos (tendrá el valornullptr
) y el número de estudiantesstudents
y de asignaturas (subjects
) será0
. Para hacer esto, puedes devolver directamenteempty_evaluations
, definido enp1.h
.- Si tras comprobarlo, se obtienen
0
estudiantes o0
asignaturas, entonces se devolveráempty_evaluations
.
- Si tras comprobarlo, se obtienen
-
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 astudent_idxs
como asubject_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.
- Pista: puedes declarar un nuevo
-
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.
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
,
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: 6Asignatura 1: 6Asignatura 2: 6Asignatura 3: 7
Promedios por estudiante:Estudiante 0: 6Estudiante 1: 6Estudiante 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 enC++
) 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á ap1.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á ap1.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 llamadairp2-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 llamadairp2-p1
. A su vez, esta carpeta estará comprimida en el fichero llamadoirp2-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 nombreirp2-p1
(todo en minúsculas). -
Dentro del directorio
irp2-p1
estará al menos el archivop1.cc
(todo en minúsculas).Recuerda que el fichero ‘p1.h’ no 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.
-