Leer datos de un cierto formato desde un fichero

Iniciado por javiepe, 12 Marzo 2014, 15:19 PM

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

javiepe

Buenas,

estoy bastante acostumbrado a programar en Fortran, pero ahora tengo un proyecto en el cual debo programar en C, y aunque tengo en mente cómo hacer el programa, me falla el proceso de lecturas de datos desde un fichero.

Tengo un fichero en el cual hay dos tipos de datos. El primer tipo tiene el siguiente formato:

AAAA-MM-DD E F

(AAAA es año, MM es mes, DD es día, E es un número entero, y F uno decimal)

El segundo tipo es muy parecido:

AAAA-MM-DD F


A mí me interesa leer los números E y F del primer grupo de datos, guardándolos en dos vectores, y leer el número F del segundo grupo de datos, guardándolo en otro vector. Aquí es donde surge mi problema.

Estoy intentando hacer esto utilizando la función fscanf, escribiendo estos dos comando después de delcarar variables y abrir los ficheros:

fscanf (f1, "%d-%d-%d %d %f\n", &ano, &mes, &dia , E1, F1);
fscanf (f1, "%d-%d-%d %f\n", &ano, &mes, &dia, F2);

He declarado E1(3000), F1(3000) y F2(3000) asegurándome de que son suficientemente grandes para almacenar todos los números que hay en el fichero.

Haciendo esto, no consigo que se almacenen los números, ni siquiera el año. No sé si la función fscanf se utiliza así o no.

Agradecería cualquier ayuda, ya que tengo el algoritmo básico en mente, pero no puedo aplicarlo por culpa de que no sé cómo leer el fichero.

Muchas gracias.

rir3760

Puedes utilizar la función fscanf siempre y cuando la entrada garantice solo esos dos tipos de linea y nada mas. Primero lees la fecha y a continuación verificas si el resto es uno de los dos casos.

Un ejemplo con sscanf:
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
   char *linea[] = {
      "1970-01-01 1 2.3",
      "1970-01-01 4.5",
      "1980-01-01 1 2.3",
      "1980-01-01 4.5",
      "1990-01-01 1 2.3",
      "1990-01-01 4.5",
   };
   size_t num_elem = sizeof linea / sizeof linea[0];
   size_t i;
   
   unsigned a;
   unsigned m;
   unsigned d;
   int num_ent;
   double num_pf;
   int nc;
   
   for (i = 0; i < num_elem; i++)
      if (sscanf(linea[i], "%u-%u-%u%n", &a, &m, &d, &nc) == 3){
         printf("%u-%02u-%02u: ", a, m, d);
         
         if (sscanf(linea[i] + nc, "%d%*[ ]%lf", &num_ent, &num_pf) == 2)
            printf("%d %.2f\n", num_ent, num_pf);
         else if (sscanf(linea[i] + nc, "%lf", &num_pf) == 1)
            printf("%.2f\n", num_pf);
         else
            puts("Error");
      }
   
   return EXIT_SUCCESS;
}


En tu caso solo tienes que leer el archivo linea por linea con fgets, a continuación la procesas con sscanf.

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

leosansan

#2
Me pasa un detalle para mi curioso. Con esto en el fichero:

Citar
1970-01-01 1 1.5
1970-01-01 1.15
1980-01-01 2 2.5
1980-01-01 1.225
1990-01-01 3 3.5
1990-01-01 1.35


la salida es:

Citar
5: 1 1.5
5: 1 0.15
5: 2 2.5
5: 1 0.225
5: 3 3.5
5: 1 0.35


Es decir, en el código que te pongo a continuación sólo actúa el scanf==5 y lo que hace el muy bandido es, cuando debería actuar para mi criterio el scanf==4, sigue actuando el scanf==5 y lo que hace es coger, en las líneas pares, el decimal y separarlo en la parte entera y la parte decimal y así no deja actuar al scanf==4.

¿Me podrían iluminar a ver qué diablos estoy haciendo fatal?.


Código (cpp) [Seleccionar]

#include <stdio.h>
#include <stdlib.h>
#define N 5

int main(){
 int i=0,j=0,k=0,aux[3],E[N];
 float F1[N],F2[N];
 FILE* fichero;
 fichero = fopen("fecha0.txt", "r");
 if ((fichero = fopen("fecha0.txt", "r")) == NULL){
   perror("fecha0.txt");
   return EXIT_FAILURE;
 }
 while (! feof(fichero)){
     if (fscanf(fichero,"%d %d %d %d %f ", &aux[0],&aux[1], &aux[2], &E[i], &F1[i])==5){
       printf("5: %d %g\n", E[i], F1[i]);
       i++;
     }
     else if (fscanf(fichero,"%d %d %d %f ", &aux[0],&aux[1], &aux[2], &F2[k])==4){
       printf("4: %g\n", F2[k]);
       k++;
     }
     else
       puts("Error");
 }
 for (j=0;j<i;j++)
   printf ("%3d  ",E[j]) ;
 putchar ('\n');
 for (j=0;j<i;j++)
   printf ("%3g  ",F1[j]) ;
 putchar ('\n');
 /*for (j=0;j<k;j++)
   printf ("%3g  ",F2[j]);*/
 putchar ('\n');
 fclose(fichero);
 return EXIT_SUCCESS;
}


Obviamente el código que  cuelga rir3760 funciona, como no podría ser de otra manera al ser suyo, a las mil maravillas. La salida es:

Citar
1    2    3
1.5  2.5  3.5
1.15  1.225  1.35


Process returned 0 (0x0)

Código (cpp) [Seleccionar]

#include <stdio.h>
#include <stdlib.h>
#define N 5

int main()
{
 int i=0,j=0,k=0,aux[3],E[N],nc;
 float F1[N],F2[N];
 char linea[81];
 FILE* fichero;
 fichero = fopen("fecha0.txt", "r");
 if ((fichero = fopen("fecha0.txt", "r")) == NULL){
   perror("fecha0.txt");
   return EXIT_FAILURE;
 }
 while (! feof(fichero)){
   fgets(linea, 80, fichero);
     if (sscanf(linea, "%d-%d-%d%n", &aux[0], &aux[1], &aux[2], &nc) == 3){
        if (sscanf(linea + nc, "%d%*[ ]%f", &E[i], &F1[i]) == 2){
          i++;
        }
        else if (sscanf(linea + nc, "%f", &F2[k]) == 1){
          k++;
        }
        else
           puts("Error");
     }
 }
 for (j=0;j<i;j++)
   printf ("%3d  ",E[j]) ;
 putchar ('\n');
 for (j=0;j<i;j++)
   printf ("%3g  ",F1[j]) ;
 putchar ('\n');
 for (j=0;j<k;j++)
   printf ("%3g  ",F2[j]) ;
 putchar ('\n');
 fclose(fichero);
 return EXIT_SUCCESS;
}


Gracias de antemano.

¡¡¡¡ Saluditos! ..... !!!!




REEDITADO:

Lo he solventado de la forma más simple. Ahora la salida es la correcta:


Citar
5: 1  1.5
4: 1.15
5: 2  2.5
4: 1.225
5: 3  3.5
4: 1.35
 1    2    3
1.5  2.5  3.5
1.15  1.225  1.35


Process returned 0 (0x0)

Código (cpp) [Seleccionar]

#include <stdio.h>
#include <stdlib.h>
#define N 5

int main(){
 int i=0,j=0,k=0,aux[3],E[N];
 float F1[N],F2[N];
 FILE* fichero;
 fichero = fopen("fecha0.txt", "r");
 if ((fichero = fopen("fecha0.txt", "r")) == NULL){
   perror("fecha0.txt");
   return EXIT_FAILURE;
 }
 while (! feof(fichero)){
     fscanf(fichero,"%d %d %d %d %f ", &aux[0],&aux[1], &aux[2], &E[i], &F1[i]);
       printf("5: %d  %g\n", E[i], F1[i]);
       i++;
     fscanf(fichero,"%d %d %d %f ", &aux[0],&aux[1], &aux[2], &F2[k]);
       printf("4: %g\n", F2[k]);
       k++;
 }
 for (j=0;j<i;j++)
   printf ("%3d  ",E[j]) ;
 putchar ('\n');
 for (j=0;j<i;j++)
   printf ("%3g  ",F1[j]) ;
 putchar ('\n');
 for (j=0;j<k;j++)
   printf ("%3g  ",F2[j]);
 putchar ('\n');
 fclose(fichero);
 return EXIT_SUCCESS;
}


Pero me sigue quedando la duda que planteé.

rir3760

Cita de: leosansan en 12 Marzo 2014, 20:19 PMMe pasa un detalle para mi curioso. Con esto en el fichero:
Citar...
1970-01-01 1.15
...
1980-01-01 1.225
...
1990-01-01 1.35

la salida es:
Citar...
5: 1 0.15
...
5: 1 0.225
...
5: 1 0.35
Es decir, en el código que te pongo a continuación sólo actúa el scanf==5 y lo que hace el muy bandido es, cuando debería actuar para mi criterio el scanf==4, sigue actuando el scanf==5 y lo que hace es coger, en las líneas pares, el decimal y separarlo en la parte entera y la parte decimal y así no deja actuar al scanf==4.

¿Me podrían iluminar a ver qué diablos estoy haciendo fatal?.
El problema se debe a que la lectura con "%d %f" siempre sera exitosa ya se trate de un int y un float o bien un float.

Tomando como ejemplo la primera linea terminada con 1.15 el especificador "%d" consumirá "1" y "%f" consumirá ".15", eso explica la salida que obtienes en el programa.

Para solucionarlo hay que recordar que scanf y familia terminaran tan pronto falle una conversión, esa es la razón por la cual utilizo "%d%*[ ]%lf", ahí el espacio no es opcional, si no se encuentra por lo menos un espacio separando los dos números la función termina y su valor de retorno es uno.

Otro error en tu programa es, al utilizar directamente fscanf, el indicador de posición en el archivo se actualizara con la llamada a esa función sin importar su resultado.

Un saludo
C retains the basic philosophy that programmers know what they are doing; it only requires that they state their intentions explicitly.
--
Kernighan & Ritchie, The C programming language

javiepe

Muchas gracias a los dos, ya me va quedando todo más claro, pero todavía tengo una duda. Resulta que en el fichero que tengo, además de estas fechas con los números posteriores, hay una línea en la que pone "Observaciones" y otra en la que pone "Predicciones", y me está dando error al implementar vuestros programas. Más concretamente, no sé qué significan esos ==1, ==2.... que ponéis después de las funciones scanf, así que no sé cómo añadir el caso en el que una línea pueda ser  "Observaciones" o "Predicciones".

Además, también tengo dudas en cómo almacenar el fichero en el vector "linea" utilizando fgets para poder después procesarlo con sscanf.

Os agradezco de nuevo toda la ayuda, ya os digo que tengo bastante soltura en Fortran, pero que estoy bastante perdido en la forma de leer datos de C.

leosansan

#5
Cita de: javiepe en 13 Marzo 2014, 10:43 AM
..............................................................................
no sé qué significan esos ==1, ==2.... que ponéis después de las funciones scanf,
........................................................................................

Representan el número de lecturas que "debe" realizar el sscanf para que este efectivamente haga la lectura. Está referido al número de %d y %f que hay en el sscanf.

Cita de: javiepe en 13 Marzo 2014, 10:43 AM
..............................................................................
Además, también tengo dudas en cómo almacenar el fichero en el vector "linea" utilizando fgets para poder después procesarlo con sscanf.
........................................................................................


Por ejemplo:

Código (cpp) [Seleccionar]

...........................................
char linea[81];
............................................
fgets(linea, 80, fichero);
........................................


¡¡¡¡ Saluditos! ..... !!!!



javiepe

Gracias, ya me ha quedado claro lo de las lecturas del scanf.

Respecto a fgets aún hay un matiz que no me queda claro. En el ejemplo que pones, ¿se guarda el fichero por completo en "char linea"? Es decir, ¿linea(1) sería la primera línea del fichero, linea(2) la segunda, y así sucesivamente? Planteado de otra forma; ¿cuando hacemos fgets lee el fichero?

Saludos!

leosansan

Cita de: javiepe en 13 Marzo 2014, 15:06 PM
Gracias, ya me ha quedado claro lo de las lecturas del scanf.

Respecto a fgets aún hay un matiz que no me queda claro. En el ejemplo que pones, ¿se guarda el fichero por completo en "char linea"? Es decir, ¿linea(1) sería la primera línea del fichero, linea(2) la segunda, y así sucesivamente? Planteado de otra forma; ¿cuando hacemos fgets lee el fichero?

Saludos!

El caso que te pongo, con fgets después del while, leería línea a línea el fichero.

¡¡¡¡ Saluditos! ..... !!!!





javiepe

Entendido! Ya me ha salido por fin! Os agradezco mucho a los dos vuestra ayuda, era muy importante que pudiese resolver esto para poder seguir.

Saludos!

Yoel Alejandro

Aunque ya está solucionado, sólo para mostrar una alternativa un poco bizarra pero que se reduce a un único sscanf(). La idea es leer ambos, el entero y el float como flotantes, y de ser necesario convertir el primero a entero. Lo probé y parece funcionar correctamente:

Código (cpp) [Seleccionar]

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
   char *linea[] = {
      "1970-01-01 1 2.3",
      "1970-01-01 4.5",
      "1980-01-01 1 2.3",
      "1980-01-01 4.5",
      "1990-01-01 1 2.3",
      "1990-01-01 4.5",
   };
   size_t num_elem = sizeof linea / sizeof linea[0];
   size_t i;

   unsigned a;
   unsigned m;
   unsigned d;
   int num_ent;
   double num_pf;

   int n_args;
   float M, N;

   for (i = 0; i < num_elem; i++)
      switch ( n_args = sscanf(linea[i], "%u-%u-%u %f%*[ ]%f", &a, &m, &d, &M, &N) ) {
      case 5:
         num_ent = (int)M;
         num_pf = N;
         printf("%u-%02u-%02u: %d, %.2f\n", a, m, d, num_ent, num_pf);
         break;
      case 4:
         num_pf = M;
         printf("%u-%02u-%02u: %.2f\n", a, m, d, num_pf);
         break;
      default:
         puts("Error");
      }

   return EXIT_SUCCESS;
}


Salida:

1970-01-01: 1, 2.30
1970-01-01: 4.50
1980-01-01: 1, 2.30
1980-01-01: 4.50
1990-01-01: 1, 2.30
1990-01-01: 4.50
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)