1

I'm currently writing a program that consists of outputing students' ID, Name, Course, Credit, and Score. The data is all in this text file:

"StudentRecords.txt"

12546 Amy   CS1 4 81 
13455 Bill  CS1 4 76
14328 Jim   CS1 4 64
14388 Henry CS3 3 80
15667 Peter CS3 3 45
12546 Amy   CS2 4 90 
13455 Bill  CS2 4 85
14328 Jim   CS2 4 71

12546 Amy   CS3 3 90 
13455 Bill  CS3 3 75
14328 Jim   CS3 3 69

The following table was used to calculate the GPA(just a reference):

Range Grade:
90 -- 100 > 4.0
80 -- 89 > 3.0
70 -- 79 > 2.0
60 -- 69 > 1.0
0 -- 59 > 0.0

The problem I'm having right now is my output. I'm trying to get it to match my expected output, but I can't seem to figure it out.

It probably has to do with some missing else if() statements in the second for loop. If anyone can provide me some advice/hints on how to get my input to work and display my expected output, I would appreciate it!

My Current Code:

#include <iostream>
#include <fstream>
#include <cmath>
using namespace std;

struct Student
{
    int ID = -1;
    string Name = "";
    string Course = "";
    int Credit = -1;
    int Score = -1;
};

const int SIZE = 99;

int main()
{
    ifstream inputFile;
    string fileName = "StudentRecords.txt";
    Student studArr[SIZE];

    inputFile.open(fileName.c_str(), ios::in);

    int n = 0;

        if (inputFile.is_open())
        {
            while(!inputFile.eof())
            {
                Student st;
                inputFile >> st.ID;
                inputFile >> st.Name;
                inputFile >> st.Course;
                inputFile >> st.Credit;
                inputFile >> st.Score;
                studArr[n] = st;
                n++;
            }

            inputFile.close();
        }
        else
        {
            cout << "File cannot be opened.";
            return 1;
        }

        // sorts the array by ID and Course
        for (int i = 0; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
            {
                if(studArr[i].ID > studArr[j].ID)
                {
                    Student temp = studArr[i];
                    studArr[i] = studArr[j];
                    studArr[j] = temp;
                }
                else if(studArr[i].Course > studArr[j].Course)
                {
                    Student temp = studArr[i];
                    studArr[i] = studArr[j];
                    studArr[j] = temp;
                }
            }
        }

        int check = 0;
        float dividend = 0;
        float divisor = 0;
        for(int i = 0; i < n; i++)
        {
            if(studArr[i].ID != studArr[check].ID)
            {
                cout << "======================\nGPA " 
                     << round((dividend / divisor)) << endl << endl;
            }
            else if(i == 0)
            {
                cout << studArr[i].ID << " " << studArr[i].Name 
                     << endl << endl;
                dividend = 0;
                divisor = 0;
            }

            float gradepoints;
            if(studArr[i].Score < 60)
            {
                gradepoints = 0.0;
            }
            else if(studArr[i].Score < 70)
            {
                gradepoints = 1.0;
            }
            else if(studArr[i].Score < 80)
            {
                gradepoints = 2.0;
            }
            else if(studArr[i].Score < 90)
            {
                gradepoints = 3.0;
            }
            else if(studArr[i].Score < 100)
            {
                gradepoints = 4.0;
            }

            dividend += gradepoints * studArr[i].Credit;
            divisor += studArr[i].Credit;
            cout << studArr[i].Course << " " 
                 << studArr[i].Score << " " 
                 << gradepoints << ".0" << endl << endl;
        }
        cout << "======================\nGPA " 
             << round((dividend / divisor)) << endl << endl;
        cout << endl << endl;

        return 0;
}

My Current Output:

12546 Amy

CS1 81 3.0

CS2 90 4.0

CS3 90 4.0

======================
GPA 4

CS1 76 2.0

======================
GPA 3

CS2 85 3.0

======================
GPA 3

CS3 75 2.0

======================
GPA 3

CS1 64 1.0

======================
GPA 3

CS2 71 2.0

======================
GPA 3

CS3 69 1.0

======================
GPA 2

CS3 80 3.0

======================
GPA 3

CS3 45 0.0

======================
GPA 2

Expected Output:

12546 Amy

CS1 4 81 3.0

CS2 4 90 4.0

CS3 3 90 4.0

======================

GPA 3.64

======================
13455 Bill

CS1 4 76 2.0

CS2 4 85 3.0

CS3 3 75 2.0

======================

GPA 2.36

======================
14328 Jim

CS1 4 64 1.0

CS2 4 71 2.0

CS3 3 69 1.0

======================

GPA 1.36

======================

14388 Henry

CS3 3 80 3.0

======================

GPA 3

======================
15667 Peter

CS3 3 45 0.0

======================

GPA 0
Orion98
  • 91
  • 5
  • According to the input data, your data structure is wrong (or not efficient). Each student can have one or more courses. You may want to create another data structure for courses. – Thomas Matthews Sep 19 '19 at 19:58
  • Instead of using the stand alone printf, consider adding a function to "struct Student" to perform this output, perhaps Student::cout(). While you are at it, consider a function for reading the student data file, maybe "Student::inFile (std::string pfn)" – 2785528 Sep 20 '19 at 00:10
  • Tip `else if(studArr[i].Score < 100)` -> `else if(studArr[i].Score >= 90)` (or at least use `<= 100`) – David C. Rankin Sep 20 '19 at 23:45
  • Also have a look at [Why !.eof() inside a loop condition is always wrong.](https://stackoverflow.com/q/5605125/9254539) – David C. Rankin Sep 20 '19 at 23:55
  • Also, can use you containers (like `std::vector`) or are you stuck using a standard array of struct? – David C. Rankin Sep 21 '19 at 00:33

1 Answers1

0

There are a number of areas where are are making things a bit more difficult on yourself, but the biggest issue involves your attempt at input. Your input is guaranteed to fail when you take input with:

        while(!inputFile.eof())
        {
            Student st;
            inputFile >> st.ID;
            inputFile >> st.Name;
            inputFile >> st.Course;
            inputFile >> st.Credit;
            inputFile >> st.Score;
            studArr[n] = st;
            n++;
        }

See: Why !.eof() inside a loop condition is always wrong.. Essentially, after your read of the last VALID inputFile >> st.Score;, eofbit is NOT set, so you loop again where inputFile >> st.ID; fails leaving all values in st indeterminate, you don't check the result of each input and therefore you assign studArr[n] = st; corrupting your studArr.

One way of ensuring you don't fall into that trap is to go ahead a write an overload of >> to handle input for your struct. In this case it is quite easy to do:

struct Student {
    int ID = -1;
    std::string Name = "";
    std::string Course = "";
    int Credit = -1;
    int Score = -1;
    /* overloading the >> and << to read and write your struct helps */
    friend std::istream& operator >> (std::istream& is, Student& s) {
        is >> s.ID >> s.Name >> s.Course >> s.Credit >> s.Score;
        return is;
    }
    ...
};

Now reading and validating all input becomes as simple as:

#define MAXS 99     /* if you need a constant, #define one (or more) */
...
int main (int argc, char **argv) {

    Student studArr[MAXS], tmp;
    size_t n = 0;
    std::ifstream f(argc > 1 ? argv[1] : "dat/StudentRecords.txt");

    while (n < MAXS && f >> tmp)
        studArr[n++] = tmp;
    ...

If, as recommended, you were using STL containers such as std::vector for your student storage instead of a basic array type, the validation would further simplify to while (f >> tmp) (you would also use a different method such as .push_back(tmp) instead of the direct assignment)

Following getting your input sorted out, while you can sort your array, there really is no need to. A simple method is to loop over your student data and collect the unique ID value in another array (or vector, etc.) Then you need only loop over the unique IDs as an outer-loop and loop over the elements of the student array as the inner-loop and handling each student where the ID matches the current outer-loop value. Rewriting your GPA mapping logic and a function to loop over your student array doing what was described could be done as follows:

/* simple map GPA function that returns grade points given score */
int mapgpa (int score)
{
    if (score < 60)
        return 0;
    else if (score < 70)
        return 1;
    else if (score < 80)
        return 2;
    else if (score < 90)
        return 3;
    else if (score >= 90)
        return 4;

    return 0;
}

And the logic for finding the GPA for each student:

/* a function to handle outputting the student grades
 * in your desired format.
 */
void dogrades (Student *s, size_t n)
{
    int uniqueID[MAXS] = {0};               /* array to hold unique IDs */
    size_t seen = 0;                        /* number of unique IDs seen */

    for (size_t i = 0; i < n; i++) {        /* loop collecting unique IDs */
        for (size_t j = 0; j < seen; j++)
            if (uniqueID[j] == s[i].ID)
                goto next;
        uniqueID[seen++] = s[i].ID;
        next:;
    }

    for (size_t j = 0; j < seen; j++) {     /* loop ever unique IDs */
        int heading = 0,        /* simple flag indicating name printed */
            sumcredits = 0,     /* variable to hold sum of credits */
            sumpoints = 0;      /* variable to hold sum of gradepoints */
        for (size_t i = 0; i < n; i++) {    /* loop over each stuct element */
            int gradepts = 0;               /* var to map score->gradepoits */
            if (s[i].ID == uniqueID[j]) {   /* if struct element == ID */
                if (!heading) {             /* if no heading output, do it */
                    std::cout << s[i].ID << " " << s[i].Name << "\n\n";
                    heading = 1;            /* set flag indicating done */
                }
                gradepts = mapgpa(s[i].Score);  /* get gradepts from score */
                /* output current course and gradepoints */
                std::cout << s[i].Course << " "
                        << s[i].Credit << " "
                        << s[i].Score << " "
                        << gradepts << ".0\n\n";
                sumcredits += s[i].Credit;              /* sum credits */
                sumpoints += s[i].Credit * gradepts;    /* sum gradepoints */
            }
        }
        std::cout.precision(3);     /* set precsion and output GPA */
        std::cout << "======================\n\n"
                << "GPA " << (double)sumpoints/sumcredits << "\n\n"
                << "======================\n\n";
    }
}

With your logic handled in your dogrades function, your main() reduces to:

int main (int argc, char **argv) {

    Student studArr[MAXS], tmp;
    size_t n = 0;
    std::ifstream f(argc > 1 ? argv[1] : "dat/StudentRecords.txt");

    while (n < MAXS && f >> tmp)
        studArr[n++] = tmp;

    dogrades (studArr, n);
}

Putting it altogether in an example you can compile that takes the name of the data file to read as the first argument (or reads from "dat/StudentRecords.txt" by default), you could do the following:

#include <iostream>
#include <iomanip>
#include <fstream>

#define MAXS 99     /* if you need a constant, #define one (or more) */

struct Student {
    int ID = -1;
    std::string Name = "";
    std::string Course = "";
    int Credit = -1;
    int Score = -1;
    /* overloading the >> and << to read and write your struct helps */
    friend std::istream& operator >> (std::istream& is, Student& s) {
        is >> s.ID >> s.Name >> s.Course >> s.Credit >> s.Score;
        return is;
    }
    friend std::ostream& operator << (std::ostream& os, const Student& s) {
        os << std::setw(5) << s.ID << "  "
            << std::setw(8) << std::left << s.Name << "  "
            << std::setw(5) << s.Course << "  "
                            << s.Credit << "  "
            << std::setw(3) << s.Score << '\n';
        return os;
    }
};

/* simple map GPA function that returns grade points given score */
int mapgpa (int score)
{
    if (score < 60)
        return 0;
    else if (score < 70)
        return 1;
    else if (score < 80)
        return 2;
    else if (score < 90)
        return 3;
    else if (score >= 90)
        return 4;

    return 0;
}

/* a function to handle outputting the student grades
 * in your desired format.
 */
void dogrades (Student *s, size_t n)
{
    int uniqueID[MAXS] = {0};               /* array to hold unique IDs */
    size_t seen = 0;                        /* number of unique IDs seen */

    for (size_t i = 0; i < n; i++) {        /* loop collecting unique IDs */
        for (size_t j = 0; j < seen; j++)
            if (uniqueID[j] == s[i].ID)
                goto next;
        uniqueID[seen++] = s[i].ID;
        next:;
    }

    for (size_t j = 0; j < seen; j++) {     /* loop ever unique IDs */
        int heading = 0,        /* simple flag indicating name printed */
            sumcredits = 0,     /* variable to hold sum of credits */
            sumpoints = 0;      /* variable to hold sum of gradepoints */
        for (size_t i = 0; i < n; i++) {    /* loop over each stuct element */
            int gradepts = 0;               /* var to map score->gradepoits */
            if (s[i].ID == uniqueID[j]) {   /* if struct element == ID */
                if (!heading) {             /* if no heading output, do it */
                    std::cout << s[i].ID << " " << s[i].Name << "\n\n";
                    heading = 1;            /* set flag indicating done */
                }
                gradepts = mapgpa(s[i].Score);  /* get gradepts from score */
                /* output current course and gradepoints */
                std::cout << s[i].Course << " "
                        << s[i].Credit << " "
                        << s[i].Score << " "
                        << gradepts << ".0\n\n";
                sumcredits += s[i].Credit;              /* sum credits */
                sumpoints += s[i].Credit * gradepts;    /* sum gradepoints */
            }
        }
        std::cout.precision(3);     /* set precsion and output GPA */
        std::cout << "======================\n\n"
                << "GPA " << (double)sumpoints/sumcredits << "\n\n"
                << "======================\n\n";
    }
}

int main (int argc, char **argv) {

    Student studArr[MAXS], tmp;
    size_t n = 0;
    std::ifstream f(argc > 1 ? argv[1] : "dat/StudentRecords.txt");

    while (n < MAXS && f >> tmp)
        studArr[n++] = tmp;

    dogrades (studArr, n);
}

(note: a redirect of the << operator was also added to simplify outputting the raw struct information if you should find a need)

Example Use/Output

$ ./bin/studentrecords
12546 Amy

CS1 4 81 3.0

CS2 4 90 4.0

CS3 3 90 4.0

======================

GPA 3.64

======================

13455 Bill

CS1 4 76 2.0

CS2 4 85 3.0

CS3 3 75 2.0

======================

GPA 2.36

======================

14328 Jim

CS1 4 64 1.0

CS2 4 71 2.0

CS3 3 69 1.0

======================

GPA 1.36

======================

14388 Henry

CS3 3 80 3.0

======================

GPA 3

======================

15667 Peter

CS3 3 45 0.0

======================

GPA 0

======================

Look things over and let me know if you have questions (and seriously consider using the STL containers that are available like std::vector and std::map)

David C. Rankin
  • 69,681
  • 6
  • 44
  • 72