Reading a file into a dynamically allocated array - c

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);

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.

Related

Reading a text file to extract coordinates in c

i am trying to read a text file and extract the cordinates of 'X' so that i can place then on a map
the text file is
10 20
9 8 X
2 3 P
4 5 G
5 6 X
7 8 X
12 13 X
14 15 X
I tried multiple times but I am unable to extract the relevant data and place it in separate variables to plot
I am quite new to c and am trying to learn things so any help is appreciated
thanks in advance
From my top comments, I suggested an array of point structs.
Here is your code refactored to do that.
I changed the scanf to use %s instead of %c for the point name. It generalizes the point name and [probably] works better with the input line because [I think] the %c would not match up correctly.
It compiles but is untested:
#include <stdio.h>
#include <stdlib.h>
struct point {
int x;
int y;
char name[8];
};
struct point *points;
int count;
int map_row;
int map_col;
void
read_data(const char *file_name)
{
FILE *fp = fopen(file_name, "r");
if (fp == NULL) {
/* if the file opened is empty or has any issues, then show the error */
perror("File Error");
return;
}
/* get the dimensions from the file */
fscanf(fp, "%d %d", &map_row, &map_col);
map_row = map_row + 2;
map_col = map_col + 2;
while (1) {
// enlarge dynamic array
++count;
points = realloc(points,sizeof(*points) * count);
// point to place to store data
struct point *cur = &points[count - 1];
if (fscanf(fp, "%d %d %s", &cur->x, &cur->y, cur->name) != 3)
break;
}
// trim to amount used
--count;
points = realloc(points,sizeof(*points) * count);
fclose(fp);
}
There are a number of way to approach this. Craig has some very good points on the convenience of using a struct to coordinate data of different types. This approach reads with fgets() and parses the data you need with sscanf(). The benefit eliminates the risk of a matching-failure leaving characters unread in your input stream that will corrupt the remainder of your read from the point of matching-failure forward. Reading with fgets() you consume a line of input at a time, and that read is independent of the parsing of values with sscanf().
Putting it altogether and allowing the filename to be provided by the first argument to the program (or reading from stdin by default if no argument is provided), you can do:
#include <stdio.h>
#define MAXC 1024 /* if you need a constand, #define one (or more) */
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to hold each line */
int map_row, map_col; /* map row/col variables */
/* use filename provided as 1st argument (stdin if none provided) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open");
return 1;
}
/* read/validate first line saving into map_row, map_col */
if (!fgets (buf, MAXC, fp) ||
sscanf (buf, "%d %d", &map_row, &map_col) != 2) {
fputs ("error: EOF or invalid map row/col data.\n", stderr);
return 1;
}
/* loop reading remaining lines, for used as line counter */
for (size_t i = 2; fgets (buf, MAXC, fp); i++) {
char suffix;
int x, y;
/* validate parsing x, y, suffix from buf */
if (sscanf (buf, "%d %d %c", &x, &y, &suffix) != 3) {
fprintf (stderr, "error: invalid format line %zu.\n", i);
continue;
}
if (suffix == 'X') { /* check if line suffix is 'X' */
printf ("%2d %2d %c\n", x, y, suffix);
}
}
if (fp != stdin) { /* close file if not stdin */
fclose (fp);
}
}
(note: this just illustrates the read and isolation of the values from lines with a suffix of 'X'. Data handling, and calculations are left to you)
Example Use/Output
With your data in dat/coordinates.txt you could do:
$ ./bin/readcoordinates dat/coordinates.txt
9 8 X
5 6 X
7 8 X
12 13 X
14 15 X
As Craig indicates, if you need to store your matching data, then an array of struct provides a great solution.

Read Text File with blank lines

The Text File was writable with 1 blank line after the struct.
Is similar to:
1 111 1 Peter
22 22 2 John Lays
3 3 3 Anne Belgs
The struct is:
struct estruturaCarro {
int id, potencia, avariado;
char name[11];
} carro;
I have write the data to the text File:
fprintf(fp, "%-2d %-3d %-1d %-10s \n\n", carro.id, carro.potencia, carro.avariado, carro.name);
I read the file data this way, but I'm sure it's not the best way:
while(true){
int result = fscanf(fp, "%d %d %d %10[^\n]", &carro.id, &carro.potencia, &carro.avariado, &carro.name);
if(result==4){
printf("%-2d %-3d %-1d % -s\n", carro.id, carro.potencia, carro.avariado, carro.name);
}else break;
}
How can I read the text file, saving the data struct, without reading the blank lines?
If I want to validate the values ​​read (ID = xx, potencia = xxx, avariado = x, name = 10 characters), is it better, before or after filling the array of struct?The format of each line of the file is:xx xxx x aaaaaaaaaa (x = digits, a = characters)For example, if one of the lines is
9 123 4 Error 1st value
stop reading the file (informing the user), because the line should be:
9 123 4 Error 1st value (one space was missing after 9) - or because NAME has more than 10 characters.
You don't show how true is set in while(true), but it must be based on the return of fscanf in the way you are approaching it. You must test three cases:
fscanf return is EOF, read done, true should be set to FALSE to break loop;
fscanf return less than 4, matching or input failure, no guarantee this occurred due to an empty line; and
fscanf return is equal to 4, good input.
Unless you are checking all three, you cannot cover all cases for fscanf and further, if there is any additional or extraneous character in your input file, your formatted read will fail.
That is why a better option is to read each line into a buffer with fgets and then parse the information you need from the buffer itself with sscanf. This provides the benefit of allowing separate validation of (1) the read; and (2) the parse of information. Further, since you are consuming a line-at-a-time, there is no uncertainty on what remains in the input stream and a failure in parsing any one line does not prevent a successful read of the remainder.
A short implementation with fgets() and sscanf() reading each struct into an array-of-struct, you can do something similar to the following:
#include <stdio.h>
#define MAXN 11 /* if you need a constant, #define one (or more) */
#define MAXC 1024 /* (don't skimp on buffer size) */
typedef struct { /* use a simple typedef */
int id, potencia, avariado;
char name[MAXN];
} carro_t;
int main (int argc, char **argv) {
char buf[MAXC]; /* buffer to read line */
carro_t carro[MAXN] = {{ .id = 0 }}; /* array of struct */
size_t ndx = 0; /* array index */
/* 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;
}
while (ndx < MAXN && fgets (buf, MAXC, fp)) { /* read each line */
carro_t tmp = { .id = 0 }; /* temp struct */
if (*buf == '\n') /* if line empty */
continue; /* get next line */
if (sscanf (buf, "%d %d %d %10[^\n]", /* separate/validate */
&tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
carro[ndx++] = tmp; /* add to array of struct */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
for (size_t i = 0; i < ndx; i++)
printf ("%3d %3d %3d %s\n",
carro[i].id, carro[i].potencia, carro[i].avariado, carro[i].name);
return 0;
}
(note: the filename is provide as the first argument to the program, or if no filename is provided, read from stdin by default)
While with your particular data file, there is no reason you cannot use fscanf, it is fragile and one character too many (like "Anne Belgss") will cause it to break. A fscanf implementation removing the true and simply looping as you have could be:
for (;;) {
carro_t tmp = { .id = 0 };
if (fscanf (fp, "%d %d %d %10[^\n]", /* separate/validate */
&tmp.id, &tmp.potencia, &tmp.avariado, tmp.name) == 4)
carro[ndx++] = tmp; /* add to array of struct */
else
break;
}
Either way, you will produce the same output with "this" input file, e.g.
Example Use/Output
$ ./bin/readwblanks ~/tmpd/file
1 111 1 Peter
22 22 2 John Lays
3 3 3 Anne Belgs
just use simple reading way is ok,like this.
while(fscanf(fp,"%d %d %d %s",&carro.id,&carro.potencia,&carro.avariado,carro.name)!=EOF)
printf("%d %d %d %s\n",carro.id,carro.potencia,carro.avariado,carro.name);
result is like this
enter image description here

Reading a file and then sorting it into 10 columns

I'm looking to sort a txt file that contains 1199 numbers in 10 columns per row, and I know that the last row will have 10 or fewer columns.
However, I'm getting 11 columns on the first row.
Thanks for the help.
#include <stdio.h>
#define t 1024
int main()
{
int i=0, c;
char p[t];
FILE *f;
f = fopen("CMB.txt", "r");
while ((c = getc(f)) != EOF)
{
fscanf(f, "%s", p);
if(i%10 == 0 && i > 0)
{
printf("\n");
}
printf("%s ", p);
if (c == '\n')
{
i++;
}
}
printf("\n %d", i+1);
fclose(f);
}
When I try your code it prints correctly the 10 columns you are expecting , perhaps you didn't recompile after modifying.
This is your code , I only removed the file reading and printing char.
#include <stdio.h>
int main()
{
int i=0;
//, c;
// char p[t];
// FILE *f;
// f = fopen("CMB.txt", "r");
while (i<30)
{
// fscanf(f, "%s", p);
if(i%10 == 0 && i > 0)
{
printf("\n");
}
printf("%d ", i);
// if (c == '\n')
// {
i++;
// }
}
// printf("\n %d", i+1);
// fclose(f);
}
Continuing from my earlier comment, when reading columns of data from a text file, it is generally better to read a line of input at a time with a line-oriented input function such as fgets or POSIX getline and then to parse the data from the buffer filled with sscanf (or walking a pointer down the buffer picking out what you need)
In your case when dealing with a fixed number of columns (10) where less than all values may be present, sscanf provides a way to determine exactly how many values are present in each row while allowing you to make use of the data if less than 10 are present.
sscanf (as with all scanf functions) returns the number of successful conversions took place based on the format string you provide (or returning EOF if the end of input is encountered before a conversion takes place). If you want to read 10-integer values from each line of data into an integer array names arr, you could use fgets to read the line into a buffer (say buf) and then separate the integer values head in buf with sscanf, e.g.
...
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
/* parse into integer values with sscanf saving return */
int rtn = sscanf (buf, "%d %d %d %d %d %d %d %d %d %d",
&arr[0], &arr[1], &arr[2], &arr[3], &arr[4],
&arr[5], &arr[6], &arr[7], &arr[8], &arr[9]);
...
Then you simply validate the return (rtn) using whatever if..else or switch... statements you need. Here we can simply sum the number of values read and output the row number for any row containing less than COL number of values for purposes of this example, e.g.
...
if (rtn > 0) /* did at least one conversion take place? */
n += rtn; /* increment count of values read */
if (rtn < COL) /* were less than COL values read? */
printf ("row[%zu]: %d values read.\n", row + 1, rtn);
row++; /* increment row count */
}
Putting it altogether, you could do something like the following:
#include <stdio.h>
#define COL 10
#define MAXC 1024
int main (int argc, char **argv) {
char buf[MAXC];
int arr[COL] = {0};
size_t n = 0, row = 0;
/* 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;
}
while (fgets (buf, MAXC, fp)) { /* read each line into buf */
/* parse into integer values with sscanf saving return */
int rtn = sscanf (buf, "%d %d %d %d %d %d %d %d %d %d",
&arr[0], &arr[1], &arr[2], &arr[3], &arr[4],
&arr[5], &arr[6], &arr[7], &arr[8], &arr[9]);
if (rtn > 0) /* did at least one conversion take place? */
n += rtn; /* increment count of values read */
if (rtn < COL) /* were less than COL values read? */
printf ("row[%zu]: %d values read.\n", row + 1, rtn);
row++; /* increment row count */
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
printf ("%zu values read from file.\n", n);
}
Example Input File
Reading a data file with 10-integers per-row for 119 rows and reading a final row with 9-integers as you describe in your question, you could use an input file like:
$ head -n4 dat/1199_10col.txt; echo "<snip>"; tail -n4 dat/1199_10col.txt
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
<snip>
1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180
1181 1182 1183 1184 1185 1186 1187 1188 1189 1190
1191 1192 1193 1194 1195 1196 1197 1198 1199
Example Use/Output
Running the code against the above file would yield the expected results of reading 1199 values, 10-integers per-row with 9-integers read from the final row with notice of the short row:
$ ./bin/read10col <dat/1199_10col.txt
row[120]: 9 values read.
1199 values read from file.
While there are many ways to do this, and arguably using fgets with strtol provides opportunity for finer grained error detection, this is probably one of the more simple and straight-forward approaches.
Look things over and let me know if you have further questions.
Why do you read a character before the first number? Why do you only increment i if this character is a newline?
If the file contains just numbers, you can simplify the code this way:
#include <stdio.h>
int main() {
int i, n;
FILE *f = fopen("CMB.txt", "r");
if (f != NULL) {
for (i = 0; fscanf(f, "%d", &n) == 1; i++) {
/* output the number followed by a tab, or by a newline every 10 numbers */
printf("%d%c", n, "\t\n"[i % 10 == 9]);
}
if (i % 10 != 0) {
printf("\n");
}
fclose(f);
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE * fp;
char *line;
int i;
size_t len = 0;
fp = fopen("CMB.txt", "r");
while (getline(&line, &len, fp) != -1)
{
fscanf(fp, "%s", line);
if(i%10 == 0 && i > 0)
{
printf("\n");
}
printf("%s\t ", line);
i++;
}
printf("\n\n%d", i);
fclose(fp);
return 0;
}
So now the numbers from the file are sorted in 10 columns, but the last line only have 8 columns when it should have 9. When I count (i) to see how many lines the while loop reads it only reads 1198, but i should read 1199. So I'm guessing it's skipping the first line? Appreciate the help :D

Using qsort and strcmp prior to my sctrucs going into txt files

My code pulls data from one text file and then totals the points and enters it into a separate text file, so I would like the program to organize the teams and scores by total points prior to frpintf into the text file. So far the program pulls and runs the data and totals it out and fprintf just fine, should I use qsort to sort and print into the text file, and where in my code do I place it. Here is the text it is reading.
Indians 54 45 5
Twins 45 53 7
Tigers 43 59 8
White_Sox 35 64 9
Royals 30 69 3
Also I know there are no ties in the MLB lol just throwing in an additional variable.
Thanks.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
int main(void)
{
struct records {
char filename;
char team[50];
int wins;
int tie;
int loss;
int points;
};
struct records roster;
FILE *ifp = NULL;
FILE *afd = NULL;
const int argv;
char filename2[64] = {0};
char filename[64] = {0};
int points;
int points2;
int total;
printf("\nPlease enter the the .txt file you would like to open: ");
scanf("%63s", filename);
printf("Opened file name %s",filename);
ifp = fopen(filename,"r");
if (ifp == NULL)
{
printf("Could not open");
printf("\nPlease enter the the .txt file you would like to open:");
scanf("%63s", filename);
printf("Opened file name %s",filename);
ifp = fopen(filename,"r");
}
printf("\nReading the file %s \n", filename);
while(fscanf(ifp, "%s %d %d%d" , roster.team, &roster.wins, &roster.loss,
&roster.tie) != EOF)
printf("%s Wins:%d Losses:%d ties:%d\n", roster.team, roster.wins,
roster.loss, roster.tie);
printf("\nWins are worth 2 points ties are worth 1 point and losses are
worth \nzero in overall league standings.");
printf("\nHere are the overall ALCentral Standings!\n");
ifp = fopen(filename, "r");
fopen(filename, "r"); while(fscanf(ifp, "%s %d %d %d", roster.team,
&roster.wins, &roster.loss, &roster.tie) != EOF)
printf("%s Wins:%d Losses:%d ties:%d Total league points:%d\n",
roster.team, roster.wins, roster.loss, roster.tie, (roster.wins * 2 +
roster.tie * 1), total);
printf("closing %s", filename);
fclose(ifp);
printf("\nPlease enter the the .txt file you would like to write to: ");
scanf("%63s", filename2);
printf("Opened file name %s", filename2);
afd = fopen(filename2, "a+");
if (afd == NULL)
{
printf("Could not open"); printf("\nPlease enter the the .txt file you
would like to open: ");
scanf("%63s", filename2);
printf("Opened file name %s", filename2);
afd = fopen(filename2,"a+");
}
ifp = fopen(filename,"r");
fopen(filename, "r");
points = roster.wins * 2;
points2 = roster.tie * 1;
total = points + points2;
while(fscanf(ifp, "%s %d %d %d", roster.team, &roster.wins, &roster.loss,
&roster.tie) != EOF)
fprintf(afd, "%s Wins:%d Losses:%d ties:%d total league points:%d\n",
roster.team, roster.wins, roster.loss, roster.tie, (roster.wins *2 +
roster.tie *1 ), total, filename2);
printf("\nYour stats have been recorded to the selected
file!\nClosing all open files! \nThanks for using the STAT-Master!
Have a great day!
fclose(afd);
fclose(ifp);
return 0;
}
Your logic is quite jumbled. If I understand what you are attempting, you want to read each teams wins, loss and tie record from your data file, and then compute the points that are then used to sort the teams into descending (or perhaps ascending) order and then output the "the overall ALCentral Standings" to either stdout or a file, or both.
Before starting let's talk about your definition of struct records inside of main(). While legal, you generally will want your struct declaration to be at file scope so that the struct type is available to any functions you write to help process the data in your code. Here, you must move the declaration outside of main() because the qsort compare function will need to know what the type struct records is. The compare function cannot be declared and defined in main() because C does not allow nested function declarations. So move your struct outside of main(), e.g.
#define MAXR 16 /* if you need a constant, #define one (or more) */
#define MAXC 64
#define MAXB 1024
typedef struct records {
char team[MAXC];
int wins,
loss,
tie,
points;
} record_t;
(note: using a typedef can make things more convenient)
To begin with, your multiple attempts to read the filename is awkward and never insures you actually have a file open. It just makes a couple of attempts and then effectively gives up validation and just blindly assumes there is a valid file stream to read and write to and presses ahead. This is a recipe for invoking Undefined Behavior.
When ever you do anything in C, and especially when you are dealing with user input or file operations, you must validate every step. You must know whether or not the file is open for reading before you attempt to read. (no matter how many times you attempt to open it) You must validate what you have read before you begin using the values in your code. You must validate the state of your input stream before you attempt your next read, and handle any characters that remain.
With that in mind you can prompt for the filename and attempt to open the filename provided in a continual loop until you validate the file is actually open, e.g.
/** helper function to empty stdin between inputs */
void empty_stdin()
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
...
FILE *fp = NULL; /* file pointer */
do { /* loop until valid filename given and file open */
printf("\nenter input filename: ");
if (scanf (" %63[^\n]", filename) != 1) {
fprintf (stderr, "error: user canceled input.\n");
return 1;
}
empty_stdin(); /* remove '\n' and any add'l chars in input buffer */
fp = fopen (filename, "r"); /* open filename */
if (fp == NULL) /* validate file open for reading */
perror ("fopen-input");
} while (fp == NULL);
printf("Opened file name %s\n\n",filename);
(note: rather than using scanf for user input, fgets provides a better approach to user input and far fewer pitfalls that scanf, example below with roster)
Next, your understanding of how to collect the data in memory in order to sort it, is not evident from your post. When you want to sort a collection of team information, you must read all team information into memory before you can pass the information to qsort for sorting.
You have a struct that holds all of information for each team, you simply need to read each line (record) from your input file into an array or struct which can then easily be passed to qsort for sorting.
To use qsort you first must define a compare function that is used to qsort to order your data. The compare function has a specific declaration:
int compare (const void *a, const void *b);
Where each const void * parameter will be a pointer to one of the elements in your array of records. (in C a void pointer, e.g. void* is a generic pointer that can be assigned from, or to, any other pointer type without a cast -- that is why the compare function's declaration uses that type for the parameters) Your job in writing a compare function is simply to provide a type to the pointers (either by assignment or by explicit cast, your choice) and then a comparison between the values that you wish to sort by. For example, your compare on the points member of each struct could simply be:
/** qsort compare function (descending order of points) */
int compare (const void *a, const void *b)
{
const record_t *ta = a;
const record_t *tb = b;
return (ta->points < tb->points) - (ta->points > tb->points);
}
(where the (a < b) - (a > b) simply evaluates to -1, 0, 1 depending on the inequalites, e.g. if a = 4 and b = 3, you have (0) - (1) which equals -1 meaning a sorts before b)
All that remains is reading each teams data into your array and computing points and then passing your array to qsort.
To read your data into an array, read each line into a buffer, parse the data in the buffer into the individual values, validate that each of the individual values where parsed correctly, compute the points value for each team, increment your array index and then go read the next line. You could do something like the following using fgets to read each line of data:
record_t roster[MAXR] = {{ .team = "" }}; /* array to hold teams */
...
/* read up to MAXR team records from file */
while (ndx < MAXR && fgets (buf, MAXB, fp)) {
/* parse data from buffer filled, saving return */
int rtn = sscanf (buf, " %49s %d %d %d" , roster[ndx].team,
&roster[ndx].wins, &roster[ndx].loss,
&roster[ndx].tie);
if (rtn < 4) /* if less than 4 conversions, get next line */
continue;
/* compute points (2 * wins + ties) */
roster[ndx].points = roster[ndx].wins * 2 + roster[ndx].tie;
ndx++; /* increment index */
}
fclose (fp); /* close file */
(note: you can add a strlen() check of buf to determine if additional characters remain in the line unread -- that is left to you).
With all you data in your array of structs roster, all that remains is passing roster to qsort and then outputting your sorted data.
The following sorts the data and outputs the results to stdout. Writing the output to a file is left to you, but hint, you don't need both ifp and afp and you don't need filename and filename2 or points2, you are only dealing with one file/filename at a time. The sort and output is simple:
/* sort roster based on points (descending order) */
qsort (roster, ndx, sizeof *roster, compare);
/* outpout results */
printf ("Here are the overall ALCentral Standings!\n\n"
"Team Wins Loss Tie Points\n\n");
for (int i = 0; i < ndx; i++)
printf ("%-12s %4d %4d %4d %4d\n", roster[i].team,
roster[i].wins, roster[i].loss, roster[i].tie,
roster[i].points);
Putting it altogether in an example, you could do something like the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXR 16 /* if you need a constant, #define one (or more) */
#define MAXC 64
#define MAXB 1024
typedef struct records {
char team[MAXC];
int wins,
loss,
tie,
points;
} record_t;
/** helper function to empty stdin between inputs */
void empty_stdin()
{
int c = getchar();
while (c != '\n' && c != EOF)
c = getchar();
}
/** qsort compare function (descending order of points) */
int compare (const void *a, const void *b)
{
const record_t *ta = a;
const record_t *tb = b;
return (ta->points < tb->points) - (ta->points > tb->points);
}
int main (void) {
int ndx = 0; /* index for roster array */
char buf[MAXB] = "", /* buffer for fgetrs */
filename[MAXC] = ""; /* filename to read */
record_t roster[MAXR] = {{ .team = "" }}; /* array to hold teams */
FILE *fp = NULL; /* file pointer */
do { /* loop until valid filename given and file open */
printf("\nenter input filename: ");
if (scanf (" %63[^\n]", filename) != 1) {
fprintf (stderr, "error: user canceled input.\n");
return 1;
}
empty_stdin(); /* remove '\n' and any add'l chars in input buffer */
fp = fopen (filename, "r"); /* open filename */
if (fp == NULL) /* validate file open for reading */
perror ("fopen-input");
} while (fp == NULL);
printf("Opened file name %s\n\n",filename);
/* read up to MAXR team records from file */
while (ndx < MAXR && fgets (buf, MAXB, fp)) {
/* parse data from buffer filled, saving return */
int rtn = sscanf (buf, " %49s %d %d %d" , roster[ndx].team,
&roster[ndx].wins, &roster[ndx].loss,
&roster[ndx].tie);
if (rtn < 4) /* if less than 4 conversions, get next line */
continue;
/* compute points (2 * wins + ties) */
roster[ndx].points = roster[ndx].wins * 2 + roster[ndx].tie;
ndx++; /* increment index */
}
fclose (fp); /* close file */
/* sort roster based on points (descending order) */
qsort (roster, ndx, sizeof *roster, compare);
/* outpout results */
printf ("Here are the overall ALCentral Standings!\n\n"
"Team Wins Loss Tie Points\n\n");
for (int i = 0; i < ndx; i++)
printf ("%-12s %4d %4d %4d %4d\n", roster[i].team,
roster[i].wins, roster[i].loss, roster[i].tie,
roster[i].points);
return 0;
}
Example Input File
The file is provided as given, with blank lines in between records.
$ cat dat/teams.txt
Indians 54 45 5
Twins 45 53 7
Tigers 43 59 8
White_Sox 35 64 9
Royals 30 69 3
Example Use/Output
$ ./bin/qsortstrcmp
enter input filename: dat/teams.txt
Opened file name dat/teams.txt
Here are the overall ALCentral Standings!
Team Wins Loss Tie Points
Indians 54 45 5 113
Twins 45 53 7 97
Tigers 43 59 8 94
White_Sox 35 64 9 79
Royals 30 69 3 63
Look things over and let me know if you have further questions.

Reading a binary file for dates

So all it gives me is the hex editor print out, the first 4 bytes is the integer stating how many dates, and then it is followed by chars MMDDYY so 6 chars per date. How would I write a program that reads a .bin file takes the first 4 bytes (int) as the number of elements, and then reads in each dates, parsing out the dates with the year 14?
Something Like this?
int void main(void) {
FILE* infile = fopen("dates.bin", "rb");
int numOfDates;
fgets(numOfDates, sizeof(int), infile);
int i, totalDates;
char allDates[numOfDates * 6];
for(i = 0; i <= numOfDates; i++) {
char tempDate[6];
fgets(tempDate, sizeof(tempDate), infile);
if(tempDate[5] == '1' && tempDate[6] == '4') {
strcpy(allDates[totalDates * 6], tempDate);
totalDates++;
}
}
fclose(infile);
int j;
FILE* outfile = fopen("datesNew.bin", "wb");
fprintf(outfile, "%d", totalDates);
fprintf(outfile, "%s", allDates);
fclose(outfile);
}
The task you have is one that quite literally has to be taken step-by-step validating each input and output along the way. Only then can you have confidence in your input and output. One thing to note, and I suspect this is intentional on the part of your lesson, you have more dates in your input file than the number contained as the first integer in the file. (Number is 7, but file actually contains 8 dates)
Generally, but not always, you can structure your reading and writing as separate operations. Here, since you have to recreate your output file in the same format as the input file (with the number of 2014 dates contained in the output as the first integer value written to the file), you almost have to do it that way to efficiently make a single pass through your input file. (you can always read your input multiple times, but that is what you want to avoid from an efficiency standpoint -- disk I/O being the primary bottleneck)
As noted in my comment main() is type int, it takes arguments, use them to pass the filenames to your code. Being type int, main() therefore returns a value.
Your first task is to open your input file and read the number of dates you are expected to read from the file. That can begin with a simple:
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#define DTSZ 6
int main (int argc, char **argv) {
FILE *fp = NULL; /* file pointer to use for both read/write */
int32_t n = 0; /* number of dates to read from input */
if (argc < 3) { /* validate input/output filenames arguments given */
fprintf (stderr, "error: insufficient input.\n"
"usage: %s infile.bin outfile.bin\n", argv[0]);
return 1;
}
if (!(fp = fopen (argv[1], "rb"))) { /* input file open for reading */
fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
return 1;
}
if (fread (&n, sizeof n, 1, fp) != 1) { /* read number of dates */
fprintf (stderr, "error: read of n-dates failed.\n");
return 1;
}
Since you now know the number of dates you will read, you can use a variable length array (a VLA) to hold the dates. Thinking forward, you will also need to track which dates are 2014 dates so creating a check chk array of length n and initializing to all zeros will allow you to increment the index in chk that corresponds to a 2014 so you have a mechanism to use to write the 2014 date out to your output file once you have determined how many there are. So after you create your VLAs, you can loop over the number of dates in your input storing them in an array, closing the file when input in done, e.g.
char buf[n][DTSZ + 1]; /* +1 to allow use as string */
int32_t chk[n], n14 = 0; /* array for 2014 indexes, number */
memset (chk, 0, sizeof chk); /* zero the chk array */
for (int i = 0; i < n; i++) { /* read/validate each date */
if (fread (buf[i], 1, DTSZ, fp) != DTSZ) {
fprintf (stderr, "error: failed to read date '%d'.\n", i);
return 1;
}
buf[i][DTSZ] = 0; /* nul-terminate for string use */
printf ("read : %s\n", buf[i]); /* output date found */
if (buf[i][4] == '1' && buf[i][5] == '4') /* 2014 ? */
n14++, chk[i]++; /* increment count, index */
}
fclose (fp); /* close input file */
Now, knowing the number of 2014 dates and the index for those dates within your date buffer, you can open your output file, loop over the indexes in chk outputting the dates in buf that correspond to the set indexes in chk, e.g
if (!(fp = fopen (argv[2], "wb"))) { /* output file open */
fprintf (stderr, "error: file open failed '%s'.\n", argv[2]);
return 1;
}
if (fwrite (&n14, sizeof n14, 1, fp) != 1) { /* output n 2014 */
fprintf (stderr, "error: write of n14-dates failed.\n");
return 1;
}
for (int i = 0; i < n; i++) /* for each date read */
if (chk[i]) { /* check 2014 index */
printf ("write : '%s' to output.\n", buf[i]);
if (fwrite (buf[i], 1, DTSZ, fp) != DTSZ) { /* write/validate */
fprintf (stderr, "error: write of '%s' failed.\n", buf[i]);
return 1;
}
}
if (fclose (fp) == EOF) { /* close output file */
/* handle error */
}
return 0;
}
note: you should also check the return of fclose after any write operation. fclose returns 0 on success or EOF on error and sets errno to contain specifics about the error encountered. A check of if (fclose (fp) == EOF) {/*... handle error ...*/} is sufficient. The concern are that a prior write operation may have encountered an error that is not reported until the stream is closed, the close itself was interrupted, or an IO error occurred. Also note that fclose does not cause data to be immediately written to disk in all cases (the kernel may buffer output, etc..). To force an immediate write disk, a call fsync to insure the buffer is flushed and data is written.
Putting those pieces together, you could expect the following:
Example Use/Output
$ ./bin/freaddates dat/bindates.bin dat/newdates.bin
read : 050514
read : 032313
read : 012514
read : 081612
read : 073114
read : 020612
read : 112315
write : '050514' to output.
write : '012514' to output.
write : '073114' to output.
Verify Written File
$ hexdump -C dat/newdates.bin
00000000 03 00 00 00 30 35 30 35 31 34 30 31 32 35 31 34 |....050514012514|
00000010 30 37 33 31 31 34 |073114|
00000016
Look things over and let me know if you have any further questions.

Resources