Problem with my C program that calculate grades in each term from .csv

106 Views Asked by At

My C code

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

#define MAX_GRADES 100
#define MAX_SUBJECTS 100
// struct to store subject data
typedef struct
{
  char code[50];
  char subjectName[50];
  float cr;
  float grade;
} Subject;
// struct to store grade data
typedef struct
{
  char grade[50];
  Subject subjects[MAX_SUBJECTS];
  int numSubjects;
  float avgGrade;
} GradeData;
// struct to store subject group data
typedef struct
{
  char subjectGroup[3];
  float totalGrade;
  int count;
} SubjectGroup;

int main()
{
  // change file name here
  char filename[] = "grades.csv";
  FILE *file, *outputFile;
  char line[100];
  GradeData grades[MAX_GRADES];
  int numGrades = 0;
  float totalGradeSum = 0;
  int totalNumSubjects = 0;
  SubjectGroup subjectGroups[MAX_SUBJECTS];
  int numSubjectGroups = 0;
  int isFirstLine = 1;

  file = fopen(filename, "r");
  // in case file does not exist
  if (file == NULL)
  {
    perror("Error opening file");
    return -1;
  }
  // create output file
  outputFile = fopen("output.csv", "w");
  // in case file does not exist
  if (outputFile == NULL)
  {
    perror("Error creating output file");
    return -1;
  }
  // read file line by line
  while (fgets(line, sizeof(line), file)) {
        if (isFirstLine) {
            isFirstLine = 0;
            continue;
        }

        if (line[0] != ',') {
            if (line[0] != '\n') {
                sscanf(line, "%[^,],", grades[numGrades].grade);
                grades[numGrades].numSubjects = 0;
                numGrades++;
            }
        } else {
            Subject newSubject;
            sscanf(line, ",%[^,],%[^,],%f,%f", newSubject.code, newSubject.subjectName, &newSubject.cr, &newSubject.grade);
            if (grades[numGrades - 1].numSubjects < MAX_SUBJECTS) {
                strcpy(grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].code, newSubject.code);
                strcpy(grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].subjectName, newSubject.subjectName);
                grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].cr = newSubject.cr;
                grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].grade = newSubject.grade;
                grades[numGrades - 1].numSubjects++;
                totalGradeSum += newSubject.grade;
                totalNumSubjects++;

      // Calculate subject group averages
      char subjectGroup[3];
      strncpy(subjectGroup, newSubject.code, 2); // get first 2 characters of subject code
      subjectGroup[2] = '\0';                    // add null terminator
      int found = 0;
      for (int i = 0; i < numSubjectGroups; i++) // check if subject group already exists
      {
        if (strcmp(subjectGroup, subjectGroups[i].subjectGroup) == 0) // if subject group exists
        {
          // add grade to total grade and increment count
          subjectGroups[i].totalGrade += newSubject.grade; // add grade to total grade
          subjectGroups[i].count++;                        // increment count
          found = 1;                                       // set found to true
          break;
        }
      }
      if (!found) // if subject group does not exist
      {
        strcpy(subjectGroups[numSubjectGroups].subjectGroup, subjectGroup); // copy subject group to subject group data
        subjectGroups[numSubjectGroups].totalGrade = newSubject.grade;      // set total grade to grade
        subjectGroups[numSubjectGroups].count = 1;                          // set count to 1
        numSubjectGroups++;                                                 // increment number of subject groups
      }
    }
  }
  // calculate average grade for each grade
  for (int i = 0; i < numGrades; i++) 
  {
    // calculate average grade
    float sum = 0;
    for (int j = 0; j < grades[i].numSubjects; j++)
    {
      sum += grades[i].subjects[j].grade; // add grade to sum
    }
    grades[i].avgGrade = sum / grades[i].numSubjects; // calculate average grade
  }

  // write to output file
  // write grade data
  fprintf(outputFile, "Grade,Average Grade\n"); // write header
  for (int i = 0; i < numGrades; i++) // write grade data
  {
    fprintf(outputFile, "%s,%.2f\n", grades[i].grade, grades[i].avgGrade);
  }
  // write overall average grade
  fprintf(outputFile, "Overall Average Grade,%.2f\n",
          totalGradeSum / totalNumSubjects);
  // write subject group averages
  fprintf(outputFile, "\nSubject Group,Average Grade\n");
  for (int i = 0; i < numSubjectGroups; i++) // write subject group data
  {
    fprintf(outputFile, "%s,%.2f\n", subjectGroups[i].subjectGroup,
            subjectGroups[i].totalGrade / subjectGroups[i].count);
  }
  
  fclose(file); // close input file
  fclose(outputFile); // close output file

  return 0;
}

The code read grades.csv

,CODE,Subject NAME,CR,Grade
Grade 10 Term 1,TH31101,Thai Language 1,1.0,1.5
,MA31101,Mathematics 1,1.0,4
,SC30101,Science (Physics),1.0,4
,EN31201,English Reading-Writing 1,1.5,4
,,,,
,,,,
Grade 10 Term 2,TH31102,Thai Language 2,1.0,4
,MA31102,Mathematics 2,1.0,4
,SC30103,Computational Science 1,0.5,3.5
,EN31202,English Reading - Writing 2,1.5,4

But it output is missing first subject in each term

Grade,Average Grade
Grade 10 Term 1,3.72
Grade 10 Term 2,3.88
Overall Average Grade,3.86

Subject Group,Average Grade
MA,3.75
SC,3.83
EN,4.00

I think there is problem that it don't read first subject in each term because I on the same row with term name.

I had try to clone lines that calc data to if condition that true when it is new subject but it doesn't work

2

There are 2 best solutions below

0
hko On

If your csv file is always formated this way you just need to read a subject for every line, including the term line.

if (line[0] != ',') {
    if (line[0] != '\n') {
        sscanf(line, "%[^,],%[^,],%[^,],%f,%f", grades[numGrades].grade, newSubject.code, newSubject.subjectName, &newSubject.cr, &newSubject.grade);
        grades[numGrades].numSubjects = 0;
        numGrades++;
    }
} else {
    sscanf(line, ",%[^,],%[^,],%f,%f", newSubject.code, newSubject.subjectName, &newSubject.cr, &newSubject.grade);
}
if (grades[numGrades - 1].numSubjects < MAX_SUBJECTS) {
    strcpy(grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].code, newSubject.code);
    strcpy(grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].subjectName, newSubject.subjectName);
    grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].cr = newSubject.cr;
    grades[numGrades - 1].subjects[grades[numGrades - 1].numSubjects].grade = newSubject.grade;
    grades[numGrades - 1].numSubjects++;
    totalGradeSum += newSubject.grade;
    totalNumSubjects++;
}

This will give you the output:

Grade,Average Grade
Grade 10 Term 1,3.58
Grade 10 Term 2,3.88
Overall Average Grade,3.70

Subject Group,Average Grade
TH,2.75
MA,4.00
SC,3.75
EN,4.00
0
Jason On

I'm not entirely sure what output you are looking for. It looks to me that the averages are off (example the science average should be 3.75 and not 3.83).

The issue I found was that you were not checking the return value of sscanf. Almost all libc functions will return some kind of useful information related to how successful the it was. sscanf is no different. From man 3 sscanf:

On success, these functions return the number of input items successfully matched and assigned; this can be fewer than provided for, or even zero, in the event of an early matching failure.

So you should expect that sscanf will return the same number as the number of format specifiers that you gave it. For example this line:

sscanf(line, ",%[^,],%[^,],%f,%f", ...

... should return 4.

When you run this on the "blank" lines that only have commas, it will not return 4, and likewise, will not touch the information that is in there. Essentially, this means for each comma only line, you process the previous non-comma only line. So here:

,EN31201,English Reading-Writing 1,1.5,4
,,,,
,,,,

... the first line gets processed 3 times.

The solution here is to simply check the output and continue if it is not what we expected:

if (sscanf(line, ",%[^,],%[^,],%f,%f", newSubject.code, newSubject.subjectName, &newSubject.cr, &newSubject.grade) != 4) {
  continue;
}