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 != '
' && c != EOF)
c = getchar();
}
...
FILE *fp = NULL; /* file pointer */
do { /* loop until valid filename given and file open */
printf("
enter input filename: ");
if (scanf (" %63[^
]", filename) != 1) {
fprintf (stderr, "error: user canceled input.
");
return 1;
}
empty_stdin(); /* remove '
' 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
",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!
"
"Team Wins Loss Tie Points
");
for (int i = 0; i < ndx; i++)
printf ("%-12s %4d %4d %4d %4d
", 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 != '
' && 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("
enter input filename: ");
if (scanf (" %63[^
]", filename) != 1) {
fprintf (stderr, "error: user canceled input.
");
return 1;
}
empty_stdin(); /* remove '
' 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
",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!
"
"Team Wins Loss Tie Points
");
for (int i = 0; i < ndx; i++)
printf ("%-12s %4d %4d %4d %4d
", 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.