here's what I need to do. I have a string in C++. For every line in the string, I need to append a few characters (like ">> ") to the beginning of the line. What I am struggling with is a good way to split the string around newlines, iterate through the elements appending the characters, and then rejoin the string together. I've seen a few ideas, such as strtok(), but I was hoping c++ strings would have something a little more elegant.
Here's a straight-forward solution. Maybe not the most efficient, but unless this is hot code or the string is huge, it should do fine. We suppose that your input string is called input:
#include <string>
#include <sstream>
std::string result;
std::istringstream iss(input);
for (std::string line; std::getline(iss, line); )
{
result += ">> " + line + "\n";
}
// now use "result"
A more functional approach would be to use a getline-based iterator as shown in this answer and then use that with std::transform for transforming all input lines, like this:
std::string transmogrify( const std::string &s ) {
struct Local {
static std::string indentLine( const std::string &s ) {
return ">> " + s;
}
};
std::istringstream input( s );
std::ostringstream output;
std::transform( std::istream_iterator<line>( input ),
std::istream_iterator<line>(),
std::ostream_iterator<std::string>( output, "\n" ),
Local::indentLine );
return output.str();
}
The indentLine helper actually indents the line, the newlines are inserted by the ostream_iterator.
If the data in your string is basically like a file, try using std::stringstream.
std::istringstream lines( string_of_lines );
std::ostringstream indented_lines;
std::string one_line;
while ( getline( lines, one_line ) ) {
indented_lines << ">> " << one_line << '\n';
}
std::cout << indented_lines.str();
You can wrap it in a stringstream and use std::getline to extract a line at a time:
std::string transmogrify(std::string const & in) {
std::istringstream ss(in);
std::string line, out;
while (getline(ss, line)) {
out += ">> ";
out += line;
out += '\n';
}
return out;
}
Related
I have a question regarding some code to process some names or numbers from a file I'm reading. So the text in the file looks like this:
Imp;1;down;67
Comp;3;up;4
Imp;42;right;87
As you can see , there are 3 lines with words and numbers delimited by the character ';' . I want to read each line at a time, and split the entire string in one line into the words and numbers , and then process the information (will be used to create a new object with the data). Then move on to the next line, and so on, until EOF.
So, i want to read the first line of text, split it into an array of strings formed out of the words and numbers in the line , then create an object of a class out of them. For example for the first line , create an object of the class Imp like this Imp objImp(Imp, 1, down, 67) .
In Java i did the same thing using information = line.split(";")' (where line was a line of text) and then used information[0], information[1] to access the members of the string array and create the object. I`m trying to do the same here
Don't use char array for buffer, and don't use std::istream::eof. That's been said, let's continue in solving the problem.
std::getline is simmilar to std::istream::getline, except that it uses std::string instead of char arrays.
In both, the parameter delim means a delimiting character, but in a way that it's the character, which when encountered, std::getline stops reading (does not save it and discards it). It does not mean a delimiter in a way that it will magically split the input for you between each ; on the whole line.
Thus, you'll have to do this:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
...
std::ifstream myFile("D:\\stuff.txt"); // one statement
if (myFile.is_open()) {
std::string line;
while (std::getline(myFile, line)) { // line by line reading
std::istringstream line_stream(line);
std::string output;
while (std::getline(line_stream, output, ';')) // line parsing
std::cout << output << std::endl;
}
}
We construct a std::istringstream from line, so we can parse it again with std::getline.
One other (slightly different) alternative:
/*
* Sample output:
* line:Imp;1;down;67
* "Imp", "1", "down", "67"
* line:Comp;3;up;4
* "Comp", "3", "up", "4"
* line:Imp;42;right;87
* "Imp", "42", "right", "87"
* line:Imp;42;right;87
* "Imp", "42", "right", "87"
*/
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void split(const std::string &s, char delim, std::vector<string> &fields)
{
fields.clear();
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
fields.push_back(item);
}
}
void print (std::vector<string> &fields)
{
cout << " ";
for (size_t i = 0; i < fields.size() - 1; i++)
cout << "\"" << fields[i] << "\", ";
cout << "\"" << fields[fields.size()-1] << "\"" << endl;
}
int main ()
{
std::ifstream fp("tmp.txt");
std::string line;
while (!fp.eof()) {
fp >> line;
cout << "line:" << line << endl;
std::vector<std::string> fields;
split(line, ';', fields);
print(fields);
}
fp.close();
return 0;
}
I have the following string "0 1 2 3 4 "(There is a space at the end of the string). Which i would like to split and add to a vector of string. When i use a loop and a stringstream, the program loops itself into a infinity loop with the last number 4. It does not want to stop.
How can I split the following and add to a vector of strings at the same time.
Please advcie.
stringstream ss(currentLine);
for(int i=0;i<strlen(currentLine.c_str());i++){
ss>>strCode;
strLevel.push_back(strCode);
}
std::ifstream infile(filename.c_str());
std::string line;
if (infile.is_open())
{
std::cout << "Well done! File opened successfully." << std::endl;
while (std::getline(infile, line))
{
std::istringstream iss(line);
std::vector<std::string> tokens { std::istream_iterator<std::string>(iss), std::istream_iterator<std::string>() };
for (auto const &token : tokens)
if (!token.compare("your_value"))
// Do something....
}
}
First of all, we read a line just by using std::istringstream iss(line), then we split words according to the whitespaces and store them inside the tokens vector.
Update: thanks to Nawaz for improvement suggestions (see comments).
stringstream ss(currentLine);
while ( ss >> strCode )
strLevel.push_back(strCode);
That should be enough.
Hey is it possible to have a text file which its contents are:
Weapon Name: Katana
Damage: 20
Weight: 6
Is it possible to assign these bits of information into member variables of a weapons class?.
So that when i call getWeaponName in my main i will get Katana?
I was looking around google and i can get the whole text file input but its not assigned to any variable.
The code i have so far is:
Weapons :: Weapons()
{
this->weaponName = "";
this->damage = 0;
this->weight = 0;
}
Weapons :: Weapons(string weaponName,int damage,int weight)
{
this->weaponName = weaponName;
this->damage = damage;
this->weight = weight;
}
void Weapons :: getWeapon()
{
ifstream myfile ("Weapons\\Katana.txt");
string line;
if (myfile.is_open())
{
while (myfile.good())
{
getline (myfile,weaponName,'\t');//This line gets the entire text file.
//getline (myfile,damage,'\t');
//getline (myfile,weight,'\t');
//myfile >> weaponName;
//myfile >> damage;
//myfile >> weight;
cout << weaponName<< "\n";
}
myfile.close();
}
else
{
cout << "Unable to open file";
}
}
Thanks in advance.
Change
getline (myfile, weaponName, '\t');
to
getline (myfile, weaponName);
What your version is doing is telling getline to grab everything in the file, up to a tab character, and I'm guessing you don't have any tab characters. The version I'm recommending - with no delimiter specified - will get characters up to a newline. So it should read in Weapon Name: Katana.
Then you still need to extract "Katana". Assuming your input file has a very fixed format, you can simply do something like
weaponName = weaponName.substr(weaponName.find_first_of(':') + 2);
This will take the substring starting at the position 2 after the ':'.
Edit
Using weaponName is not exactly proper for your getline statement. weaponName is a string, but at that point, you're just looking for a line. You already have the proper variables in place in getWeapon(). We just need to use them:
void Weapons :: getWeapon()
{
ifstream myfile ("Weapons\\Katana.txt");
string line;
string number;
if (myfile.is_open())
{
while (myfile.good())
{
getline (myfile,line);
weaponName = line.substr(line.find_first_of(':') + 2);
getline (myfile,line);
number = line.substr(line.find_first_of(':') + 2);
damage = atoi(number.c_str());
getline (myfile,line);
number = line.substr(line.find_first_of(':') + 2);
weight = atoi(number.c_str());
cout << weaponName<< "\n";
}
myfile.close();
}
else
{
cout << "Unable to open file";
}
}
Note: you'll need to #include <stdlib.h> for atoi to work.
Honestly, this still isn't very robust. Others have offered you better solutions for looking at the input to see what the data is, and reading and storing all your data, but this should show you the very basics.
You will need to parse each line of your file. So, change the function
getline(myfile, weaponName, '\t');
to
getline(myfile, weaponName);
and parse result.
Do something like that:
#include <cstdio>
#include <string>
using namespace std;
int main()
{
string line = "Weapon Name: Katana";
int pos = line.find(':');
string weaponName;
if ( line.substr(0, pos) == "Weapon Name")
weaponName = line.substr(pos+1, line.npos);
printf("%s\n", weaponName.c_str());
}
First, you need/want to distinguish between "weapons" (plural) and a single weapon. To make much sense, each individual weapon has the characteristics you're reading (name, weight, damage). So, weapons will be a collection of individual weapon objects, each of which has the characteristics.
Based on that, we can attempt to write some meaningful code:
class weapon {
std::string name;
int damage;
int weight;
public:
std::string get_name() { return name; }
Now, we want a weapon to be able to "reconstitute" itself from data stored in a file. Note, however, that right now we're writing a weapon class, so we're only going to deal with one weapon, not a whole collection of them:
friend std::istream &operator>>(std::istream &is, weapon &w) {
std::ignore(is, 1024, ':'); // ignore the "Weapon Name:" header
std::getline(is, w.name);
std::ignore(is, 1024, ':'); // ignore the "Damage:" header
is >> w.damage;
std::ignore(is, 1024, ':'); // ignore the "Weight:" header
is >> w.weight;
return is;
}
Though we don't need it just yet, let's create a matching function to write out a weapon in the correct format as well:
std::ostream &operator<<(std::ostream &os, weapon const &w) {
return os << "Weapon Name: " << w.name << "\n"
<< "Damage: " << w.damage << "\n"
<< "Weight: " << w.weight << "\n";
}
With that, we can read the data for a single weapon. Then we need some way to store multiple weapons. Lacking a reason to do otherwise, our first choice for that is normally an std::vector. If we want to fill that with the data from a file, we can do it something like this:
// open a file of all the weapons data:
std::ifstream in("weapons.txt");
// initialize the vector from the data in the file:
std::vector<weapon> weapons((std::istream_iterator<weapon>(in)),
std::istream_iterator<weapon>());
With this in place we can (for example) list all the weapons (here we're going to use the "operator<<" we defined above):
std::copy(weapons.begin(), weapons.end(),
std::ostream_iterator<weapon>(std::cout, "\n"));
If we want an abbreviated list with just the name of each weapon, we can do something like this:
for (auto const &w : weapons)
std::cout << w.get_name() << "\n";
Your format looks like a variant of a typical .ini file. There
are lots of parsers around for that, if you can modify the
format to make it conform. That would be by far the easiest
solution. Otherwise: how are the various weapons separated in
the file? Is it by an empty line, or is it because the first
entry is always "Weapon Name"? In the first case, I would use
something like the following to read the file (in a free
function, not as a member):
std::auto_ptr<Weapon> currentWeapon;
Line line;
int lineNumber = 0;
while ( source >> line ) {
++ lineNumber;
if ( line.empty() ) {
if ( currentWeapon.get() != NULL ) {
weaponCollection.insert( currentWeapon );
}
currentWeapon.release();
} else {
Line::const_iterator pivot
= std::find( line.begin(), line.end(), ':' );
if ( pivot == line.end() ) {
// Output error message, using lineNumber...
} else {
if ( currentWeapon.get() == NULL ) {
currentWeapon.reset( new Weapon );
}
std::string key( strip( std::string( line.begin(), pivot ) ) );
std::string value( strip( std::string( pivot + 1, line.end() ) ) );
if ( key == "WeaponName" ) {
currentWeapon->weaponName = value;
} else if ( key == "Damage" ) {
currentWeapon->damage = asInt( value );
} else if ( key == "Weight" ) {
currentWeapon->weight = asInt( value );
} // ...
else {
// output error message...
}
}
}
}
Line is a class in my toolbox, that I use a lot for this sort
of thing. It's basically:
class Line : private std::string
{
public:
typedef std::string::iterator iterator;
// ...
using std::string::empty;
using std::string::begin;
// ...
};
The only difference between it and std::string is that its
operator>> calls std::getline, then "cleans up" the results,
by removing any trailing white space (including a possible
'\r', because the file was written under Windows, but I'm
reading it under Unix); in this case, it might be useful to make
it also remove any leading white space. (Mine also has
a manipulator which sets a comment character; if this is set, it
removes any text from this character to the end of the line,
before trimming trailing whitespace.
(From experience: do provide some sort of facility for commenting
in the file. You'll regret it if you don't.)
And asInt is, of course:
int
asInt( std::string const& original )
{
std::istringstream s( original );
int results;
s >> results >> std::ws;
if ( !s || s.get() != EOF ) {
// Error...
}
return results;
}
Again, something that you should have in your toolbox already.
If the key to a new weapon is the "Weapon Name" attribute,
skip the empty line business (or treat empty lines as comments),
and store any existing Weapon and create the new one in the
handling for "Weapon Name".
If you throw on error, above, you'll need to use a try...catch
block to output the error and continue. You might also want to
mark that there was an error somewhere, and abort the game if
so.
Let's say I have a string that has multiple carriage returns in it, i.e:
394968686
100630382
395950966
335666021
I'm still pretty amateur hour with C++, would anyone be willing to show me how you go about: parsing through each "line" in the string ? So I can do something with it later (add the desired line to a list). I'm guessing using Find("\n") in a loop?
Thanks guys.
while (!str.IsEmpty())
{
CString one_line = str.SpanExcluding(_T("\r\n"));
// do something with one_line
str = str.Right(str.GetLength() - one_line.GetLength()).TrimLeft(_T("\r\n"));
}
Blank lines will be eliminated with this code, but that's easily corrected if necessary.
You could try it using stringstream. Notice that you can overload the getline method to use any delimeter you want.
string line;
stringstream ss;
ss << yourstring;
while ( getline(ss, line, '\n') )
{
cout << line << endl;
}
Alternatively you could use the boost library's tokenizer class.
You can use stringstream class in C++.
#include <iostream>
#include <sstream>
#include <vector>
using namespace std;
int main()
{
string str = "\
394968686\
100630382\
395950966\
335666021";
stringstream ss(str);
vector<string> v;
string token;
// get line by line
while (ss >> token)
{
// insert current line into a std::vector
v.push_back(token);
// print out current line
cout << token << endl;
}
}
Output of the program above:
394968686
100630382
395950966
335666021
Note that no whitespace will be included in the parsed token, with the use of operator>>. Please refer to comments below.
If your string is stored in a c-style char* or std::string then you can simply search for \n.
std::string s;
size_t pos = s.find('\n');
You can use string::substr() to get the substring and store it in a list. Pseudo code,
std::string s = " .... ";
for(size_t pos, begin = 0;
string::npos != (pos = s.find('\n'));
begin = ++ pos)
{
list.push_back(s.substr(begin, pos));
}
I have a simple text file, that has following content
word1 word2
I need to read it's first line in my C++ application.
Following code works, ...
std::string result;
std::ifstream f( "file.txt" );
f >> result;
... but result variable will be equal to "word1". It should be equal to "word1 word2" (first line of text file)
Yes, i know, that i can use readline(f, result) function, but is there a way to do the same, using >> style. This could be much more pretty.
Possible, some manipulators, i don't know about, will be useful here ?
Yes define a line class and define the operator >> for this class.
#include <string>
#include <fstream>
#include <iostream>
struct Line
{
std::string line;
// Add an operator to convert into a string.
// This allows you to use an object of type line anywhere that a std::string
// could be used (unless the constructor is marked explicit).
// This method basically converts the line into a string.
operator std::string() {return line;}
};
std::istream& operator>>(std::istream& str,Line& line)
{
return std::getline(str,line.line);
}
std::ostream& operator<<(std::ostream& str,Line const& line)
{
return str << line.line;
}
void printLine(std::string const& line)
{
std::cout << "Print Srting: " << line << "\n";
}
int main()
{
Line aLine;
std::ifstream f( "file.txt" );
f >> aLine;
std::cout << "Line: " << aLine << "\n";
printLine(aLine);
}
No, there isn't. Use getline(f, result) to read a line.
You can create a local that only has newlines as whitespace, but that would be a confusing hack. Here is an example doing just that with commas.