1

I'm trying to load in input from a file, representing students. Each student has a name and list of scores, like so:

Name John Wayne
Scores 100 90 80 100 0

Name Bob Dwight
Scores 0 0 10 100

Name Dummy Student
Scores 0 0

I am reading this through a class structure like so. A classroom object keeps a list of all students.

class Student
{
    string first_name;
    string last_name;
    vector<int> quizzes;

public:
    void read(istream & in){
        string line;

        // Name initialization
        getline(in, line);
        stringstream namereader(line);

        string word;
        namereader >> word; // Go past "Name"
        namereader >> word;
        first_name = word; // Second word in a line is firstname
        namereader >> word;
        last_name = word; // Third word in a line is lastname
        
        // Quizzes
        getline(in, line); // line after name contains quiz scores
        stringstream quizreader(line);
        quizreader >> word; // Go past "Quiz"
        int quizgrade;
        while(quizreader >> quizgrade){ // Read quiz scores and insert
            quizzes.insert(quizzes.end(), quizgrade);
        }

        // Putting quizreader.str(""), quizreader.clear() does not work

        //Empty Line between each student
        getline(in, line);
    }
    
    void print(ostream & out) const{
        out << "Name    " << first_name << " " << last_name << endl;
        out << "QZ Ave: " <<  endl;

        for (int i : quizzes){
            cout << i << " ";
        }
        cout << endl;
    }

    friend ostream & operator <<(ostream &out, const Student & student){
        student.print(out);
        return out;
    }

    friend istream & operator >>(istream &in, Student & student){
        student.read(in);
        return in;
    }
};

Classroom:

class Classroom{
    vector<Student> students;
public:
    void print(ostream & out) const{
        for (Student s : students){
            out << s << endl;
        }
    }

    void read(istream & in){
        Student s;
        while (in >> s){
            students.insert(students.end(), s);
        }
    }

    friend ostream & operator <<(ostream &out, const Classroom & cl){
        cl.print(out);
        return out;
    }

    friend istream & operator >>(istream &in, Classroom & cl){
        cl.read(in);
        return in;
    }
};

Main function:

int main(){
    ifstream in("classroom.txt");
    Classroom cl;
    in >> cl;
    cout << cl;
    return 0;
}

When I try to print this out, I noticed that my stringstream does not reset across the calls done by the for loop in Classroom::read.

This means the output of the main function is:

John Wayne
Scores 100 90 80 100 0

Bob Dwight
Scores 100 90 80 100 0 0 0 10 100

Dummy Student
Scores 100 90 80 100 0 0 0 10 100 0 0

I have tried clear all of my stringstreams with .clear(), and .str(""). They don't work, regardless of where I place them in read(), and in which order. What is curious to me is that quizreader retains all of the previous numbers, but namereader keeps going on and takes the next name as expected. Is this a problem with opening multiple stringstreams? Improper initialization?

I am assuming all inputs are of the correct type, those problems I will be fixing later. I have read almost every post I could find on this site related to this topic, but I cannot seem to find a working solution.

Kevin Li
  • 21
  • 3
  • I think you are complicating things. `istream` can extract tokens using `>>` operator, even newlines can be taken care of using the same. – kiner_shah Mar 09 '23 at 06:32
  • 3
    You're reading into the same `Student` over and over without resetting its content between reads. This has nothing to do with string streams. What happens when you plop `quizzes.clear();` before entering the quiz-reader loop? – WhozCraig Mar 09 '23 at 06:37
  • @WhozCraig I guessed that at first, but when I called students.size() it returns the expected number of students, so I assumed that wasn't the problem. How should I rebuild the loop to properly construct a new student? – Kevin Li Mar 09 '23 at 06:41
  • @KevinLi A quick fix is to call `quizzes.clear();` before reading quiz scores. That ensures the vector is empty before reading new values. – Blastfurnace Mar 09 '23 at 06:43

2 Answers2

1

In your Classroom::read member function you construct a Student object that is reused to read each student record:

void read(istream & in){
    Student s;
    while (in >> s){
        students.insert(students.end(), s);
    }
}

The problem is that in the Student::read function you reuse the vector<int> quizzes; member for each student but you never .clear() or empty it between students. You keep appending quiz scores. That explains your output.

A quick fix, with minimal changes to what you've written, is to call quizzes.clear(); before reading quiz scores. That ensures the vector is empty before reading new values.

Note: When adding a value to the end of a std::vector, instead of doing students.insert(students.end(), s); you can just say students.push_back(s);.

Blastfurnace
  • 18,411
  • 56
  • 55
  • 70
  • How should I fix this? Wouldn't clear() remove all the data that I need to calculate things? – Kevin Li Mar 09 '23 at 06:45
  • @KevinLi When you add the `Student` to the vector you are adding a **copy** to the container. That data is fine. You just need to "reset" the temporary `Student` object you reuse in the input loop. – Blastfurnace Mar 09 '23 at 06:46
0

You are unnecessarily complicating things by using std::stringstream, std::istream and std::getline. You can simply use std::istream without using anything else. Only you have to do additional state management.

As for retained values in quizzes, you can change your reading logic a bit in Classroom::read().

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

// Name ... Scores ... Name ... Scores ...
class Student
{
    string first_name;
    string last_name;
    vector<int> quizzes;
public:
    friend istream & operator >>(istream &in, Student & student){
        student.read(in);
        return in;
    }

    void read(istream& in)
    {
        string token;
        int state = 0;  // 0 - name, 1 - scores
        int name_cnt = 0;
        
        while (in >> token)
        {
            if (token.empty())
                continue;
                
            
            if (state == 1 && token == "Name")
            {
                break;
            }
            
            // cout << token << '\n';
            if (token == "Name")
            {
                state = 0;
                continue;
            }
            else if (token == "Scores")
            {
                name_cnt = 0;
                state = 1;
                continue;
            }
            
            if (state == 0)
            {
                if (name_cnt == 0)
                    first_name = token;
                else
                    last_name = token;
                name_cnt++;
            }
            else if (state == 1)
            {
                quizzes.push_back(std::stoi(token));
            }
        }
        cout << first_name << ' ' << last_name << '\n';
        for (auto x : quizzes) cout << x << ' '; cout << '\n';
    }
};


class Classroom
{
public:
    void read(istream & in){
        while (true)
        {
            Student s;
            if (in >> s)
                students.push_back(s);
            else
                break;
        }
    }
private:
    friend istream & operator >>(istream &in, Classroom & cl){
        cl.read(in);
        return in;
    }
    
    vector<Student> students;
};
int main()
{
    ifstream in("sample.txt");
    if (!in.is_open())
    {
        return 1;
    }
    Classroom C;
    C.read(in);
    return 0;
}
kiner_shah
  • 3,939
  • 7
  • 23
  • 37