-1

I'm fairly new to the C++ syntax and wondered if someone could provide how they would approach a problem I have.

My task is to read a files txt contents which contains a combination of strings and integers. I then need to store all the integers into one vector and all strings into another vector. I have managed to store all the contents into a vector, but now I want to separate the different data types into their own vectors, however I'm struggling to find the best approach for this. Would I iterate through the whole vector and then use if conditions for the data types, or is there another way? I have posted an example of my read file code and file to give you a clearer understanding of what I mean.

Thanks,

// Basic read file code

    fstream file("filepath.txt", ios::in); // reads file
    string line;  // temp storage of lines in file
    vector<string> lines; // creates for permanent storage  
    while (getline(file, line))
    {
        lines.push_back(line);
    };  // pushes each line to back of vector until file end.

file example - each string is a question with the line below being the answer as an int. 88 lines in total.

1, string"
2, int
3,"string"
4, int
5,"string"
6, int
  • If the string on 1 line is tied to the integer on the next you may want to store the 2 in a single struct and have a vector of that struct. – drescherjm Dec 11 '21 at 20:09
  • You might reconsider putting the strings and ints into separate vectors. If the strings and ints are related to each other as is usually the case, it's best to put them in a struct or `std::pair` It's just as easy to work with them and you don't risk losing the relationship. Like you could sort on the string and the related int would tag along. – doug Dec 11 '21 at 20:12

3 Answers3

2

You should create two vectors and push_back data alternately, hope this help :)

  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 11 '21 at 23:57
2

You are asking here how you should approach the given problem.

In normal software development we do several steps. First, we analyze the requirements, then think of a design and after that, we start coding. And finally, we verify and qualify the program. In reality there are more processes. But please take one important recommendation:

Before writing code. We should first clarify the “What” and then the “how”.

Last, but not least, when doing the coding, we should follow standard patterns to avoid the most common problems.

So, now let us look at your problem. You want to read a text file. The text file contains lines with comma separated values. The lines, with the comma separated values shall be split.

Then, there are always 2 lines that belong together. The first line contains index as a integer number and the question as a string, the second line consists of also an integer index and then an integer number denoting the answer.

All the data shall be read and stored for further processing.

At this point in time, we have done the requirement analysis in general.

Next is the “How”, the design, the “how we want to do things”

You were mentioning that you want to use 2 different vectors to store the questions and the answers. That approach is basically not that good.

Because the general rule is that you should store values that belong somehow together, even having different types, like int and string, in a “struct” or “class”. The same is valid for data in the first and then in the next line.

On the other hand, many data having the same type, should store in a container, like a std::array or std::vector (or others, depending of the use case).

And in your case, you would have a combination of both. So, first the data with different types in a struct and then a std::vector of these structs.

Example for the above (one of many possible solutions):

#include <iostream>
#include <vector>

struct Question {
    int index{};
    std::string text{};
};
struct Answer {
    int index{};
    int data{};
};

struct QuestionAndAnswer {
    Question question{};
    Answer answer{};
};

std::vector<QuestionAndAnswer> questionAndAnswer{};

OK, understood next.

We want to open a file and read it line by line. Opening a file for reading, can be done by defining a std::ifstream and then handing a filename to its constructor. This will open the file for you. And, at the end, when the variable of type std::ifstream falls out of scope, then destructor of the std::ifstream will automatically close the file for you.

You should always check the CPP Reference for any kind of questions to C++ functionalities.

As a general rule, you should always check the result of any IO-operation. This can be done with if (ifstreamVaraible). If you look in the definition of the IO-Stream functions than you can see that many of them return again a reference to the IO-Stream, with which that have been called. Example:

// Open the file
std::ifstream sourceFileStream(“test.txt”);

// Check, if it could be opened successfully
if (sourceFileStream) {

    // Read the line and get the result
    std::string line{};
    if (std::getline(sourceFileStream, line)) {
        . . .
}

How does this work? If you look in the documentation of the stream functions, then you will see that their bool and not operator ! are overwritten and return the status of a stream. For the above example if (sourceFileStream) { the compiler sees a stream variable in an if-statement, were it expects a boolean expression. It will then take the bool function of the stream and evaluates it.

Same is valid for if (std::getline(sourceFileStream, line)). This will first do the getline - operation, which will read the line. And then getline returns a reference to the stream. Then the if- statement contains quasi again if (sourceFileStream) and the bool operator will be called.

With this mechanism, you can (and should) check the result of all IO-operations.

If we want to read many lines in a loop then the rule is, to put the std::getline(sourceFileStream, line) statement into the while statements condition part. Otherwise you will always read a line to much.

You will see from not so experienced developers often something like ‘while (!sourceFileStream.eof())’ or, similar, while (sourceFileStream). This is considered to be wrong. There are many many statements her on SO, explaining that in more detail.

Next, if you want to learn C++ and use better approaches, then you should make use of object-oriented programming. The first step is, to put data and methods operating on this data in one class or struct. For your case, it would mean that the input functions should be part of the structs.

In C++ input is done via the extractor operator >>. And therefore we should add an extractor operator to your structs.

The syntax for that is (with the example of the answer struct=:

struct Answer {
    int index{};
    int data{};

    friend std::istream& operator >> (std::istream& is, Answer& a) {
        // Read an index, a comma, and the answer
        char comma{};
        return is >> a.index >> comma >> a.data;
    }
};

This is a function for the class(or struct) “Answer”, that takes a stream and an answer as input and will return again a reference to the stream. Now, we could write:

Answer answer{};
if (!(std::cin >> answer)) 
    std::cerr << “Problem while reading answer\n”;

The if- statement will execute the embedded extraction operation. The compiler will see a stream, the extractor operator >> and a variable of type Answer. So, it will call the function defined in the above struct. The operation will return a reference to the stream and the ! operator of the stream, will indicate, if the stream ha and issue.

A similar mechanism can be implemented for output, so we can overwrite the inserter operator >> as well.


With all that, we can split the above big problem into very small units, which can be implemented in an easy and understandable way.

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>

struct Question {
    // Data
    int index{};
    std::string text{};

    // Extractor operator
    friend std::istream& operator >> (std::istream& is, Question& q) {
        char comma{};
        // First read the index, then the comma, then eat up white space. A reference to is will be returned. getline
        // will read the rest of the line by using is. It will also return is, which is then the function return value
        return std::getline(is >> q.index >> comma >> std::ws, q.text);
    }
    // Simple output
    friend std::ostream& operator << (std::ostream& os, const Question& q) {
        return os << "Question: Index: " << q.index << "\tText: " << q.text;
    }
};
struct Answer {
    // Data
    int index{};
    int data{};

    // Extractor operator
    friend std::istream& operator >> (std::istream& is, Answer& a) {
        char comma{};
        // Read the index, then the comma, then data. A reference to is will be returned. 
        return is >> a.index >> comma >> a.data;
    }
    // Simple output
    friend std::ostream& operator << (std::ostream& os, const Answer& a) {
        return os << "Answer: Index: " << a.index << "\tData: " << a.data;
    }
};
struct QuestionAndAnswer {
    // Data
    Question question{};
    Answer answer{};

    // Extractor operator
    friend std::istream& operator >> (std::istream& is, QuestionAndAnswer& q) {
        // Read question and answer 
        return is >> q.question >> q.answer;
    }
    // Simple output
    friend std::ostream& operator << (std::ostream& os, const QuestionAndAnswer& q) {
        return os << q.question << "\t\t" << q.answer;
    }
};


int main() {

    // Here we will store all questions and answers
    std::vector<QuestionAndAnswer> questionAndAnswer{};

    // Open the source file and check, if it could be opened
    std::ifstream sourceFileStream("r:\\filepath.txt");
    if (sourceFileStream) {

        QuestionAndAnswer temp{};

        // Read all questions and answers in a loop
        while (sourceFileStream >> temp)
            questionAndAnswer.push_back(temp);

        // Show debug output
        for (const QuestionAndAnswer& qa : questionAndAnswer)
            std::cout << qa << '\n';
    }
    else
        std::cerr << "\nError: Could not open source file\n";
}

Using this approach would be one of many recommended approaches for a c++ programmer.


A source file containing

1, Question 1
2, 1
3, Question 2
4, 2
5, Question 3
6, 3
7, Question 4
8, 4
9, Question 5
10, 5

will create the output:

Question: Index: 1      Text: Question 1                Answer: Index: 2        Data: 1
Question: Index: 3      Text: Question 2                Answer: Index: 4        Data: 2
Question: Index: 5      Text: Question 3                Answer: Index: 6        Data: 3
Question: Index: 7      Text: Question 4                Answer: Index: 8        Data: 4
Question: Index: 9      Text: Question 5                Answer: Index: 10       Data: 5

If you want to stick to you original idea, then you could use:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <iomanip>

int main() {

    // Here we will store all questions and answers
    std::vector<std::string> questions{};
    std::vector<int> answers{};

    // Open the source file and check, if it could be opened
    std::ifstream sourceFileStream("r:\\filepath.txt");
    if (sourceFileStream) {

        std::string question{};
        int temp{}, answer{}; 
        char comma{};

        // Read all questions and answers in a loop
        while (std::getline(sourceFileStream >> temp >> comma >> std::ws, question))
            if (sourceFileStream >> temp >> comma >> answer) {
                // We do not want to go out fo sync. Always store questions and answers together
                questions.push_back(question);
                answers.push_back(answer);
            }

        // Debug output
        for (unsigned int k = 0; k < questions.size(); ++k)
            std::cout << "Question: " << questions[k] << "\t\tAnswer: " << answers[k] << '\n';
    }
    else
    std::cerr << "\nError: Could not open source file\n";
}

But maybe not that recommended...

A M
  • 14,694
  • 5
  • 19
  • 44
  • 1
    Thank you for your very detailed explanation. It was very helpful and I appreciate the time you put into explaining it to me. – Jhincredible Dec 12 '21 at 23:53
1

You got almost there, the code in your example is good. Just missing a second step:

    // storage
    std::vector<int> integers;
    std::vector<std::string> strings;

    // open file and iterate
    std::ifstream file( "filepath.txt" );
    while ( file ) {

        // read one line
        std::string line;
        std::getline(file, line, '\n');

        // create stream for fields
        std::istringstream ils( line );
        std::string token;

        // read integer (I like to parse it and convert separated)
        if ( !std::getline(ils, token, ',') ) continue;
        int ivalue;
        try { 
            ivalue = std::stoi( token );
        } catch (...) {
            continue;
        }
        integers.push_back(  ivalue );

        // Read string
        if ( !std::getline( ils, token, ',' )) continue;
        strings.push_back( token );
    }

Godbolt: https://godbolt.org/z/4aMv6MW4K

BTW the using std; practice can bite you in the future. Try to keep the std:: prefix in the code, it's safer.

  • 1
    Why `while ( file ) {` is considered bad practice? Please search for "[c++] why while(eof)" and SO will give you many answers. There are also hundreds of comments stating the same. Please kindly consider. Additionally: Just providing code without some explanation is also not considered good practice. Please very kindly consider. – A M Dec 12 '21 at 16:29
  • Hi @ArminMontigny I will kindly consider. Thank you for your thoughtful comment! –  Dec 12 '21 at 17:03