Use getline to split a string and check for int - c++

I have a file:
name1 8
name2 27
name3 6
and I'm parsing it into vector. This is my code:
int i=0;
vector<Student> stud;
string line;
ifstream myfile1(myfile);
if (!myfile1.is_open()) {return false;}
else {
while( getline(myfile1, line) ) {
istringstream iss(line);
stud.push_back(Student());
iss >> stud[i].Name >> stud[i].Grade1;
i++;
}
myfile1.close();
}
I need to check if the stud[i].Grade1 is int. If it isn't it return false.
File can contain:
name1 haha
name2 27
name3 6
How can I do it?
EDIT:
I have tried another way (without getline) and it seems to work. I don't understand why :/
int i=0;
vector<Student> stud;
ifstream myfile1(myfile);
if (!myfile1.is_open()) {return false;}
else {
stud.push_back(Student());
while( myfile1 >> stud[i].Name ) {
if(!(myfile1 >> stud[i].Points1)) return false;
i++;
stud.push_back(Student());
}
myfile1.close();
}

If type of Grade1 in numerical such as int, Use std::istringstream::fail() :
// ...
while( getline(myfile1, line) ) {
istringstream iss(line);
stud.push_back(Student());
iss >> stud[i].Name;
iss >> stud[i].Grade1;
if (iss.fail())
return false;
i++;
}
myfile1.close();
}
// ...

It could look like this:
std::vector<Student> students;
std::ifstream myfile1(myfile);
if (!myfile1.is_open())
return false;
std::string line;
while (std::getline(myfile1, line))
{
// skip empty lines:
if (line.empty()) continue;
Student s;
std::istringstream iss(line);
if (!(iss >> s.Name))
return false;
if (!(iss >> s.Grade1))
return false;
students.push_back(s);
}
just note that iss >> s.Grade1 will succeed not only for decimal, but also for octal and hexadecimal numbers too. To make sure that only decimal value will be read, you could read it into the temporary std::string object and validate it before you use it to retrieve the number. Have a look at How to determine if a string is a number with C++?

Related

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;
}

C++ Reading text file into struct data members

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;
}

how to extract specific integers in a mixed string-integer line in c++

I am reading a text file in c++, this is example of some lines in it:
remove 1 2 cost 13.4
How could I disregard all things except two integers after remove, "1" and "2" and put them in two integer variable?
my incomplete code:
ifstream file("input.txt");
string line;
int a, b;
if(file.is_open())
{
while (!file.eof())
{
getline (file, line);
istringstream iss(line);
if (line.find("remove") != string::npos)
{
iss >> a >> b; // this obviously does not work, not sure how to
// write the code here
}
}
}
Here are a few options:
Use the stringstream created for the line to find the remove token and parse the next two integers. In other words, replace this:
if (line.find("remove") != string::npos)
{
iss >> a >> b; // this obviously does not work, not sure how to
// write the code here
}
with this:
string token;
iss >> token;
if (token == "remove")
{
iss >> a >> b;
}
Create a stringstream for the rest of the line (6 is the length of the "remove" token).
string::size_type pos = line.find("remove");
if (pos != string::npos)
{
istringstream iss(line.substr(pos + 6));
iss >> a >> b;
}
Call the seekg method on the line stringstream to set the input position indicator of the stream after the "remove" token.
string::size_type pos = line.find("remove");
if (pos != string::npos)
{
iss.seekg(pos + 6);
iss >> a >> b;
}

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 */