How to achieve scanf("%d # %d",&a,&b);sort of effect with cin in C++ ?
You can skip the # by extracting it into a character:
std::istringstream iss("10 # 20");
int main()
{
int a, b; char hash;
iss >> a >> hash >> b;
assert(a == 10 && b == 20);
}
You could create your own stream manipulator. It is fairly easy.
#include <ios>
#include <iostream>
using namespace std;
// skips the number of characters equal to the length of given text
// does not check whether the skipped characters are the same as it
struct skip
{
const char * text;
skip(const char * text) : text(text) {}
};
std::istream & operator >> (std::istream & stream, const skip & x)
{
ios_base::fmtflags f = stream.flags();
stream >> noskipws;
char c;
const char * text = x.text;
while (stream && *text++)
stream >> c;
stream.flags(f);
return stream;
}
int main()
{
int a, b;
cin >> a >> skip(" # ") >> b;
cout << a << ", " << b << endl;
return 0;
}
There isn't a direct function inside the istream class that mimics it, unfortunately. There are functions that you might be able to use to manipulate the stream and get the correct input, but I'm not familiar with how they work so I couldn't tell you how.
My best suggestion on how I would personally do it is to use getline() to put the input into a string and then from there I would do a few checks to see if it matches the format. So in your case I would grab the first substring up until the first space, make sure it's a valid decimal, check to make sure the pound sign ('#') is in the correct spot, then grab the ending number to make sure it's valid. If any one of those three objects are incorrect I would set some boolean variable to false to kick out or return or something to indicate that the input was invalid and not the correct format.
Pseudo Code:
...
getline(cin,myStr);
while(!formatCheck(myStr))
{
cout<<"Not valid format for input";
getline(cin,myStr);
}
...
bool formatCheck(string str)
{
string firstPart=str.subString(0,firstSpaceLocation);
string middle=str[firstSpaceLocation+1];
string lastPart=str.subString(firstSpaceLocation+3,end);
if(first part not a valid number || middle!="#" || last part not a valid number)
{
return false;
}
return true;
}
Here's another way. You can classify # as a whitespace character through the std::ctype<char> facet imbued in the locale:
#include <iostream>
#include <sstream>
#include <vector>
namespace detail
{
enum options { add, remove };
class ctype : public std::ctype<char>
{
private:
static mask* get_table(const std::string& ws, options opt)
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
for (char c : ws)
{
if (opt == add)
table[c] |= space;
else if (opt == remove)
table[c] &= ~space;
}
return &table[0];
}
public:
ctype(const std::string& ws, options opt)
: std::ctype<char>(get_table(ws, opt)) { }
};
}
class adjustws_impl
{
public:
adjustws_impl(const std::string& ws, detail::options opt) :
m_ws(ws),
m_opt(opt)
{ }
friend std::istream& operator>>(std::istream& is,
const adjustws_impl& manip)
{
is.imbue(std::locale(is.getloc(),
new detail::ctype(manip.m_ws, manip.m_opt)));
return is;
}
private:
std::string m_ws;
detail::options m_opt;
};
adjustws_impl setws(const std::string& ws)
{
return adjustws_impl(ws, detail::add);
}
adjustws_impl unsetws(const std::string& ws)
{
return adjustws_impl(ws, detail::remove);
}
int main()
{
std::istringstream iss("10 # 20");
int a, b;
iss >> setws("#");
iss >> a >> b;
iss >> unsetws("#");
std::cout << a << ' ' << b; // 10 20
}
You can skip the #, or any single character, by using std::istream::ignore
std::istringstream sstr("1024 # 768");
int main()
{
int a, b;
sstr >> a;
sstr.ignore(256,'#'); // ignore until hash character
sstr >> b;
std::cout << "a: " << a << " b: " << b << std::endl;
}
Related
I am currently developing an open-source Text-based storage utility called WaterBase. The aim is to facilitate easy saving and access of persistent key-value data, like we have in Android SharedPreferences.
The data storage scheme is like this:
type:key:value
The problem I am facing is that if someone uses : as a character in their key or value, the code breaks as it counts : as separator.
How do I overcome this behavior? I don't want to restrict the use of separators in user data. I looked about encoding but couldn't find any working code without external libraries.
You can have a look in the .h file here.
A mechanism that can be easily implemented in all languages instead of just C++ would be better so as to diversify the use case.
If you indeed want no special characters in the output string, you need to store the information about the string length beforehand. You could use an approach similar to name mangling: store the length of the next entry as integer followed by a seperator followed by the actual content:
Example
A string is stored as
<string length(decimal)> '_' <string content>
struct Entry
{
std::string type;
std::string key;
std::string value;
};
void WriteMangled(std::ostream& s, std::string const& str)
{
s << str.length() << '_' << str;
}
void ParseMangled(std::istream& s, std::string& str)
{
size_t size;
char c;
if ((s >> size) && (s >> c))
{
assert(c == '_');
str.resize(size, '\0');
s.read(str.data(), size);
}
}
std::ostream& operator<<(std::ostream& s, Entry const& entry)
{
WriteMangled(s, entry.type);
WriteMangled(s, entry.key);
WriteMangled(s, entry.value);
return s;
}
std::istream& operator>>(std::istream& s, Entry& entry)
{
ParseMangled(s, entry.type);
ParseMangled(s, entry.key);
ParseMangled(s, entry.value);
return s;
}
int main() {
std::ostringstream oss;
oss << Entry{ "_Td$a", "8X0_8", "foo bar baz"};
std::string str = std::move(oss).str();
std::cout << str << '\n';
std::istringstream iss(std::move(str));
Entry e;
iss >> e;
std::cout << e.type << '\n' << e.key << '\n' << e.value << '\n';
}
Adding an escape char could be simpler though, e.g. using the backslash char as character simply marking the next char as a char that is not a special character, like a seperator. The drawback is that you have to replace backslashes in the original strings with double backslashes when writing the output.
constexpr char EscapeChar = '\\';
constexpr char SeparatorChar = ':';
bool ReadEscapedString(std::istream& s, std::string& str)
{
bool escaped = false;
char c;
while (s >> c)
{
switch (c)
{
case EscapeChar:
if (!(s >> c))
{
return false; // could not read escaped char
}
break;
case SeparatorChar:
return true;
default:
break;
}
str.push_back(c);
}
return true;
}
std::istream& operator>>(std::istream& s, Entry& entry)
{
ReadEscapedString(s, entry.type)
&& ReadEscapedString(s, entry.key)
&& ReadEscapedString(s, entry.value);
return s;
}
int main() {
std::istringstream iss(R"(foo\:bar:\:baz\:\:a:x)"); // Note: Raw string literal for easier readability, see https://en.cppreference.com/w/cpp/language/string_literal
Entry e;
iss >> e;
std::cout << e.type << '\n' << e.key << '\n' << e.value << '\n';
}
How to achieve scanf("%d # %d",&a,&b);sort of effect with cin in C++ ?
You can skip the # by extracting it into a character:
std::istringstream iss("10 # 20");
int main()
{
int a, b; char hash;
iss >> a >> hash >> b;
assert(a == 10 && b == 20);
}
You could create your own stream manipulator. It is fairly easy.
#include <ios>
#include <iostream>
using namespace std;
// skips the number of characters equal to the length of given text
// does not check whether the skipped characters are the same as it
struct skip
{
const char * text;
skip(const char * text) : text(text) {}
};
std::istream & operator >> (std::istream & stream, const skip & x)
{
ios_base::fmtflags f = stream.flags();
stream >> noskipws;
char c;
const char * text = x.text;
while (stream && *text++)
stream >> c;
stream.flags(f);
return stream;
}
int main()
{
int a, b;
cin >> a >> skip(" # ") >> b;
cout << a << ", " << b << endl;
return 0;
}
There isn't a direct function inside the istream class that mimics it, unfortunately. There are functions that you might be able to use to manipulate the stream and get the correct input, but I'm not familiar with how they work so I couldn't tell you how.
My best suggestion on how I would personally do it is to use getline() to put the input into a string and then from there I would do a few checks to see if it matches the format. So in your case I would grab the first substring up until the first space, make sure it's a valid decimal, check to make sure the pound sign ('#') is in the correct spot, then grab the ending number to make sure it's valid. If any one of those three objects are incorrect I would set some boolean variable to false to kick out or return or something to indicate that the input was invalid and not the correct format.
Pseudo Code:
...
getline(cin,myStr);
while(!formatCheck(myStr))
{
cout<<"Not valid format for input";
getline(cin,myStr);
}
...
bool formatCheck(string str)
{
string firstPart=str.subString(0,firstSpaceLocation);
string middle=str[firstSpaceLocation+1];
string lastPart=str.subString(firstSpaceLocation+3,end);
if(first part not a valid number || middle!="#" || last part not a valid number)
{
return false;
}
return true;
}
Here's another way. You can classify # as a whitespace character through the std::ctype<char> facet imbued in the locale:
#include <iostream>
#include <sstream>
#include <vector>
namespace detail
{
enum options { add, remove };
class ctype : public std::ctype<char>
{
private:
static mask* get_table(const std::string& ws, options opt)
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
for (char c : ws)
{
if (opt == add)
table[c] |= space;
else if (opt == remove)
table[c] &= ~space;
}
return &table[0];
}
public:
ctype(const std::string& ws, options opt)
: std::ctype<char>(get_table(ws, opt)) { }
};
}
class adjustws_impl
{
public:
adjustws_impl(const std::string& ws, detail::options opt) :
m_ws(ws),
m_opt(opt)
{ }
friend std::istream& operator>>(std::istream& is,
const adjustws_impl& manip)
{
is.imbue(std::locale(is.getloc(),
new detail::ctype(manip.m_ws, manip.m_opt)));
return is;
}
private:
std::string m_ws;
detail::options m_opt;
};
adjustws_impl setws(const std::string& ws)
{
return adjustws_impl(ws, detail::add);
}
adjustws_impl unsetws(const std::string& ws)
{
return adjustws_impl(ws, detail::remove);
}
int main()
{
std::istringstream iss("10 # 20");
int a, b;
iss >> setws("#");
iss >> a >> b;
iss >> unsetws("#");
std::cout << a << ' ' << b; // 10 20
}
You can skip the #, or any single character, by using std::istream::ignore
std::istringstream sstr("1024 # 768");
int main()
{
int a, b;
sstr >> a;
sstr.ignore(256,'#'); // ignore until hash character
sstr >> b;
std::cout << "a: " << a << " b: " << b << std::endl;
}
I want to parse a string of numbers into a vector of elements. The string consists of blocks of four numbers, separated by ( ) : /, and each block is separated by a ;.
Specifically, the string is in this format: int(int):float/float;, see code sample below. I think I could use a regular expression, but since the data is so structured, I'm sure there must be a more approachable and easier way to parse such a string. I'm using istringstream, but it feels a bit clumsy.
std::string line = "0(0):0/0;1(2):0.01/0.02;2(4):0.02/0.04;3(6):0.03/0.06;"
struct Element {
int a;
int b;
int c;
int d;
};
std::vector<Element> = parse(line);
std::vector<Element> parse(std::string line)
{
std::vector<Element> elements;
std::istringstream iss(line);
while(iss) {
char dummy;
Element element;
iss >> element.a;
iss.read(&dummy,sizeof(dummy)); // (
iss >> element.b;
iss.read(&dummy,sizeof(dummy)); // )
iss.read(&dummy,sizeof(dummy)); // :
iss >> element.c;
iss.read(&dummy,sizeof(dummy)); // /
iss >> element.d;
iss.read(&dummy,sizeof(dummy)); // ;
if (!iss) {break;}
elements.push_back(element);
}
return elements;
}
My questions:
What would be a good way to parse? Should I use std::stringstream and read in number by number and 'chop off' the characters in between? As done in the code sample?
This code has a bug and attempts to read one extra set of values, because while(iss) is still true, after the last character has been read in. How to terminate this loop without testing after each iss>>? Or more generally, how to loop over extractions from istringstream?
Your data are well structured, you can easily overload operator>> to extract the class members from an std::ifstream and then keep reading them from an istringstream or a file stream.
Here is a possible implementation:
#include <iostream>
#include <vector>
#include <string>
#include <sstream>
#include <fstream>
#include <iterator>
#include <stdexcept>
class Element
{
public:
Element() {}
Element(int aa, int bb, float cc, float dd) : a{aa}, b{bb}, c{cc}, d{dd} {}
friend std::istream &operator>> (std::istream &in, Element &e);
friend std::ostream &operator<< (std::ostream &out, Element const &e);
private:
int a;
int b;
float c;
float d;
};
std::istream &operator>> (std::istream &in, Element &e)
{
char delimiter;
if ( not ( in >> e.a >> delimiter and delimiter == '(' and
in >> e.b >> delimiter and delimiter == ')' and
in >> delimiter and delimiter == ':' and
in >> e.c >> delimiter and delimiter == '/' and
in >> e.d >> delimiter and delimiter == ';' )
and not in.eof() )
{
in.setstate(std::ios_base::failbit);
}
return in;
}
std::ostream &operator<< (std::ostream &out, Element const &e)
{
return out << e.a << '(' << e.b << "):" << e.c << '/' << e.d << ';';
}
std::vector<Element> read_Elements_from(std::istream &in)
{
std::vector<Element> tmp (
std::istream_iterator<Element>{in},
std::istream_iterator<Element>{}
);
if ( not in.eof() )
throw std::runtime_error("Wrong format");
return tmp;
}
int main()
{
try
{
using std::cout;
std::istringstream iss {
"0(0):0/0;1(2):0.01/0.2;2(4):0.02/0.04;3(6):0.03/0.06;"
};
auto els_s = read_Elements_from(iss);
cout << "Elements read from the string:\n";
for ( auto const &i : els_s )
{
cout << i << '\n';
}
// assuming a file which lines are similar to the string provided
std::ifstream input_file {"input_data.txt"};
if ( not input_file )
throw std::runtime_error("Can't open input file");
auto els_f = read_Elements_from(input_file);
cout << "\nElements read from the file:\n";
for ( auto const &i : els_f )
{
cout << i << '\n';
}
}
catch ( std::exception const &e )
{
std::cerr << "\nAn unexpected problem cause this application to end:\n\n"
<< e.what() << ".\n\n";
return EXIT_FAILURE;
}
}
I want to make a custom istream manipulator that reads 2 characters from the input, then skips 2 characters from the input, and does that until it runs out of any input.
For example, if I have code like this:
std::string str;
std::cin >> skipchar >> str;
Where skipchar is my manipulator, if the user enters 1122334455, str should contain 113355.
This is what I've got so far, I don't know what I should put in the while loop condition to make this code work properly:
istream& skipchar(istream& stream)
{
char c;
while(1)
{
for (int i = 0; i < 2; ++i)
stream >> c;
for (int i = 0; i < 2; ++i)
stream.ignore(1, '\0');
}
return stream;
}
Any help would be appreciated.
That's a very nice question. I don't know if it's possible. But I implemented something different which gives you the same short syntax you wanted, by overloading the >> operator with a new class called Skip2. Here is the code (which I really enjoyed writing! :-) )
#include <iostream>
#include <string>
#include <istream>
#include <sstream>
using namespace std;
class Skip2 {
public:
string s;
};
istream &operator>>(istream &s, Skip2 &sk)
{
string str;
s >> str;
// build new string
ostringstream build;
int count = 0;
for (char ch : str) {
// a count "trick" to make skip every other 2 chars concise
if (count < 2) build << ch;
count = (count + 1) % 4;
}
// assign the built string to the var of the >> operator
sk.s = build.str();
// and of course, return this istream
return s;
}
int main()
{
istringstream s("1122334455");
Skip2 skip;
s >> skip;
cout << skip.s << endl;
return 0;
}
It's tricky; istream manipulators don't operate as "filters" on the stream but rather as single-shot operations. The istream manipulators provided by the Standard (noskipws, hex etc.) do their work by setting and clearing flags on the stream, so they only expose functionality that is already available.
However, it is possible to create a filtering streambuf wrapping the streambuf of cin (or any input stream) and use a manipulator to install or remove it:
struct skipbuf : std::streambuf {
std::unique_ptr<std::streambuf> src;
int i;
char buf[4];
skipbuf(std::streambuf* src) : std::streambuf{*src}, src{src} {
setg(buf, buf + 2, buf + 2);
}
std::streambuf* unwrap() {
while (buf + i != gptr())
src->sputbackc(buf[--i]);
return src.release();
}
std::streambuf::int_type underflow() override {
setg(buf, buf, buf + std::min(i = src->sgetn(buf, 4), 2));
return i ? buf[0] : traits_type::eof();
}
};
std::istream& skipchar(std::istream& is) {
is.rdbuf(new skipbuf{is.rdbuf()});
return is;
}
std::istream& noskipchar(std::istream& is) {
if (auto* buf = dynamic_cast<skipbuf*>(is.rdbuf()))
delete (is.rdbuf(buf->unwrap()), buf);
return is;
}
Example of usage:
int main() {
std::istringstream iss{"1122334455 hello"};
std::string s1, s2;
iss >> skipchar >> s1 >> noskipchar >> s2;
std::cout << s1 << ' ' << s2 << std::endl;
}
Expected output (run it online):
113355 hello
I am trying to read complex numbers of the form x + y*i from a file which looks like this:
2 + 3i
4 + 5i
If I implement it like this it only works for the first line and I would like to be able to read again in the same manner the second line. Any ideas?
istream& operator>>(istream& in, Complex& c) {
in >> c.r >> c.i;
return in;
}
EDIT: Do not throw exceptions directly, as this is not the usual way of doing thing with iostreams.
EDIT: Process the sign character separately so that spaces in the input are allowed.
A quick and dirty solution:
istream& operator>>(istream& in, Complex& c)
{
char sign;
char suffix;
in >> c.r >> sign >> c.i >> suffix;
if ((sign != '+' && sign != '-') || suffix != 'i') {
in.setstate(ios::failbit);
}
if (sign == '-') {
c.i = -c.i;
}
return in;
}
You need to make sure that you read both the "+" and the "i" when processing the input.
The following implementation works:
struct complex
{
double r;
double i;
};
std::istream& operator>>(std::istream& in, complex& c)
{
char plus, i;
in >> c.r >> plus >> c.i >> i;
return in;
}
std::string initdata = "2 + 3i\n4 + 5i\n";
int main()
{
std::stringstream ss(initdata);
std::vector<complex> values;
std::istream_iterator<complex> begin(ss), end;
std::copy(begin, end, std::back_inserter<std::vector<complex>>(values));
}
I would do it like this :
#include <iostream>
struct complex
{
int r, i;
};
int main ()
{
complex co;
char c;
while ((std::cin >> co.r >> c /* '+' */ >> co.i >> c /* 'i' */))
{
std::cout << co.r << ' ' << co.i << "i\n";
}
}
This should work for you:
#include <iostream>
#include <sstream>
int main(int argc, char *argv[])
{
std::istringstream s(
"2 + 3i\n"
"4 + 5i\n"
);
char sgn;
double r;
double i;
while(s >> r >> sgn >> i) {
if(s.get() != 'i') {
std::cerr << "Missing i\n";
return -1;
}
std::cout << r << sgn << i << "i\n";
}
return 0;
}
Note: The space before the imaginary part and the trailing i are breaking the input.