So I need some ideas on how nicely parse a text file in C++. The files that I am parsing have the following format :
Command_A list of arguments
Command_B list of arguments
etc etc
Right now I am using an ifstream to open up the file and then I have this super long series of if-else statements to determine what to do for each type of command. This is proving to be a bit unwieldy (especially since some of the commands are for parsing other files...so I have nested if-elses with multiple ifstreams for the different files).
I was looking for another way of doing this but am not really sure what is the best approach. I was thinking about using a std::map where the keys are the command strings and the values are the function pointers but I am not familiar with storing function pointers in a map (especially if the different functions are of different return types, etc).
Below is basically what I am currently doing. I loop through the file and use "getline" to get the current line. Then I use a stringstream to parse the command. Then I use a very long list of if-elses to determine which function to call. Each line in the file also comes with a list of arguments so I use the stringstream to parse those and then pass those parameters into the function I call.
The problem here is two-fold
1) I have a very very large number of if-elses (around 50)
2) Some of the commands require me to parse new files and thus I have to open up another ifstream within the current ifstream. (see command_c)
So I'm looking for an easier/more efficient/prettier looking way to do this.
/*Open file and verify validity*/
std::ifstream parseFile(filename.c_str());
if(!parseFile.good())
{
cerr<<"ERROR: File is either corrupt or does not exist."<<endl;
exit(1); //Terminate program
}
//Loop over file line by line
std::string line;
while(!parseFile.eof())
{
std::getline(parseFile, line);
std::stringstream ss;
std::string command;
ss.str(line);
ss >> command;
if(command == "COMMAND_A")
{
float x,y,z;
ss >> x >> y >> z;
FunctionA(x,y,z);
}
else if(command == "COMMAND_B")
{
float a,b,c,d,e,f;
ss >> a >> b >> c >> d >> e >> f;
FunctionB(a,b,c,d,e,f);
}
else if(command == "Command_C")
{
string nextFile;
ss >> nextFile;
ParseFile(nextFile); //This is not recursive...this is another function
}
else if(...)
{
...
}
// etc, etc (this continues on for a long time)
}
parseFile.close();
You could have a command map and register a bunch of functions:
#include<fstream>
#include<functional>
#include<iostream>
#include<map>
#include<sstream>
int main() {
typedef std::function<bool (std::istringstream&)> command_function;
typedef std::map<std::string, command_function> command_map;
command_map map;
// register commands
map.insert(command_map::value_type("print", [](std::istringstream& s) {
std::string line;
if( ! getline(s, line)) return false;
std::cout << line << '\n';
return true;
}));
map.insert(command_map::value_type("add", [](std::istringstream& s) {
double a;
double b;
if( ! (s >> a >> b)) return false;
std::cout << "a + b = " << a + b << '\n';
return true;
}));
// sample data
std::istringstream file(
"print Hello World\n"
"add 1 2\n");
// command parsing
std::string line;
while(getline(file, line)) {
std::istringstream line_stream(line);
std::string command;
if(line_stream >> command >> std::ws) {
auto pos = map.find(command);
if(pos != map.end())
pos->second(line_stream);
}
}
return 0;
}
I've written many types of parsers, and I find that it's often a good idea to write a fairly generic function that takes a line and produces a list of strings (e.g. std::vector<std::string>, and then process the first element in that list as a "what do we do next", and let each command use the arguments as it likes (e.g. translate to float, use as a filename, etc).
This can then be combined with a table-based system, where a function [or object] is associated with the string. For example std::map<std::string, BaseCommand> table;.
Then you end up with something like this:
class CommandA : BaseCommand
{
public:
virtual int Run(const std::vector<std::string>& argv);
};
table["CommandA"] = new CommandA;
table["CommandB"] = new CommandB;
...
std::vector<std::string> argv = parseLine(line);
if (table.find(argv[0]) != table.end())
{
int result = table[argv[0]].second->Run(argv);
if (result < 0)
{
... do error handling here...
}
}
Of course, there are many different ways you COULD do this, and this is just one possible solution.
Yes, put the functions in a map. The key to doing this is std::function<void()>. Unfortunately, the void() means it holds functions that take no parameters, and return nothing. Obviously, your functions have parameters. So what we do, is store functions that each take a std::stringstream& (the line), parse out the parameters they need, and then call the function. The easiest way to do this is simply to use inline lambdas. Lambdas that take stringstreams and return nothing look like this: [](std::stringstream& ss) {code}.
Additionally, I use this function for easy retrieving of your parameters:
template<class T>
T get(std::stringstream& ss)
{
T t;
ss<<t;
if (!ss) // if it failed to read
throw std::runtime_error("could not parse parameter");
return t;
}
Here's the map:
std::unordered_set<std::string, std::function<void(std::stringstream&))> cmd_map=
"COMMAND_A", [](std::stringstream& ss)
{FunctionA(get<float>(ss), get<float>(ss), get<float>(ss));},
"COMMAND_B", [](std::stringstream& ss)
{FunctionB(get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss), get<float>(ss));},
"COMMAND_C", [](std::stringstream& ss)
{FunctionA(get<string>(ss));},
And here's the parser:
//Loop over file line by line
std::string line;
while(std::getline(parseFile, line)) //use this instead of eof
{
std::stringstream ss(line);
std::string command;
ss >> command;
auto it = cmd_map.find(command);
if (it != cmd_map.end())
{
try
{
(*it)(); //call the function
} catch(std::runtime_error& err) {
std::cout << "ERROR: " << err.what() << '\n';
}
} else {
std::cout << "command " << command << " not found";
}
}
parseFile.close();
Related
The contents of file.txt are:
5 3
6 4
7 1
10 5
11 6
12 3
12 4
Where 5 3 is a coordinate pair.
How do I process this data line by line in C++?
I am able to get the first line, but how do I get the next line of the file?
ifstream myfile;
myfile.open ("file.txt");
First, make an ifstream:
#include <fstream>
std::ifstream infile("thefile.txt");
The two standard methods are:
Assume that every line consists of two numbers and read token by token:
int a, b;
while (infile >> a >> b)
{
// process pair (a,b)
}
Line-based parsing, using string streams:
#include <sstream>
#include <string>
std::string line;
while (std::getline(infile, line))
{
std::istringstream iss(line);
int a, b;
if (!(iss >> a >> b)) { break; } // error
// process pair (a,b)
}
You shouldn't mix (1) and (2), since the token-based parsing doesn't gobble up newlines, so you may end up with spurious empty lines if you use getline() after token-based extraction got you to the end of a line already.
Use ifstream to read data from a file:
std::ifstream input( "filename.ext" );
If you really need to read line by line, then do this:
for( std::string line; getline( input, line ); )
{
...for each line in input...
}
But you probably just need to extract coordinate pairs:
int x, y;
input >> x >> y;
Update:
In your code you use ofstream myfile;, however the o in ofstream stands for output. If you want to read from the file (input) use ifstream. If you want to both read and write use fstream.
Reading a file line by line in C++ can be done in some different ways.
[Fast] Loop with std::getline()
The simplest approach is to open an std::ifstream and loop using std::getline() calls. The code is clean and easy to understand.
#include <fstream>
std::ifstream file(FILENAME);
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
// using printf() in all tests for consistency
printf("%s", line.c_str());
}
file.close();
}
[Fast] Use Boost's file_description_source
Another possibility is to use the Boost library, but the code gets a bit more verbose. The performance is quite similar to the code above (Loop with std::getline()).
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <fcntl.h>
namespace io = boost::iostreams;
void readLineByLineBoost() {
int fdr = open(FILENAME, O_RDONLY);
if (fdr >= 0) {
io::file_descriptor_source fdDevice(fdr, io::file_descriptor_flags::close_handle);
io::stream <io::file_descriptor_source> in(fdDevice);
if (fdDevice.is_open()) {
std::string line;
while (std::getline(in, line)) {
// using printf() in all tests for consistency
printf("%s", line.c_str());
}
fdDevice.close();
}
}
}
[Fastest] Use C code
If performance is critical for your software, you may consider using the C language. This code can be 4-5 times faster than the C++ versions above, see benchmark below
FILE* fp = fopen(FILENAME, "r");
if (fp == NULL)
exit(EXIT_FAILURE);
char* line = NULL;
size_t len = 0;
while ((getline(&line, &len, fp)) != -1) {
// using printf() in all tests for consistency
printf("%s", line);
}
fclose(fp);
if (line)
free(line);
Benchmark -- Which one is faster?
I have done some performance benchmarks with the code above and the results are interesting. I have tested the code with ASCII files that contain 100,000 lines, 1,000,000 lines and 10,000,000 lines of text. Each line of text contains 10 words in average. The program is compiled with -O3 optimization and its output is forwarded to /dev/null in order to remove the logging time variable from the measurement. Last, but not least, each piece of code logs each line with the printf() function for consistency.
The results show the time (in ms) that each piece of code took to read the files.
The performance difference between the two C++ approaches is minimal and shouldn't make any difference in practice. The performance of the C code is what makes the benchmark impressive and can be a game changer in terms of speed.
10K lines 100K lines 1000K lines
Loop with std::getline() 105ms 894ms 9773ms
Boost code 106ms 968ms 9561ms
C code 23ms 243ms 2397ms
Since your coordinates belong together as pairs, why not write a struct for them?
struct CoordinatePair
{
int x;
int y;
};
Then you can write an overloaded extraction operator for istreams:
std::istream& operator>>(std::istream& is, CoordinatePair& coordinates)
{
is >> coordinates.x >> coordinates.y;
return is;
}
And then you can read a file of coordinates straight into a vector like this:
#include <fstream>
#include <iterator>
#include <vector>
int main()
{
char filename[] = "coordinates.txt";
std::vector<CoordinatePair> v;
std::ifstream ifs(filename);
if (ifs) {
std::copy(std::istream_iterator<CoordinatePair>(ifs),
std::istream_iterator<CoordinatePair>(),
std::back_inserter(v));
}
else {
std::cerr << "Couldn't open " << filename << " for reading\n";
}
// Now you can work with the contents of v
}
Expanding on the accepted answer, if the input is:
1,NYC
2,ABQ
...
you will still be able to apply the same logic, like this:
#include <fstream>
std::ifstream infile("thefile.txt");
if (infile.is_open()) {
int number;
std::string str;
char c;
while (infile >> number >> c >> str && c == ',')
std::cout << number << " " << str << "\n";
}
infile.close();
Although there is no need to close the file manually but it is good idea to do so if the scope of the file variable is bigger:
ifstream infile(szFilePath);
for (string line = ""; getline(infile, line); )
{
//do something with the line
}
if(infile.is_open())
infile.close();
This answer is for visual studio 2017 and if you want to read from text file which location is relative to your compiled console application.
first put your textfile (test.txt in this case) into your solution folder. After compiling keep text file in same folder with applicationName.exe
C:\Users\"username"\source\repos\"solutionName"\"solutionName"
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream inFile;
// open the file stream
inFile.open(".\\test.txt");
// check if opening a file failed
if (inFile.fail()) {
cerr << "Error opeing a file" << endl;
inFile.close();
exit(1);
}
string line;
while (getline(inFile, line))
{
cout << line << endl;
}
// close the file stream
inFile.close();
}
This is a general solution to loading data into a C++ program, and uses the readline function. This could be modified for CSV files, but the delimiter is a space here.
int n = 5, p = 2;
int X[n][p];
ifstream myfile;
myfile.open("data.txt");
string line;
string temp = "";
int a = 0; // row index
while (getline(myfile, line)) { //while there is a line
int b = 0; // column index
for (int i = 0; i < line.size(); i++) { // for each character in rowstring
if (!isblank(line[i])) { // if it is not blank, do this
string d(1, line[i]); // convert character to string
temp.append(d); // append the two strings
} else {
X[a][b] = stod(temp); // convert string to double
temp = ""; // reset the capture
b++; // increment b cause we have a new number
}
}
X[a][b] = stod(temp);
temp = "";
a++; // onto next row
}
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
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'm coding in C++ and I'm trying to read in a file that I'd like to access certain chars at later. As in, what is the char at (line x, char y), at any given point in the file.
My only thought right now is to look for a newline character, and somehow index them so that I can refer back to newline x, check the length of a line, and pull a char at whatever position given the line length.
I'm not sure if that is a good approach or not.
Try this (for character in line "lineNum" and column "columnNum"):
ifstream inf;
inf.open(filename); //filename being c-string
string str;
for (int i = 0; i < lineNum; i++)
{
std::getline(inf, str);
}
This way "str" stores the line you are interested in (automatically checks for newline character and stops).
Then you can use:
char chr = str[columnNum];
to store the character you want in "chr" variable. And don't forget:
inf.close();
Unfortunately, to my knowledge you need to repeat this process every time you want to access a character.
#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <sstream>
#define FILENAME "File.txt"
class FileGrid {
public:
typedef std::vector<std::string> Line;
typedef std::vector<std::string>::const_iterator LineIter;
typedef std::vector<std::vector<std::string>> StringMap;
typedef std::vector<std::vector<std::string>>::const_iterator StringMapIter;
void FillGrid(char* fileName) {
grid.clear();
std::ifstream in(FILENAME, std::ifstream::in);
if (!in.is_open()) {
std::cout << "problem reading " << FILENAME << std::endl;
return;
}
std::string words;
std::string word;
std::stringbuf buffer;
while (in.is_open() && std::getline(in, words)) {
std::stringstream ss(words);
Line line;
while (ss >> word) {
line.push_back(word);
}
grid.push_back(line);
}
}
void PrintGrid() {
StringMapIter b = grid.begin();
StringMapIter e = grid.end();
std::cout << "\t\tFile Content:" << std::endl;
while(b != e) {
for (unsigned int i = 0; i < b->size(); ++i) {
std::cout << b->operator[](i) << " ";
}
std::cout << std::endl;
++b;
}
}
char const & GetChar(int lineNo, int charNo) {
// LineNo checks etc
Line const & line = grid[lineNo];
for(std::string const & word : line ) {
if(charNo > word.size() + 1) {
charNo -= word.size() + 1;
}
else {
return word[charNo];
}
}
throw std::exception("charNo higher");
}
private:
StringMap grid;
};
void main() {
FileGrid grid;
grid.FillGrid(FILENAME);
grid.PrintGrid();
std::cout << grid.GetChar(0, 3); // should return first line, 4th character
}
Not the best code I've ever written but pretty much what I could do in a short time.
FileGrid handles reading and accessing the data. It reads the file line by line and stores it in a std::vector. When it finishes reading a line, it pushes that into another std::vector. In the end, we have a (sort of) 2D array of strings.
Again, not the best code and definitely not the most optimized code but the idea is still the same: read from the file line by line, separate each word and put them into an array of strings. If you can't use STL, you can dynamically create a 2D array for each line but since I don't know the specific requirements of your question, I just wrote something simple and bruteforce to show you the main way of storing grid of strings into the memory.
As long as it works. But reading the entire file into memory, if that's an option, would be simpler.
Here is a sample program that uses stringstream. The goal is to accept lines from the user(standard input) and print each word in a separate line.
int main()
{
std::istringstream currentline;
std::string eachword;
std::string line;
// Accept line from the standard input till EOF is reached
while ( std::getline(std::cin,line) )
{
currentline.str(line); // Convert the input to stringstream
while ( currentline >> eachword ) // Convert from the entire line to individual word
{
std::cout << eachword << std::endl;
}
currentline.clear();
}
return 0;
}
I'm wondering, is there a way , I can avoid the intermediate string variable(object), line and directly store the user input to the currentline (istringstream object).
Note:
I know, the following solution already.
while ( std::cin >> eachword)
{
std::cout << eachword << std::endl;
}
std::getline needs a string reference argument, and that's where it places the line it has obtained, so of course you can't avoid passing such an argument (and still use that function). You could elegantly encapsulate the construct, if you need it often -- e.g.:
bool getline(std::istream& i, std::istringstream& current)
{
std::string line;
if ( std::getline(i, line) ) {
current.str(line);
return true;
}
return false;
}
If you want to simplify the first solution,
while ( currentline(line) >> eachword )
I assume you want to not use an intermediate object to prevent unnecessary copying?
You can achieve the same affect by explicitly setting the stream buffers buffer area.
int main()
{
std::string line;
std::istringstream currentline;
std::string eachword;
// Accept line from the standard input till EOF is reached
while ( std::getline(std::cin,line) )
{
// Set the buffer without copying.
currentline.clear();
currentline.rdbuf()->pubsetbuf(&line[0], line.length() );
while ( currentline >> eachword )
{
std::cout << eachword << std::endl;
}
}
return 0;
}
Because of the order of destruction. You just need to make sure the istringstream is destroyed before the object you are using as a buffer. So you need to re-arrange the declarations at the top of main() to make sure that line is created first and thus will be destroyed last (otherwise the destructor of the istringstream has the potential for accessing the memory of a free'ed object.