0

I am trying to input three pieces of information from a .txt file.

First column is the course mark. Second column is the course code. Third column(s) is the course name.

I would like to store these as 3 vectors of strings.

Would using stringstream be a good option here? and maybe iterators?

The .txt file is like

65.6 10071   Mathematics 1
66.7 10101   Dynamics
60.0 10121   Quantum Physics and Relativity
66.9 10191   Introduction to Astrophysics and Cosmology
...  ...    ...

and my code so far is

#include<iostream>
#include<iomanip>
#include<fstream>
#include<cmath>
#include<algorithm>
#include<string>
#include<iterator>
#include<sstream>
#include<vector>

//Main Function
int main()
{

  //Define variables
  std::string course_mark, course_code, course_name;
  std::vector<std::string> course_mark_vector;
  std::vector<std::string> course_code_vector;
  std::vector<std::string> course_name_vector;

  std::string data_file[100];

  // Ask user to enter filename
  std::cout<<"Enter data filename: ";
  std::cin>>data_file;

  int i{0};

  // Open file and check if successful
  std::fstream course_stream(data_file);

  if(course_stream.is_open()) {
        while (!course_stream.eof()) //while the end of file is NOT reached
        {
            //I have 2
            getline(course_stream, course_mark, ' ');
            course_mark_vector.push_back(course_mark);

            getline(course_stream, course_code, ' ');
            course_code_vector.push_back(course_code);

            getline(course_stream, course_name, '\n');
            course_name_vector.push_back(course_name);
            
            i += 1; //increment number of lines
        }
        course_stream.close(); //closing the file
        std::cout << "Number of entries: " << i-1 << std::endl;
    }

  else{
        std::cout << "Unable to open file. Please run again" << std::endl;
        return 1;
  }

Any help would be greatly appreciated

  • 1
    For one thing, see [Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?](https://stackoverflow.com/q/5605125/10077). – Fred Larson Mar 03 '21 at 18:30
  • 1
    Reading per line, then using a string stream to pull apart that line would probably be a good fit. Worth trying. Unrelated, [this: `while (!course_stream.eof())` is wrong](https://stackoverflow.com/questions/5605125/why-is-iostreameof-inside-a-loop-condition-i-e-while-stream-eof-cons). As-structured you will insert a duplicate entry in your list *unless* the last line does *not* terminate with a newline char; a problem you're apparently attempting to mask via your `i-1` report of the number of items read. – WhozCraig Mar 03 '21 at 18:30
  • 1
    Why is `data_file` an *array* of `std::string`? – Fred Larson Mar 03 '21 at 18:32
  • In general Step 1: Read data. Step 2: Ensure you read data in step 1. Step 3: Use data read in step 1 if step 2 passed; take action to correct the error if step 2 did not pass. Try and read data in any other order and the program won't always work. – user4581301 Mar 03 '21 at 18:33
  • 1
    Better encapsulation can be had by creating a class that represents the course. Then creating a signle vector of course. – Martin York Mar 03 '21 at 19:27

1 Answers1

1

Would using stringstream be a good option here?

Yes.

and maybe iterators?

There is no need for iterators in this case.

Try this:

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

//Main Function
int main()
{
    //Define variables
    std::string course_mark, course_code, course_name, data_file, line;
    std::vector<std::string> course_mark_vector, course_code_vector, course_name_vector;
    int i = 0;

    // Ask user to enter filename
    std::cout << "Enter data filename: ";
    std::cin >> data_file;

    // Open file and check if successful
    std::ifstream course_stream(data_file);

    if (!course_stream.is_open())
    {
        std::cout << "Unable to open file. Please run again" << std::endl;
        return 1;
    }

    while (std::getline(course_stream, line)) //while the end of file is NOT reached
    {
        std::istringstream iss(line);

        iss >> course_mark;
        course_mark_vector.push_back(course_mark);

        iss >> course_code;
        course_code_vector.push_back(course_code);

        std::getline(iss >> std::ws, course_name);
        course_name_vector.push_back(course_name);
            
        ++i; //increment number of lines
    }

    course_stream.close(); //closing the file
    std::cout << "Number of entries: " << i << std::endl;

    return 0;
}

Demo

Remy Lebeau
  • 454,445
  • 28
  • 366
  • 620
  • 1
    May it be good to explain the line with the second `getline` a little further?: 1) it operates on a different stream than the outer `getline`, a stream containing a copy of each file's line, so it doesn't interfere with the outer `getline`, 2) it first, in the same line, gets rid of the front whitespaces with `iss >> std::ws` and 3) we can do that last operation there because it returns a stream. – rturrado Mar 03 '21 at 19:24
  • BTW, why didn't you consider necessary to use a `getline` as well for the `course_code`? – rturrado Mar 03 '21 at 19:26
  • 1
    @rturrado "*why didn't you consider necessary to use a `getline` as well for the `course_code`?*" - because the `course_mark` and `course_code` are space-delimited strings, which is exactly what `operator>>` is designed to read. – Remy Lebeau Mar 03 '21 at 19:39
  • 1
    @MartinYork "*The issue is that if any read from the iss fails you push the previous value into the vector*" - That is not true. When `operator>>` and `std::getline()` are reading in a `std::string` value, the first thing they do is clear the output `std::string` before then reading anything from the stream into it. – Remy Lebeau Mar 03 '21 at 19:43
  • @RemyLebeau OK, thanks! I hadn't reached to that conclusion after reading the question. – rturrado Mar 03 '21 at 19:55
  • 1
    @MartinYork "*Do they clear the string if the state is already bad?*" - the string is cleared after the stream's [`sentry`](https://en.cppreference.com/w/cpp/io/basic_istream/sentry) is constructed. If the stream is already in a bad state, the sentry constructor will set `failbit` on the stream, which will throw an exception if [`exceptions(failbit)`](https://en.cppreference.com/w/cpp/io/basic_ios/exceptions) is enabled on the stream. So, it is *possible* for the string to not be cleared. But in my example, `iss.exceptions(failbit)` is not used, so the string will always be cleared, yes. – Remy Lebeau Mar 03 '21 at 21:23
  • @RemyLebeau fair enougn :-) Comment removed. – Martin York Mar 03 '21 at 21:33