I am using a istringstream to read a string word by word. However, when my condition fails I need to be able to revert the istringstream to before the previous word was read. My example code works, but I want to know if there is a more direct way to use streams to accomplish this.
std::string str("my string");
std::istringstream iss(str);
std::ostringstream ossBackup << iss.rdbuf(); // Writes contents of buffer and in the process changes the buffer
std::string strBackup(ossBackup.str()); // Buffer has been saved as string
iss.str(strBackup); // Use string to restore iss's buffer
iss.clear(); // Clear error states
iss >> word; // Now that I have a backup read the 1st word ("my" was read)
// Revert the `istringstream` to before the previous word was read.
iss.str(strBackup); // Restore iss to before last word was read
iss.clear(); // Clear error states
iss >> word; // "my" was read again
You can use tellg() and seekg() to save and restore your position if you like:
#include <string>
#include <sstream>
int main()
{
std::istringstream iss("some text");
std::string word;
// save the position
std::streampos pos = iss.tellg();
// read a word
if(iss >> word)
std::cout << word << '\n';
iss.clear(); // clear eof or other errors
iss.seekg(pos); // move to saved position
while(iss >> word)
std::cout << word << '\n';
}
This is really only guaranteed to work for stringstream's, but you can repeatedly call unget() until you've reached a space character:
#include <iostream>
#include <sstream>
template <int n>
std::istream& back(std::istream& is)
{
bool state = is.good();
auto& f = std::use_facet<std::ctype<char>>(is.getloc());
for (int i = 0; i < n && is; ++i)
while (is.unget() && !f.is(f.space, is.peek()));
if (state && !is)
is.clear();
return is;
}
int main()
{
std::stringstream iss("hello world");
std::string str;
std::cout << "Value Before: ";
iss >> str;
std::cout << str << std::endl;
iss >> back<1>; // go back one word
std::cout << "Value after: ";
iss >> str;
std::cout << str;
}
Live Demo
Related
I need to load in the vertices of a .obj file in c++. I just copied over the verts from the object I downloaded so I didn't have to worry about texture mapping or anything. I was able to separate it into a single line and get rid of the v but am not able to isolate the x,y,z coordinates so I can assign it to three separate variables.
main.cpp
// ConsoleApplication3.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <memory>
#include <fstream>
#include <string>
using namespace std;
string pos1, pos2, pos3;
int main()
{
string line;
ifstream myfile("fox.txt");
if (myfile.is_open())
{
while (getline(myfile, line))
{
line.erase(std::remove(line.begin(), line.end(), 'v'), line.end());
cout << line << endl;
}
myfile.close();
}
else cout << "Unable to open file";
return 0;
}
fox.txt
v 10.693913 60.403057 33.765018
v -7.016389 46.160694 36.028797
v 9.998714 51.307644 35.496368
v -8.642366 49.095310 35.725204
A simple way to read in the line
v 10.693913 60.403057 33.765018
and separate it into 3 different variables is to first read in a char, then read in three doubles:
ifstream fin("fox.txt");
vector <vector<double>> data; // holds sets of coordinates
double a, b, c;
char v;
while(fin >> v >> a >> b >> c){
data.push_back({a, b, c});
}
If you wanted, you could also use std::stringstream to parse the input into doubles.
An easy way is to simply use std::stringstream and treat it like you would any other stream.
#include <sstream>
...
std::string pos1, pos2, pos3;
std::stringstream lineStream;
...
while (getline(myfile, line))
{
/* Make a string stream out of the line we read */
lineStream.str(line);
char skip; // Temp var to skip the first 'v'
lineStream >> skip >> pos1 >> pos2 >> pos3;
/* Reset error state flags for next iteration */
lineStream.clear();
}
Or you could avoid all that by using the >> operator on myfile directly.
std::string temp, pos1, pos2, pos3;
while (myfile >> temp >> pos1 >> pos2 >> pos3)
{
...
}
I'm going to figure you want to store this data in the likes of a std::vector. This is one way of doing it.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
const char* test_str = R"(
v 10.693913 60.403057 33.765018
v -7.016389 46.160694 36.028797
v 9.998714 51.307644 35.496368
v -8.642366 49.095310 35.725204
)";
struct data_item {
double x;
double y;
double z;
};
using data_set = std::vector<data_item>;
int main()
{
//std::ifstream myfile("fox.txt");
//if (!myfile.is_open()) {
// std::cout << "Unable to open file\n";
// return -1;
//}
std::stringstream as_file;
as_file << test_str;
data_set set;
for (; ;) {
std::string dummy;
data_item item;
as_file >> dummy >> item.x >> item.y >> item.z;
if (!dummy.size())
break;
set.push_back(item);
}
for (auto& item : set)
std::cout << item.x << " " << item.y << " " << item.z << std::endl;
return 0;
}
Don't do: using namespace std; It will save you a lot of headaches down the road. It also makes your code more readable to know stuff is out of the standard library.
When testing, it is sometimes more simple to use local data as I have with test_str. As pointed out in the comments, you could just let the stream do the conversion from text to doubles.
Note I've taken care of a failed file error in one place, the commented file stuff. Putting an else way down from the failure is not so clear and creates a large unneeded scope.
I have a file with following lines:
51:HD L80 Phone:78
22:Nokia Phone:91
I need to split these into 3 separate variables
(int, string, int)
int id = line[0]
string phoneName = line[1]
int price = line [2]
I have tried many solutions for example:
std::ifstream filein("records");
for (std::string line; std::getline(filein, line); )
{
// std::cout << line << std::endl;
std::istringstream iss (line);
std::string word;
std::vector<string> tempString;
while(std::getline(iss,word,',')){
tempString.push_back(word);
// std::cout << word << "\n";
}
However in this example I do get the values but they are coming in a stream and not in one go. I do not want to save them into vector (no other way to store the incoming values) but call a function immediately after getting all the 3 values.
SOLUTION
This is a modification of the accepted answer:
`for (std::string line; std::getline(filein, line); )
{
// std::cout << line << std::endl;
std::istringstream iss (line);
for (int stockID; iss >> stockID; )
{
char eater;
iss >> eater; // this gets rid of the : after reading the first int
std::string stockName;
std::getline(iss, stockName, ':'); // reads to the next :, tosses it out and stores the rest in word
std::string catagory;
std::getline(iss, catagory, ':'); // reads to the next :, tosses it out and stores the rest in word
std::string subCatagory;
std::getline(iss, subCatagory, ':');
int stockPrice;
iss >> stockPrice;
iss >> eater; // this gets rid of the : after reading the first int
int stockQTY;
iss >> stockQTY; // get the last int
// iss >> eater;
// std::cout << stockName << "\n";
Record recordd = Record(stockID,stockName,catagory,subCatagory,stockPrice,stockQTY);
record.push_back(recordd);
}
}`
for when text file contains:
51:HD L80 Phone:Mobile:Samsung:480:40
22:Nokia Phone:Mobile:Nokia:380:200
There is no reason to use a std::stringstream here if you know you are going to have exactly 3 columns in every row. Instead you can read those values directly from the file, store them in temporaries, and then call the function with those temporary variables.
for (int a; filein >> a; )
{
char eater;
filein >> eater; // this gets rid of the : after reading the first int
std::string word;
std::getline(filein, word, ':'); // reads to the next :, tosses it out and stores the rest in word
int b;
filein >> b; // get the last int
function_to_call(a, word, b);
}
You can find different ways to split a string here:
https://www.fluentcpp.com/2017/04/21/how-to-split-a-string-in-c/
Example:
std::vector<std::string> split(const std::string& s, char delimiter)
{
std::vector<std::string> tokens;
std::string token;
std::istringstream tokenStream(s);
while (std::getline(tokenStream, token, delimiter))
{
tokens.push_back(token);
}
return tokens;
}
I have a code like this, concerning stringstream. I found a strange behavior:
#include <iostream>
#include <fstream>
#include <sstream>
using namespace std;
int main()
{
int p, q;
fstream file;
string str;
stringstream sstr;
file.open("file.txt", ios::in);
if(file.is_open()) {
while(getline(file, str)) {
sstr << str;
sstr >> p >> q;
cout << p << ' ' << q << endl;
sstr.str("");
}
}
file.close();
return 0;
}
Suppose I have file.txt as
4 5
0 2
with return after 5 in the first line and 2 in the second line. The program gives me:
4 5
4 5
which means p and q are not correctly assigned. But I checked that each time sstr.str() with get the correct string of the line.
Why stringstream has a behaviour like this?
The stream is in a non-good state after reading the second integer, so you have to reset its error state before resuming.
Your real mistake was to not check the return value of the input operations, or you would have caught this immediately!
The simpler solution may be to not try to reuse the same stream, but instead make it anew each round:
for (std::string line; std::getline(file, line); )
{
std::istringstream iss(line);
if (!(iss >> p >> q >> std::ws) || !iss.eof())
{
// parse error!
continue;
}
std::cout << "Input: [" << p << ", " << q << "]\n";
}
When you read p, then q, you reach the end of your stream and the flag eofbit is set and you can't do anything anymore.
Just clear() it and your code will work as you expect.
But you may want to use directly file instead, and file.close(); will have a better place within your if:
fstream file;
file.open("file.txt", ios::in);
if(file.is_open()) {
int p, q;
while(file >> p >> q) {
cout << p << ' ' << q << endl;
}
file.close();
}
Your code has some redundant lines: fstream could be opened during the definition and no explicit file close() is needed, as it is automatically destroyed at the end of main().
Additionally, in your file reading loop, the line: sstr << str should be replaced with stringstream sstr(line); if you want to initialize a new stringstream for each line, which will make the line: sstr.str(""); redundant as well.
Applying the above corrections, here is your code:
int main() {
int p, q;
fstream file("file.txt", ios::in);
// check status
if (!file) cerr << "Can't open input file!\n";
string line;
// read all the lines in the file
while(getline(file, line)) {
// initialize the stringstream with line
stringstream sstr(line);
// extract line contents (see Note)
while (sstr >> p >> q) {
// print extracted integers to standard output
cout <<"p: " << p <<" q: "<< q << endl;
}
}
return 0;
}
Note: The line while (sstr >> p >> q) assumes that a line contains only integers, separated by white space.
This is my problem: I read some lines from a txt. This txt is like this:
Ciao: 2000
Kulo: 5000
Aereo: 7000
ecc. I have to assign every word before(':') to a string and then to a map; and the numbers to a int and then to a map. The problem is that beginning from the second line, my string become ("\nKulo") ecc! I don't want this! What can I do?
This is the code:
#include <iostream>
#include <fstream>
#include <string>
#include <map>
using namespace std;
int main()
{
map <string, int> record;
string nome, input;
int valore;
ifstream file("punteggi.txt");
while (file.good()) {
getline(file, nome, ':');
// nome.erase(0,2); //Elimina lo spazio iniziale
file >> valore;
record[nome] = valore;
cout << nome;
}
file.close();
cout << "\nNome: ";
cin >> input;
cout << input << ": " << record[input] << "\n";
cout << "\n\n";
return 0;
}
The issue you have is that std::getline() is an unformatted input function and as such doesn't skip leading whitespace. From the looks of it, you want to skip leading whitespace:
while (std::getline(in >> std::ws, nome, ':') >> valore) {
...
}
Alternatively, if there are leading spaces, you can ignore() all characters up to the end of line after reading a value.
BTW, since I saw someone over here recommending the use of std::endl: do not use std::endl unless you really intend to flush the buffer. It is a frequent major performance problem when writing files.
Use the standard line reading idiom:
for (std::string line; std::getline(file, line); )
{
std::string key;
int n;
std::istringstream iss(line);
if (!(iss >> key >> n) || key.back() != ':') { /* format error */ }
m.insert(std::make_pair(std::string(key.cbegin(), std::prev(key.cend()),
n));
}
(Instead of the temporary string-from-iterators, you can also use key.substr(0, key.length() - 1), although I imagine that my version may be a bit more efficient. Or add a key.pop_back(); before inserting the data into the map.)
another request sorry..
Right now I am reading the tokens in one by one and it works, but I want to know when there is a new line..
if my file contains
Hey Bob
Now
should give me
Hey
Bob
[NEW LINE]
NOW
Is there a way to do this without using getline?
Yes the operator>> when used with string read 'white space' separated words. A 'White space' includes space tab and new line characters.
If you want to read a line at a time use std::getline()
The line can then be tokenized separately with a string stream.
std::string line;
while(std::getline(std::cin,line))
{
// If you then want to tokenize the line use a string stream:
std::stringstream lineStream(line);
std::string token;
while(lineStream >> token)
{
std::cout << "Token(" << token << ")\n";
}
std::cout << "New Line Detected\n";
}
Small addition:
Without using getline()
So you really want to be able to detect a newline. This means that newline becomes another type of token. So lets assume that you have words separated by 'white spaces' as tokens and newline as its own token.
Then you can create a Token type.
Then all you have to do is write the stream operators for a token:
#include <iostream>
#include <fstream>
class Token
{
private:
friend std::ostream& operator<<(std::ostream&,Token const&);
friend std::istream& operator>>(std::istream&,Token&);
std::string value;
};
std::istream& operator>>(std::istream& str,Token& data)
{
// Check to make sure the stream is OK.
if (!str)
{ return str;
}
char x;
// Drop leading space
do
{
x = str.get();
}
while(str && isspace(x) && (x != '\n'));
// If the stream is done. exit now.
if (!str)
{
return str;
}
// We have skipped all white space up to the
// start of the first token. We can now modify data.
data.value ="";
// If the token is a '\n' We are finished.
if (x == '\n')
{ data.value = "\n";
return str;
}
// Otherwise read the next token in.
str.unget();
str >> data.value;
return str;
}
std::ostream& operator<<(std::ostream& str,Token const& data)
{
return str << data.value;
}
int main()
{
std::ifstream f("PLOP");
Token x;
while(f >> x)
{
std::cout << "Token(" << x << ")\n";
}
}
I don't know why you think std::getline is bad. You can still recognize newlines.
std::string token;
std::ifstream file("file.txt");
while(std::getline(file, token)) {
std::istringstream line(token);
while(line >> token) {
std::cout << "Token :" << token << std::endl;
}
if(file.unget().get() == '\n') {
std::cout << "newline found" << std::endl;
}
}
This is another cool and much less verbose way I came across to tokenize strings.
vector<string> vec; //we'll put all of the tokens in here
string token;
istringstream iss("put text here");
while ( getline(iss, token, '\n') ) {
vec.push_back(token);
}