Randy G.
Randy G.

Reputation: 111

Reading a file into a dynamically allocated array

How can write this file

5 
Llewellyn Mark 
1 15 19 26 33 46 
Young Brian 
17 19 33 34 46 47 
Cazalas Jonathan 
1 4 9 16 25 36 
Siu Max 
7 19 34 46 47 48 
Balci Murat 
5 10 17 19 34 47`

into a dynamically allocated array

typedef struct KnightsBallLottoPlayer{
char firstName[20];
char lastName[20];
int  numbers[6]
} KBLottoPlayer;

the first row of the file indicates the number of individual names and number sets.

I have tried to use the below code to dynamically allocate the memory needed for n people using the struct but I am lost on how to read the information in, following the first read to get the n amount of users.

  int n; 
  FILE *fin;
  fin = fopen("text.in","r");

  if (fin == NULL){
   printf("Error: No File!");
  }

  fscanf(fin, "%d", &n);
  printf("%d",n); //reads file correctly

  struct KnightsBallLottoPlayer *p = calloc(sizeof(KBLottoPlayer), n);

Upvotes: 0

Views: 158

Answers (1)

David C. Rankin
David C. Rankin

Reputation: 84521

While the basic premise of the question has been answered many times, each implementation varies just enough based on the data file format, it makes it difficult to find an exact duplicate.

Continuing from the comment, the approach is the same, but the details of how you handle the read will vary based on your input file format and struct members. When you are given a first line specifying how many of what comes next you will need to read, the easiest way to handle storage for your array of stuct is simply to allocate storage for that many struct with malloc (you can use calloc if you wish to zero all bytes at the time of allocation). While you can use a VLA (Variable Length Array), it is just as easy to allocate for 'n' struct.

The read in your case becomes the challenge. You are needing to read data from two separate lines into a single struct. While I would generally suggest fgets to read each line and then call sscanf to parse the data (which you could still do here), fscanf with its format string will simplify the process by allowing you to read both lines of data in a single call if, and only if you already know the number of elements in your member array (your numbers). In your case it is fixed at 6, so as long as that is the case, you can take the simple route.

When reading with any input function (and especially the scanf family of functions), you must validate the return of the function to insure there was a successful conversion for each conversion specifier in your format string. The scanf family of functions return the number of successful number of conversions that took place (or EOF if end-of-file was encountered before a conversion took place). Since fscanf will ignore the intervening '\n', you can read both lines of your data for each struct with the following:

        int rtn = fscanf (fp, "%19s %19s %d %d %d %d %d %d", 
                    players[i].firstName, players[i].lastName,
                    &players[i].numbers[0], &players[i].numbers[1],
                    &players[i].numbers[2], &players[i].numbers[3],
                    &players[i].numbers[4], &players[i].numbers[5]);

You would validate that a successful read occurred before you increment i with something like the following:

        if (rtn == EOF)         /* validate fscanf return not EOF */
            break;
        else if (rtn != 8) {    /* check for matching or input failure */
            fputs ("error: matching or input failure occurred.\n", stderr);
            break;
        }
        else        /* all struct values read, increment counter */
            i++;

You place that in a loop and loop until your index reaches the number of elements you were told to read (or EOF occurs) and you are essentially done (don't forget to close the file you are reading from and free the memory you allocate). A short example putting it altogether could be:

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

typedef struct KnightsBallLottoPlayer {
    char firstName[20];
    char lastName[20];
    int  numbers[6];
} KBLottoPlayer;

int main (int argc, char **argv) {

    size_t i = 0, n = 0;
    KBLottoPlayer *players = NULL;  /* pointer to players */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    if (fscanf (fp, "%zu", &n) != 1) {  /* read first value into size_t */
        fputs ("error: invalid format - 'n'\n", stderr);
        return 1;
    }

    /* allocate/validate array of 'n' KBLottoPlayer */
    if ((players = malloc (n * sizeof *players)) == NULL) {
        perror ("malloc-players");
        return 1;
    }

    while (i < n) { /* read until 'n' struct worth of data read */
        int rtn = fscanf (fp, "%19s %19s %d %d %d %d %d %d", 
                    players[i].firstName, players[i].lastName,
                    &players[i].numbers[0], &players[i].numbers[1],
                    &players[i].numbers[2], &players[i].numbers[3],
                    &players[i].numbers[4], &players[i].numbers[5]);
        if (rtn == EOF)         /* validate fscanf return not EOF */
            break;
        else if (rtn != 8) {    /* check for matching or input failure */
            fputs ("error: matching or input failure occurred.\n", stderr);
            break;
        }
        else        /* all struct values read, increment counter */
            i++;
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    if (i < n) {    /* validate 'n' value read or reduce 'n' */
        fputs ("error: less than all data read.\n", stderr);
        n = i;
    }

    for (i = 0; i < n; i++)     /* output results */
        printf ("%s %s\n%d %d %d %d %d %d\n", 
                players[i].firstName, players[i].lastName,
                players[i].numbers[0], players[i].numbers[1],
                players[i].numbers[2], players[i].numbers[3],
                players[i].numbers[4], players[i].numbers[5]);

    free (players); /* don't forget to free the memory you allocate */

    return 0;
}

(note: the %19s in fscanf prevents reading more than 19-characters (plus the nul-terminating character for both the firstName, lastName variables to prevent writing beyond their array bounds)

Example Use/Output

$ ./bin/players_struct <dat/playerdata.txt
Llewellyn Mark
1 15 19 26 33 46
Young Brian
17 19 33 34 46 47
Cazalas Jonathan
1 4 9 16 25 36
Siu Max
7 19 34 46 47 48
Balci Murat
5 10 17 19 34 47

Look things over and let me know if you have further questions. There are many different ways to do this and using a VLA would avoid the allocation, but this is probably on par with any of the simple approaches. There are always additional validations you can use, but the above covers the salient ones.

Upvotes: 1

Related Questions