Reemplazar información de un fichero

Iniciado por ciquee, 25 Abril 2019, 11:42 AM

0 Miembros y 1 Visitante están viendo este tema.

ciquee

Buenas compañeros/as!

Necesito ayuda porque estoy bloqueado con el tema ficheros.
Tengo un archivo (notas.dat) con nombres de alumnos y notas, y necesito anonimizar el fichero, es decir, sustituir los nombres de los alumnos por "Alumno 1"... y guardar la información en otro nuevo fichero.

Yo sé abrir ficheros para leer y para escribir, pero no tengo ni idea de como identificar el nombre de los alumnos para sustituirlos.

Por otro lado también necesito trabajar con esas notas para calcular la mas alta, la mas baja y la media...

Para la realización de estos ejercicios NO puedo usar funciones o procedimientos auxiliares, ni la utilización de datos estructurados (cadenas, registros, vectores o matrices)

¿Me podéis guiar un poquito? Muchas gracias!

K-YreX

Los ficheros son de lectura/escritura por lo que para trabajar con los datos debes guardar esos datos en variables. Para ello debes conoces la estructura que tiene el fichero.
Imagina que el fichero tiene la siguiente estructura:

Nombre Nota

Vamos a dividir el problema en dos partes:
  • PARTE 1: Modificar los nombres
    Como no puedes usar estructuras de datos (arrays) necesitarás una variable para el nombre <string> o <char*> y otra para la nota <double> o <float>. Entonces deberás seguir la siguiente estructura:

    mientras !fin_fichero_entrada
        nombre <-- leer(fichero_entrada)
        edad <-- leer(fichero_entrada)
        nombre := nuevo_nombre
        escribir(nombre, fichero_salida)
        escribir(edad, fichero_salida)
    fin mientras


  • PARTE 2: Calcular la media
    Para esto necesitas otras dos variables <suma_notas> y <num_alumnos>. Entonces la estructura es la siguiente:

    suma_notas := 0
    num_alumnos := 0
    mientras !fin_fichero_entrada
        nombre <-- leer(fichero_entrada) // aunque no lo usamos debemos recorrer todo el fichero
        edad <-- leer(fichero_entrada)
        suma_notas := suma_notas + edad
        num_alumnos := num_alumnos + 1
    fin mientras

    media := suma_notas / num_alumnos


    Espero que te sirva para resolver el problema. Si tienes algún problema más puedes dejar tus avances para poder ayudarte mejor. Tampoco sé si estás programando en C o C++...
    Suerte :-X
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

ciquee

Muchas gracias YreX-DwX!

He estado dandole vueltas a tu solución, pero creo que aún soy muy malo en esto del C++ porque no me aclaro. Aunque pienso que debe haber una forma más fácil, me explico, que la otra vez no me explique muy bien. El fichero ue tengo es del tipo:

Nombre alumno 1   Pepito Pérez
nota_alumno_1      5.8
Nombre alumno 2   Juan Gómez
nota_alumno_2      3.3
...               ...
Nombre alumno n   Mauricio Colmenero
nota_alumno_n      8.2

Con lo cual bastaría con leer linea a linea y copiar en otro archivo solo las lineas pares ¿no crees? pero no tengo ni idea de como hacerlo. A ver si me puedes orientar por favor! Este es el código que tengo por ahora...


#include <iostream>
#include <fstream>
#include <stdlib.h>

using namespace std;

int main(void) {
ofstream archivo;


archivo.open("notas.dat",ios::out);

if(archivo.fail()){
cout<<"No se pudo abrir el archivo";
exit(1);
}

while (!archivo.EOF())
{


}

archivo.close();
}


Saludos! y Buen finde!

K-YreX

Claro a ver, todo depende de cuál quieras que sea el resultado. Pongamos el supuesto de ejemplo que has comentado tú:

Nombre alumno 1   Pepito Pérez
nota_alumno_1      5.8
Nombre alumno 2   Juan Gómez
nota_alumno_2      3.3
...               ...
Nombre alumno n   Mauricio Colmenero
nota_alumno_n      8.2

Supongo que "Nombre alumno n" no va con _ (lo cuál sería más fácil de leer todo el bloque junto).

  • OPCION 1: Como tú dices copiar sólo las líneas pares.

    nota_alumno_1   5.8
    nota_alumno_2   3.3
    ...                         ...
    nota_alumno_n   8.2

    Para conseguir esto debes ir leyendo línea a línea y escribir en el fichero de salida sólo las pares. Para leer una línea usa la función <getline()>
    Código (cpp) [Seleccionar]

    istream& getline(istream&, string&, char delimitador) // esta es la funcion para que veas los parametros
    // el char es opcional y es para que lea solo hasta un caracter delimitador

    // Te pongo un ejemplo de uso
    string linea;
    while(!file.eof()){
        getline(file, linea);
        cout << linea << endl;
    }


  • OPCION 2: Sustituir el nombre del alumno por "Alumno 1", "Alumno 2", ... , "Alumno n". Para esto sabiendo el formato de la línea que es "Nombre alumno n" no hace falta copiar esa línea sino simplemente sustituirla y hacer algo así:

    Nombre alumno 1   Alumno 1
    nota_alumno_1      5.8
    Nombre alumno 2   Alumno 2
    nota_alumno_2      3.3
    ...               ...
    Nombre alumno n   Alumno n
    nota_alumno_n      8.2

    Igual que antes copias las líneas pares pero antes intercalas esto:
    Código (cpp) [Seleccionar]

    file << "Nombre alumno " << contador << "  Alumno " << contador++ << endl;

    Inicializando contador en 1 para que vaya incrementándose en cada iteración.

    Espero que te sirva y suerte :-X
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

ciquee

Buenas YreX-DwX!!

Nada, que tengo un cloqueo con esto de los ficheros o archivos...

He resuelto el primer problema, porque se que getline lee una linea entera y la puedo guardar en una variable y ya operar con ella. Aunque igual no lo he hecho de la manera más eficaz, el programa consigue lo que me piden, elimino las lineas impares y me quedo con las pares:


int main(void) {
string linea;
ifstream archivo1;
ofstream archivo2;

archivo1.open ("notas.dat");
archivo2.open ("notas_sin_nombre.dat");

if(!archivo1)
cout<<"No se pudo abrir el archivo"<<endl;
else
{
while (!archivo1.eof())
{
getline(archivo1, linea);
getline(archivo1, linea);
archivo2<< linea << endl;
}
}

archivo1.close();
archivo2.close();

return 0;
}



Y, sí, en las lineas pares va todo junto: "Nombre_alumno_n" ¿porque es más fácil así? ilumíname porfa!

Y respecto al ejercicio 2 por más vueltas que le dé no consigo hacer nada, porque el problema lo tengo en que no se como sacar los datos del fichero para poder operar con ellos. Sé que getline lee una linea entera, también sé leer y copiar todo el contenido del fichero, y que  con .seekg(pos) puedo leer una posición concreta (pero un solo carácter). Pero ¿cómo puedo sacar esas notas concretas 5.8, 3.3 y 8.2 para guardarlos en float? ayúdame por favor! O dime si no se hace así y estoy demasiado perdido!!

Muchas gracias de antemano!
Saludos!

K-YreX

Muy bien, vamos por partes.
  • ¿Por qué es más fácil si "Nombre_alumno_n" va todo junto?
    Muy simple. Porque es "una sola palabra". Entiéndase como una sola palabra que no va separado por espacios lo cual permite leerlo de otra forma, en vez de usando <getline()> podemos usar el operador >> como hacemos cuando ponemos < cin >> variable >.
    En este caso lo que te propuse para evitar darle tantas vueltas a esto fue lo siguiente, si recuerdas:
    Citar
    Igual que antes copias las líneas pares pero antes intercalas esto:
    Código (cpp) [Seleccionar]

    file << "Nombre alumno " << contador << "  Alumno " << contador++ << endl;

    Inicializando contador en 1 para que vaya incrementándose en cada iteración.
    Como conocíamos el formato de la línea podíamos escribirla nosotros sin necesidad de usar lo que habíamos leído. Pero imagina que desconocemos el formato, o que en algún momento cambiaremos el formato del fichero de entrada y cambiamos "Nombre alumno n" por "Nombre". Entonces tendríamos que cambiarlo en nuestro código si queremos que siga quedando igual.
    En cambio si lo tenemos en una sola palabra (usando _) podemos leer esa primera palabra, copiarla en el fichero de salida y reemplazar el nombre únicamente. Para ello podemos leer palabra a palabra de la siguiente manera:
    Código (cpp) [Seleccionar]

    string palabra;
    while(!fichero.eof())
        fichero >> palabra;

    No sé si lo ves. Si leyésemos la línea entera tendríamos que ver dónde empieza el nombre para ver dónde empezar a reemplazar. De esta forma leemos la primera palabra, la copiamos, leemos la segunda (para poder avanzar en el fichero) pero la obviamos y reemplazamos por lo que nosotros queramos poner. También podemos leer el nombre si fuera una sola palabra y modificar el <string> que almacena el nombre y volver a guardarlo. Por hacerlo menos manual.

    Esto que te comento se ve mucho mejor en la otra línea, en la que se encuentran las notas. Si leemos la línea entera pues luego tendríamos que calcular nosotros dónde empieza la nota para separarla y convertirla en un <double> por ejemplo y luego guardarla en otra variable para poder trabajar con ella... En definitiva, un rollo :-\
    Al poder leer por palabras podemos guardar la primera en un <string> que sea siempre el mismo ya que no lo necesitamos y la segunda palabra (la nota en este caso) en otra variable.
    Para ello cómo te he comentado antes debemos usar el operador >> en lugar de <getline()>. Te pongo un ejemplo de cómo quedaría:
    Código (cpp) [Seleccionar]

    string palabra, linea; // linea es innecesaria, podemos usar la misma variable para todo pero es para verlo mejor
    double notas[SIZE]; // si no trabajamos con memoria dinamica ponemos un SIZE suficientemente grande
    int num_notas = 0;
    while(!fichero.eof()){
        getline(fichero, linea);
        fichero >> palabra >> notas[num_notas++];
    }

    Con ese código vamos almacenando las líneas impares en línea (que son las que menos nos interesan ahora) y luego de las líneas pares guardamos la primera palabra (notas_alumno_n) en <palabra> y la nota en <notas>. Repito, como no estamos utilizando ni <linea> ni <palabra> para trabajar podemos usar la misma variable para ambas cosas, pero lo he puesto así para que fuera más visual.
    Hecho esto tenemos en el array <notas> todas las notas almacenadas para trabajar con ellas y en la variable <num_notas> el número de notas que hemos almacenado para no recorrer el array entero en caso de que nos sobren posiciones.

    Respecto a lo que has mencionado de la función <seekg()>, eso son funciones para acceso aleatorio de un fichero pero si estás empezando te recomiendo trabajar con acceso secuencial hasta que hayas cogido práctica al menos. Casi siempre tendrás que usar alguna variable "vertedero" donde ir dejando lo que no te interesa pero se como has visto, se puede hacer.

    Espero que te sirva para completar tu programa. Suerte :-X
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

ciquee

Muchísimas gracias por tu explicación, es genial y está muy bien argumentaba!

Lo conseguí!! y lo mejor de todo... aprendí algo nuevo!

Un saludo!

ciquee

Y una pregunta más, si el dato que tengo que extraer está en medio de muchos otros ¿he de ir guardando todos los datos en la "variable vertedero" hasta llegar al dato que quiero? Me explico, tengo un fichero con varios datos sobre estrellas...

Nombre de la estrella 1            Alfa Centauri
Distancia a la tierra (en años luz)      4.367
Radio (en Radios solares)            1.2
Masa (en kg)                  2.167e30
Magnitud absoluta               15.49
Temperatura (en Kelvin)             5790
...

Para coger el dato del nombre y la masa de cada una de las estrellas, debería descartar palabra por palabra las 5 primeras hasta llegar al nombre de la estrella, y luego descartar las 2 lineas siguientes y otras 3 palabras mas para llegar al dato de la masa? ¿O hay otra forma de hacerlo más directa y corta?

Sigo sin poder hacer uso ni de funciones o procedimientos auxiliares, ni utilizar datos estructurados (cadenas, registros, vectores o matrices).

Saludos y gracias de antemano!


K-YreX

Diría que sí, tienes que ir pasando por todas las palabras mediante una variable "vertedero" (al final se le va a quedar ese nombre :xD)
Porque para trabajar con ficheros de acceso aleatorio tienes que saber moverte en un fichero binario calculando los Bytes por los que quieres pasar.Te recomiendo usar acceso secuencial mejor.
Código (cpp) [Seleccionar]

cout << "Todos tenemos un defecto, un error en nuestro código" << endl;

ciquee

Ok! Pues muchas gracias de nuevo!

Seguimos!