8
\$\begingroup\$

I had to create a program that had a struct that held data inside of it. I had to read an input file and take the contents and read it into an array of structs. Then, I had to solve for some particular statistical data such as finding the lowest and highest scores from the data. I completed my source code and it runs and gives me the correct output, but I would like second opinions. Please note that the comments are for my own personal benefit. Even though the source code easily allows readers to understand what is going on, I have included them to help myself stay organized.

The instructions for the assignment are down below:

Using functional decomposition, write a program to keep records and perform statistical analysis for a class of 9 students. There are 3 exams, each worth 100 points, during the semester. Each student is identified by a 4-digit student ID with a first name and a last name and followed by three exam scores.

You will read the input data from an input file scores.txt (will be posted in Etudes); and data is in the format (studentID, first name, last name, exam1, exam2 and exam3).

Each line of data for one student will be read from file and then assigned to a struct variable. Consequently, you will need an array of structs to store all the data read from the input file. This will be a 1-dimensional array.

Once you read data from the file to your array, you are required to calculate and display the following statistics for each exam. The statistics to be shown will be the:

  • lowest score.

  • highest score.

  • average score rounded to 2 decimal place.

  • standard deviation rounded to 2 decimal place.

It will be better to have an array for each statistic of size 3, that is an array for lowest values, and so on.

The ‘xx’ values above needs to be calculated using data provided. You may assume the scores from the file had been validated and they are all valid.

Also display the count of students as indicated above on the left side of each row.

The grade will be based on the total of all the 3 exams which will be out of 300, and grade will be as follows:

Total Score Grade
270 or more A
240 to 269 B
210 to 239 C
180 to 209 D
179 or less F

You should also have a function to sort the result in ascending order of last name.

Input File:

1234 David Dalton 82 86 80
9138 Shirley Gross 90 98 94
3124 Cynthia Morley 87 84 82
4532 Albert Roberts 56 89 78
5678 Amelia Pauls 90 87 65
6134 Samson Smith 29 65 33
7874 Michael Garett 91 92 92
8026 Melissa Downey 74 75 89
9893 Gabe Yu 69 66 68

Expected output (or something similar to it):

enter image description here

My output:

enter image description here

#include "stdafx.h"
#include <iostream> 
#include <string> 
#include <fstream>
#include <iomanip>
#include <cmath> 

using namespace std; 

struct StudentData
{
    int studentID; 
    string first_name; 
    string last_name; 
    int exam1; 
    int exam2; 
    int exam3; 
    int total; 
    char ch; 
}; 

const int SIZE = 9; 
const int INFO = 4; 

// Function prototypes
void openInputFile(ifstream &, string); 
void getTotal(StudentData[]); 
void getGrade(StudentData[]); 
void calcLowest(StudentData[], int &, int &, int &, int &, int[]);  
void calcHighest(StudentData[], int &, int &, int &, int &, int[]);  
void getAverage(StudentData[], int, double &, double &, double &, double &, double[]); 
void getStd(StudentData[], double &, double &, double &, double &, double &, double &, double &, double &, double[]); 
void print(StudentData[], int[], int[], double[], double[]); 
void sort(StudentData[]); 

int main()
{
    // Variables 
    StudentData arr[SIZE]; 
    int lowest1, lowest2, lowest3, lowest4; // Stores lowest exam scores
    int highest1, highest2, highest3, highest4; // Holds highest exam scores
    double average1 = 0, average2 = 0, average3 = 0, average4 = 0; // Represents average of each exam 
    double std1 = 0, std2 = 0, std3 = 0, std4 = 0; // Holds standard deviation for Exams 1-3 and Total 
    int lowest[INFO] = {};
    int highest[INFO] = {}; 
    double average[INFO] = {}; 
    double standardDeviation[INFO] = {}; 

    ifstream inFile; 
    string inFileName = "C:\\Users\\Lisa\\Desktop\\scores.txt"; 

    // Call function to read data in file
    openInputFile(inFile, inFileName);

    // Read data into an array of structs 
    for(int count = 0; count < SIZE; count++)
    {
        inFile >> arr[count].studentID >> arr[count].first_name >> arr[count].last_name >> arr[count].exam1 >> arr[count].exam2 >> arr[count].exam3; 
    }

    // Close input file
    inFile.close();  

    // Get score total for each student 
    getTotal(arr); 

    // Determine grade for each student
    getGrade(arr); 

    // Calculate lowest scores in each exam and total scores
    calcLowest(arr, lowest1, lowest2, lowest3, lowest4, lowest); 

    // Calculate highest scores in each exam and total scores  
    calcHighest(arr, highest1, highest2, highest3, highest4, highest); 

    // Calculate average of each exam and the average of the total scores
    getAverage(arr, SIZE, average1, average2, average3, average4, average); 

    // Calculate standard deviation of each category 
    getStd(arr, std1, std2, std3, std4, average1, average2, average3, average4, standardDeviation); 

    cout << "\n"; 

    // Print unsorted data
    print(arr, lowest, highest, average, standardDeviation); 

    cout << "\n"; 

    // Sort data 
    sort(arr); 

    // Print sorted data
    print(arr, lowest, highest, average, standardDeviation); 

    system("PAUSE"); 

    return 0; 
}

/**
* Pre-condition: 
* Post-condition: 
*/
void openInputFile(ifstream &inFile, string inFileName)
{
    //Open the file
    inFile.open(inFileName);

    //Input validation
    if (!inFile)
    {
        cout << "Error to open file." << endl;
        cout << endl;
        return;
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void getTotal(StudentData arr[])
{
    for(int i = 0; i < SIZE; i++)
    {
        arr[i].total = arr[i].exam1 + arr[i].exam2 + arr[i].exam3; 
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void getGrade(StudentData arr[])
{
    for(int i = 0; i < SIZE; i++)
    {
        if(arr[i].total >= 270)
            arr[i].ch = 'A'; 
        else if(arr[i].total >= 240)
            arr[i].ch = 'B'; 
        else if(arr[i].total >= 210)
            arr[i].ch = 'C'; 
        else if(arr[i].total >= 180)
            arr[i].ch = 'D'; 
        else 
            arr[i].ch = 'F'; 
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void calcLowest(StudentData arr[], int &lowest1, int &lowest2, int &lowest3, int &lowest4, int lowest[])
{
    int smallest = 0; 

    lowest1 = arr[0].exam1; 
    lowest2 = arr[0].exam2; 
    lowest3 = arr[0].exam3; 
    lowest4 = arr[0].total; 

    // Loop to determine lowest score from Exam1, 2, 3, and Total
    for (int i = 0; i < SIZE; i++)
    {
        if (lowest1 > arr[i].exam1)
        {
            lowest1 = arr[i].exam1; 
            smallest = i; 
        }

        if (lowest2 > arr[i].exam2)
        {
            lowest2 = arr[i].exam2; 
            smallest = i; 
        }

        if (lowest3 > arr[i].exam3)
        {
            lowest3 = arr[i].exam3; 
            smallest = i; 
        }

        if (lowest4 > arr[i].total)
        {
            lowest4 = arr[i].total; 
            smallest = i; 
        }
    }

    // Loop lowest values into an array of size 4 
    for(int index = 0; index < INFO; index++)
    {
        if(index == 0)
            lowest[0] = lowest1; 
        else if(index == 1)
            lowest[1] = lowest2; 
        else if(index == 2)
            lowest[2] = lowest3; 
        else if(index == 3)
            lowest[3] = lowest4; 
        else 
            cout << "Fail!" << endl; 
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void calcHighest(StudentData arr[], int &highest1, int &highest2, int &highest3, int &highest4, int highest[])
{
    int biggest = 0; 

    highest1 = arr[0].exam1; 
    highest2 = arr[0].exam2; 
    highest3 = arr[0].exam3; 
    highest4 = arr[0].total; 

    // Loop to determine highest score from Exam1, 2, 3, and Total 
    for (int i = 0; i < SIZE; i++)
    {
        if (highest1 < arr[i].exam1)
        {
            highest1 = arr[i].exam1; 
            biggest = i; 
        }

        if (highest2 < arr[i].exam2)
        {
            highest2 = arr[i].exam2; 
            biggest = i; 
        }

        if (highest3 < arr[i].exam3)
        {
            highest3 = arr[i].exam3; 
            biggest = i; 
        }

        if (highest4 < arr[i].total)
        {
            highest4 = arr[i].total; 
            biggest = i; 
        }
    }

    // Loop highest values into an array of size 4 
    for(int index = 0; index < INFO; index++)
    {
        if(index == 0)
            highest[0] = highest1; 
        else if(index == 1)
            highest[1] = highest2; 
        else if(index == 2)
            highest[2] = highest3; 
        else if(index == 3)
            highest[3] = highest4; 
        else 
            cout << "Fail!" << endl; 
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void getAverage(StudentData arr[], int size, double &average1, double &average2, double &average3, double &average4, double average[])
{
    int sum1 = 0, sum2 = 0, sum3 = 0, sum4 = 0; 

    // Get sum of each category (Exam1, 2, 3, and Total)
    for(int i = 0; i < SIZE; i++)
    {
        sum1 += arr[i].exam1;
        sum2 += arr[i].exam2; 
        sum3 += arr[i].exam3;
        sum4 += arr[i].total; 
    }

    // Calculate average for each category 
    average1 += static_cast<double>(sum1)/size;  

    average2 += static_cast<double>(sum2)/size; 

    average3 += static_cast<double>(sum3)/size; 

    average4 += static_cast<double>(sum4)/size; 

    // Loop average values into an array of size 4 
    for(int index = 0; index < INFO; index++)
    {
        if(index == 0)
            average[0] = average1; 
        else if(index == 1)
            average[1] = average2; 
        else if(index == 2)
            average[2] = average3; 
        else if(index == 3)
            average[3] = average4; 
        else 
            cout << "Fail!" << endl; 
    }
}

/**
* Pre-condition: 
* Post-condition: 
*/
void getStd(StudentData arr[], double &std1, double &std2, double &std3, double &std4, double &average1, double &average2, double &average3, double &average4, double standardDeviation[])
{
    double deviationSum1 = 0, deviationSum2 = 0, deviationSum3 = 0, deviationSum4 = 0; 

    for(int i = 0; i < SIZE; i++)
    {
        deviationSum1 += pow((arr[i].exam1 - average1), 2); 
        deviationSum2 += pow((arr[i].exam2 - average2), 2); 
        deviationSum3 += pow((arr[i].exam3 - average3), 2); 
        deviationSum4 += pow((arr[i].total - average4), 2);
    }

    std1 = sqrt(deviationSum1 / ((SIZE) - 1)); 
    std2 = sqrt(deviationSum2 / ((SIZE) - 1)); 
    std3 = sqrt(deviationSum3 / ((SIZE) - 1)); 
    std4 = sqrt(deviationSum4 / ((SIZE) - 1)); 

    // Loop average values into an array of size
    for(int index = 0; index < INFO; index++)
    {
        if(index == 0)
            standardDeviation[0] = std1; 
        else if(index == 1)
            standardDeviation[1] = std2; 
        else if(index == 2)
            standardDeviation[2] = std3; 
        else if(index == 3)
            standardDeviation[3] = std4; 
        else 
            cout << "Fail!" << endl; 
    }
}



    cout << "\n"; 
}

/**
* Pre-condition: 
* Post-condition: 
*/
void sort(StudentData arr[])
{
    StudentData temp; 

    for (int i = 0; i < (SIZE - 1); i++)
    {
        for (int j = i + 1; j < SIZE; j++)
        {
            if (arr[i].last_name > arr[j].last_name)
            {
                temp = arr[i];    
                arr[i] = arr[j];
                arr[j] = temp;
            }
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ There seems to be missing the print(StudentData[], int[], int[], double[], double[]) function. \$\endgroup\$ Commented Jan 20, 2017 at 13:15

1 Answer 1

9
\$\begingroup\$

I have found a number of things that could help you improve your code.

Don't abuse using namespace std

Putting using namespace std at the top of every program is a bad habit that you'd do well to avoid. Know when to use it and when not to (as when writing include headers).

Avoid the use of global variables

I see that SIZE and INFO are declared as global variables rather than as local variables. It's generally better to explicitly pass variables your function will need rather than using the vague implicit linkage of a global variable. For example, the calcLowest gets the StudentData array as a passed parameter -- the size of that array should also be passed.

Use more meaningful variable names

The variable names SIZE, and INFO are not as descriptive as they could be. Size of what? What info?

Don't use system("PAUSE")

There are two reasons not to use system("cls") or system("PAUSE"). The first is that it is not portable to other operating systems which you may or may not care about now. The second is that it's a security hole, which you absolutely must care about. Specifically, if some program is defined and named PAUSE or pause, your program will execute that program instead of what you intend, and that other program could be anything. First, isolate these into a seperate functions pause() and then modify your code to call those functions instead of system. Then rewrite the contents of those functions to do what you want using C++. For example:

void pause() {
    getchar();
}

General portability

This code could be made portable if, in addition to the changes in the previous point, you omit the Windows-only include files #include "stdafx.h".

Prefer arrays to serially numbered variables

The code currently has a great number of variables that are serially numbered such as std1, std2, etc. This is a very clear indication that you would be better off using an array. The code can then become more compact, more efficient and easier to maintain. For example, consider how much code you would have to change if there were 4 exams instead of 3.

Consider efficiency

Instead of making one pass through the data to find the lowest value and then another to find the highest, why not get them both at the same time with a single pass through the data? In fact, all of your statistics can be calculated with a single pass through the data.

Use const where practical

Most of the routines in this code take references to data which is not altered. For one obvious example, the print routine takes five different arrays as arguments but alters none of them. You can make this clear by using const in the declaration of the parameters.

Use better routine names

The function getTotal doesn't appear to actually get anything. What it actually does is to calculate the student's total score. A better name might be calculatTotal. Also, it would make sense to have it calculate the total for just one student record. I'd write it like this:

int calculateTotal(const StudentData& student)
    return student.exam1 + student.exam2 + student.exam3; 
}

Return something useful from the subroutines

Every single one of the routines is declared as returning void. Something is wrong there. For example, the calculateTotal routine mentioned above returns the total. You would then use it in a loop outside the routine:

for(int i = 0; i < NUM_STUDENTS; ++i) {
    students[i].total = calculateTotal(students[i]);
}

The advantage is that you can now make a single loop and update both grades and totals simultaneously.

Always pass a size when passing a pointer to an array

As mentioned earlier, when passing the StudentData array, you should pass the array's size as well. Generally you should get into the habit of doing that for every array that is passed.

Don't hardcode file names

Generally, it's not a good idea to hardcode a file name in software, and generally espeically bad if it's an absolute file name (as contrasted with one with a relative path). Instead, it would be better to allow the user of the program to specify the name, as with a command line parameter.

Be more flexible with data

The program currently requires that there are exactly nine student records in the input file. This is not particularly flexible. Instead, it would be nice for the user if one could have any number of students. Strive to provide that flexibility in your software. For example, your program could read in student records until it encounters either the end of the input file or an error in the input data.

Consider adding error checking

Data input usually requires a lot of careful consideration for most software. Checking for malformed input and handling it gracefully often takes a considerable amount of thought, time and code. You might consider isolating the input function to a separate routine and providing error messages that might help the user pinpoint and correct any problem. At the moment, for example, if the input file is not found, the program will attempt to proceed anyway. Surely that can't be right!

Consider using standard routines

C++ provides a great number of useful utilites such as std::sort that you could use instead of your own hand-written sort function. Even if the current assignment does not allow their use, it's extremely useful to know about them and to use them preferentially to writing your own. They're almost always faster and less buggy and by using library software, you can develop your own code more quickly and accurately.

Omit return 0

When a C++ program reaches the end of main the compiler will automatically generate code to return 0, so there is no reason to put return 0; explicitly at the end of main.

Eliminate unused variables

Unused variables are a sign of poor code quality, so eliminating them should be a priority. In this code, calcLowest sets smallest and calcHighest sets biggest but neither of those variables are ever actually used. My compiler also tells me that. Your compiler is probably also smart enough to tell you that, if you ask it to do so.

Use object orientation

Because you're writing in C++, it would make sense to have methods that operate on a class such as StudentData be member functions rather than separate functions. You may not yet have learned about objects or classes, but they're one of the main strengths of C++ and something you should learn soon if you haven't already. Use objects where they make sense.

\$\endgroup\$
3
  • \$\begingroup\$ I'm personally against omitting a return from main. It's a syntactic abnormality. \$\endgroup\$
    – Reinderien
    Commented May 26, 2018 at 9:49
  • \$\begingroup\$ Also, stdafx isn't Windows-specific, it's MSVC specific. In other words, it wouldn't compile on Bloodshed. \$\endgroup\$
    – Reinderien
    Commented May 26, 2018 at 9:50
  • \$\begingroup\$ Bloodshed? Who's using a 13 year old compiler? GCC is probably a better example for the OP of another compiler they might like to target. \$\endgroup\$
    – the6p4c
    Commented Dec 29, 2018 at 11:41

Not the answer you're looking for? Browse other questions tagged or ask your own question.