Ok, I'm back, and I'm drunk.. So please take this with a grain of salt.
I've got a working example, it will read your input.txt file, and show you what it found in it.. I didn't oncorporate the desired functioanlity, but on teh other hand, this will take an unlimited number of students, with an unlimited number of grades per student.. What you decide to do with it si entirely up to you..
The code is base on a lot of pointer arritmetic, which at some points might seem obscure, but if you dont know what the h*** it is doing, then feel free to question that part, I might be sober nough tomorrow to even answer a qustion like that.. Or I might have fogotten why I did it in the first place...
Code:
#include <stdio.h> // printf(), fope(), fgets(), fclose()
#include <stdlib.h> // malloc(), free(), atoi()
#include <string.h> // strlen(), strncpy(), strtok()
#define NAME_LENGTH 64
#define LINE_LENGTH 1024
#define DELIMITER " \n"
/* The scores can be of any count */
typedef struct SCORES{
int score;
struct SCORES* prev;
struct SCORES* next;
}SCORES;
/* the names can be of any count */
typedef struct RECORDS{
char first_name[NAME_LENGTH +1];
char last_name[NAME_LENGTH +1];
SCORES* scores;
struct RECORDS* prev;
struct RECORDS* next;
}RECORDS;
int read_entries(FILE* fp, RECORDS* record, int verbose);
int free_me(RECORDS* record);
/* Run this sucker, instead of limiting the user to some uniq input.txt
* file, we give them the selection to parse that to teh program,
* aswell as a request for a verbose output, where they can follow
* what is happening during teh run of it all.
*/
int main(int argc, char** argv)
{
FILE* in_file;
/* record will be the initial record,
* the first one at all time
*/
RECORDS* record;
/* cur_record will be pointing to the
* curent used record at all times
*/
RECORDS* cur_record;
/* scores will be pointing to the current
* score assigned in the cur_record
*/
SCORES* scores;
int count_records, count_scores, verbose = 0;
/* we need to check if the program should be
* with verbose output or if it's just the file_name
* thats been passed to it.
*/
if(argc <= 1 || argc > 3)
{
printf("Usage: %s [-v] <filename>\n", argv[0]);
return -1;
}
/* The arguments given are indeed "-v" "filename" */
if(argv[1][0] == '-' && argc == 3)
{
verbose = 1;
if(!(in_file = fopen(argv[2], "r")))
{
printf("Unable to open file: %s\n", argv[2]);
return -1;
}
}
/* it was just a filename that was given */
else
if(!(in_file = fopen(argv[1], "r")))
{
printf("Unable to open file: %s\n", argv[2]);
return -1;
}
/* we allocate room for the very first read record */
record = (RECORDS*) malloc(sizeof(RECORDS));
record->prev = NULL;
record->next = NULL;
cur_record = record;
if(read_entries(in_file, cur_record, verbose))
{
printf("Error reading entries from file.\n");
free_me(record);
return -1;
}
fclose(in_file);
cur_record = record;
count_records = 1;
/* print what we've read */
while(cur_record)
{
count_scores = 1;
/* since it will allocate a NULL user, when theres an empty line
* we will have to correct that
*/
if(strlen(cur_record->first_name) <=1)
break;
printf("Summary for user [%.3d]: %s, %s\n", count_records, cur_record->last_name, cur_record->first_name);
scores = cur_record->scores;
while(scores)
{
/* since strtok() will give a NULL value, when dealing with EOL
* we will have to correct that
*/
if(!scores->score)
break;
printf(" \t %d%c", scores->score, ((count_scores % 4) ? ' ' : '\n'));
scores = scores->next;
count_scores++;
}
printf("\n");
count_records++;
cur_record = cur_record->next;
}
free_me(record);
return 0;
}
/* when reading everthing in the scores file, we need to dynamicaly
* allocate the memory needed from the heap.
* since we're allocating one instance at a time, theres a slight
* overkill in the allocation, but since the fgets() will consume much
* of the time spent here anyway, it's affordable.
*/
int read_entries(FILE* fp, RECORDS* record, int verbose)
{
RECORDS* cur_record;
RECORDS* next_record;
SCORES* cur_score;
SCORES* next_score;
char *str, buffer[LINE_LENGTH +1];
cur_record = (RECORDS*) malloc(sizeof(RECORDS));
if(!cur_record)
{
printf("Error allocating space on the heap for cur_record\n");
return 1;
}
cur_record->next = NULL;
cur_record->prev = NULL;
cur_record = record;
while(fgets(buffer, LINE_LENGTH, fp))
{ /* buffer should contain "first_name last_name score1 ... scoreN\n" */
/* any line should be longer than 2 chars */
if(strlen(buffer) < 2)
break;
if(verbose)
printf("Read line: %s\n", buffer);
/* first we strip the first_name and last_name */
strncpy(cur_record->first_name, strtok(buffer, DELIMITER), NAME_LENGTH);
if(verbose)
printf("Stored into first_name: %s\n", cur_record->first_name);
strncpy(cur_record->last_name, strtok(NULL, DELIMITER), NAME_LENGTH);
if(verbose)
printf("Stored into last_name: %s\n", cur_record->last_name );
/* then run through the rest, collecting all scores */
cur_score = (SCORES*) malloc(sizeof(SCORES));
if(!cur_score)
{
printf("Error allocating space on the heap for cur_score\n");
return 1;
}
cur_score->prev = NULL;
cur_score->next = NULL;
cur_record->scores = cur_score;
while(NULL != (str = strtok(NULL, DELIMITER)))
{
cur_score->score = atoi(str);
if(verbose)
printf("Stored into score: %d\n", cur_score->score);
next_score = (SCORES*) malloc(sizeof(SCORES));
if(!next_score)
{
printf("Error allocating space on the heap for next_score\n");
return 1;
}
next_score->prev = cur_score;
cur_score->next = next_score;
cur_score = next_score;
}
next_record = (RECORDS*) malloc(sizeof(RECORDS));
if(!next_record)
{
printf("Error allocating space on the heap for next_record\n");
return 1;
}
next_record->prev = cur_record;
cur_record->next = next_record;
cur_record = next_record;
}
return 0;
}
/* when dealing with malloc, a nice thing to do, is to
* free everything you've allocated, just to give back
* to the system what you've proclaimed
*/
int free_me(RECORDS* record)
{
RECORDS* cur_record;
SCORES* cur_score;
cur_record = record;
/* now work your way to the last record
* deleting everything that comes in your way
*/
while(cur_record)
{
cur_score = cur_record->scores;
while(cur_score)
{
if(!cur_score->next)
break;
cur_score = cur_score->next;
free(cur_score->prev);
}
if(!cur_record->next)
break;
cur_record = cur_record->next;
free(cur_record->prev);
}
return 0;
}
Boy I'm glad I wrote the explanations to the functions while I was still sober... Now It would have been filled with some extraordinarily remark, that wouldn't make any sence what so ever...
Sorry, but I'm too drunk to highlight any of this code, as I would normaly do, in order to point out the key terms and restricted variables, such as int, void, stdout, stdin, etc.