C++ Reading text file into struct data members - c++

I'm currently trying to load a text file into struct data members. Each number is separated by a comma.
#include<string>
#include<sstream>
using namespace std;
struct server{
bool isBusy;
};
struct pass{
double arrivalTime = 0;
double serviceTime = 0;
int classType = 0;
};
int main(){
string fileName;
string line;
pass Pass;
cout << "Enter file name: ";
cin >> fileName;
ifstream fin(fileName);
while (getline(fin, line, ','))
{
/*NEED HELP HERE*/
fin >> Pass[0].arrivalTime;
fin >> Pass[0].serviceTime;
fin >> Pass[0].classType;
}
}
Here is an example of the text file.
0.951412936,2.131445423,0
1.902743503,2.010703852,0
2.537819984,2.326199911,0
3.425838997,1.603712153,0
3.502553324,0.998192867,0
3.917348666,1.49223429,0
4.391605986,0.831661367,0
4.947059678,0.8557003,0
5.429305232,2.42029408,0
The data in the text file follows this format:
arrivalTime,serviceTime,classType
As you can see i have split the line up and stored it in "line" using the comma delimiter, but i am unsure how to load each number into the struct in the while loop.
Any help would be appreciated.

Define an istream operator >> for your struct. Something like
struct pass {
double arrivalTime = 0;
double serviceTime = 0;
int classType = 0;
friend std::istream & operator >>(std::istream & in, pass & p) {
char c;
in >> p.arrivalTime >> c >> p.serviceTime >> c >> p.classType;
return in;
}
};
Then, simply
pass Pass;
fin >> Pass;

while (getline(fin, line))
{
sscanf(line.c_str(), "%lf,%lf,%d", &arrivalTime, &serviceTime, &classType);
}

This loop is wrong:
while (getline(fin, line, ','))
{
/*NEED HELP HERE*/
fin >> Pass[0].arrivalTime;
fin >> Pass[0].serviceTime;
fin >> Pass[0].classType;
}
You are reading everything from the stream up to the next ',' character, then trying to read more from the stream.
Given the input file:
0.951412936,2.131445423,0
1.902743503,2.010703852,0
2.537819984,2.326199911,0
Your program reads "0.951412936" into line (and discards the ',') then tries to read the next input into Pass[0].arrivalTime but the next input is 2.131445423, which was meant to be the serviceTime (which you already read into line).
As Shreevardhan suggests you can define an operator for reading your struct from a stream. I would make it more reliable like so:
struct ExpectedChar { char expected; };
// read a character from a stream and check it has the expected value
std::istream& operator>>(std::istream& in, const ExpectedChar& e)
{
char c;
if (in >> c)
if (c != e.expected) // failed to read expected character
in.setstate(std::ios::failbit);
return in;
}
// read a struct pass from a stream
std::istream& operator>>(std::istream& in, pass& p)
{
ExpectedChar comma{ ',' };
in >> p.arrivalTime >> comma >> p.serviceTime >> comma >> p.classType;
return in;
}
This will stop reading if the input file does not meet the expected format. Now you can do:
while (fin >> Pass)
{
// do something with each pass
}
if (!fin.eof()) // stopped reading before end-of-file
throw std::runtime_error("Invalid data in input file");
This will keep reading a pass from the file until reading fails, either because it reached the end of the file, or because there was some bad data in the file. If there is bad data it throws an exception.

#include<string>
#include<iostream>
#include<fstream>
#include<vector>
using namespace std;
struct server{
bool isBusy;
};
struct pass{
double arrivalTime;
double serviceTime;
int classType;
friend std::istream & operator >>(std::istream &in, pass &p) {
char c;
in >> p.arrivalTime >> c >> p.serviceTime >> c >> p.classType;
return in;
}
};
int main(){
string fileName;
string line;
cout << "Enter file name: ";
cin >> fileName;
ifstream fin(fileName.c_str(), ifstream::in);
vector<pass> passes;
pass Pass;
while (fin>>Pass)
passes.push_back(Pass);
for(vector<pass>::const_iterator iter = passes.begin();
iter != passes.end();
++iter)
std::cout<<iter->arrivalTime<<" "<<iter->serviceTime<<" "
<<iter->classType<<std::endl;
}

Here is hint;
string line;
string temp;
string::size_type sz;
while (getline(cin, line))
{
istringstream ss( line );
getline( ss, temp, ',' );
double arrivalTime = stod(temp, &sz);
getline( ss, temp, ',' );
double serviceTime = stod(temp, &sz);
getline( ss, temp, ',' );
double classType = stod(temp, &sz);
cout << arrivalTime << ' '
<< serviceTime << ' '
<< classType << endl;
}

Related

Read different data types from .csv file and store them into a struct array

I need to read a .csv file, put the information into a struct, then insert the struct into a binary file. Each column means:
(int)Year; (int)Rank; (char*)University's Name; (float)Score; (char*)City; (char*)Country
My .csv file looks like:
2018,1,Harvard University,97.7,Cambridge,United States
2018,2,University of Cambridge,94.6,Cambridge,United Kingdom
2018,3,University of Oxford,94.6,Oxford,United Kingdom
2018,4,Massachusetts Institute of Technology (MIT),92.5,Cambridge,United States
2018,5,Johns Hopkins University,92.1,Baltimore,United States
As you can see, it contains the five best universities in the world.
The issue is my code can read neither the integers (perhaps due to commas in the file) nor the char[30].
struct Data
{
int year;
int rank;
char name[30];
float score;
char city[30];
char country[30];
};
`Data *universitie = new Data [5];
ifstream read ( "myFile.csv" );
int i = 0;
while ( !read.eof() ) {
read >> universitie[i].year;
read >> universitie[i].rank;
read.getline (universitie[i].name, 30, ','); //here a segmentation fault happened
read >> universitie[i].score;
read.getline (universitie[i].city, 30, ','); //here a segmentation fault happened
read.getline (universitie[i].country, 30, '\n'); //here a segmentation fault happened
i++;
}
read.close ();
I'm actually using char[30] for name, city and country instead of string because then I'll write this struct array into a binary file.
How can I properly read the integers until the comma?
How to read a character array from a file using getline() with delimeters like ,?
This is for my Computer Science course's task.
The usual technique with CSV files is to model a record with a class, then overload operator>> to input a record:
struct Record
{
int year;
int rank;
std::string name;
double score;
std::string city;
std::string country;
friend std::istream& operator>>(std::istream& input, Record& r);
};
std::istream& operator>>(std::istream& input, Record& r)
{
char comma;
input >> r.year;
input >> comma;
input >> r.rank;
input >> comma;
std::getline(input, r.name, ',');
input >> r.score;
std::getline(input, r.city, ',');
std::getline(input, r.country, '\n');
return input;
}
A use case for the above could look like:
std::vector<Record> database;
ifstream data_file ( "myFile.csv" );
Record r;
while (data_file >> r)
{
database.push_back(r);
}
Note: I have changed your char[] to std::string for easier handling. Also, I changed the while loop condition.
//Open the file
Data *universitie = new Data [5];
int i = 0;
std::string line;
while(std::getline(file, line))
{
std::stringstream ss;
ss << line;
ss >> universitie[i].year;
ss.ignore();
ss >> universitie[i].rank;
ss.ignore();
ss >> universitie[i].name;
ss.ignore();
ss >> universitie[i].score;
ss.ignore();
ss >> universitie[i].city;
ss.ignore();
ss >> universitie[i].country;
i++;
}
This is a solution with std::stringstream. The ignore() function is used to skip the ',' between the entries.
Also in my opinion is better to use the C++ std::string class instead of char[30]. If at some point you need c string then you can use the c_str() function.

Read text file and store student info into a a vector [duplicate]

This question already has an answer here:
Read lines from text file and store into array
(1 answer)
Closed 1 year ago.
the text file looks something like this:
9528961 Adney Smith CS 4.2
9420104 Annalynn Jones EE 2.6
9650459 Bernadette Williams IT 3.6
...
there are 45 lines in the text file meaning 45 students. I have read the text file and when I run the program I get this:
9428167
Mason
Taylor
CS
4.8
9231599
Alexander
Jones
CS
2.3
My main file looks like this:
int main()
{
auto student = new Student<string>();
std::vector<string> students;
std::ifstream inputFile;
inputFile.open("enroll_assg.txt");
std::string line;
if(inputFile.is_open()){
while(std::getline(inputFile, line)){
std::istringstream iss(line);
std::string word;
while(iss >> word){
std::cout << word << std::endl;
for(int i = 0; i < 5; i++){
}
}
}
}
return 0;
}
Each student has 5 columns (id, fname, lname, department, gpa) and I need make a vector which includes all these student object. I need some help doing this so comments and answers are most welcome. Thank you.
IMHO, the best method is to use a struct or class to model or represent the data record you need to read.
struct Student
{
unsigned int id;
std::string first_name;
std::string last_name;
std::string major_code;
double gpa;
friend std::istream& operator>>(std::istream& input, Student& s);
};
std::istream& operator>>(std::istream& input, Student& s)
{
input >> s.id;
input >> s.first_name;
input >> s.last_name;
input >> s.major_code;
input >> s.gpa;
input.ignore(10000, '\n'); // Synchronize to next line.
return input;
}
Your input code could look like this:
std::vector<Student> database;
Student s;
//... open file.
while (student_file >> s)
{
database.push_back(s);
}
The above code will read each student record into a database, so you can analyze it.
Try something more like this instead:
int main()
{
std::ifstream inputFile("enroll_assg.txt");
if (inputFile.is_open()){
std::vector<Student<string>> students;
std::string line;
while (std::getline(inputFile, line)){
std::istringstream iss(line);
Student<string> student;
iss >> student.id;
iss >> student.fname;
iss >> student.lname;
iss >> student.department;
iss >> student.gpa;
students.push_back(student);
}
// use students as needed...
}
return 0;
}
Then, you should consider having Student overload the operator>>, which will greatly simplify the loop so you can do something more like this instead:
template<typename T>
std::ostream& operator>>(std::ostream &in, Student<T> &student)
{
std::string line;
if (std::getline(in, line))
{
std::istringstream iss(line);
iss >> student.id;
iss >> student.fname;
iss >> student.lname;
iss >> student.department;
iss >> student.gpa;
}
return in;
}
int main()
{
std::ifstream inputFile("enroll_assg.txt");
if (inputFile.is_open()){
std::vector<Student<string>> students;
Student<string> student;
while (inputFile >> student){
students.push_back(student);
}
// use students as needed...
}
return 0;
}
First this question is a duplicate of Read lines from text file and store into array which already has the answer you're looking for in this question.
The below shown program uses struct to represent a given Student and it also used a std::vector. You can use this program as a reference(starting point). It reads student information from the input text file and store that information in a vector of Student.
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
//this class represents a Student
class Student
{
public:
std::string firstName, lastName, courseName ;
unsigned long id = 0;
float marks = 0;
};
int main()
{
std::ifstream inputFile("input.txt");
std::string line;
std::vector<Student> myVec;//create a vector of Student objects
if(inputFile)
{
while(std::getline(inputFile, line))
{
Student studentObject;
std::istringstream ss(line);
//read the id
ss >> studentObject.id;
//read the firstname
ss >> studentObject.firstName;
//read the lastname
ss >> studentObject.lastName;
//read the courseName
ss >> studentObject.courseName;
//read the marks
ss >> studentObject.marks;
if(ss)//check if input succeded
{
myVec.emplace_back(studentObject);//add the studentObject into the vector
}
}
}
else
{
std::cout<<"File cannot be opened"<<std::endl;
}
//lets print out the elements of the vecotr to confirm that all the students were correctly read
for(const Student &elem: myVec)
{
std::cout << elem.id << ": "<<elem.firstName<<" "<<elem.lastName<<" "<<elem.courseName<<" "<<elem.marks <<std::endl;
}
return 0;
}
The output of the above program can be seen here.

Input file reading wrong value and eof controlled loop does not end

I'm attempting to read data from an input file using a loop that should stop at the end of the file within a function. The data in the file is arranged in lines like this:
Zip Drive
89
Warehouse C
75.89
Keyboard
133
Warehouse C
25.95
On the fourth line the variable is filled with some garbage input and everything read after is blank. The loop also will not end.
int ReadData(string productName[], string productLocation[], int productQty[], double productPrice[])
{
ifstream infile;
int i = 0;
infile.open("prog1.txt");
do {
getline(infile, productName[i]);
infile >> productQty[i];
getline(infile, productLocation[i]);
infile >> productPrice[i];
i++;
} while (!infile.eof());
infile.close();
return i;
}
The infile >> productQty[i] and infile >> productPrice[i] statements are reading int/double values and leaving the line breaks after them in the stream, to be read by the next std::getline() call, which is not what you want to happen.
You need to either:
use std::getline() to read every line, and then use std::istringstream to parse the int/double values from their respective lines:
int ReadData(string productName[], string productLocation[], int productQty[], double productPrice[])
{
ifstream infile("prog1.txt");
int i = 0;
while (getline(infile, productName[i]))
{
getline(infile, line);
istringstream(line) >> productQty[i];
getline(infile, productLocation[i]);
getline(infile, line);
istringstream(line) >> productPrice[i];
++i;
}
infile.close();
return i;
}
call infile.ignore() after reading the int/double values, to read and dismiss the line breaks:
int ReadData(string productName[], string productLocation[], int productQty[], double productPrice[])
{
ifstream infile("prog1.txt");
int i = 0;
while (getline(infile, productName[i]))
{
infile >> productQty[i];
infile.ignore(numeric_limits<streamsize>::max(), '\n');
getline(infile, productLocation[i]);
infile >> productPrice[i];
infile.ignore(numeric_limits<streamsize>::max(), '\n');
++i;
}
infile.close();
return i;
}
In either case, the arrays must be pre-allocated with enough elements to hold as many products as are in the file. Since the code has no way of actually verifying that count, I would suggest an alternative safer approach:
struct productInfo
{
string name;
string location;
int quantity;
double price;
};
istream& operator>>(istream &in, productInfo &out)
{
string line;
if (!getline(in, out.name)) {
// don't set failbit yet, in case this is just EOF...
return in;
}
// at this point, a new product is encountered, so
// any missing data is considered a failure...
if (!getline(in, line)) {
in.setstate(failbit);
return in;
}
if (!(istringstream(line) >> out.quantity)) {
in.setstate(failbit);
return in;
}
if (!getline(in, out.location)) {
in.setstate(failbit);
return in;
}
if (!getline(in, line)) {
in.setstate(failbit);
return in;
}
if (!(istringstream(line) >> out.price)) {
in.setstate(failbit);
return in;
}
return in;
}
int ReadData(vector<productInfo> &products)
{
ifstream infile("prog1.txt");
int i = 0;
productInfo info;
while (infile >> info)
{
products.push_back(info);
++i;
}
infile.close();
return i;
}

How can I read lines from file and cut it pieces?

[EDIT]
I specified my question, maybe it has more information:
I have a file with lots of lines like "string1 string2 int1 int2", so a line contains two strings and two integers. I would like to read the file line by line and push those four datas into a vector of my struct( which has the same variable types and num ). My question is, how can I do this, because the >> operator won't work anyway. Nor the getline().
and the code:
void FromFile(string filename)
{
ifstream stream;
stream.open(filename);
adatok adattemp;
while(stream.good())
{
stream >> adattemp.agresszor >> adattemp.vedo >> adattemp.haborukezdete >> adattemp.haboruvege >> ws;
cout << adattemp.agresszor;
vektor.push_back(adattemp);
}
stream.close();
}
Assuming each string is just a single word, this should work:
#include <vector>
#include <string>
#include <fstream>
struct Entry {
std::string s1;
std::string s2;
int i1;
int i2;
};
std::vector<Entry> entries;
int main()
{
std::ifstream file("yourfile");
while (file.good()) {
Entry entry;
file >> entry.s1 >> entry.s2 >> entry.i1 >> entry.i2 >> std::ws;
entries.push_back(entry);
}
return 0;
}
Note: it's important to include >> std::ws at the end of reading each line. It eats up extra whitespace. Otherwise you'll end up with an extra junk entry at the end of the file.
EDIT: As Simple pointed out in the comments, if any errors occur while reading the file, the above code will store a junk entry at the end of the vector. This code will fix that by making sure there are no errors before storing the entry:
Entry entry;
while (file >> entry.s1 >> entry.s2 >> entry.i1 >> entry.i2 >> std::ws)
{
entries.push_back(entry);
}
I'd overload >> and use std::copy
#include<vector>
#include<algorithm>
//...
struct Reader {
std::string str1;
std::string str2;
int int1;
int int2;
friend std::istream& operator << (std::istream& is, Reader &r)
{
return is >> r.str1 >> r.st2 >> r.int1 >> r.int2 ;
}
};
std::vector<Reader> vec;
std::ifstream fin("file_name");
std::copy(std::istream_iterator<Reader>(fin),
std::istream_iterator<Reader>(),
std::back_inserter(vec)
) ;
This assumes all string and int are seperated by white space
You can also overload >> too to display content in similar fashion
Since you do not know the file size, a vector would be more suited IMO.
while ( myFile >> string1 >> string2 >> myInt1 >> myInt2 ) {
stringvec1.push_back( string1 );
stringvec2.push_back( string2 );
intvec1.push_back( myInt1 );
intvec2.push_back( myInt2 );
}
EDIT:
If the 4 variables that you are reading correspond to a particular logically meaningful class, then you can have one vector of class/struct with all these 4 as members.
Something like:
struct myFileVariables {
std:string m_string1;
std:string m_string2;
int m_myInt1;
int m_myInt2;
myFileVariables ( string string1, string string2, int myInt1, int myInt2 ) :
m_string1( string1 ), m_string2( string2 ), m_myInt1( myInt1 ),
m_myInt2( myInt2 ) {}
};
And in your main function:
while ( myFile >> string1 >> string2 >> myInt1 >> myInt2 ) {
myFileVariables newEntry( string1, string2, myInt1, myInt2 );
myVec.push_back( newEntry );
}

C++ Variable Checking

Let's say I want the user to input an integer but he enters a double or a character value, how do I check that the the user has input the correct type.
string line;
getline(cin, line);
// create stringstream object called lineStream for parsing:
stringstream lineStream(line);
string rname;
double res;
int node1,node2;
lineStream >> rname >> res >> node1 >> node2;
How do I check for valid input type?
You check the stream is OK:
if (lineStream >> rname >> res >> node1 >> node2)
{
// all reads worked.
}
You may want to check for garbage on the end.
if (lineStream >> rname >> res >> node1 >> node2)
{
char x;
if (lineStream >> x)
{
// If you can read one more character there is junk on the end.
// This is probably an error. So in this situation you
// need to do somethings to correct for this.
exit(1);
}
// all reads worked.
// AND there is no junk on the end of the line.
}
Comment expanded.
From the comments below:
if i input an integer for rname, it still works. for exmaple:
string line; getline(cin, line);
stringstream lineStream(line); // created stringstream object called lineStream for parsing
string rname;
if (lineStream >> rname) { cout << "works"; }
Lets assume there is some properties about rname that allow us to distinguish it from a number. For example: it must be a name. i.e. it must only contain alpha characters.
struct Name
{
std::string value;
friend std::istream& operator>>(std::istream& s, Name& data)
{
// Read a word
s >> data.value;
// Check to make sure that value is only alpha()
if (find_if(data.value.begin(), data.value.end(), [](char c){return !isalpha(c);}) != str.end())
{
// failure is set in the stream.
s.setstate(std::ios::failbit);
}
// return the stream
return s;
}
};
Now you can read a name.
Name rname;
if (lineStream >> rname) { cout << "works"; }
This will fail if you enter an integer for rname.
Stretch Answer
If you have multiple lines of the same information you want to read. Then it is worth wrapping it in a class and defining a input stream operator.
strcut Node
{
Name rname;
double res;
int node1;
int node2;
friend std::istream& operator>>(std::istream& s, Node& data)
{
std::string line;
std::getline(s, line);
std::stringstream linestream(line);
lineStream >> data.rname >> data.res >> data.node1 >> data.node2;
if(!linestream)
{
// Read failed.
s.setstate(std::ios::failbit);
}
return s;
}
};
Now it becomes easy to read the lines in a loop:
Node n;
while(std::cin >> n)
{
// We read another node successfully
}
Since string 123 will also be considered as string, and not an integer, so the better way is to iterate the string to end, until we find any non-digit character. here's how you may do it:
bool is_number(const std::string& s)
{
std::string::const_iterator it = s.begin();
while (it != s.end() && std::isdigit(*it)) ++it;
return !s.empty() && it == s.end();
}
Read node1 and node2 into strings first, then validate with a regular expression.
#include <regex>
...
string node1_s, node2_s;
linestream >> rname >> node1_s >> node2_s
if (regex_match(node1_s, regex("[+-]?[0-9]+") {
/* convert node1_s to int */
} else {
/* node1_s not integer */
}
/* Do same for node2_s */