Reading file with header - c++

I have a file similar to the one below
#
#
1 2 3
4 5 6
7 8 9
I want to ignore lines starting with an '#' character. My current code to parse the file is straightforward.
string line;
while(getline(in, line)) {
if(line[0] == '#')
continue
// do something with line
}
The amount of # tokens in the file will be small and will always occur at the beginning of the file, but I don't want to have to go through the if check after every read. I would rather read the header section in a separate function, then start reading the desired data without the need for the if check. How can I do this?

Of course you can do something like::
do{
getline(in, line);
}while(line[0] == '#')
do{
//do something with line
}while(/* not EOF*/)
But you might also be interested in the famous answer about branch prediction. It basically tells you that processors are usually very good at "guessing" the right outcome of an if-statement, especially if, as you stated, after a few lines the outcome will be always the same. So your version should not only be pretty much the same speed but also be valid in case there is another line starting with '#' later in your file.

Anedar is correct, you could also do:
string line;
while(getline(in, line))
{
if(line[0] == '#'){
continue;
//do something
}
else{
break;
}
}
some people like do/while loops, some don't, whatever floats your boat or is the standard you have the write by. :)

Related

C++: Using getline to input from a text file either skips the first line or messes up the rest

I'm trying to read in from a specially formatted text file to search for specific names, numbers, etc. In this case I want to read the first number, then get the name, then move on to the next line. My problem seems to be with while loop condition for reading through the file line by line. Here is a sample of the txt file format:
5-Jon-4-Vegetable Pot Pie-398-22-31-Tue May 07 15:30:22
8-Robb-9-Pesto Pasta Salad-143-27-22-Tue May 07 15:30:28
1-Ned-4-Vegetable Pot Pie-398-22-31-Tue May 07 15:30:33
I'll show you two solutions I've tried, one that skips the first line in the file and one that doesn't take in the very last line. I've tried the typical while(!iFile.eof()) as a last ditch effort but got nothing.
transactionLog.clear();
transactionLog.seekg(0, std::ios::beg);
std::string currentName, line, tempString1, tempString2;
int restNum, mealNum;
bool nameFound = false;
int mealCount[NUMMEALS];
std::ifstream in("patronlog.txt");
while(getline(in, line))
{
getline(in, tempString1, '-');
getline(in, currentName, '-');
if(currentName == targetName)
{
if(getline(in, tempString2, '-'))
{
mealNum = std::stoi(tempString2);
mealCount[mealNum - 1] += 1;
nameFound = true;
}
}
I believe I understand what's going in this one. The "getline(in, line)" is taking in the first line entirely, and since I'm not using it, it's essentially being skipped. At the very least, it's taking in the first number, followed by the name, and then doing the operations correctly. The following is the modification to the code that I thought would fix this.
while(getline(in, tempString1, '-'))
{
getline(in, currentName, '-');
// same code past here
}
I figured changing the while loop condition to the actual getline of the first item in the text file would work, but now when I look at it through the debugger, on the second loop it sets tempString1 to "Vegetable Pot Pie" rather than the next name on the next line. Ironically though this one does fine on line #1, but not for the rest of the list. Overall I feel like this has gotten me farther from my intended behavior than before.
You need to parse the contents of lines after they are read. You can use a std::istringstream to help you with that.
while(getline(in, line))
{
// At this point, the varible line contains the entire line.
// Use a std::istringstream to parse its contents.
std::istringstream istr(line);
getline(istr, tempString1, '-'); // Use istr, not in.
getline(istr, currentName, '-'); // ditto
...
}

C++ read different kind of datas from file until there's a string beginning with a number

In C++, I'd like to read from an input file which contains different kind of datas: first the name of a contestant (2 or more strings with whitespaces), then an ID (string without whitespaces, always beginning with a number), then another strings without ws and a numbers (the sports and their achieved places).
For example:
Josh Michael Allen 1063Szinyei running 3 swimming 1 jumping 1
I show you the code what I started to write and then stucked..
void ContestEnor::next()
{
string line;
getline(_f , line);
if( !(_end = _f.fail()) ){
istringstream is(line);
is >> _cur.contestant >> _cur.id; // here I don't know how to go on
_cur.counter = 0;
//...
}
}
Thank you for your help in advance.
You should look into using std::getline with a delimiter. This way, you can delimit on a space character and read until you find a string where the first character in a number. Here is a short code example (this seems rather homework-like, so I don't want to write too much of it for you ;):
std::string temp, id;
while (std::getline(_f, temp, ' ')) {
if (temp[0] >= 0 && temp[0] <= '9') {
id = temp;
}
// you would need to add more code for the rest of the data on that line
}
/* close the file, etc. */
This code should be pretty self-explanatory. The most important thing to know is that you can use std::getline to get data up until a delimiter. The delimiter is consumed, just like the default behavior of delimiting on a newline character. Thus, the name getline isn't entirely accurate - you can still get only part of a line if you need to.

I filed my vector from a text file and it wont cout as one line. How can I do this?

Long story short I need my vector to cout as a single line without creating its own new lines for my program to work correctly. the text file i read into the vector was
laptop#a small computer that fits on your lap#
helmet#protective gear for your head#
couch#what I am sitting on#
cigarette#smoke these for nicotine#
binary#ones and zeros#
motorcycle#two wheeled motorized bike#
oj#orange juice#
test#this is a test#
filled the vector using the loop:
if(myFile.is_open())
{
while(getline(myFile, line, '#'))
{
wordVec.push_back(line);
}
cout << "words added.\n";
}
and printed it using this:
for(int i = 0; i < wordVec.size(); i++)
{
cout << wordVec[i];
}
and it outputs as such:
laptopa small computer that fits on your lap
helmetprotective gear for your head
couchwhat I am sitting on
cigarettesmoke these for nicotine
binaryones and zeros
motorcycletwo wheeled motorized bike
ojorange juice
testthis is a test
my program works if I manually input the words and add them to my data structure but if added from the vector which is filled via text file, half of the program doesnt work. before anyone says asks for a better description of the problem, all I need to know is how to fill the vector so that it will output as a single line.
You code getline(myFile, line, '#') reads everything up to end-of-file or the next '#' into line - that includes any newlines. So, as you read text file content...
laptop#a small computer that fits on your lap#
helmet#protective gear for your head#
...which you could also think of as...
"laptop#a small computer that fits on your lap#\nhelmet#protective gear for your head#"
...line takes on successive values...
"laptop"
"a small computer that fits on your lap"
"\nhelmet"
...etc....
Note the newline in "\nhelmet".
There are many ways to avoid or correct this, such as...
while ((myFile >> std::skipws) and getline(myFile, line, '#'))
...
...or...
if (not line.empty() and line[0] == '\n')
line.erase(0, 1);
...or (as Barry suggests in comments)...
while (getline(myFile, line))
{
std::istringstream iss(line);
std::string field;
while (getline(iss, field, '#'))
...
}
while(getline(myFile, line, '#'))
Here, you told std::getline to use the '#' character instead of a newline, '\n', as a delimiter.
So, this simply means that std::getline will no longer think there's anything special about '\n'. It's just another character that std::getline() will keep reading, looking for the next #.
So, you end up reading newline characters into your individual strings, and then outputing them to std::cout, as part of the strings you've printed.

copying after a line has been found from a file from that position till the end of that file in c++

I have a file which holds protein coordinates as well as other information preceding it. My aim is to look for a certain line called "$PARAMETERS" and then copy from there every line succeeding it till the end of the file.
How can I get that done? This is the small code I wrote part of the entire program (that someone else wrote years ago, and I took over to upgrade his code for my research):
ifstream InFile;
InFile.open (DC_InFile.c_str(), ios::in);
while ( not InFile.eof() )
{
Line = NextLine (&InFile);
if (Line.find ("#") == 0) continue; // skip lines starting with # (comments)
if (Line.length() == 0) continue; // skip empty lines
size_t pos = Line.find("$PARAMETERS");
Line.copy(Line.begin("$PARAMETERS")+pos, Line.end("$END"));
&Line.copy >> x_1 >> y_2 >> z_3;
}
Bearing in mind that I defined Line as string
I guess you need to read data between $PARAMETERS and $END, not from $PARAMETERS until end of file. If so, you can use the following code:
string str;
while (getline(InFile, str))
{
if (str.find("#") == 0)
continue;
if (str.length() == 0)
continue;
if (str.find("$PARAMETERS") == 0)
{
double x_1, y_2, z_3; // you want to read numbers, i guess
while (getline(InFile, str))
{
if (str.find("$END") == 0)
break;
stringstream stream(str);
if (stream >> x_1 >> y_2 >> z_3)
{
// Do whatever you want with x_1, y_2 and z_3
}
}
}
}
This will handle multiple sections of data; not sure if you really want this behavior.
For example:
# comment
$PARAMETERS
1 2 3
4 5 6
$END
#unrelated data
100 200 300
$PARAMETERS
7 8 9
10 11 12
$END
I'm not sure what you want on the first line of the copied file but assuming you get that straight and you haven't read beyond the current line, you can copy the tail of the fike you are reading like this:
out << InFile.rdbuf();
Here out is the std::ostream you want to send the data to.
Note, that you should not use InFile.eof() to determine whether there is more data! Instead, you should read what you want to read and then check that the read was successful. You need to check after reading because the stream cannot know what you are trying to read before you have done so.
Following up on Dietmar's answer: it sounds to me like you
should be using std::getline until you find a line which
matches your pattern. If you want that line as part of your
output, then output it, then use Dietmar's solution to copy the
rest of the file. Something like:
while ( std::getline( in, line ) && ! isStartLine( line ) ) {
}
if ( in ) { // Since you might not have found the line
out << line << '\n'; // If you want the matching line
// You can also edit it here.
out << in.rdbuf();
}
And don't put all sorts of complicated parsing information,
with continue and break, in the loop. The results are both
unreadable and unmaintainable. Factor it out into a simple
function, as above: you'll also have a better chance of getting
it right. (In your case, should you match "$PARAMETERS #
xxx", or not?) In a separate function, it's much easier to get
it right.

Reading BSDF data format

I have been required to write a function that reads the BSDF data format defined by Zemax
An example of such file can be found at the following page: BSDF file example
I would like to use, if possible, only standard ifstream functions.
I have already prepared all the necessary datamembers inside a dedicated class.
I am now trying to write the function that reads the data from the file.
Problems:
how do I exclude comment lines? as documented, they start with an hash # I was going for something like
void ReadBSDFFile(myclass &object)
{
ifstream infile;
infile.open(object.BRDFfilename);
char c;
infile.get(c);
while (c == "#") // Problem, apparently I cannot compare in this way. How should I do it?
{
getline(infile, line);
infile.get(c);
}
// at this point I would like to go back one character (because I do not want to lose the non-hash character that ended up in *c*)
infile.seekg(-1, ios_base::cur);
// Do all the rest
infile.close();
}
in a similar way, I would like to verify that I am at the correct line later on (e.g. the "AngleOfIncidence" line). Could I do it in this way?
string AngleInc;
infile >> AngleInc;
if (AngleInc != "AngleOfIncidence")
{
//error
}
Thanks to anyone who will comment/help. Constructive criticism is welcomed.
Federico
EDIT:
Thanks to Joachim Pileborg below, I managed to proceed up to the data blocks part of the file.
Now I have the following problem. When reaching the datablocks, I wrote the following piece of code, but at the second iteration (i = 1) i receive the error message for the TIS line.
Could someone help me understand why this does not work?
Thanks
Note: blocks is the number on the AngleOfIncidence line, rows the one on the ScatterAzimuth line and columns the one on the ScatterRadial. I tested and verified that this part of the function works as desired.
// now reading the data blocks.
for (int i=0; i<blocks; i++)
{
// TIS line
getline(infile, line);
if (line.find("TIS") == string::npos)
{
// if not, error message
}
// Data block
for (int j=0; j<rows; j++)
{
for (int k=0; k<columns; k++)
{
infile >> object.BRDFData[i][j][k];
}
}
}
EDIT 2:
solved adding infile.seekg(+2, ios_base::cur); as a last line of the i loop.
The reading loop could be simplified like this:
std::string line;
while (getline(infile, line))
{
if (line[0] != '#')
{
// Not a comment, do something with the line
if (line.find("AngleOfIncidence") != std::string::npos)
{
// On the AngleOfIncidence line, do special things here
}
}
}
It's might not be optimal, just something written at the top of my head, but should work.
From the description of the format you provided:
Any line that starts with the # symbol is ignored as a comment line.
So what you need to do is the following
Read the file line by line
If the line starts with # ignore it
Otherwise process the line.
The while you have used is wrong. Use the getLine function instead and compare its first character with the #.