I have a text file, that looks like this:
Name_of_st1 67 5 87 4 78 4
Name_of_st2 89 5 56 3 79 4
...
I have written a program that reads the data and creates a vector of students with its own vector of subjects. The program reads the first line correctly, but when i ask it to write out name of second student, std::cout displays garbage. Here is the code:
struct Subject {
int mark0;
int mark1;
};
struct Student {
char name[50];
char surname[50];
vector<Subject>my_marks;
};
istream& operator>>(istream& is, Subject& sub);
istream& operator>>(istream& is, Student& st);
int main()
try
{
ifstream ifs("Text.txt");
vector<Student>group;
Student s1, s2;
ifs >> s1;
group.push_back(s1);
getline(ifs, temp);
ifs >> s2;
group.push_back(s2);
cout << group.size();
cout << group[0].name;
cout << group[0].surname;
cout << group[1].name;
cout << group[0].my_marks[1].mark0;
}
catch (exception& e){
cerr << e.what() << endl;
return 1;
}
catch (...)
{
cerr << "exception \n";
return 2;
}
istream& operator>>(istream& is, Subject& sub)
{
int m0, m1;
is >> m0;
if (!is) return is;
is>> m1;
sub.mark0 = m0;
sub.mark1 = m1;
return is;
}
istream& operator>>(istream& is, Student& st)
{
char n1[50];
char n2[50];
is >> n1;
is >> n2;
strcpy_s(st.name, n1);
strcpy_s(st.surname, n2);
if (!is) return is;
while (true)
{
Subject sub;
if (!(is >> sub)) break;
st.my_marks.push_back(sub);
}
return is;
}
I have tried this, but still the same:
string temp
ifs >> s1;
getline(ifs, temp);
group.push_back(s1);
And this:
char n1[50];
char n2[50];
for (int i = 0;; ++i)
{
char n0;
is.get(n0);
if (isspace(n0)) break;
n1[i] = n0;
}
for (int i = 0;; ++i)
{
char n0;
is.get(n0);
if (isspace(n0)) break;
n2[i] = n0;
}
if (!is) return is;
strcpy_s(st.name, n1);
strcpy_s(st.surname, n2);
Then i added ':' after every Name of Student and used is.get(n1, 100, ':').
It still reads the first line correctly, but refuses to read the next. Whatever i did, i can`t move text cursor to the beginning of new line. If anyone can help me, i would appreciate it.
Your second student is not being read because you put the stream in a bad state reading the first student.
if (!(is >> sub)) break;
You basically read subjects until the input breaks (which is when you hit a name). Since you don't reset the state of the stream to good any further attempts to read will be ignored.
You have two choices.
Set the state to good after you have finished reading the subjects.
This has an issue in that you will probably go into a infinite loop as your checking of the stream state is spotty currently.
Read a line then parse the subject information from the line.
I would go for option 2.
// After you have read the name.
// The rest of the line is scores. So read a whole line.
std::string line;
std::getline(is, line);
// convert the line into a stream.
std::stringstream linestream(line);
// Now parse the stream for subject.
Subject sub;
while(linestream >> sub) { // Note this reads an item and checks the
// state of the stream. The while body
// is only entered on a successful read.
st.my_marks.push_back(sub);
}
Related
I am trying to read a file of the following format
id1 1 2 3
id2 2 4 6
id3 5 6 7
...
using this code
Dataset::Dataset(ifstream &file) {
string token;
int i = 0;
while (!file.eof() && (file >> token)){
// read line tokens one-by-one
string ID = token;
vector<int> coords;
while ((file.peek()!='\n') && (!file.eof()) && (file >> token)) {
coords.push_back(atoi(token.c_str()));
}
points.push_back(new Point(ID, coords));
i++;
}
cout << "Loaded " << i << " points." << endl;
}
But it tells me I have read 0 points. What am I doing wrong?
Edit: I am openning this using input_stream.open(input_file) and file.good() returns true.
Edit #2: actually .good() returns true the first time and then false. What is that all about?
Edit #3: GUYS. IT'S FREAKING WINDOWS. When i put the path as Dataset/test.txt by cin it works and when I do it like Dataset\test.txt by the commandline it doesn't...
Now the problem is it seems not stop at new lines!
Edit #4: Freaking windows again! It was peeking '\r' instead of '\n'.
Here's an idea: overload operator>>:
struct Point
{
int x, y, z;
friend std::istream& operator>>(std::istream& input, Point& p);
};
std::istream& operator>>(std::istream& input, Point& p)
{
input >> p.x;
input >> p.y;
input >> p.z;
input.ignore(10000, '\n'); // eat chars until end of line.
return input;
}
struct Point_With_ID
: public Point
{
std::string id;
friend std::istream& operator>>(std::istream& input, Point_With_ID& p);
};
std::istream& operator>>(std::istream& input, Point_With_ID& p)
{
input >> p.id;
input >> static_cast<Point&>(p); // Read in the parent items.
return input;
}
Your input could look like this:
std::vector<Point_With_ID> database;
Point_With_ID p;
while (file >> p)
{
database.push_back(p);
}
I separated the Point class so that it can be used in other programs or assignments.
I managed to make it work by accounting for both '\r' and '\n' endings and ignoring trailing whitespace like this:
Dataset::Dataset(ifstream &file) {
string token;
int i = 0;
while (file >> token){
// read line tokens one-by-one
string ID = token;
vector<int> coords;
while ((file.peek()!='\n' && file.peek()!='\r') && (file >> token)) { // '\r' for windows, '\n' for unix
coords.push_back(atoi(token.c_str()));
if (file.peek() == '\t' || file.peek() == ' ') { // ignore these
file.ignore(1);
}
}
Point p(ID, coords);
points.emplace_back(p);
i++;
// ignore anything until '\n'
file.ignore(32, '\n');
}
cout << "Loaded " << i << " points." << endl;
}
Probably not the best of the solutions suggested but it's working!
You should not use eof() in a loop condition. See Why is iostream::eof inside a loop condition considered wrong? for details. You can instead use the following program to read into the vector of Point*.
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
class Point
{
public:
std::string ID = 0;
std::vector<int> coords;
Point(std::string id, std::vector<int> coord): ID(id), coords(coord)
{
}
};
int main()
{
std::vector<Point*> points;
std::ifstream file("input.txt");
std::string line;
int var = 0;
while (std::getline(file, line, '\n'))//read line by line
{
int j = 0;
std::istringstream ss(line);
std::string ID;
ss >> ID;
std::vector<int> coords(3);//create vector of size 3 since we already know only 3 elements needed
while (ss >> var) {
coords.at(j) = var;
++j;
}
points.push_back(new Point(ID, coords));
}
std::cout<<points.size()<<std::endl;
//...also don't forget to free the memory using `delete` or use smart pointer instead
return 0;
}
The output of the above program can be seen here.
Note that if you're using new then you must use delete to free the memory that you've allocated. This was not done in the above program that i have given since i only wanted to show how you can read the data in your desired manner.
You've baked everything up in a complex deserializing constructor. This makes the code hard to understand and maintain.
You have a coordinate, so make class for that, we can call it Coord, that is capable of doing its own deserializing.
You have a Point, which consists of an ID and a coordinate, so make a class for that, that is capable of doing its own deserializing.
The Dataset will then just use the deserializing functions of the Point.
Don't limit deserializing to ifstreams. Make it work with any istream.
Deserializing is often done by overloading operator>> and operator<< for the types involved. Here's one way of splitting the problem up in smaller parts that are easier to understand:
struct Coord {
std::vector<int> data;
// read one Coord
friend std::istream& operator>>(std::istream& is, Coord& c) {
if(std::string line; std::getline(is, line)) { // read until end of line
c.data.clear();
std::istringstream iss(line); // put it in an istringstream
// ... and extract the values:
for(int tmp; iss >> tmp;) c.data.push_back(tmp);
}
return is;
}
// write one Coord
friend std::ostream& operator<<(std::ostream& os, const Coord& c) {
if(not c.data.empty()) {
auto it = c.data.begin();
os << *it;
for(++it; it != c.data.end(); ++it) os << ' ' << *it;
}
return os;
}
};
struct Point {
std::string ID;
Coord coord;
// read one Point
friend std::istream& operator>>(std::istream& is, Point& p) {
return is >> p.ID >> p.coord;
}
// write one Point
friend std::ostream& operator<<(std::ostream& os, const Point& p) {
return os << p.ID << ' ' << p.coord;
}
};
struct Dataset {
std::vector<Point> points;
// read one Dataset
friend std::istream& operator>>(std::istream& is, Dataset& ds) {
ds.points.clear();
for(Point tmp; is >> tmp;) ds.points.push_back(std::move(tmp));
if(!ds.points.empty()) is.clear();
return is;
}
// write one Dataset
friend std::ostream& operator<<(std::ostream& os, const Dataset& ds) {
for(auto& p : ds.points) os << p << '\n';
return os;
}
};
If you really want a deserializing constructor in Dataset you just need to add these:
Dataset() = default;
Dataset(std::istream& is) {
if(!(is >> *this))
throw std::runtime_error("Failed reading Dataset");
}
You can then open your file and use operator>> to fill the Dataset and operator<< to print the Dataset on screen - or to another file if you wish.
int main() {
if(std::ifstream file("datafile.dat"); file) {
if(Dataset ds; file >> ds) { // populate the Dataset
std::cout << ds; // print the result to screen
}
}
}
Demo
I'm trying to read a CSV file and I have three fields that I'm supposed to read in and the very last field is an integer and I am crashing on the last line of the file with stoi function since there is no newline character and I am not sure how to detect when I am on the last line. The first two getline statements are reading the first two fields and my third getline is reading and expecting an integer and my delimiter for that one only is '\n' but this will not work for the very last line of input and I was wondering was there any workaround for this?
My field types that I am expecting are [ int, string, int ] and I have to include spaces with the middle field so I don't think using stringstream for that will be effective
while (! movieReader.eof() ) { // while we haven't readched end of file
stringstream ss;
getline(movieReader, buffer, ','); // get movie id and convert it to integer
ss << buffer; // converting id from string to integer
ss >> movieid;
getline(movieReader, movieName, ','); // get movie name
getline(movieReader, buffer, '\n');
pubYear = stoi(buffer); // buffer will be an integer, the publish year
auto it = analyze.getMovies().emplace(movieid, Movie(movieid, movieName, pubYear ) );
countMovies++;
}
For reading and writing objects one would idomatically overload the stream extraction and stream insertion operators:
Sample csv:
1, The Godfather, 1972
2, The Shawshank Redemption, 1994
3, Schindler's List, 1993
4, Raging Bull, 1980
5, Citizen Kane, 1941
Code:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iterator>
void skip_to(std::istream &is, char delim) // helper function to skip the rest of a line
{
char ch;
while ((ch = is.get()) && is && ch != delim);
}
std::istream& eat_whitespace(std::istream &is) // stream manipulator that eats up whitespace
{
char ch;
while ((ch = is.peek()) && is && std::isspace(static_cast<int>(ch)))
is.get();
return is;
}
class Movie
{
int movieid;
std::string movieName;
int pubYear;
friend std::istream& operator>>(std::istream &is, Movie &movie)
{
Movie temp; // use a temporary to not mess up movie with a half-
char ch; // extracted dataset if we fail to extract some field.
if (!(is >> temp.movieid)) // try to extract the movieid
return is;
if (!(is >> std::skipws >> ch) || ch != ',') { // read the next non white char
is.setf(std::ios::failbit); // and check its a comma
return is;
}
is >> eat_whitespace; // skip all whitespace before the movieName
if (!std::getline(is, temp.movieName, ',')) { // read the movieName up to the
return is; // next comma
}
if (!(is >> temp.pubYear)) // extract the pubYear
return is;
skip_to(is, '\n'); // skip the rest of the line (or till eof())
is.clear();
movie = temp; // all went well, assign the temporary
return is;
}
friend std::ostream& operator<<(std::ostream &os, Movie const &movie)
{
os << "Nr. " << movie.movieid << ": \"" << movie.movieName << "\" (" << movie.pubYear << ')';
return os;
}
};
int main()
{
char const * movies_file_name{ "foo.txt" };
std::ifstream is{ movies_file_name };
if (!is.is_open()) {
std::cerr << "Couldn't open \"" << movies_file_name << "\"!\n\n";
return EXIT_FAILURE;
}
std::vector<Movie> movies{ std::istream_iterator<Movie>{is},
std::istream_iterator<Movie>{} };
for (auto const & m : movies)
std::cout << m << '\n';
}
Output:
Nr. 1: "The Godfather" (1972)
Nr. 2: "The Shawshank Redemption" (1994)
Nr. 3: "Schindler's List" (1993)
Nr. 4: "Raging Bull" (1980)
Nr. 5: "Citizen Kane" (1941)
I've seen other people reporting this problem with cin.ignore() and getline(). I understand it's some problem involving newlines, but I'm not entirely sure how to debug this with >>. I'm trying to implement a gradebook that takes in a student name and test grades and outputs their name (and eventually, course grade) [from Chapter 4 of Accelerated C++]. I'm having trouble even outputting the names properly, though.
// Student.cpp
#include "Student.h"
#include <iostream>
#include <vector>
istream& read(istream& in, Student& s) {
in >> s.name >> s.midterm;
read_hw(in, s.homework);
return in;
}
istream& read_hw(istream& in, vector<double>& hw) {
if (in) {
hw.clear();
double x;
while (in >> x)
hw.push_back(x);
in.clear();
}
return in;
}
And here I try to test it with my main function:
int main() {
vector<Student> students;
Student curr_student;
while (read(cin, curr_student)) {
cout << curr_student.name;
students.push_back(curr_student);
cout << students.size() << endl;
}
cout << students.size() << endl;
for (int i = 0; i < students.size(); i++) {
cout << students[i].name << endl;
}
return 0;
}
When I input something into the command line, though, the output of the student names after the first one are cut off:
Input in terminal:
Alice 50 50 50 50 (<enter>)
Bob 100 100 100 100 (<enter>)
Carl 50 50 50 50 (<enter>)
(<Ctrl-D>)
And then it outputs:
Alice
ob
rl
From the look of the result the grades are read using hex format: B, C, and a are vaild hex digits. That shouldn't be happen according to your cide, though.
In any case note that formatted reading will normally skip all leading white space, including newlines. There are a few approaches to deal with line ends. The usual one is to resd lines into a std::string and use the string to initialize an std::istringstream.
An alternative approach would be the use of a custom definition of line ends by specializing the std::ctype<char> facet to not consider newline a space character. Another approach is using a manipulator which consumes whitespace but sets std::ios_base::failbit upon encountering a newline. For example:
std::istream& skip(std::istream& in) {
if (std::istream::sentry kerberos{in, true}) {
std::istreambuf_iterator<char> it(in), end;
if (end != (it = std::find_if(it, end, [](unsigned char c){
return !std::isspace(c) || char(c) == '\n'; }))
&& *it == '\n') {
++it;
in.setstate(std::ios_base::failbit);
}
}
return in;
}
// ...
if (in) {
while (in >> skip >> x) {
hw.push_back(x);
}
if (!hw.empty()) {
in.clear();
}
}
While I don't think I'd be able that the original code actually reproduces the problem I'm fairly sure that the above approach does fix it!
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;
}
I'm having an issue with istringstream not storing the values it reads. Here is what I have:
if(inputFile.good()){ //Make sure file is open before trying to work with it
//Begin Working with information
cout << "\tIn File: " << input << endl;
cout << "------------------------------------" << endl;
int number_of_lines = 0;
std::string line;
while (std::getline(inputFile, line)){
++number_of_lines;
}
Time times[number_of_lines];
double math[number_of_lines];
std::string input;
int hh, mm;
for(int loop=0;loop<number_of_lines;loop++){
std::getline(inputFile, input);
std::istringstream(input) >> mm >> hh >> math[loop];
cout << "hours = " << hh << endl;
times[loop].setTimeHours(hh);
times[loop].setTimeMinutes(mm);
times[loop].show();
cout << "*" << math[loop] << endl;
}
std::cout << "Number of lines in text file: " << number_of_lines << "\n" << endl;
}else{
cout << "Could not open file!!!" << endl;
}
The file I'm reading looks like this:
90 1 3.0
1 1 100.0
2 34 5.1
And the output when I run:
In File: data04.txt
------------------------------------
hours = 0
Operation To Be Done = 0:2336552*1.15384e-317
hours = 0
Operation To Be Done = 0:2336552*1.58101e-322
hours = 0
Operation To Be Done = 0:2336552*1.15397e-317
Number of lines in text file: 3
Anyone know why its not storing the values?
There are several key problems in this code
It doesn't check if inputs are successful. You always need to make sure you verify that the input operations worked before you process the data you read. Failing so will cause random data to be processed.
You first read to the end of the stream and then hope that the stream magically restarted. That won't work. Read the stream just once and keep appending to a std::vector<Time> (or similar container). Aside from only traversing the file once, on UNIXes the file size can change while reading.
C++ doesn't have variable sized arrays although some compiler may offer an extension similar to C's variable sized array. In C++ you'd use a std::vector<Time> instead.
First and foremost, your program is wrong. After the while loop ends, there is nothing more to read in the file (unless you seekg() back to the beginning), so the std::getline() call in the for loop body basically does nothing.
A second problem is that concerns are not properly separated.
Here is how I would have implemented this program:
struct line_data
{
Time t;
double x;
};
// This handles reading a single Time value.
std::istream & operator >> (std::istream & is, Time & t)
{
int hh, mm;
if (is >> hh >> mm)
{
// Not happy with the following two lines, too Java-like. :-(
t.setTimeHours(hh);
t.setTimeMinutes(mm);
}
return is;
}
// This handles reading a single line of data.
std::istream & operator >> (std::istream & is, line_data & ld)
{
std::string s;
if (std::getline(is, s))
{
std::istringstream iss(s);
// Ensure errors are propagated from iss to is.
if (!(iss >> ld.t >> ld.x))
is.setstate(std::ios::failbit);
}
return is;
};
// This handles processing a single line of data.
struct line_manip // satisfies concept OutputIterator<line_data>
{
std::back_insert_iterator<std::vector<Time>> ti;
std::back_insert_iterator<std::vector<double>> xi;
line_manip(std::vector<Time> & ts, std::vector<double> & xs)
: ti(std::back_inserter(ts))
, xi(std::back_inserter(xs))
{
}
line_manip & operator = (const line_data & ld)
{
ti = ld.t;
xi = ld.x;
return *this;
}
line_manip & operator * () { return *this; }
line_manip & operator ++ () { return *this; }
line_manip & operator ++ (int) { return *this; }
};
int main()
{
std::ifstream ifs("input.txt");
std::vector<Time> ts;
std::vector<double> xs;
std::copy(std::istream_iterator<line_data>(ifs),
std::istream_iterator<line_data>(),
line_manip(ts, xs));
// ...
}