Filestream input kickin my arse

D

drmario

I normally hate asking for help when I feel like I should be able to get
something on my own. But after about 10 hours of trying to figure out where
I'm going wrong, I have to surrender.

I need to open a file and read the data in. It will be in this format:

Gali leo 100.0
Michael Angelo 100.00
Mario Hollibaugh 99.4
Christopher Polites 92.7
Rodney Monson 100
Michael Jordan 74.28
Bill Gates 95
Joanof Arc 89.6
How Now 94
Dane Cook 98.8

It's supposed to be first name, last name, test score. The only thing
guaranteed about the format is that it will be:

(x)firstname(x)lastname(x)grade(x)

where x=any possible number of spaces or tabs. I'm not allowed to use
getline, and I can't use the datatype string. I have to read the first and
last name as cstrings, and then read the grade in as double. If the person
who builds the text file puts in an entry like:

95 Angelo 100.00

well then that's their fault and the output will list 95 as Angelo's first
name. However, if they make such an entry:

Michael Angelo b

he simply ends up without a grade recorded.

The general algorithm I'm using is: (I will omit EOF checks for simplicity's
purpose)
1) If leading whitespace exists: (if ifstream.peek()==' ' ||
ifstream.peek()=="\t")
use ifstream.get to read it into a junk char var
2) Once first char of first name is found
use ifstream.get to read the chars of the first name, one by one,
into a string var
3) Once last char of first name is found
insert ' ' after first name using simple assignment
repeat all that for last name
4) a simlple ifstream >> to read the grade into double variable (which
should ignore all whitespace)
5) Check if ifstream is good with if(!ifstream), in other words check if
the user was a moron and can't follow formatting instructions. If not,
clear the stream.
6) Ignore all characters (using numeric limits) until I reach the '\n'
character.
7) Repeat on next line

It's working perfectly for the first line, but for some reason it messes up
after that. Furthermore, when I put a cout << test inside my if(!ifstream),
it gets printed up on the screen a dozen times. I can't understand why.
Nothing in my sample file should be causing input failure.

If I haven't bored you to death, any suggestions? :) Thanks guys!

cheers,
Mario
 
M

Michael.Boehnisch

I normally hate asking for help when I feel like I should be able to get
something on my own. But after about 10 hours of trying to figure out where
I'm going wrong, I have to surrender.

Try this one, some STL magic but no std::strings (Note, the main()
function does not need any variables other than the container for the
score records and the program contains no explicit loops, checking of
input format, or checking of stream state :)

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <list>

class ScoreRecord {

friend std::istream& operator >> ( std::istream&, ScoreRecord& );
friend std::eek:stream& operator << ( std::eek:stream&, const
ScoreRecord& );

public:

const char* FirstName() const { return firstname; }
const char* LastName() const { return lastname; }
double Score() const { return score; }

private:

char firstname[1000];
char lastname[1000];
double score;

}; // class ScoreRecord

int main() {
std::list<ScoreRecord> scores;

// Read the datafile
std::copy(
std::istream_iterator<ScoreRecord>( std::ifstream( "data.txt" ) ),
std::istream_iterator<ScoreRecord>(),
std::back_inserter( scores )
);

// Dump the records to the console.
std::copy(
scores.begin(),
scores.end(),
std::eek:stream_iterator<ScoreRecord>( std::cout, "\n" )
);

return 0;

} // main()

std::istream& operator >> ( std::istream& is, ScoreRecord& srecord ) {
char score[1000];

is >> srecord.firstname
>> srecord.lastname
>> score;

srecord.score = atof( score );
// returns 0 in case of garbage score

return is;
}

std::eek:stream& operator << ( std::eek:stream& os, const ScoreRecord&
srecord ) {
return os
<< srecord.FirstName() << ' '
<< srecord.LastName() << ' '
<< srecord.Score();
}

best,

Michael
 
D

drmario

Jeeze Michael thanks for that advice!! I think I'm gonna go ahead and write
it that way just to learn about all those standard temp lib classes.
Unfortunately this is for a fairly basic class in computer programming and I
think the professor assumed we wouldn't know about the STL so didn't bother
to say we can't use it. I'm gonna ask him if I can use the STLs anyway, but
I think what he wants is for us to have to come up with a way to do this
using only the basic functions on cstrings (strcpy, >>, get, getline, etc.
etc.). But thank you for your input. Regardless of what my prof says I'm
gonna explore your solution. Thanks!!

Mario


I normally hate asking for help when I feel like I should be able to get
something on my own. But after about 10 hours of trying to figure out
where
I'm going wrong, I have to surrender.

Try this one, some STL magic but no std::strings (Note, the main()
function does not need any variables other than the container for the
score records and the program contains no explicit loops, checking of
input format, or checking of stream state :)

#include <algorithm>
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <iterator>
#include <list>

class ScoreRecord {

friend std::istream& operator >> ( std::istream&, ScoreRecord& );
friend std::eek:stream& operator << ( std::eek:stream&, const
ScoreRecord& );

public:

const char* FirstName() const { return firstname; }
const char* LastName() const { return lastname; }
double Score() const { return score; }

private:

char firstname[1000];
char lastname[1000];
double score;

}; // class ScoreRecord

int main() {
std::list<ScoreRecord> scores;

// Read the datafile
std::copy(
std::istream_iterator<ScoreRecord>( std::ifstream( "data.txt" ) ),
std::istream_iterator<ScoreRecord>(),
std::back_inserter( scores )
);

// Dump the records to the console.
std::copy(
scores.begin(),
scores.end(),
std::eek:stream_iterator<ScoreRecord>( std::cout, "\n" )
);

return 0;

} // main()

std::istream& operator >> ( std::istream& is, ScoreRecord& srecord ) {
char score[1000];

is >> srecord.firstname
srecord.score = atof( score );
// returns 0 in case of garbage score

return is;
}

std::eek:stream& operator << ( std::eek:stream& os, const ScoreRecord&
srecord ) {
return os
<< srecord.FirstName() << ' '
<< srecord.LastName() << ' '
<< srecord.Score();
}

best,

Michael
 
J

James Kanze

I normally hate asking for help when I feel like I should be
able to get something on my own. But after about 10 hours of
trying to figure out where I'm going wrong, I have to
surrender.
I need to open a file and read the data in. It will be in this format:
Gali leo 100.0
Michael Angelo 100.00
Mario Hollibaugh 99.4
Christopher Polites 92.7
Rodney Monson 100
Michael Jordan 74.28
Bill Gates 95
Joanof Arc 89.6
How Now 94
Dane Cook 98.8
It's supposed to be first name, last name, test score. The only thing
guaranteed about the format is that it will be:

where x=any possible number of spaces or tabs. I'm not
allowed to use getline, and I can't use the datatype string.

In which case, I'd strongly suggest finding a different course,
because this one won't teach you anything good about C++. (If I
had to implement it in a context where std::getline and
std::string weren't available, the first thing I'd do is
implement something similar to them.)
I have to read the first and last name as cstrings, and then
read the grade in as double. If the person who builds the
text file puts in an entry like:
95 Angelo 100.00
well then that's their fault and the output will list 95 as
Angelo's first name. However, if they make such an entry:
Michael Angelo b
he simply ends up without a grade recorded.

And what do they require if the line is just:
xxx
If I understand correctly, the requirement is that any error in
input format results in undefined behavior, and you don't have
to program for it. Which is just wrong, but in that case:
std::cin >> firstName >> lastName >> grade ;
should do the trick. (Provided you've defined an appropriate
type for firstName and lastName. You should never input like
this to a char[].)
The general algorithm I'm using is: (I will omit EOF checks
for simplicity's purpose)
1) If leading whitespace exists: (if ifstream.peek()==' ' ||
ifstream.peek()=="\t")
use ifstream.get to read it into a junk char var

You don't have to read into anything. Just source.get(). Or
just use:
source >> std::ws ;
However:

You really should be inputting into a type which follows the
standard input conventions. The most basic standard convention
is that the >> operator look like:

std::istream&
operator>>( std::istream& source, SomeType& dest )
{
std::istream::sentry s( source ) ;
if ( s ) {
// actual input here...
}
return source ;
}

The constructor of the sentry object will take care of skipping
white space.

Note that the system's definition of white space includes new
lines. The standard way (IMHO the only "correct" way) of
handling this is to read line by line (using getline---but you
should easily be able to write an equivalent function yourself),
then parse using the standard extraction operators on an
istringstream (or an istrstream) initialized with the line.
2) Once first char of first name is found
use ifstream.get to read the chars of the first name, one by one,
into a string var

I though you weren't allowed to use string.
3) Once last char of first name is found
insert ' ' after first name using simple assignment
repeat all that for last name
4) a simlple ifstream >> to read the grade into double variable (which
should ignore all whitespace)

Reading into an std::string variable, or a char[], also ignores
all whitespace.

Including newline. If newline is significant as a record
separator, read complete lines, then use istringstream to read
the fields in the line.
5) Check if ifstream is good with if(!ifstream), in other words check if
the user was a moron and can't follow formatting instructions. If not,
clear the stream.
6) Ignore all characters (using numeric limits) until I reach the '\n'
character.
7) Repeat on next line
It's working perfectly for the first line, but for some reason
it messes up after that. Furthermore, when I put a cout <<
test inside my if(!ifstream), it gets printed up on the screen
a dozen times. I can't understand why. Nothing in my sample
file should be causing input failure.

Without seeing exactly what is happening, it's hard to say. But
in general, the restrictions you specify seem awfully
artificial. Unless the goal is to teach you how to parse a
source on which you cannot back up (in which case, either a
simple state machine or a recursive descent parser can be used).
Note, however, that all >> operators use whitespace as
delimiter, and skip leading whitespace. And that they consider
a new line as whitespace, and will skip it as well.
 
D

drmario

I don't really have the restrictions that I implied I did. The
professor said we can do it however we want, but gave no guidelines as to
how much error/format checking is involved. So of course most students will
use proven libraries (which is smart of course, cuz it's faster and more
efficient), and assume that everything will be formatted 100% correctly, in
order to do the least ammount of work.

Not me. I don't like using libraries when I'm first learning, and I
hate wizards in that capacity too. I like to know what's going on behind
the scenes before I start using libraries that encapsulate that
functionality. If it were up to me I'd write every program in assembly
first, to understand what was going on, then rewrite in a higher language.
So the restrictions you saw were self-imposed to force an understanding of
I/O. It works too cuz a few days ago I knew only basics about C++ I/O.
Today I'm digging through the debugger values for my ifstream variable
looking for the value that represents ios_base::iostate. I wanna find out
exactly when the input failure is occuring.

Thanks for responding though, that was a lotta helpful stuff you wrote!!



regards,

Mario





I normally hate asking for help when I feel like I should be
able to get something on my own. But after about 10 hours of
trying to figure out where I'm going wrong, I have to
surrender.
I need to open a file and read the data in. It will be in this format:
Gali leo 100.0
Michael Angelo 100.00
Mario Hollibaugh 99.4
Christopher Polites 92.7
Rodney Monson 100
Michael Jordan 74.28
Bill Gates 95
Joanof Arc 89.6
How Now 94
Dane Cook 98.8
It's supposed to be first name, last name, test score. The only thing
guaranteed about the format is that it will be:

where x=any possible number of spaces or tabs. I'm not
allowed to use getline, and I can't use the datatype string.

In which case, I'd strongly suggest finding a different course,
because this one won't teach you anything good about C++. (If I
had to implement it in a context where std::getline and
std::string weren't available, the first thing I'd do is
implement something similar to them.)
I have to read the first and last name as cstrings, and then
read the grade in as double. If the person who builds the
text file puts in an entry like:
95 Angelo 100.00
well then that's their fault and the output will list 95 as
Angelo's first name. However, if they make such an entry:
Michael Angelo b
he simply ends up without a grade recorded.

And what do they require if the line is just:
xxx
If I understand correctly, the requirement is that any error in
input format results in undefined behavior, and you don't have
to program for it. Which is just wrong, but in that case:
std::cin >> firstName >> lastName >> grade ;
should do the trick. (Provided you've defined an appropriate
type for firstName and lastName. You should never input like
this to a char[].)
The general algorithm I'm using is: (I will omit EOF checks
for simplicity's purpose)
1) If leading whitespace exists: (if ifstream.peek()==' ' ||
ifstream.peek()=="\t")
use ifstream.get to read it into a junk char var

You don't have to read into anything. Just source.get(). Or
just use:
source >> std::ws ;
However:

You really should be inputting into a type which follows the
standard input conventions. The most basic standard convention
is that the >> operator look like:

std::istream&
operator>>( std::istream& source, SomeType& dest )
{
std::istream::sentry s( source ) ;
if ( s ) {
// actual input here...
}
return source ;
}

The constructor of the sentry object will take care of skipping
white space.

Note that the system's definition of white space includes new
lines. The standard way (IMHO the only "correct" way) of
handling this is to read line by line (using getline---but you
should easily be able to write an equivalent function yourself),
then parse using the standard extraction operators on an
istringstream (or an istrstream) initialized with the line.
2) Once first char of first name is found
use ifstream.get to read the chars of the first name, one by one,
into a string var

I though you weren't allowed to use string.
3) Once last char of first name is found
insert ' ' after first name using simple assignment
repeat all that for last name
4) a simlple ifstream >> to read the grade into double variable (which
should ignore all whitespace)

Reading into an std::string variable, or a char[], also ignores
all whitespace.

Including newline. If newline is significant as a record
separator, read complete lines, then use istringstream to read
the fields in the line.
5) Check if ifstream is good with if(!ifstream), in other words check if
the user was a moron and can't follow formatting instructions. If not,
clear the stream.
6) Ignore all characters (using numeric limits) until I reach the '\n'
character.
7) Repeat on next line
It's working perfectly for the first line, but for some reason
it messes up after that. Furthermore, when I put a cout <<
test inside my if(!ifstream), it gets printed up on the screen
a dozen times. I can't understand why. Nothing in my sample
file should be causing input failure.

Without seeing exactly what is happening, it's hard to say. But
in general, the restrictions you specify seem awfully
artificial. Unless the goal is to teach you how to parse a
source on which you cannot back up (in which case, either a
simple state machine or a recursive descent parser can be used).
Note, however, that all >> operators use whitespace as
delimiter, and skip leading whitespace. And that they consider
a new line as whitespace, and will skip it as well.
 
J

James Kanze

I don't really have the restrictions that I implied I did.
The professor said we can do it however we want, but gave no
guidelines as to how much error/format checking is involved.

In which case, I would guess that not handling errors graciously
would result in a definitely lower grade.
So of course most students will use proven libraries (which is
smart of course, cuz it's faster and more efficient),

You'd be surprised. Most students aren't that intelligent, and
will reinvent the wheel, rather than looking things up to do
them correctly.
and assume that everything will be formatted 100% correctly,
in order to do the least ammount of work.

Assuming 100% correct formatting is an error, IMHO. Unless
you've been explicitly told that it's OK (since the goal of the
project is to teach something else).
Not me. I don't like using libraries when I'm first learning,

The line between standard libraries and the compiler is a fine
one. In this case, as I said, if I didn't have the standard
library, the first thing I'd do is write some sort of string
class and a getline function. (In fact, I learned C++ long
before there was a standard library, a my string class was
probably the first thing I wrote. Like most other people at the
time.)

In the case of I/O, of course, you almost have to use the
standard library if you want your code to be portable. The
system level calls are not the same for Unix and for Windows.
and I hate wizards in that capacity too. I like to know
what's going on behind the scenes before I start using
libraries that encapsulate that functionality. If it were up
to me I'd write every program in assembly first, to understand
what was going on, then rewrite in a higher language.

That's the way I learned. But I'm not sure it's the most
effective way. I actually started with transistors, learned how
to implement gates with them, then how do put the gates together
to create registers and an adder. Before I wrote my first line
or assembler. So where to you stop?

The important reason for using a high level language is the
abstraction. The fact that you don't have to understand all
this to use it. And that even if you do understand it, you more
or less forget it when using the language.
So the restrictions you saw were self-imposed to force an
understanding of I/O. It works too cuz a few days ago I knew
only basics about C++ I/O. Today I'm digging through the
debugger values for my ifstream variable looking for the value
that represents ios_base::iostate. I wanna find out exactly
when the input failure is occuring.

Have fun:). One really major weakness of I/O, both in C and in
C++, is that their error handling tends to mask the real causes.

Personally, I think you'd be better off doing thing the opposite
way. Write something that works first, then try to undestand
why it works: what services the library is providing, for
example. The try replacing some parts of the library with your
own code.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top