Reputation: 2842
Short Version of the Question
If I'm reading in data like so:
while (in >> x) {
hw.push_back(x);
}
// clear the stream so that input will work for the next student
in.clear();
where in
is a std::istream
, x
is a double
and hw
is a vector<double>
. Wouldn't I need to put back whatever read attempt caused me to break out of the while loop? Otherwise, wouldn't my next read from in
skip some data?
Full Question
I'm trying to read from a text file that contains a string and a series of numbers. I'm processing this data into a struct which contains member attributes for this data.
The input data looks as follows:
Moore 75 85 77 59 0 85 75 89
This data represents a student's name, their final exam grade, their midterm exam grade, and some homework.
And to read such data in, I have the following:
#include <boost/format.hpp>
#include <iostream>
#include <string>
using std::istream;
using std::vector;
using std::cout;
using std::string;
using std::cin;
struct Student_info {
std::string name;
double midterm, final;
std::vector<double> homework;
};
istream& read(istream&, Student_info&);
istream& read_hw(istream&, vector<double>&);
istream& read(istream& is, Student_info& s) {
// Read and store th studen's name and midterm and final exam grades
is >> s.name >> s.midterm >> s.final;
read_hw(is, s.homework); // read and store all the student's homework grades
return is;
}
istream& read_hw(istream& in, vector<double>& hw)
{
if (in) {
// get rid of previous contents
hw.clear();
// read homework grades
double x;
while (in >> x) {
hw.push_back(x);
}
// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
In short, and if my understanding is correct, I first read the name, then two doubles (final and midterm exam) and then however many homeworks are present.
I know when to stop reading into a vector<double>
of homework grades with the following:
while (in >> x) {
hw.push_back(x);
}
// clear the stream so that input will work for the next student
in.clear();
This all looks perfectly sensible to me, but when I read in a series of data lines, the data does not get read in properly.
For example, with the following input:
Moo 100 100 100 100 100 100 100 100
Moore 75 85 77 59 0 85 75 89
Norman 57 78 73 66 78 70 88 89
I get the following output:
Name: Moo, Midterm: 100, Final: 100, Num HW: 6
Name: Moore, Midterm: 75, Final: 85, Num HW: 6
Name: orman, Midterm: 57, Final: 78, Num HW: 6
Notice the name is orman, NOT Norman. The N is missing. That's not a typo, that's the problem I'm trying to understand.
It would seem to me I need to "unget", though when I try to literally call in.unget()
it doesn't improve things.
Below is some full input data and the complete source for a driver program if anyone wants to try this out in their hands:
Full Input Data
Moo 100 100 100 100 100 100 100 100
Moore 75 85 77 59 0 85 75 89
Norman 57 78 73 66 78 70 88 89
Olson 89 86 70 90 55 73 80 84
Peerson 47 70 82 73 50 87 73 71
Russel 72 87 88 54 55 82 69 87
Thomas 90 96 99 99 100 81 97 97
Vaughn 81 97 99 67 40 90 70 96
Westerly 43 98 96 79 100 82 97 96
Baker 67 72 73 40 0 78 55 70
Davis 77 70 82 65 70 77 83 81
Edwards 77 72 73 80 90 93 75 90
Franklin 47 70 82 73 50 87 73 71
Jones 77 82 83 50 10 88 65 80
Harris 97 90 92 95 100 87 93 91
Smith 87 92 93 60 0 98 75 90
Carpenter 47 90 92 73 100 87 93 91
Fail1 45 55 65 80 90 70 65 60
Fail2 55 55 65 50 55 60 65 60
Full Source of Driver Program
#include <boost/format.hpp>
#include <iostream>
#include <string>
using std::istream;
using std::vector;
using std::cout;
using std::string;
using std::cin;
struct Student_info {
std::string name;
double midterm, final;
std::vector<double> homework;
};
istream& read(istream&, Student_info&);
istream& read_hw(istream&, vector<double>&);
istream& read(istream& is, Student_info& s) {
// Read and store th studen's name and midterm and final exam grades
is >> s.name >> s.midterm >> s.final;
read_hw(is, s.homework); // read and store all the student's homework grades
return is;
}
istream& read_hw(istream& in, vector<double>& hw)
{
if (in) {
// get rid of previous contents
hw.clear();
// read homework grades
double x;
while (in >> x) {
hw.push_back(x);
}
// clear the stream so that input will work for the next student
in.clear();
}
return in;
}
int main() {
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;
while (read(cin, record)) {
// find length of longest name
cout << boost::format("Name: %1%, Midterm: %2%, Final: %3%, Num HW: %4%\n") % record.name % record.midterm % record.final % record.homework.size();
students.push_back(record);
}
return 0;
}
Using the full input data, the output looks like this (notice many names have been incorrectly):
Name: Moo, Midterm: 100, Final: 100, Num HW: 6
Name: Moore, Midterm: 75, Final: 85, Num HW: 6
Name: orman, Midterm: 57, Final: 78, Num HW: 6
Name: Olson, Midterm: 89, Final: 86, Num HW: 6
Name: rson, Midterm: 47, Final: 70, Num HW: 6
Name: Russel, Midterm: 72, Final: 87, Num HW: 6
Name: Thomas, Midterm: 90, Final: 96, Num HW: 6
Name: Vaughn, Midterm: 81, Final: 97, Num HW: 6
Name: Westerly, Midterm: 43, Final: 98, Num HW: 6
Name: ker, Midterm: 67, Final: 72, Num HW: 6
Name: vis, Midterm: 77, Final: 70, Num HW: 6
Name: wards, Midterm: 77, Final: 72, Num HW: 6
Name: ranklin, Midterm: 47, Final: 70, Num HW: 6
Name: Jones, Midterm: 77, Final: 82, Num HW: 6
Name: Harris, Midterm: 97, Final: 90, Num HW: 6
Name: Smith, Midterm: 87, Final: 92, Num HW: 6
Name: rpenter, Midterm: 47, Final: 90, Num HW: 6
Name: l1, Midterm: 45, Final: 55, Num HW: 6
Name: l2, Midterm: 55, Final: 55, Num HW: 6
Update 1
I tried adding in.seekg(-1, in.cur);
after breaking out of the following while loop:
double x;
while (in >> x) {
hw.push_back(x);
}
// Going to try and get the istream back to where it was when I broke out of the while loop.
in.seekg(-1, in.cur);
// clear the stream so that input will work for the next student
in.clear();
Thinking this would take me back to whatever caused me to break out of the while loop. But still Student names are still not being read in correctly
Update 2
I see there is a nearly identical question here:
Why is istream.clear() removing part of my strings while reading doubles and strings?
However the accepted solution does not explain why what is being done here is wrong -- it just offers a workaround.
Update 3
I appreciate all the workarounds, but consider this more focused question, why doesn't every subsequent line have a letter missing here or there? Only some lines do.
Upvotes: 2
Views: 113
Reputation: 141564
The reason for the weird extraction is that the letters ABCDEFINP
can occur in a double
and the others can't. See strtof spec for detail.
This is a fundamental problem with stream I/O without lookahead. The standard specifies (roughly speaking) that extraction continues until finding a character that can't occur in the destination type, and then try to convert what was extracted. There have been various tweaks to the spec (including changing the list of valid characters for a double) over the years but no real solution.
There's no provision to put back characters on conversion failure , you will have to use a different extraction method. As suggested in the other answer: since your input is line-oriented (i.e. newlines are significant) it would be good to use a line-oriented read function to read a line, and then parse a line. Your method of using >>
until error has no way of breaking at newlines (that operator treats all whitespace as identical).
Upvotes: 3
Reputation: 96810
There's usually no need to use unget()
on standard streams. The problem you have is that you need to know when to stop reading a line. The function std::getline
is made for this purpose. You can iterate through each line, store that line in a std::istringstream
, and parse out the record from there:
std::istream& read_hw(std::istream& in, std::vector<double>& hw) {
hw.clear();
std::string line;
if (std::getline(in, line)) {
hw.assign(
std::istream_iterator<double>{std::istringstream{line} >> std::skipws}, {}
);
}
return in;
}
Upvotes: 1