reading from text file to struct vector, but text file lines are of different length
Asked Answered
D

4

1

I am trying to read from a text file of students and assign each line to a struct data member.

The text file structure is as follows:

Name,Student code, Ability,Consistency,Program name:Subject list

Two students in the file:

Average Ant,204932,50,5,Short course:1
Brilliant Bison,234543,80,3,Bachelor of Bounciness:2,5,3

I can read all of the information to a struct no problem except for the last part(subject list),that are of varying length between students. How can I write code so that if a student only has 1 subject like average ant I can push it into the subjectList vector but if they have 3 like brilliant bison I can still push them all in no problem?

My code:

struct Student
{
    string name;
    int code;
    int ability;
    int consistency;
    string programName;
    vector<int> subjectList;
};

void createStudents(string fileName)
{
    string tempSubjectId;
    int subjectId;

    //temp variable to then use to convert them to int.
    string codeTemp, abilityTemp, consistencyTemp;

    std::ifstream studentFile(fileName);

    //A new student data member is created
    Student newStudent;


    if(studentFile.is_open() && studentFile.good())
    {
        cout << " " << endl;
        cout << "---Reading from Students---" << endl;
        while(getline(studentFile,newStudent.name, ','))
        {

            //we go get each value which is delimited by a comma and one by a colon
            //getline(studentFile, newStudent.name, ',');

            //To convert the strings to an int, the string is given to a temporary variable
            //Then the temporary variable is parsed to an int using stoi and the code datamember from the struct is assign to that new int
            getline(studentFile, codeTemp, ',');
            newStudent.code = stoi(codeTemp);

            getline(studentFile, abilityTemp, ',');
            newStudent.ability = stoi(abilityTemp);

            getline(studentFile, consistencyTemp, ',');
            newStudent.consistency = stoi(consistencyTemp);

            getline(studentFile, newStudent.programName, ':');

//want to push ints into subject list here.


            //The new struct data member is added to the vector and returned for further use.
            studentList.push_back(newStudent);
        }
        //file is then closed
        studentFile.close();```
Duramen answered 21/8, 2019 at 7:26 Comment(1)
Offtopic: you are doing double work, if the file couldn't be opened, then good will fail as well. Actually, there's a conversion operator to bool, returning same value as good, so you can simply check: if(studentFile)Whirlybird
Z
3

In your main loop, read an entire line into a std::string, then use a std::istringstream to parse each line, using an inner loop to read the subject ints, eg :

#include <string>
#include <sstream>
#include <fstream>
#include <vector>

struct Student
{
    std::string name;
    int code;
    int ability;
    int consistency;
    std::string programName;
    std::vector<int> subjectList;
}; 

std::vector<Student> studentList;

void createStudents(std::string fileName)
{
    std::string tempLine;

    //temp variable to then use to convert them to int.
    std::string tempStr;

    std::ifstream studentFile(fileName);

    if (studentFile.is_open())
    {
        std::cout << " " << std::endl;
        std::cout << "---Reading from Students---" << std::endl;

        while (std::getline(studentFile, tempLine))
        {
            std::istringstream iss(tempLine);

            //A new student data member is created
            Student newStudent;

            //we go get each value which is delimited by a comma and one by a colon

            std::getline(iss, newStudent.name, ',');

            //To convert the strings to an int, the string is given to a temporary variable
            //Then the temporary variable is parsed to an int using stoi and the code datamember from the struct is assign to that new int
            std::getline(iss, tempStr, ',');
            newStudent.code = std::stoi(tempStr);

            std::getline(iss, tempStr, ',');
            newStudent.ability = std::stoi(tempStr);

            std::getline(iss, tempStr, ',');
            newStudent.consistency = std::stoi(tempStr);

            std::getline(iss, newStudent.programName, ':');

            // push ints into subject list
            while (std::getline(iss, tempStr, ',')) {
                newStudent.subjectList.push_back(std::stoi(tempStr));
            } 

            //The new struct data member is added to the vector and returned for further use.
            studentList.push_back(std::move(newStudent));
        }

        //file is then closed
        studentFile.close();
    }
}
Zonation answered 21/8, 2019 at 7:55 Comment(2)
hey mate, this seems to almost work perfectly but for some reason it keep collecting all of the subjectlists for each student into that tempStr in the new while loop. The value of temp str is: 1253, which is both average andy and brilliant bisons subject lists combined.Duramen
@Duramen std::getline() always clears the std::string it reads into. But, your code is reusing the same Student variable in the outer loop and not clearing its subjectList on each iteration. So in that regard, the newStudent.subjectList would accumulate subjects for multiple students. On each iteration of the outer loop, you need to either 1) call subjectList.clear(), or 2) use a new Student object. I have updated my answer to show the latter case.Zonation
H
2

I am big proponent of Arash Partow's String Toolkit Library. It can actually handle reading and parsing text almost magically. You could setup a lamba that parses line by line and a great deal of other stuff based on your structure. Here is a crude example.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>  // String Toolkit Library


struct Student_t
{
    string name;
    int code;
    int ability;
    int consistency;
    string programName;
    vector<int> subjectList;
};


const char *whitespace  = " \t\r\n\f";

// in case you wanted to parse on stuff with comma or whitespace
// it is just a matter of choosing what you want to parse
// by
/// const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

const char *comma_and_colon = ",:";

int main()
{
   std::fstream input("file.txt", std::ios::in );
   std::string line;
   std::vector<Student_t> students;
   while(getline(input_strm, line) ) 
   {

       // clean up the string (precaution)
       strtk::remove_leading_and_trailing(whitespace, line);

       Student_t s;  // declare an instance

       // The parse function will fill in & convert the values into the variables
       // Since the last element is a vector, all the remaining integers will  
       // be converted.

       // the example does assume that a colon is used only once
       if(!strtk::parse( line, comma_and_colon,  s.name, s.code, 
                   s.ability, s.consistency, s.programName, s.subjectList ) ) {
           continue;
           // you may want to handle the parse error differently
        }

        students.push_back(s);

   }

}

I've answered a number of questions using the String Toolkit library and others have too. The library is C++, header only and fast. The Library have tons of examples, good documentation.

Hydrograph answered 2/9, 2019 at 15:36 Comment(0)
A
0

You need another loop, and an string stream to read from, like this

#include <sstream>

string subjectList;
getline(studentFile, subjectList); // read all the subjects
istringstream iss(subjectList);     // put into a string stream for parsing
string subject;
while (getline(iss, subject, ','))  // read from string stream, spliting on commas
{
    newStudent.subjectList.push_back(stoi(subject)); // add to subject list
}

Untested code.

Abbottson answered 21/8, 2019 at 7:44 Comment(0)
K
0

Here is modified code you can try, [not tested]

struct Student
{
    string name;
    int code;
    int ability;
    int consistency;
    string programName;
    vector<int> subjectList;
};

void createStudents(string fileName)

{
    string tempSubjectId;
    int subjectId;
//temp variable to then use to convert them to int.
string codeTemp, abilityTemp, consistencyTemp;

std::ifstream studentFile(fileName);

//A new student data member is created
Student newStudent;


if(studentFile.is_open() && studentFile.good())
{
    cout << " " << endl;
    cout << "---Reading from Students---" << endl;
    while(getline(studentFile,newStudent.name, ','))
    {

        //we go get each value which is delimited by a comma and one by a colon
        //getline(studentFile, newStudent.name, ',');

        //To convert the strings to an int, the string is given to a temporary variable
        //Then the temporary variable is parsed to an int using stoi and the code datamember from the struct is assign to that new int
        getline(studentFile, codeTemp, ',');
        newStudent.code = stoi(codeTemp);

        getline(studentFile, abilityTemp, ',');
        newStudent.ability = stoi(abilityTemp);

        getline(studentFile, consistencyTemp, ',');
        newStudent.consistency = stoi(consistencyTemp);

        getline(studentFile, newStudent.programName, ':')
        std::string line;
        std::vector <int> arr;
        getline(studentFile, line);
        boost::split(arr, line, [](char c) {return (c==',');})

        newStudent.subjectList = arr;


    }
    //file is then closed
    studentFile.close();```
Kristeenkristel answered 21/8, 2019 at 8:23 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.