Writing a program to read a text file and storing it in a struct. An example of the text file:
chicken
dog
car
765
When there is some text in the line, it will get store into the struct. I have tried the following:
getline(file, aLine);
Info.animalchicken = aLine;
getline(file, aLine);
Info.animaldog = aLine;
getline(file, aLine);
Info.car = aLine;
getline(file, aLine);
Info.number = aLine;
I realised that the getline is literally getting every single line. When I run this in my program, the chicken will be stored in the struct Info.animalchicken. The next line, which is empty, will store into Info.animaldog. Dog will be stored in Info.car and so on.
I think a control loop is required here but can't think of a good one. How can I ignore the empty line so my text can enter into the struct correctly?
This is my struct
struct Info {
string animalchicken;
string animaldog;
string car;
int number;
}
The loop idea, while quite primitive, should do the trick; the easiest way would be to wrap the logic in a separate function:
std::string getlineFilterEmpty(std::istream& s) {
std::string line;
do {
if (!s) {
throw std::runtime_error("End of stream");
}
getline(s, line);
} while(line.size() == 0);
return line;
}
Then getting your values is as simple as:
Info.animalchicken = getlineFilterEmpty(file);
Info.animaldog = getlineFilterEmpty(file);
Info.car = getlineFilterEmpty(file);
The number member will require parsing the string to an integer, the code for which you'll find elsewhere on SO.
The logic needs to go something like,
Read a line.
If read succeeded
If line not empty
Provide line
Else
Try again
Else
Handle error
Translating that into code and bundling it into a function for easy reuse, we get
std::string getNotEmptyLine(std::istream & in)
{
while (true) // repeat forever!
{
std::string temp;
std::getline(in, temp); // get a line
if (in) // test the line
{
if (line.size() != 0) // line not empty
{
return temp; //give it to caller
}
}
else
{
// handle error. We'll throw an exception, but this isn't the best solution
throw std::runtime_error("Couldn't read a line!");
}
}
}
As with all literal translations, it needs a bit of work. It would also be helpful to make this function work exactly like getline so the caller can use it as a drop-in replacement.
std::istream & getNotEmptyLine(std::istream & in, // stream to read
std::string & line, // somewhere to put the string
char delim = '\n') // allow different delimiters
{
while (true) // repeat forever!
{
if (std::getline(in, line, delim)) // get a line right in line and test that we got it.
{
if (line.size() != 0) // line not empty
{
break; // success. exit.
}
}
else
{
// line will contain whatever this implementation of `getline` puts or
// leaves in the string on failure.
break; // fail. Let the caller decide what to do
}
}
return in;
}
Usage:
Info info;
std::string aLine;
if (getNotEmptyLine(in, info.animalchicken) &&
getNotEmptyLine(in, info.animaldog) &&
getNotEmptyLine(in, info.car) &&
getNotEmptyLine(in, aLine))
{
info.number = std::stoi(aLine);
}
else
{
// handle error
}
Note: even this may be too simplistic. It can't handle a line that contains nothing but whitespace. A single misplaced and nigh-invisible space will wreak havoc. If this is a concern, add more logic to if (line.size() != 0)
Here's an option adding stream operators and a helper function to skip empty lines.
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
struct Info {
std::string animalchicken;
std::string animaldog;
std::string car;
int number;
};
// a helper function to do getline but skip empty lines
std::istream& getline_with_content(std::istream& is, std::string& s) {
while(std::getline(is, s)) if(not s.empty()) break;
return is;
}
// an istream operator to read one Info
std::istream& operator>>(std::istream& is, Info& i) {
getline_with_content(
getline_with_content(
getline_with_content(is,
i.animalchicken),
i.animaldog),
i.car);
is >> i.number;
// ignore everything after the number until a newline appears:
is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return is;
}
// an ostream operator to print one Info
std::ostream& operator<<(std::ostream& os, const Info& i) {
return os << i.animalchicken << '\n'
<< i.animaldog << '\n'
<< i.car << '\n'
<< i.number << '\n';
}
int main() {
// an example istream with a lot of blank lines:
std::istringstream file(
"chicken\n\n"
"dog\n\n"
"car\n\n\n"
"765\n");
Info i;
file >> i; // read one Info from the stream
std::cout << i; // print one Info
}
Demo
Related
I have the following content in a file:
A(3#John Brook)
A(2#Allies Frank)
A(1#Lucas Feider)
I want to read the line piecemeal. First I want to read in order. For example, A than 3 than John Brook. Every thing is fine till 3 but how can I read John Brook without "#" and ")" as string.
I have a funciton and you can have a look my codes:
void readFile()
{
ifstream read;
char process;
char index;
string data;
read.open("datas.txt");
while(true)
{
read.get(process);
read.get(index);
// Here, I need to read "John Brook" for first line.
// "Allies Frank" for second line.
// "Lucas Feider" for third line.
}
read.close();
}
First organize your data into some structure.
struct Data {
char process;
char index;
std::string data;
};
Then implement function which is able to read single item. Read separators into temporary variables and then later check if they contain proper values.
Here is an example assuming each item is in single line.
std::istream& operator>>(std::istream& in, Data& d) {
std::string l;
if (std::getline(in, l)) {
std::istringstream in_line{l};
char openParan;
char separator;
if (!std::getline(
in_line >> d.process >> openParan >> d.index >> separator,
d.data, ')') ||
openParan != '(' || separator != '#') {
in.setstate(std::ios::failbit);
}
}
return in;
}
After that rest is quick and simple.
https://godbolt.org/z/aGYvPeWfW
I have an input stream with a series of bytecode-like instructions
function foo
push x
pop y
...
return
function bar
...
return
function other
...
I.e. a series of function declarations back-to-back. Each function is defined from one "function" until the next. There may be multiple "returns" within a function so I cannot use that as a delimiter. All instructions must be inside a function (i.e. the first line of the stream is always a "function" and the last line is always a "return").
I want to basically remove certain functions from the list. I have a list of the functions I want to keep and I thought about copying to an output stream, skipping over any function not on the list, something like
vector<string> wanted_functions = { "foo", "other" }
ostringstream oss;
bool skip = false;
for (string line; getline(input_stream, line);) {
istringstream iss(line);
string command;
iss >> command;
if (command == "function") {
skip = false;
string function_name;
iss >> function_name;
if (std::find(wanted_function.begin(), wanted_functions.end(), function_name)
== wanted_functions.end()) {
skip = true;
}
if (!skip) oss << line;
}
I haven't tested the above solution; it looks like it may work but I don't think it's very elegant.
I feel like stream iterators would be good here but I don't know how to use them. How can I achieve the skipping behavior using iterators, or maybe native stream methods like ignore() or seekg()?
Bonus: If there's a better way to read the first two words in the line that creating a new stream just for them I'd also like to know please.
Edit: Functions are always sequential. There are no nested functions. I.e. "function" is always immediately preceded by "return".
If it's text, you can't easily just jump/skip (seekg) without actually reading it since you don't have a known offset to go to (many binary file formats will contain such information), but you can just filter what you do read, the code in your question nearly does this.
istream_iterator<std:string> will give you each word / white-space delimited, but you can't tell where the new lines are. You can make a istream_iterator that will read lines instead, but the simplest way involves sub-classing std::string to redefine operator >>, but that is basically what getline gets you anyway, or you might make your own type containing more useful information (below).
You might use std::unordered_set<std::string> wanted_functions as that is easier to check if an item exists or not than searching a std::vector (with std::find or similar). skip also ends up working slightly weirdly as you are setting it on "unwanted" functions, then doing like if (!unwanted).
unordered_set<string> wanted_functions = { "foo", "other" };
bool is_wanted_function = false;
for (string line; getline(input_stream, line);) {
istringstream iss(line);
string command;
iss >> command;
if (command == "function") {
string function_name;
iss >> function_name;
is_wanted_function = wanted_functions.count(function_name) != 0;
}
if (is_wanted_function) {
oss << line << std::endl;
}
}
An alternative to the is_wanted_function flag would be to consume the function within the if (command == "function") {, this needs some more careful management of reading the next line, so as to not accidentally skip the one following the inner loop
unordered_set<string> wanted_functions = { "foo", "other" };
string line;
getline(input_stream, line);
while (input_stream) {
istringstream iss(line);
string command;
iss >> command;
if (command == "function") {
string function_name;
iss >> function_name;
if (wanted_functions.count(function_name)) {
oss << line << std::endl;
while (getline(input_stream, line) && line.rfind("function", 0) != 0) {
oss << line << std::endl;
}
continue; // already have a line
}
}
getline(input_stream, line); // next line
}
As is I don't think that that is much of an improvement, but if the actual parsing (iss >> command;, iss >> function_name, etc.) was refactored out elsewhere, then it would be somewhat simpler.
You might make the actual parsing (getting the command name like "function", and arguments like "foo") it's own class which can tidy up having the istringstream iss(line); iss >> command; etc. being directly in this code.
istream_iterator basically just uses operator >> to get the next item until the stream is in a failure state, so can be used with your own types, although you can get something very similar doing largely the same yourself without istream_iterator.
class command
{
public:
const std::string &cmd()const { return _cmd; }
const std::string &source_line()const { return _source_line; }
const std::string &arg(size_t i)const
{
if (i < _args.size()) return _args[i];
else throw std::out_of_range("Command does not have this many arguments.");
}
friend std::istream &operator >> (std::istream &is, command &cmd)
{
if (std::getline(is, cmd._source_line))
{
std::stringstream ss(cmd._source_line);
ss >> cmd._cmd;
cmd._args.clear(); // istream_iterator uses the same command object every time
while (true)
{
std::string val;
ss >> val;
if (!ss) break;
cmd._args.push_back(std::move(val));
}
}
return is;
}
private:
std::string _source_line;
std::string _cmd;
std::vector<std::string> _args;
};
int main()
{
using namespace std;
std::stringstream input_stream(
"function foo\n"
"push x\n"
"pop y\n"
"...\n"
"return\n"
"function bar\n"
"...\n"
"return\n"
"function other\n"
"...\n"
"return\n");
std::ostream &oss = std::cout;
std::unordered_set<string> wanted_functions = { "foo", "other" };
std::istream_iterator<command> eos; // end of stream
std::istream_iterator<command> it(input_stream); // iterator
while (it != eos)
{
if (it->cmd() == "function" && wanted_functions.count(it->arg(0)))
{
do
{
oss << it->source_line() << std::endl;
} while (++it != eos && it->cmd() != "function");
}
else ++it; // on true the while loop already advanced
}
}
istream_iterator of course does also bring compatibility with the other iterator based algorithms and constructors (std::find, etc.), and you can build some more complex things out of that. For example if you add another layer on top of this to create a istream_iterator<function>, then maybe you could use the Boost C++ filter_iterator, and then you will have an iterator with just the functions you want.
Note that if you need to start dealing with any nested constructs (like if (...) { ... } else if (...) { ... }), you might find parsing into a tree structure more convenient to do operations on than a flat sequence. See Abstract Syntax Tree. This somewhat depends on your syntax, e.g. if you use just goto if offset/label instead of while(expr), if(expr), else if, else, etc. type constructs.
I am quite new to C++ and I have a txt file with data which looks something like this:
test:123:lock
qwerty:4321:unlock
asdf:12:lock
Is it possible for me to read the data line by line into a variable / array using ":" as the delimiter?
I tried doing something like:
while(!myfile.eof()) {
for (int i = 0; i < 3; i++) {
getline(myfile,UserN[i],':');
}
}
What I want to achieve is to store the data of the first line into the UserN[0], UserN[1], and UserN[2]. And when it start reading the second line, the data on the second line will replace the value in UserN[0], UserN[1], and UserN[2]. Thanks in advance!
Read the line first, then tokenize it with std::stringstream:
#include <sstream>
...
std::string line;
while(std::getline(myfile, line)) { // cache the line
std::istringstream tokenizer(line);
std::getline(tokenizer, UserN[0], ':'); // then get the tokens from it
std::getline(tokenizer, UserN[1], ':');
std::getline(tokenizer, UserN[2]); // last token: get the remainder
// of the line.
if(tokenizer) {
// success!
} else {
// There were fewer than two colons in the line
}
}
In essence, std::istringstream wraps a string in a stream interface -- the resulting stream behaves (roughly) like a file with the same contents as the string with which it was built. It is then possible to use >> or getline or anything else that you could use on files or std::cin or other input streams with it, and here we use it to take the string apart into the tokens you require.
You can do this simply with
ifstream myfile( "aFile.txt" );
// .. check whether the file is open: if( !myfile.is_oppen() ) error
for( string userN[3]
; getline( getline( getline( myfile >> ws, userN[0], ':' ), userN[1], ':' ), userN[2] ); )
{
// userN[0..2] is read correctly
}
or in a more elegant way, perhaps more suitable to Your requirements. I assume, that the second text is always a number and the third text is either 'lock' or 'unlock' or something else like an enum.
enum class LockState
{
lock, unlock
};
// -- reading a LockState
// please consider, that behind the text must follow a white space character (Space, LF, ..)
std::istream& operator>>(std::istream& in, LockState& s)
{
std::string word;
if( in >> word )
{
if( word == "lock" )
s = LockState::lock;
else if( word == "unlock" )
s = LockState::unlock;
else
in.setstate( std::ios_base::failbit );
}
return in;
}
struct Entry // change the name 'Entry' of the struct suitable for Your requirements
{
std::string someText;
int aNr;
LockState lockState;
};
// -- function to read an 'Entry'-object
std::istream& operator>>(std::istream& in, Entry& e)
{
char colon;
if( getline( in >> std::ws, e.someText, ':' ) >> e.aNr >> colon
&& colon != ':' )
in.setstate( std::ios_base::failbit );
else
in >> e.lockState;
return in;
}
and later in Your main-program
ifstream myfile( "aFile.txt" );
// .. check whether the file is open: if( !myfile.is_oppen() ) error
for( Entry e; myfile >> e; )
{
// use here the Entry-object 'e'
}
if( myfile.eof() )
cout << "Ok - You read the file till the end" << endl;
Avoid trouble here and use the split function from Boost:
#include <fstream>
#include <vector>
#include <string>
#include <boost/algorithm/string.hpp>
// ...
// Read file and throw exception on error.
std::ifstream infile;
infile.open(file_name);
std::string line;
while (std::getline(infile, line))
{
// Strip of the comments.
std::vector<std::string> strings;
boost::split(strings, line, boost::is_any_of(":"));
// You have now a vector of strings, which you can process...
}
I've a problem with writing to file. If I write something with spaces, it write each word as a single line. Why?
void backstart()
{
thread t1(backing);
thread t2(thr2);
t1.join();
t2.join();
}
void thr2()
{
string tmp;
tmp = "";
while (tmp != "/exit")
{
cin >> tmp;
if (tmp != "/exit")
{
writefile(tmp);
}
}
runing = false;
}
void writefile(string msg)
{
ofstream myfile("file.txt", ios::out | ios::app);
myfile << userna + ": " + msg + ",\n";
myfile.close();
}
Thanks
Damon
Consider writing it like this:
void thr2()
{
std::string line;
while(std::getline(cin, line)) // this check the read succeeded as well
{
if (line=="/exit") break; // stop on "/exit" command
writefile(line); // write out the line
}
running = false; // stop the thread, I guess
}
The most important line is
while(std::getline(std::cin, line))
which reads a whole line at a time into the std::string called line, and then checks the state of the stream to make sure the read succeeded. Read about std::getline here.
Edit:
Be very careful (and in fact I suggest just avoiding it) mixing reading with >> and getline. If you read, for example an int, with >> it will leave the '\n' character at the end of the line, so when you next read with getline, you get the rest of the line (which is basically nothing), rather than what you probably wanted, which is the next line.
If you have just read with >> and want to use getline, read into the whitespace eater first, like this std::cin >> std::ws.
I am creating a program (In C++) that takes an ASCII file and reads a few values from each line until it reaches the end of the file. I am using ifstream to read the file, and I have never had problems with it stopping when I use the ifstream.eof() method. This time, however, even though it found the eof character in my test case, when I analyzed my other files, it is infinite looping because it never finds the eof character. Is this a coding issue, or an issue with my files?
string line = "";
unsigned long pos = 0;
ifstream curfile(input.c_str());
getline(curfile, line);
int linenumber = 0;
cout<<"About to try to read the file"<<endl;
if (!curfile.good())
cout<<"Bad file read"<<endl;
while (!curfile.eof())
{
cout<<"Getting line "<<linenumber<<endl;
linenumber++;
pos = line.find_first_of(' ');
line = line.substr(pos+1, line.size()-1);
pos = line.find_first_of(' ');
current.push_back(atof(line.substr(0, pos).c_str()));
for (int i = 0; i<4; i++)
{
pos = line.find_first_of(' ');
line = line.substr(pos+1, line.size()-1);
}
pos = line.find_first_of(' ');
dx.push_back(atof(line.substr(0, pos).c_str()));
pos = line.find_first_of(' ');
line = line.substr(pos+1, line.size()-1);
pos = line.find_first_of(' ');
dy.push_back(atof(line.substr(0, pos).c_str()));
getline(curfile, line);
}
EDIT: When I first run the loop, currentfile.good() returns false...what am I doing that causes it to return that?
First thing is first, you shouldn't check like that. eof() doesn't return true until after a failed read. But you can do better (and easier)!
check the stream state with the implicit conversion to void* which can be used in a bool context. Since most of the read operations on streams return a reference to the stream, you can write some very consice code like this:
std::string line;
while(std::getline(currentfile, line)) {
// process line
}
Basically what it is doing is saying "while I could successfully extract a line from currentfile, do the following", which is what you really meant to say anyway ;-);
Like I said, this applies to most stream operations, so you can do things like this:
int x;
std::string y;
if(std::cin >> x >> y) {
// successfully read an integer and a string from cin!
}
EDIT: The way I would rewrite your code is like this:
string line;
unsigned long pos = 0;
int linenumber = 0;
ifstream curfile(input.c_str());
std::cout << "About to try to read the file" << std::endl;
while (std::getline(curfile, line)) {
std::cout << "Getting line " << linenumber << std::endl;
linenumber++;
// do the rest of the work with line
}
Do not do it like that.
EOF is not the only thing you'll encounter while reading. There's a bunch of errors you might get, and so the best is to simply test the stream itself:
while(currentfile)
{
// read somehow
}
If you're reading lines, then, the simplest way is:
std::string line;
while(std::getline(currentfile, line))
{
// use line
}
Your first call to getline is triggering one of the fail-bits on the ifstream object. That is why if you do a check for a fail-bit using ios::good(), you never enter your read loop. I would check to see what the value of line is ... it's probably empty, meaning you're having another issue reading your file, like maybe permissions problems, etc.
The problem is here:
if (!curfile.good())
cout<<"Bad file read"<<endl; // OK you print bad.
while (!curfile.eof()) // But the loop is still entered.
// Another reason to **NEVER** to use
// while (file.eof()) // as bad does not mean eof
// though eof is bad
Try this:
void readFile(std::istream& str)
{
std::string line;
while(std::getline(str, line))
{
std::stringstream lineStream(line);
std::string ignoreWord;
int number[3];
lineStream >> ignoreWord // reads one space seporated word
>> number[0] // reads a number
>> ignoreWord >> ignoreWord >> ignoreWords // reads three words
>> number[1] // reads a number
>> number[2]; // reads a number
current.push_back(number[0]);
dx.push_back(number[1]);
dy.push_back(number[2]);
}
}