2

Acabo de empezar a estudiar ficheros en C++. Estaba intentado hacer un ejercicio pero no he sido capaz de sacarlo.

El enunciado es el siguiente:

Realizar un programa que lea por teclado el nombre de un fichero. Este fichero contendrá una serie de preguntas tipo test. El número de preguntas que contendrá el fichero nunca será mayor de 50.

Para cada pregunta, el fichero contendrá tres posibles respuestas y un número indicando cual de las tres posibles respuesta es la correcta (ver fichero adjunto de ejemplo). De esta forma, el formato del fichero será el siguiente:

pregunta1 --salto de linea
respuesta_correcta_a_la_pregunta1 salto de linea
respuesta1 salto de linea
respuesta2 salto de linea
respuesta3 salto de linea
pregunta2 salto de linea
respuesta_correcta_a_la_pregunta2 salto de linea
respuesta1 salto de linea
respuesta2 salto de linea
respuesta3 salto de linea
...

El programa, tras cargar los datos del fichero a memoria en una estructura de datos adecuada, deberá realizar el test mostrando por pantalla cada una de las preguntas y posibles respuestas y leyendo por teclado la respuesta a cada pregunta.

Al finalizar, el programa mostrará por pantalla el número de respuesta correctamente respondidas, el número de respuestas incorrectamente respondidas y la puntuación calculada según la siguiente fórmula:

puntuación = 10 * (respuestas_correctas – (respuestas_incorrectas / 2)) / total_de_preguntas_en_el_fichero

Tener en cuenta que la puntuación deberá ser calcula como un número real.

Os dejo mi codigo:

#include <iostream>
#include <fstream>
#include <array>
#include <string>

using namespace std;

const int MAX_PREG = 10;

    struct preguntas{

        string pregunta;
        int respuesta;
        string respuesta1;
        string respuesta2;
        string respuesta3;
    };

    typedef array <preguntas,MAX_PREG> array_dat;

    struct Ttest{
        int np = 0;
        array_dat pr;
    };

int main(){

    int respuesta_introducida;
    int contador = 0;


    
    Ttest x;
    array_dat y;

    string fichero;

    cout << "Introduce el nombre del fichero " << endl;
    cin >> fichero;

    ifstream fichero1;

    fichero1.open(fichero);

    string linea;
    int correcta;
    //getline(fichero1,linea);
    //contador++;
    while(!fichero1.eof()){
        
        if (contador == 0 || contador % 4 == 0){
            x.pr[x.np].pregunta = linea;
        }
        if (contador == 1 || contador % 1 == 1){
            x.pr[x.np].respuesta = correcta;
        }
        if (contador == 2 || contador % 2 == 0){
            x.pr[x.np].respuesta1 = linea;
        }
        if(contador == 2 || contador % 2 == 0){
            x.pr[x.np].respuesta2 = linea;
        }
        if(contador == 3 || contador % 3 == 0){
            x.pr[x.np].respuesta3 = linea;
        }
        
        getline(fichero1,linea);
    }
}

El txt de ejemplo que estaba usando era este:

De los siguientes animales cual no es un mamifero?
2
La ballena
El pulpo
La vaca
Cuando vale 0 dividido por 5?
1
0
1
No se puede calcular
Un bucle for
2
Puede utilizar una variable de control de tipo double
Tiene que ser determinista
Ninguna de las otras dos respuestas es correcta

La dudas que tengo es que no se como asignar cada linea a cada elemento de la estructura, es decir elegir la linea que asigno a cada elemento de la estructura.De eso surge el lio que tengo abajo con los contadores, etc

Skydez
  • 31
  • 2

2 Answers2

2

Que te digan que el máximo de preguntas posibles es 50 no quiere decir que SIEMPRE vayan a existir 50 preguntas. std::array implementa un vector de tamaño fijo y no parece muy práctico porque no le puedes preguntar cuántos elementos contiene realmente (porque en tu caso siempre contendrá 50 aunque el fichero tenga únicamente 5 preguntas).

En su lugar yo te recomendaría usar std::vector. Este contenedor puede almacenar un número variable de elementos y una gran ventaja de ello es que no tendrás que lidiar con elementos fantasma.

// Lo siento, no me gusta usar "using namespace std"
typedef std::vector<preguntas,MAX_PREG> array_dat;

Ahora vamos con la lectura.

Se supone que las lecturas son secuenciales:

  1. Se lee la pregunta
  2. Se lee la solución
  3. Se lee la opción 1
  4. Se lee la opción 2
  5. Se lee la opción 3

Luego para cada pregunta tendrás que leer 5 líneas. Esto hace que, necesariamente, tengas que llamar a std::getline 5 veces dentro de cada iteración del while:

while(true){

  // Planteate que lo mismo el nombre de la estructura
  // no es el más adecuado
  preguntas pregunta;

  std::getline(fichero1,pregunta.pregunta);

  if( pregunta.pregunta.empty() )
    break;

  fichero1 >> pregunta.respuesta;
  fichero1.ignore();

  std::getline(fichero1,pregunta.respuesta1);
  std::getline(fichero1,pregunta.respuesta2);
  std::getline(fichero1,pregunta.respuesta3);

  y.push_back(pregunta);
}

Dos detalles que pueden llamar la atención:

  1. El bucle no tiene, por defecto, fin. Dado que llegaremos al final del fichero al encontrar una pregunta en blanco (según la especificación del fichero que te han facilitado, las respuestas SIEMPRE tienen un salto de línea), utilizaremos esta pregunta vacía para salir del bucle.
  2. El entero se lee de forma diferente porque existe un método especial para leer este tipo de valor y no lidiar con la conversión. El problema de esta lectura es que no elimina el salto de línea, de ahí la línea fichero.ignore(), que eliminará el salto de línea.
  3. El vector del test se va rellenando sobre la marcha. Al finalizar la lectura bastará llamar a y.size() para saber el número de preguntas almacenadas (asumiendo que estás usando std::vector en vez de std::array).
eferion
  • 49,291
  • 5
  • 30
  • 72
  • Excelente respuesta como siempre, eferion. Ojalá más gente siguiese el consejo de [no usar using namespace std;](http://es.stackoverflow.com/questions/460/por-qu%c3%a9-el-usar-using-namespace-std-se-considera-mala-pr%c3%a1ctica) Me lo encuentro contínuamente en código heredado de terceros. – Anonymous Coward Feb 25 '17 at 09:27
0

Veamos. Un problema a considerar en el código original es el siguiente:

struct Ttest{
        int np = 0;
        array_dat pr;
    };

Entiendo que esta estructura la utilizas para almacenar el número de pregunta, y su bloque de preguntas y respuestas. Sin embargo, se puede utilizar directamente la posición de cada estructura de tipo preguntas dentro del array para este fin, por lo que esta estructura ya no es necesaria.

Con respecto a la lectura del archivo, yo te recomendaría que utilices dos contadores: uno para llevar el conteo del siguiente bloque de pregunta y respuestas, y otro para el manejo de cada linea de ese bloque.

El siguiente código (es el tuyo con unas modificaciones, y está probado) realiza la lectura del archivo y lo almacena en la estructura que definiste. Revisa el código, y a partir de aquí puedes continuar con tu ejercicio:

    #include "stdafx.h"
        #include <iostream>
        #include <stdio.h>
        #include <string>
        #include <conio.h>
        #include <fstream>
        #include <array>


        using namespace std;

        const int MAX_PREG = 10;

        struct preguntas{

            string pregunta;
            int respuesta;
            string respuesta1;
            string respuesta2;
            string respuesta3;
        };

        typedef array <preguntas, MAX_PREG> array_dat;

        array_dat lista_preguntas;//solo es necesario este array

         /*Siempre inicializar las estructuras y variables*/
        void inicializar_arreglo(array_dat arreglo)
        {
            for (int contador = 0; contador < MAX_PREG; contador++)
            {
                arreglo[contador].pregunta = "";
                arreglo[contador].respuesta = 0;
                arreglo[contador].respuesta1 = "";
                arreglo[contador].respuesta2 = "";
                arreglo[contador].respuesta3 = "";

            }
        }

        int main(int argc, _TCHAR* argv[])
        {
            int respuesta_introducida;
            int contador_preguntas = 0;
            int contador_lineas = -1;
            string linea;
            int correcta = 0;

            string fichero;
            inicializar_arreglo(lista_preguntas);
            cout << "Introduce el nombre del fichero " << endl;
            cin >> fichero;

            ifstream fichero1;

            fichero1.open(fichero);

            while (!fichero1.eof()){
                getline(fichero1, linea);
                contador_lineas++;
                if (contador_lineas == 0)
                {
                    lista_preguntas[contador_preguntas].pregunta = linea;           
                }

                if (contador_lineas == 1)
                {
                    lista_preguntas[contador_preguntas].respuesta = std::stoi(linea);   

//Se utiliza el stoi para castear el tipo de dato string (de la linea) a int (la variable de la estructura que almacena el número de respuesta correcta)        
                }
                if (contador_lineas == 2)
                {
                    lista_preguntas[contador_preguntas].respuesta1 = linea;         
                }
                if (contador_lineas == 3)
                {
                    lista_preguntas[contador_preguntas].respuesta2 = linea;         
                }
                if (contador_lineas == 4)
                {
                    lista_preguntas[contador_preguntas].respuesta3 = linea;         

    /* Cuando llegamos a la ultima respuesta del bloque, se debe inicializar el contador de lineas para leer un nuevo bloque, y se incrementa en uno el contador de preguntas para que se almacene la siguiente*/
                    contador_lineas = -1;
                    contador_preguntas++;
                }

            }


            contador_preguntas = 0;
            while (contador_preguntas < 10)
            {
                if (!(lista_preguntas[contador_preguntas].pregunta == ""))
                {
                    cout << "Pregunta: " << contador_preguntas << endl;
                    cout << "Pregunta" << lista_preguntas[contador_preguntas].pregunta << endl;
                    cout << "Respuesta Correcta" << lista_preguntas[contador_preguntas].respuesta << endl;
                    cout << "Respuesta 1" << lista_preguntas[contador_preguntas].respuesta1 << endl;
                    cout << "Respuesta 2" << lista_preguntas[contador_preguntas].respuesta2 << endl;
                    cout << "Respuesta 3" << lista_preguntas[contador_preguntas].respuesta3 << endl;            
                }
                contador_preguntas++;
            }

            cin >> correcta;


            return 0;
        }

Una anotación mas que te servirá en el desarrollo de tus programas: utiliza nombres descriptivos (no tengas miedo que sean largos) para tus variables y procedimientos, puedes observar que es mas fácil de leer y entender:

lista_preguntas[contador_preguntas].pregunta = linea;

Que por ejemplo:

x.pr[x.np].pregunta = linea;

Así no te dará mucho problema seguirle la lógica a tu código.

JYass
  • 1,534
  • 8
  • 14