copying after a line has been found from a file from that position till the end of that file in c++ - 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.

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.

Reading file with header

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. :)

how to discard from streams? .ignore() doesnt work for this purpose, any other methods?

I have a lack of understanding about streams. The idea is, to read a file to the ifstream and then working with it. Extract Data from the stream to a string, and discard the part which is now in a string from the stream. Is that possible? Or how to handle those problems?
The following method, is for inserting a file which is properly read by the ifstream. (its a text file, containing informations about "Lost" episodes, its an episodeguide. It works fine, for one element of the class episodes. Every time i instantiate a episode file, i want to check the stream of that file, discard the informations about one episode (its indicated by "****", then the next episode starts) and process the informations discarded in a string. If I create a new object of Episode I want to discard the next informations about the episodes after "****" to the next "****" and so on.
void Episode::read(ifstream& in) {
string contents((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
size_t episodeEndPos = contents.find("****");
if ( episodeEndPos == -1) {
in.ignore(numeric_limits<char>::max());
in.clear(), in.sync();
fullContent = contents;
}
else { // empty stream for next episode
in.ignore(episodeEndPos + 4);
fullContent = contents.substr(0, episodeEndPos);
}
// fill attributes
setNrHelper();
setTitelHelper();
setFlashbackHelper();
setDescriptionHelper();
}
I tried it with inFile >> words (to read the words, this is a way to get the words out of the stream) another way i was thinking about is, to use .ignore (to ignore an amount of characters in the stream). But that doesnt work as intended. Sorry for my bad english, hopefully its clear what i want to do.
If your goal is at each call of Read() to read the next episode and advance in the file, then the trick is to to use tellg() and seekg() to bookmark the position and update it:
void Episode::Read(ifstream& in) {
streampos pos = in.tellg(); // backup current position
string fullContent;
string contents((istreambuf_iterator<char>(in)), istreambuf_iterator<char>());
size_t episodeEndPos = contents.find("****");
if (episodeEndPos == -1) {
in.ignore(numeric_limits<char>::max());
in.clear(), in.sync();
fullContent = contents;
}
else { // empty stream for next episode
fullContent = contents.substr(0, episodeEndPos);
in.seekg(pos + streamoff(episodeEndPos + 4)); // position file at next episode
}
}
In this way, you can call several time your function, every call reading the next episode.
However, please note that your approach is not optimised. When you construct your contents string from a stream iterator, you load the full rest of the file in the memory, starting at the current position in the stream. So here you keep on reading and reading again big subparts of the file.
Edit: streamlined version adapted to your format
You just need to read the line, check if it's not a separator line and concatenate...
void Episode::Read(ifstream& in) {
string line;
string fullContent;
while (getline(in, line) && line !="****") {
fullContent += line + "\n";
}
cout << "DATENSATZ: " << fullContent << endl; // just to verify content
// fill attributes
//...
}
The code you got reads the entire stream in one go just to use some part of the read text to initialize an object. Imagining a gigantic file that is almost certainly a bad idea. The easier approach is to just read until the end marker is found. In an ideal world, the end marker is easily found. Based on comments it seems to be on a line of its own which would make it quite easy:
void Episode::read(std::istream& in) {
std::string text;
for (std::string line; in >> line && line != "****"; ) {
text += line + "\n";
}
fullContent = text;
}
If the separate isn't on a line of its own, you could use code like this instead:
void Episode::read(std::istream& in) {
std::string text;
for (std::istreambuf_iterator<char> it(in), end; it != end; ++it) {
text.push_back(*it);
if (*it == '*' && 4u <= text.size() && text.substr(text.size() - 4) == "****") {
break;
}
if (4u <= text.size() && text.substr(text.size() - 4u) == "****") {
text.resize(text.size() - 4u);
}
fullContent = text;
}
Both of these approaches would simple read the file from start to end and consume the characters to be extracted in the process, stopping as soon as reading of one record is done.

How do I break out of a getline with a file?

I have code where I am inputting stuff from a file. My txt file looks like this:
file.txt
hello world
...
1 2
The numbers at the bottom are supposed to be read into variables. As for "hello world", it should be picked up by getline. But I don't know how many lines there will be in the txt file so I don't know how to break out of it. Here is my code:
while (getline(file, line))
{
std::cout << line << std::endl;
// ...
}
file >> a >> b; // 1 2
If I was doing this with cin I could just do Ctrl+Z to stop getline loop from running. How do I break out of the while loop at the right time before I get to 1 2?
For each line string line, you can put it into an istringstream iss. And then try to stream it into a and b using iss >> a >> b, if it can be done successfully, it means you enter the right line. Otherwise, you go on checking the next line.
int a, b;
while (getline(file, line))
{
istringstream iss(line);
if (iss >> a >> b)
{
// you are in the right line, and a,b has the values e.g. 1 2
}
}
It should also work for other strings besides "hello world", like "aaa bbb cc" etc. as long as they are not the numbers you are looking for.
P.S.: you can also take use of regex if you use C++11 to check if given line has/matches the pattern you are looking for.
Use a condition, and a break; statement.
E.g.:
while (getline(file, line))
{
std::cout << line << std::endl;
// ...
if(line == "hello world"/){
break;//Exits the loop
}
}
A break statement makes your code exit the most inner loop it's used in. In this case, it exits the while loop.
EDIT:
If you don't want to break on a specific line, then you'll better use regular expression or another mechanism (like std::stringstream) to find a match of the string you're looking for, and capture the part you're interested in. I suggest you take a look at Boost.Regex for this.
The idea is to loop on the lines, i.e. just as you do. As soon as you have a match, you can break (the same way) and capture from the string you're currently reading (which in your code would be in the line variable).
It's not too clear how you determine that you want to break out
of the loop. What is the criterion? If you want to read all
lines but the last, the simplest solution is to simply read all
of the lines into an std::vector<std::string>, and then
process that; you can iterate over a vector until the next to
the last element (which you can't do on a stream). If it's some
pattern your looking to match (say "\\d+\\s+\\d+"), then you
can add this to the condition:
std::string line;
std::regex matchNumbers( "\\d+\\s+\\d+" );
while ( std::getline( file, line ) && ! regex_match( line, matchNumbers ) ) {
// ...
}
std::istringstream numbers( line );
numbers >> a >> b;
And so on.