-1

I am VERY new to C++ and attempting to read in some arrays from a text file with the following data:

Number_of_students 3
Number_of_assignments 3
Paul Smith 5 6 7
Tina Smith 6 7 7
Andrew Smith 4 5 10

What I need to do is put the names into an array and the numbers following them into a separate array. Below is my code, I am getting 0's whenever I try to output a value from one of the arrays.

#include <string>
#include <fstream>
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
    const int MAX_CLASS_SIZE = 100;
    const int MAX_NUMBER_OF_ASSIGNMENTS = 100;
    string names[MAX_CLASS_SIZE][2]
    int scores[MAX_CLASS_SIZE][MAX_NUMBER_OF_ASSIGNMENTS]
    int number_of_students
    int number_of_assignments

    ifstream file;
    file.open("grades.txt");

    if (file.is_open())
    {
        string nos;
        string noa;

        file >> nos >> number_of_students;
        file >> noa >> number_of_assignments;

        for (int i = 0; i < number_of_students; i++) {
            for (int j = 0; j < 2; j++) {
                for (int s = 0; s < number_of_assignments; s++) {
                    file >> names[i][j] >> scores[i][s];

                }
            }
        }

        file.close();
    }

    else cout << "Unable to open file";
    return 0;
}
  • Better use something along the answers here: [Why does reading a record struct fields from std::istream fail, and how can I fix it?](https://stackoverflow.com/questions/23047052/why-does-reading-a-record-struct-fields-from-stdistream-fail-and-how-can-i-fi) isntead of managing separate arrays. Also use `std::string` and `std::vector` instead of raw arrays. – πάντα ῥεῖ Oct 24 '18 at 23:00
  • `for(int i = 0; i < number_of_students; i++)` For each student. This makes sense. `for(int j = 0; j < 2; j++)` For each student, do everything twice. This does not make sense. I recommend sitting down with a pencil and some paper and mapping out exactly what you want to do and then sort out the order in which you need to do it because it looks like you have the order wrong. – user4581301 Oct 24 '18 at 23:03
  • `file>>nos>>number_of_students;` is a good trick, by the way. It's surprising how many people get stuck trying to figure out how to discard little bits of header information like that. – user4581301 Oct 24 '18 at 23:05
  • @user4581301 Thank you for the advice. I have that second for loop in there because I have seen others use a second loop for 2 dimensional arrays, is this not right? – Christopher Oct 24 '18 at 23:08
  • @Christopher Never do things just cause others do them. –  Oct 24 '18 at 23:10
  • @J.Doe Lol thank you for the advice. – Christopher Oct 24 '18 at 23:14
  • Don't blindly copy what other people do. Understand why they do it and apply that knowledge to what you do. You do not want to be what's called a [Cargo Cult Programmer](https://en.wikipedia.org/wiki/Cargo_cult_programming). Think about what `for(int j = 0; j < 2; j++)` does. It means that `for(int s = 0; s < number_of_assignments; s++)` runs twice. that means `file>>names[i][j]>>scores[i][s];` will run `number_of_assignments * 2` times, grabbing `number_of_assignments * 2` pieces of names and assignment scores, which is not correct. – user4581301 Oct 24 '18 at 23:16
  • @user4581301 Ohhhh okay! That makes more sense. Again, I am very new to C++ and really appreciate the guidance. – Christopher Oct 24 '18 at 23:18
  • Forget about C++ for now. Make sure you have the process right before you start wring code. Draw little pictures, write little to do lists, do whatever works best for your brain, but make sure you have the process right. Code comes after. If you write code without a plan, you're going to have to rewrite the code. Probably many times. – user4581301 Oct 24 '18 at 23:20
  • After your guidance I have successfully read in the first array with the names, but I am still attempting to figure out how to skip over this array when trying to read in the next array of the scores. Any suggestions? – Christopher Oct 24 '18 at 23:31
  • You have a file open. You can read two pieces of information per-iteration with `file >> some_name >> some_number;` You can validate the read succeeds by using it is a conditional in a `while` loop, e.g. `while ((file >> some_name >> some_number)) { /* add name to vector of strings; add number to vector of int */ }` your data is read and validated. – David C. Rankin Oct 24 '18 at 23:34
  • Um. Chris, streams are line-based. After the first name comes the first row of the array of scores. –  Oct 24 '18 at 23:35
  • @J.Doe I'm sure this task is trivial to you, but as someone new to this I am still stuck. This project is an assignment for a course I am in, but I am supposed to use resources outside of class to figure this out, so I would appreciate more details or a specific example. It may also help to add that this code needs to work for any file with any number of students and assignments under the max (100). – Christopher Oct 24 '18 at 23:39
  • I suspect where @J.Doe is headed this time is the trick described by [option 2 of this answer](https://stackoverflow.com/a/7868998/4581301). Read a line, stuff it into a `stringstream` read first and last name out of `stringstream` then loop through the numbers remaining in the `stringstream` until you have enough, are out of numbers, or something goes wrong. You can pick the best approach for you after you have determined what it is you need to do. Always start with the plan, even if it is a rudimentary plan. – user4581301 Oct 24 '18 at 23:51
  • @user4581301 I can only use the packages already listed in my code. Is `stringstream` from the sstream package? – Christopher Oct 24 '18 at 23:58
  • @user4581301 Hell, NO! I'd never suggest geting stuff from one stream just to suff it in another!! –  Oct 24 '18 at 23:59
  • Never say never, @J.Doe . It's slow, but the approach is highly resistant to irregular data and hard to get wrong. Yes, Christopher, `stringstream` is found in the sstream header. – user4581301 Oct 25 '18 at 00:08
  • @user4581301 - has the right "plan". After reading number of students and assignments, use `getline` to read a line at a time and make a `stringstream` from the line, find the last `[a-zA-Z]` in the stream, extract the characters (`getline` and a character buffer with the `count` parameter works fine) (don't forget to clear the `failbit` after the extraction limited by `count`) and then simply loop reading integers from the stream until `eofbit` is triggered. Eliminates rigid format of only `first last`. – David C. Rankin Oct 25 '18 at 01:26

2 Answers2

2

You are reading the 6 names and 6 scores per student.

       for (int j = 0; j < 2; j++) {
            for (int s = 0; s < number_of_assignments; s++) {
                file >> names[i][j] >> scores[i][s];

            }
        }

I think you meant:

       for (int j = 0; j < 2; j++) {
           file >> names[i][j];
       }
       for (int s = 0; s < number_of_assignments; s++) {
           file >> scores[i][s];
       }

If this fixes it. Time to get it reviewed for best practice: CodeReview

Martin York
  • 257,169
  • 86
  • 333
  • 562
1
#include <cstddef>  // std::size_t
#include <cstdlib>  // EXIT_FAILURE
#include <string>
#include <fstream>
#include <iostream>

int main()
{
    // All caps for MAX_CLASS_SIZE and MAX_NUMBER_OF_ASSIGNMENTS suggests
    // that you #defined them. Better use constant expressions than preprocessor
    // defines since they are typesafe:
    constexpr std::size_t MAX_CLASS_SIZE{ 42 };            // I don't know what would        
    constexpr std::size_t MAX_NUMBER_OF_ASSIGNMENTS{ 42 }; // be good numbers.

    std::string names[MAX_CLASS_SIZE];
    int scores[MAX_CLASS_SIZE][MAX_NUMBER_OF_ASSIGNMENTS];
    std::size_t number_of_students;     // std::size_t because it is guaranteed that
    std::size_t number_of_assignments;  // it is big enough to hold all possible sizes
                                        // of objects in memory and indexes into arrays.

    char const *filename{ "scores.txt" };  // give it a name so you can use it in
                                           // error messages
    std::ifstream file{ filename };        // use the constructor of ifstream to
                                           // open the file
    if (!file.is_open()) {  // if the file couldn't be opened exit early instead
                            // of encasing everything in one giant if-block
        std::cerr << "Couldn't open \"" << filename << "\" for reading :(\n\n";
        return EXIT_FAILURE;
    }

    std::string dummy;  // i named it dummy cause thats just what it is

    // always check if read operations didn't fail:
    if (!(file >> dummy >> number_of_students >> dummy >> number_of_assignments)) {
        std::cerr << "Couldn't read number of students and/or number of assignments from \""
                  << filename << "\" :(\n\n";
        return EXIT_FAILURE;
    }

    if (number_of_students > MAX_CLASS_SIZE ||
        number_of_assignments > MAX_NUMBER_OF_ASSIGNMENTS)
    {
        std::cerr << "I am sorry, but I was not designed to handle more than " 
                  << MAX_CLASS_SIZE << " Students or more than "
                  << MAX_NUMBER_OF_ASSIGNMENTS << " assignments :(\n\n";
        return EXIT_FAILURE;
    }

    std::size_t num_students_read{}; // keep track of how many students we read

                                                 //   / only continue if reading
                                                 //  /  didn't fail
    for (std::size_t i{}; i < number_of_students && (file >> names[i]); ++i) {

        // As long as the next thing we read is not the first score it is
        // part of the name:
        std::string name_part;
        while (!(file >> scores[i][0]) && (file.clear(), (file >> name_part))) {
            names[i] += ' ';
            names[i] += name_part;
        }

        // We read the name and the first score.
        // Now read the following number_of_assignments - 1 scores:

        std::size_t num_scores_read{ 1 };    // keep track of how many scores we actually read
        for (std::size_t s{1}; s < number_of_assignments && (file >> scores[i][s]); ++s)
            ++num_scores_read;

        if (num_scores_read != number_of_assignments) {  // and check if the number
                                                         // of scores read matches
                                                         // the number that was
                                                         // promised
            std::cerr << "There are scores missing for " << names[i] << " :(\n\n";
            return EXIT_FAILURE;
        }
        ++num_students_read;
    }

    if (num_students_read != number_of_students) {
        std::cerr << "There sould be " << number_of_students << " students but I was "
                  << "only able to read " << num_students_read << " :(\n\n";
        return EXIT_FAILURE;
    }

    // print what we read:
    for (std::size_t i{}; i < number_of_students; ++i) {
        std::cout << names[i] << ": ";
        for (std::size_t s{}; s < number_of_assignments; ++s)
            std::cout << scores[i][s] << ' ';
        std::cout.put('\n');
    }

    // no need for file.close() ... the destructor of ifstream will
    // take care of that.
}
  • That works great until you have a name like `"Tina Louise Smith"`, suggest using `find_last_of()` to find last character in line. Then read that number of characters. That avoids the first_name/last_name limitation. – David C. Rankin Oct 25 '18 at 01:21
  • @DavidC.Rankin Now *Siddig El Tahir El Fadil El Siddig Abderrahman Mohammed Ahmed Abdel Karim El Mahdi* can take part in the class as well. Btw: Tina Louise is no longer in that class cause she is serving a life sentence for killing her parents for giving her *that* 2nd name. –  Oct 25 '18 at 01:51
  • Chuckling... I guess she does have a lot of time for quite reflection there... Good job on the multiple validations of assignments and number of students. – David C. Rankin Oct 25 '18 at 02:12