fgdark fgdark - 26 days ago 6
C Question

How to sort strings from a file by giving each line an ordered index using insertion sort in C

I'm having trouble sorting this file, giving each line an index. The whole point is to prompt the user to type in the index so the program can return the program line that corresponds to the index number.
Here is my code:

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

void printUnsortedStringFromFile(int amount, char A[]);
void printSortedStringFromFile(int amount, char A[]);
//bool binSearchNUM(int amount, int A[amount], int target, int *current);

int main()
{

FILE* spData = fopen("grades.csv", "r");
int ch, number_of_lines = 0;
do
{
ch = fgetc(spData);
if (ch == '\n')
number_of_lines++;
} while (ch != EOF);

if (ch != '\n' && number_of_lines != 0)
number_of_lines++;

fclose(spData);

printf("There are %d lines in file grades.csv . \n", number_of_lines);
int amount = number_of_lines;
char A[amount];
printUnsortedStringFromFile(amount, A);
printSortedStringFromFile(amount, A);
return 0;
}


void printUnsortedStringFromFile(int amount, char A[])
{
FILE *spData;
spData = fopen("grades.csv", "r");
if(spData == NULL)
{
fprintf(stderr, "Error opening the file grades.csv.\n");
exit(1);
}


int ex1;
int ex2;
int ex3;
int StudentNUM;
char StudentAVG;

printf("+-------+------+------+------+-----+\n");
printf("|Student|Exam 1|Exam 2|Exam 3|Grade|\n");
printf("+-------+------+------+------+-----+\n");
int z = 0;
while((fgets(A, amount, spData)) != NULL)
{
sscanf(A, "%d, %d, %d, %d, %c", &StudentNUM, &ex1, &ex2, &ex3, &StudentAVG);
printf("| %d| %d| %d| %d| %c| \n", StudentNUM, ex1, ex2, ex3, StudentAVG);
z++;
//prints unsorted correctly
}
printf("+-------+------+------+------+-----+\n");

if (fclose(spData) == EOF)
{
fprintf(stderr, "Error closing the file grades.csv. \n");
exit(2);
}
}
void printSortedStringFromFile(int amount, char A[])
{
FILE *spData;
spData = fopen("grades.csv", "r");
if(spData == NULL)
{
fprintf(stderr, "Error opening the file grades.csv.\n");
exit(1);
}
//help needed implementing insertion sort to sort each string as an index here
{
int walk;
int temp;
for (int cur = 1; cur < amount; cur++)
{
bool located = false;
temp = A[cur], walk = cur-1;
while (walk >= 0 && !located)
{
if (temp < A[walk])
{
A[walk+1] = A[walk];
walk--;
}
else
{
located = true;
}
}
A[walk+1] = temp;
}
}


int StudentNUM;
char StudentAVG;

printf("+-----+-------+-----+\n");
printf("|Index|Student|Grade|\n");
printf("+-----+-------+-----+\n");
int z = 0;
while((fgets(A, amount, spData)) != NULL)
{
sscanf(A, "%d, %c", &StudentNUM, &StudentAVG);
printf("| %d| %c| \n", StudentNUM, StudentAVG);
z++;
//student ID prints, grade average doesn/t, unsure how to sort these strings into a numbered(index) list
}
if (fclose(spData) == EOF)
{
fprintf(stderr, "Error closing the file grades.csv. \n");
exit(2);
}

}
/* (correct) example output:
There are 5 lines in file grades.csv.
Original:
+-------+------+------+------+-----+
|Student|Exam 1|Exam 2|Exam 3|Grade|
+-------+------+------+------+-----+
| 535743| 67| 96| 93| B|
| 112213| 87| 65| 72| C|
| 612778| 59| 58| 97| C|
| 151774| 52| 100| 86| C|
| 406704| 54| 72| 80| D|
+-------+------+------+------+-----+
Sorted:
+-----+-------+-----+
|Index|Student|Grade|
+-----+-------+-----+
| 1| 112213| C|
| 2| 151774| C|
| 3| 406704| D|
| 4| 535743| B|
| 5| 612778| C|
+-----+-------+-----+
*/

Answer

Answer Part One.

The main problem in your source code is the string char A[amount];.

In the main() function the variable A is allocated to the number of lines ?!!

In your example, number_of_lines = 5 means A[amount] = A[5] is able to store only a 4-characters string + null terminator.

printf("There are %d lines in file grades.csv . \n", number_of_lines);
int amount = number_of_lines;
char A[amount];
printUnsortedStringFromFile(amount, A);
printSortedStringFromFile(amount, A);

Then on both printUnsortedStringFromFile() and printSortedStringFromFile() functions the same variable A is used as a buffer to load and read one line.

In your example, the first line of 'grades.csv' is longer 4 characters and is truncated before calling sscanf().

while((fgets(A, amount, spData)) != NULL)
{
    sscanf(A, "%d, %d, %d, %d, %c", &StudentNUM, &ex1, &ex2, &ex3, &StudentAVG);

A solution could be to use a local char sTmp[80] for the fgets() and sscanf() and use the A[amount] only for the indexation.


Answer Part Two.

The second problem in your source code is that the suggested indexation in order to ascending student-id sort records by insertion sort, needs to store not only the index but also the content of each record. I suggest to use define a structure as follow:

struct gradesRecord {
    int iIndex;       // index on the file
    int iStudentNUM;  // 'Student' field
    int iExamVAL[3];  // 'Exam 1'..'Exam 3' fields
    char cStudentAVG; // 'Grade' field
};

Then convert your A[] array from char to struct gradesRecord(in main()):

int amount = number_of_lines;
struct gradesRecord A[amount];
printUnsortedStringFromFile(amount, A);
printSortedStringFromFile(amount, A);

In the printUnsortedStringFromFile() function, the array A[] is used directly in the reading loop:

To prevent a bad formatted text-file, it is recommended to check the returned value of sscanf() in order to detect missing parameters (see the nArg variable and how to check bellow).

char sLine[81]; // local string to read one row
int z = 0; // storage index
int nArg;

while((fgets(sLine, 80, spData)) != NULL)
{
    nArg = sscanf(sLine, "%d, %d, %d, %d, %c",
        &(A[z].iStudentNUM), &(A[z].iExamVAL[0]),
        &(A[z].iExamVAL[1]), &(A[z].iExamVAL[2]),
        &(A[z].cStudentAVG));

    if (nArg != 5) {
        // the input line is not correct !!!
        // manage that error.
    }

    printf("|%7d| %5d| %5d| %5d|    %c| \n", A[z].iStudentNUM,
        A[z].iExamVAL[0], A[z].iExamVAL[1], A[z].iExamVAL[2],
        A[z].cStudentAVG);
    z++; // next row

Then in the printSortedStringFromFile() function, the array A[] is used to store, sort in the reading loop, then displayed in a second loop:

First loop, reading and selection sort of all rows:

char sLine[81];
int iLine = 0, iRow;
struct gradesRecord grRow,grTmp;

while((fgets(sLine, 80, spData)) != NULL)
{
    // extract one Row and store it into grRow
    sscanf(sLine, "%d, %d, %d, %d, %c",
        &(grRow.iStudentNUM), &(grRow.iExamVAL[0]),
        &(grRow.iExamVAL[1]), &(grRow.iExamVAL[2]),
        &(grRow.cStudentAVG));
    // keep the line index of that row
    grRow.iIndex = iLine;
    // search loop = insertion sort algorithm
    for (iRow=0;iRow<iLine;iRow++) {
        //  detect if new student is before the store one
        if (grRow.iStudentNUM < A[iRow].iStudentNUM) {
            // exchange both stuident records through grTmp
            memcpy(&grTmp,&(A[iRow]),sizeof(struct gradesRecord));
            memcpy(&(A[iRow]),&grRow,sizeof(struct gradesRecord));
            memcpy(&grRow,&grTmp,sizeof(struct gradesRecord));
        }
    }
    // store the biggest student at the end
    memcpy(&(A[iLine]),&grRow,sizeof(struct gradesRecord));
    iLine++;
}

Second loop, display the sorted table:

while (z < amount)
{
    StudentNUM = A[z].iStudentNUM;
    StudentAVG = A[z].cStudentAVG;
    index = A[z].iIndex;
    printf("| %4d|%7d|    %c| \n", index, StudentNUM, StudentAVG);
    z++;
}