views:

346

answers:

4

The code below tries to parse a file containing these 3 lines:

 0 2 5 9 10 12
 0 1 0 2 4 1 2 3 4 2 1 4
 2 3 3 -1 4 4 -3 1 2 2 6 1

and stores them in these arrays:

int Line1[] = { 0, 2, 5, 9, 10, 12 };

int Line2[] =    { 0, 1, 0,  2, 4, 1,  2, 3, 4, 2, 1, 4 };

double Line3[] = { 2, 3, 3, -1, 4, 4, -3, 1, 2, 2, 6, 1 };

However in practice the number of fields in the actual input file are not fixed. Hence they can be greater than 6, 12 and 12 for each line.

Is there any way I can generalize the define and sscanf for this purpose? Here is the complete code:

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

// This is hard coded
#define LINE1_COUNT 6
#define LINE2_COUNT 12
#define LINE3_COUNT 12 

int main() {
    int Line1[LINE1_COUNT], Line2[LINE2_COUNT] ;
    float Line3[LINE1_COUNT] ;
    int j, check;

    FILE *file = fopen("test.dat","r");

    if (file) {
        char line[BUFSIZ];

        if (fgets(line, BUFSIZ, file)) { // read line 1, integers
      int *i = Line1;//for easier reading
            check = sscanf(line, "%i%i%i%i%i%i", &i[0],&i[1],&i[2],&i[3],&i[4],&i[5]) ;
      if (check != LINE1_COUNT){
       fprintf(stderr, "Failed to read expected %d values from line 1\n", LINE1_COUNT);
       exit(1);
      }
     }else fprintf(stderr, "Couldn't read line 1!\n");
        if (fgets(line, BUFSIZ, file)) { // read line 2, integers
      int *i = Line2;//for easier reading
            check = sscanf(line, "%i%i%i%i%i%i%i%i%i%i%i%i", 
       &i[0],&i[1],&i[2],&i[3],&i[4],&i[5],&i[6],&i[7],&i[8],&i[9],&i[10],&i[11]) ;
      if (check != LINE2_COUNT){
       fprintf(stderr, "Failed to read expected %d values from line 2\n", LINE2_COUNT);
       exit(1);
      }
     }else fprintf(stderr, "Couldn't read line 2!\n");
        if (fgets(line, BUFSIZ, file)) { // read line 3, floats
      float *f = Line3;//for easier reading
            check = sscanf(line, "%f%f%f%f%f%f%f%f%f%f%f%f", 
       &f[0],&f[1],&f[2],&f[3],&f[4],&f[5],&f[6],&f[7],&f[8],&f[9],&f[10],&f[11]) ;
      if (check != LINE3_COUNT){
       fprintf(stderr, "Failed to read expected %d values from line 3\n", LINE3_COUNT);
       exit(1);
      }
     }else fprintf(stderr, "Couldn't read line 3!\n");
        fclose(file);
    }else {
         perror("test.dat");
    }

    for (j=0;j<LINE1_COUNT;j++){
        printf("%i\t",Line1[j]);
    }
    printf("\n");
    for (j=0;j<LINE2_COUNT;j++){
        printf("%i\t",Line2[j]);
    }
    printf("\n");
    for (j=0;j<LINE3_COUNT;j++){
        printf("%f\t",Line3[j]);
    }
    printf("\n");

    printf("Press return to exit");
    getchar();
    return 0;
}
A: 

I recommend using either stl container class vector<> or dynamically allocated arrays for this purpose.

erelender
@erelender: It has to be strictly in C.
neversaint
okay then, i recommend dynamically allocated arrays.
erelender
+2  A: 

If the number of elements in a line is not fixed (and maybe the number of lines as well) you can do one of the following things:

  1. Use an array of arrays - int line_elements[MAX_LINES][MAX_LINE_LENGTH] - but this still has only a static size
  2. Use an array of pointers - int* lines[] and then dynamically allocate the needed space when you iterate trough lines.

You cannot use a sscanf with predefined number of %s inside. Try using strtok for tokenizing the string into tokens since you separate the numbers by a space.

Ezekiel Rage
I don't see any commas in his example input. He could honestly use `strtol` (or `atoi`) and let those functions eat up the whitespace for him. It'd be easier.
Chris Lutz
Sorry about the commas. You are right - but he still wants to read all the numbers in a line into a single array. `atoi` is good for a single number only, `strtol` could work but I think tokenizing is a more 'general' approach.
Ezekiel Rage
`strtol()`'s second parameter can be the address of the first parameter - thus, when `strtol()` is done reading a number in a string, it will "move the string up" past the number, so the next time you call `strtol()` on that string, it will read the next number. Of course, this requires a rather intimate knowledge of pointers, and should be abstracted into a separate function so we don't mess up our original pointer, and would also require some `malloc()`ing, none of which the OP displayed any knowledge of, either in this post or the last. So in the end, the OP should learn the language...
Chris Lutz
...he's trying to use, or he should ask us for help in how that language works, but he shouldn't ask us to write is code for him.
Chris Lutz
... There are always more ways to achieve a desired goal. Is this a competition of some sort?
Ezekiel Rage
A: 

This looks like the same problem as this question. Since the number of fields is variable, sscanf just won't get the job done. It needs a fixed number of fields. And #defines most definitely can't be parameterized at run time. strtok() is definitely the way to go. Refer to the other question's answers for more details and gotchas.

Steve K
He could probably use a regular `fscanf` on the file if he knew what he was doing.
Chris Lutz
fscanf has the same problem as sscanf- a fixed number of format specifiers. If the number of fields is unknown how do you know how to call fscanf? The only way is to have more specifiers than you will ever need and let fscanf tell you how many it converted, like foolishbrat is already doing, but that doesn't scale up very well.
Steve K
+1  A: 

Well, this seems to do what youre asking for, but it doesnt use generics (a c++ feature if im not mistaken) or generalized #define (which i think is impossible). Also, im not sure about the efficiency:

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

//dont (cant) hard-code sizes (see main())

int* parse_int_line(char* line, int* sz)
    {
    int* line_data;
    int* tmp;
    line_data = malloc(sizeof(int) * ((*sz)=1));
    if(!line_data){return 0;}
    while(1==sscanf(line,"%i",&(line_data[(*sz)-1])))
        {
        while(*(line++)==' '){ /*pass*/ }//chew through leading spaces
        while(*(line++)!=' '){*line=' ';}//and the number we just got
        tmp = realloc(line_data,sizeof(int) * (++(*sz)));
        if(tmp){line_data = tmp;}
        else{fprintf(stderr,"nonfatal memory allocation error\n");break;}
        }
    (*sz)--;
    return line_data;
    }
void print_int_line(int* line, int sz)
    {
    int i;
    for(i=0;i<sz;i++)
        {
        printf(" %i",line[i]);
        }
    printf("\n");
    }
float* parse_float_line(char* line, int* sz)
    {
    float* line_data;
    float* tmp;
    line_data = malloc(sizeof(float) * ((*sz)=1));
    if(!line_data){return 0;}
    while(1==sscanf(line,"%f",&(line_data[(*sz)-1])))
        {
        while(*(line++)==' '){ /*pass*/ }//chew through leading spaces
        while(*(line++)!=' '){*line=' ';}//and the number we just got
        tmp = realloc(line_data,sizeof(float) * (++(*sz)));
        if(tmp){line_data = tmp;}
        else{fprintf(stderr,"nonfatal memory allocation error\n");break;}
        }
    (*sz)--;
    return line_data;
    }
void print_float_line(float* line, int sz)
    {
    int i;
    for(i=0;i<sz;i++)
        {
        printf(" %.2f",line[i]);
        }
    printf("\n");
    }

int main(int argc, char** argv)
    {
    int sz1=0,sz2=0,sz3=0;
    int* line1 = 0;
    int* line2 = 0;
    float* line3 = 0;
    FILE* file = 0;

    file = fopen("C:/DevCpp/Projects/junk/test.txt","r");

    if(file)
        {
        char line[BUFSIZ];
        if(fgets(line,BUFSIZ,file))
            {
            line1 = parse_int_line(line,&sz1);
            print_int_line(line1,sz1);
            }
        else{fprintf(stderr,"line read error\n");}
        if(fgets(line,BUFSIZ,file))
            {
            line2 = parse_int_line(line,&sz2);
            print_int_line(line2,sz2);
            }
        else{fprintf(stderr,"line read error\n");}
        if(fgets(line,BUFSIZ,file))
            {
            line3 = parse_float_line(line,&sz3);
            print_float_line(line3,sz3);
            }
        else{fprintf(stderr,"line read error\n");}
        }
    else
        {
        fprintf(stderr,"could not open \"test.txt\"\n");
        }
    if(line1){free(line1);}
    if(line2){free(line2);}
    if(line3){free(line3);}
    printf("Press return to exit");
    getchar();
    return 0;
    }

By the way, please comment on if/how well this works for you.

David X
@DX: Thanks a million David, this is life saving.
neversaint