-1

I am facing some problems in my C assignment program:

  1. At option #4, the grade only sorted to descending, while at #5, the grade won't change, only the name of the students and their scores are swapped.

  2. At option #8, the string and float that inputted from file won't show up and I want the option 8 to be flexible (show from file when file was inputted via option #7 or only show the input from #1 menu option). Here is the example of the file:

    80.64 John
    90.40 Jane
    78.00 Jake
    

The code:

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

struct Studata{
    float min, max;
    int many;
    char *max1, *min1, gx, gn;
}studata;

struct Student{
    char name[100], grade;
    float score[100];
};

float average(struct Student student[100]){
    float sum;
    for(int i=0; i<student.many; i++){
        sum += studata[i].score;
    }
    return sum/(float)student.many;
}

void MM(struct Student student[100]){
    int i;
    studata.min = 0;
    studata.max = 100;
    for (i=0; i<studata.many; i++){
        if(*student[i].score > studata.min){
            studata.min = student[i].score;
            studata.min1 = student[i].name;
            studata.gn = student[i].grade;
        }
    }
    for (i=0; i<studata.many; i++){
        if(student[i].score < studata.min){
            studata.max = student[i].score;
            studata.max1 = student[i].name;
            studata.gx = student[i].grade;
        }
    }

}

void swapname(char *a, char *b){
    char z[100];
    strcpy(z, a);
    strcpy(a, b);
    strcpy(b, z);
}

void swapscore(float a, float b){
    float temporary = a;
    a = b;
    b = temporary;
}

void swapgrade(char A1, char B1) {
    char C1 = A1;
    A1 = B1;
    B1 = C1;
}

void Bubblesort(int mode, struct Student student[100]) {
    int i, j;
    if(mode == 1) {
        for (i=0; i<studata.many; i++) {
            for (j=i+1; j<studata.many; j++) {
                if(student[j].score > student[i].score) {
                    swapname(student[i].name, student[j].name);
                    swapscore(student[i].score, student[j].score);
                    swapgrade(student[i].grade, student[j].grade);
                }
            }
        }
    }
    else if(mode == 0) {
        for(i=0; i<studata.many; i++) {
            for(j=i+1; j<studata.many; j++) {
                if(student[j].score < student[i].score) {
                    swapname(student[i].name, student[j].name);
                    swapscore(student[i].score, student[j].score);
                    swapgrade(student[i].grade, student[j].grade);
                }
            }
        }
    }
}

int main(){
    struct Student student[100];
    int selection=1;
    FILE *file;
    
    while (selection <= 8 && selection >= 1) {
        printf("\n\n\t-------MENU-------\n\n");
        printf("0. Enter Data of Students\n");
        printf("1. Calculate the Average\n");
        printf("2. Show Maximum and Minimum\n");
        printf("3. Sort Score Ascending\n");
        printf("4. Sort Score Descending\n");
        printf("5. Save Scores\n");
        printf("6. Load Scores from File\n");
        printf("7. Load All Data\n");
        printf("Choice (Other than 1-8 to Exit): ");
        scanf("%d", &selection);
        
        if(selection == 1) {
            printf("=============================\n");
            printf("\nHow many students would you like to input: ");
            scanf(" %d", &studata.many);
             for (int i=0; i<studata.many; i++) {
                printf("\nStudent-%d Name\t: ", i+1);
                scanf(" %[^\n]s", student[i].name);
             
                printf("Student-%d Score\t: ", i+1);
                scanf(" %f", &student[i].score);
                while(student[i].score > 100 || student[i].score < 0) {
                     printf("Hey, wrong input, please input correctly, okay?");
                     printf("\nStudent-%d Score\t: ", i+1);
                     scanf(" %f",&student[i].score);
                }

                if (student[i].score <= 100 && student[i].score >= 90 ) {
                    student[i].grade= 'A';
                }
                else if (student[i].score < 90 && student[i].score >= 80) {
                    student[i].grade= 'B';
                }
                else if (student[i].score < 80 && student[i].score >=70) {
                    student[i].grade= 'C';
                }
                else if (student[i].score < 70 && student[i].score >=60) {
                    student[i].grade= 'D';
                }
                else if (student[i].score < 60 && student[i].score >=50) {
                    student[i].grade= 'E';
                }
                else {
                    student[i].grade = 'F';
                }
            }
        }

        else if(selection == 2) {
            printf("=============================\n");
            printf("Average of Score is %.2f", average(student));
        }
        else if(selection == 3) {
            MM(student);
            printf("=============================\n");
            printf("Minimum\t: %s || %4.2f || %c\n", studata.max1, studata.max, studata.gx);
            printf("Maximum\t: %s || %4.2f || %c\n", studata.min1, studata.min, studata.gn);
        }
        else if(selection == 4) {
            printf("=============================\n");
            Bubblesort(0,student);
            for(int i=0; i<studata.many; i++) {
                printf("   %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
        else if(selection == 5) {
            printf("=============================\n");
            Bubblesort(1,student);
            for(int i=0; i<studata.many; i++) {
                printf("   %s : %5.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
        else if(selection == 6) {
            char filename[100];
            printf("=============================\n");
            printf("Name of the file (with ext.): ");
            scanf(" %[^\n]s", filename);
            file = fopen(filename, "w");
            for(int i=0; i<studata.many; i++) {
                fprintf(file,"%.2f %s\n", student[i].score, student[i].name);
            }
            fclose(file);
        }
        else if(selection == 7) {
            char filename[100];
            char sub_ch;
            int i;

            printf("Enter name of file you want to open (with extension): ");
            scanf(" %[^\n]s", filename);
            file = fopen(filename, "r");
            while (file == NULL) {
                printf("I'm Error! Reinput? (Y/n): ");
                scanf("%c", &sub_ch);
                if(sub_ch == 'Y') {
                    printf("Enter name of file you want to open (with extension): ");
                    scanf(" %[^\n]s", filename);
                }
                file = fopen(filename, "r");
                if(sub_ch == 'n') {
                    exit(1);
                }
            }
            
            printf("=============================\n");
            fscanf(file, "%f %s", &student[i].score, student[i].name);
            while (!feof(file)) {
                if (student[i].score <= 100 && student[i].score >= 90 ) {
                    student[i].grade= 'A';
                }
                else if (student[i].score < 90 && student[i].score >= 80) {
                    student[i].grade= 'B';
                }
                else if (student[i].score < 80 && student[i].score >=70) {
                    student[i].grade= 'C';
                }
                else if (student[i].score < 70 && student[i].score >=60) {
                    student[i].grade= 'D';
                }
                else if (student[i].score < 60 && student[i].score >=50) {
                    student[i].grade= 'E';
                }
                else {
                    student[i].grade= 'F';
                }
                printf("%s %8.2f --> %c\n", student[i].name, student[i].score, student[i].grade);
                fscanf(file, "%f %s", &student[i].score, student[i].name);
            }
            fclose(file);
        }
        else if(selection == 8) {
            printf("=============================\n");
            for (int i=0; i<studata.many; i++) {
                printf("Name || Score || Grade\t: %s || %3.2f || %c\n", student[i].name, student[i].score, student[i].grade);
            }
        }
    }
    return 0;
}

I don't know what to do again after I tried to give pointer on every possible variable.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Maou2pt
  • 1
  • 1
  • Why is `score` an _array_ (e.g. `float score[100];`) instead of a _scalar_ (e.g. `float score;`)? As you have it, it's _treated_ as a scalar because you do: `*studata[i].score` everywhere. That only looks at `score[0]`. – Craig Estey Dec 11 '20 at 20:32
  • If i'm not wrong you are comparing wrong. it must be studata[j].score[index] < studata[i].score[index], in other case you are comparing pointers. – Hamboy75 Dec 11 '20 at 20:34
  • the base pointer of the array pointers. and as @CraigEstey , why are you using an array instead of a float. – Hamboy75 Dec 11 '20 at 20:36
  • You put `name` in `Studata` [which is the student's name]. Why not in `Student` instead? What are the meanings of the two structs? For example, ordinarily, I would have a struct `Student` that describes the student. The `Studata` would be about grades and test scores the student received in various classes they took. So, I'd clarify how you want to organize the data. – Craig Estey Dec 11 '20 at 20:37
  • Sorry, I didn't plan ahead about the name of struct. I've changed it now though, and I already deleted all the pointers. Thanks @CraigEstey All that problem left is the descending ascending swap, they only change the second and the third line. Also loading the file makes it segmentation fault – Maou2pt Dec 12 '20 at 06:51
  • Several tips (1) unit test, (2) don't combine your implementation with your interface until (1) is complete, (3) C provides `qsort()` capable of sorting an array of anything based on the `compare()` function you write for it. It is far more efficient and thoroughly tested that what you happen to re-invent on the fly. (4) [How to debug small programs](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) is a critical skill (don't forget the duck), [What is a debugger and how can it help me diagnose problems?](https://stackoverflow.com/q/25385173/3422102) will get you started. – David C. Rankin Dec 12 '20 at 07:02
  • @DavidC.Rankin thank you for the good tips – Maou2pt Dec 12 '20 at 12:46

1 Answers1

0

Expanding on the tips above, the thing that makes your code so difficult to read (and maintain) is you ignore (2) from the comment above. Your implementation (the logic applied to your data) is not separate from your interface (how you interact with the user). Instead you have your code all jumbled together in a menu that spans screenfuls of lines.

Keeping your implementation separate from interface keeps the logic for each separate and readable. Here are some additional areas where you can improve both the interface code and the implementation:

Don't Clutter The Implementation With Unnecessary Repeated Output Function Calls

During compilation, the compile with concatenate all adjacent string-literals that a separate only by whitespace. That means you do not need 10 separate calls to printf() to output your menu. In fact, since there are no conversions necessary in the menu you display, you don't need to call the variadic printf() function at all, much less call it 10 times. Since you want end-of-line control, a single call to fputs() is all you need, e.g.

    fputs ("\n\n\t-----------MENU-----------\n\n"
                 " 0. Enter Data of Students\n"
                 " 1. Calculate the Average\n"
                 " 2. Show Maximum and Minimum\n"
                 " 3. Sort Score Ascending\n"
                 " 4. Sort Score Descending\n"
                 " 5. Save Scores\n"
                 " 6. Load Scores from File\n"
                 " 7. Load All Data\n\n"
                 "Choice (Other than 1-8 to Exit): ", stdout);

If you wanted a '\n' output at the end, then simply using puts() would do. A single call makes your menu readable.

Create Logical Functions To Implement Your Interface

You don't need to include the 10 lines of menu within the while loop in the body of your code. It makes things much more readable and maintainable if you create a short function to display the menu such as:

void show_menu (void)
{
    fputs ("\n\n\t-----------MENU-----------\n\n"
                 " 0. Enter Data of Students\n"
                 " 1. Calculate the Average\n"
                 " 2. Show Maximum and Minimum\n"
                 " 3. Sort Score Ascending\n"
                 " 4. Sort Score Descending\n"
                 " 5. Save Scores\n"
                 " 6. Load Scores from File\n"
                 " 7. Load All Data\n\n"
                 "Choice (Other than 1-8 to Exit): ", stdout);
}

Then your main loop can be kept readable, e.g.

while (selection <= 8 && selection >= 1) {
    show_menu();

That is far more readable and easier to maintain than 10 printf() functions at the beginning of your main loop.

Validate EVERY Input To Your Program

You must validate ever input to your program and every conversion. What happens if the user slips reaching for '4' and instead presses 'e' for the menu choice? Try it.

    scanf("%d", &selection);

With no validation, you invoke Undefined Behavior when selection (which now holds an indeterminate value) is access in if(selection == 1). At minimum you must catch the error and exit, e.g.

    if (scanf("%d", &selection) != 1) {
        fputs ("error: invalid integer input.\n", stderr);
        exit (EXIT_FAILURE);
    }

To gracefully handle the error, you would need to know that with scanf(), when a matching-failure occurs, character extraction from the input stream ceases at that point leaving the offending characters causing the failure unread in the input stream. You must clear the offending characters before the next attempted input or the input will fail again for the same reason resulting in an infinite loop in many cases. There are hundreds of answers on this site showing how to correctly use scanf().

The better ones explain that you do not take user input with scanf(), but instead use fgets() with an adequately sized buffer (character array) and then use sscanf() to retrieve any values from the buffer you need -- which eliminates any possibility of characters being left unread in your input stream.

Don't Clutter Your Interface With Your Implementation

There is no reason the if .. else if .. else if ... logic for determining the letter grade should be in the middle of your program loop. Just as with show_menu() above, that implementation should be in a short function you can call from your main loop, e.g.

char get_ltrgrade (float f)             /* return letter grade given float score */
{
    if      (f >= 90) return 'A';
    else if (f >= 80) return 'B';
    else if (f >= 70) return 'C';
    else if (f >= 60) return 'D';
    else              return 'F';
}

Then instead of a dozen lines of implementation after if(selection == 1) , you simply have a short call to get_ltrgrade();

Match Your Data Structures To The Data You Have

Your use of struct Student and struct Studata does not fit the input data you show. The input data you show has one score per student. To match your data, struct Student alone would do. Now if you have many classes of students, then struct Studata starts to make more sense. float score[100]; would allow multiple grades per-student, but again that doesn't match the data you show, and you have no counter within struct Student to track the number of valid grades for that student. From what you show, float score: makes more sense.

Sort Arrays With qsort()

C provides qsort() to handle sorting arrays of any type. The qsort() function will be orders of magnitude more efficient than any sort you write and it has been tested for decades. New C programmers usually avoid qsort() because it requires writing a short compare() function that is generally less than 5-10 lines of code telling qsort() how to compare elements of the array. It is actually quite easy.

For explantion of the compare function used by qsort and how to use it, see How to sort an array of a structure using qsort? and further detail in How can I stop taking inputs from user when I press the key 'q'?.

The following puts together the pieces of your implementation that reads the student data from the file, assigning a letter grade to the score and storing all data in an array of struct. The program then calls qsort() to sort the array of struct and outputs the data. Since you include char grade; (char ltrgrade; below), the grade is assigned when the data is read from the file, eliminating the all need to compute the grade within the middle of your implementation.

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

#define MAXC 1024       /* if you need a constant, #define one (or more) */
#define MAXN  128
#define MAXS  256

typedef struct {        /* typedef allows use of student_t as type */
    char name[MAXN],
        ltrgrade;
    float score;
} student_t;

char get_ltrgrade (float f)             /* return letter grade given float score */
{
    if      (f >= 90) return 'A';
    else if (f >= 80) return 'B';
    else if (f >= 70) return 'C';
    else if (f >= 60) return 'D';
    else              return 'F';
}

/* qsort compare by student_t->scoore (descending),
 * change to >, < for ascending sort.
 */
int compare_score (const void *a, const void *b)
{
    student_t *sa = (student_t*)a,
              *sb = (student_t*)b;
    
    return (sa->score < sb->score) - (sa->score > sb->score);
}

int main (int argc, char **argv) {
    
    char buf[MAXC];                     /* buffer to hold each line read from file */
    student_t student[MAXS];            /* array of student_t (MAXS of them) */
    size_t n = 0;                       /* array index (counter) */
    /* 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 array not full, read each line into buf, VALIDATE read */
    while (n < MAXS && fgets (buf, MAXC, fp)) {
        /* split line into score and name, VALIDATE conversion */
        if (sscanf (buf, "%f %127[^\n]", &student[n].score, student[n].name) == 2) {
            /* get letter grade based on score */
            student[n].ltrgrade = get_ltrgrade (student[n].score);
            n += 1;     /* increment index only on valid converison */
        }
        else            /* handle error */
            fprintf (stderr, "error: invalid line format, student[%zu].\n", n);
    }
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    qsort (student, n, sizeof *student, compare_score);     /* sort by score */
    
    for (size_t i = 0; i < n; i++)                          /* output result */
        printf ("%-12s  %6.2f  %c\n", 
                student[i].name, student[i].score, student[i].ltrgrade);
    
    return 0;
}

Note: in this case the compare() function takes a whole 2-lines of code (split into 3 for readability). Instead of the conditional (a < b) - (a > b) for sort descending, you could have used b - a, but that is prone to overflow when b is a large positive value and a a large negative value (or vice-versa). The difference of the conditional are used to return -1, 0, 1 for (-1 - a sorts before b), (0 - a and b are equal), and (1 - b sorts before a) which eliminates potential under/overflow.

Also note, qsorting any array, not matter how complicated, takes only 1-line of code:

    qsort (student, n, sizeof *student, compare_score);     /* sort by score */

Example Use/Output

Given your sample data in the file dat/studata.txt, the program operation and output is:

$ ./bin/studata dat/studata.txt
Jane           90.40  A
John           80.64  B
Jake           78.00  C

Whenever you go to write code that includes a user-interface, work to keep the interface and implementation separate (to the greatest extent possible). In theory, your interface should be a complete stand-alone program that doesn't depend on any given implementation. You should be about to operate and run your menu-system without having to worry about any other data (using dummy data where needed). Not only does this keep your code readable an maintainable, it also allows you to create your implementation logic in small testable units that do not depend on any given interface. This keeps your implementation short, readable and testable. There are always small areas of overlap, but your goal is to minimize and eliminate all but what is absolutely necessary.

There is a lot here, take your time going through it and if you have further questions, just let me know.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85