How to make generic stringToVector function in c++? - c++

I am trying to make a generic stringToVector function.
The input is a string that contains multiple int or string separated by comma (let's ignore char)
ex)
[1, 5, 7] or [conver, to, string, vector]
I want a generic function like this
template <class T>
vector<T> stringToVector(string input) {
vector<T> output;
input = input.substr(1, input.length() - 2);
stringstream ss;
ss.str(input);
T item;
char delim = ',';
while (getline(ss, item, delim)) {
if (is_same(T, int)) {
output.push_back(stoi(item)); // error if T is string
} else {
output.push_back(item); // error if T is int
}
}
return output;
}
Is there any way around?
I know this function is dumb, but I just want this for a competitive programming.

Usually it is done by a helper function:
template<class T>
T my_convert( std::string data );
template<>
std::string my_convert( std::string data )
{
return data;
}
template<>
int my_convert( std::string data )
{
return std::stoi( data );
}
inside your function:
str::string str;
while (getline(ss, str, delim))
output.push_back( my_convert<T>( std::move( str ) ) );
it will fail to compile for any other type than std::string or int but you can add more specializations of my_convert if you need to support other types.

Related

Extracting a specific thing from a string

I have a string in the format <a,b>, which represents an edge in a directed graph (a is source and b is target). a and b are also strings themselves (for example, a can be "Square" and b is "Circle").
I need to build a function which extracts a, and another function which extracts b. So the signature will be:
string getSource(String edge); //will return b
string getTarget(String edge); //will return a
I am using the std::string library to represent those strings.
I know that I need to find a way to find the ',' separating them in the middle of the string, and get rid of the '<' and '>'. But I couldn't find a function in std::string that will help me to do that.
How would you go about on doing this?
This seems to be a good use case for a regex:
std::regex sd {R"(<(.*),(.*)>)"};
and then your functions can be written as:
std::string getSource(std::string const & edge) {
std::smatch m;
std::regex_match(edge, m, sd);
return m[1].str();
}
and in getTarget you would return m[2].str();.
If you know for certain that the string is in the correct format, this is just a matter of using std::find to locate the characters of interest and then constructing a new string from those iterators. For example:
std::string getSource(std::string const & edge) {
return {
std::next(std::find(std::begin(edge), std::end(edge), '<')),
std::find(std::begin(edge), std::end(edge), ',')
};
}
std::string getTarget(std::string const & edge) {
return {
std::next(std::find(std::begin(edge), std::end(edge), ',')),
std::find(std::begin(edge), std::end(edge), '>')
};
}
If the strings are not in the correct format then these functions could exhibit undefined behavior. This could be fixed trivially with the use of a helper function:
template <typename T>
std::string checkedRangeToString(T begin, T end) {
if (begin >= end) {
// Bad format... throw an exception or return an empty string?
return "";
}
return {begin, end};
}
std::string getSource(std::string const & edge) {
return checkedRangeToString(
std::next(std::find(std::begin(edge), std::end(edge), '<')),
std::find(std::begin(edge), std::end(edge), ',')
);
}
std::string getTarget(std::string const & edge) {
return checkedRangeToString(
std::next(std::find(std::begin(edge), std::end(edge), ',')),
std::find(std::begin(edge), std::end(edge), '>')
);
}
This sounds like it belongs in a class whose constructor takes that std::string argument and parses it.
class edge {
public:
edge(const std::string& str);
std::string source() const { return src; }
std::string target() const { return tgt; }
private:
std::string src;
std::string tgt;
};
edge::edge(const std::string& str) {
auto comma = std::find(std::begin(str), std::end(str), ',');
if (str.length() < 3 || comma == std::end(str) || str.front() != '<' || str.back() != '>')
throw std::runtime_error("bad input");
src = std::string(std::next(std::begin(str)), comma);
tgt = std::string(std::next(comma), std::prev(std::end(str)));
}
I wouldn't use a regular expression for such a simple parse. Regular expressions are expensive and highly overrated.

C++ reading in a comma delimited file[with one section as mm/dd/yyyy] by line, placing into a struct securely

To expand on the title, I am reading a file line-by-line that appears as so:
FirstName,LastName,mm/dd/yyyy,SSN,Role,Salary,Zip,Phone
I have this code I just wrote but am having some trouble placing it into my struct as I'm using std::string as opposed to char[]. I want to continue using std::string for purposes down the road. Also, excuse any syntax errors as I haven't written in c/c++ in a while. I have also read the most elegant way to iterate to words of a string but I am still confused on how to do that with the slashes involved in the date format. SSN and Salary are private members of a struct that will be pushed into a vector for later use. How can I do this using c++ libraries? To be honest, the istringstream confuses me as they include some type of parser inside their struct directly. Is this honestly the best way to accomplish what I'm trying to do?
char stringData[150]; //line to be read in
while(fgets(stringData, 150, infile) != NULL) {
if( currentLine == 1) {
fgets(stringData, 150, infile); //get column names | trash
}
else {
lineSize = sscanf(stringData, "%[^,],%[^,],%d/%d/%d,%d,%[^,],%lf,%[^,],%s", temp.firstName,temp.lastName,
&temp.birthMonth,&temp.birthDay,&temp.birthYear,
&tempSSN, temp.role, &tempSalary, temp.zip,
temp.phoneNum);
if(lineSize != 10) { //error message due to a row being incorrect
cerr << "/* ERROR: WRONG FORMAT OF INPUT(TOO FEW OR TOO MANY ARGUMENTS) ON LINE: */" << currentLine << '\n';
exit(1);
}
temp.setSSN(tempSSN);
temp.setSalary(tempSalary);
vector.push_back(temp);//push Employee temp into the vector and repeat loop
}
currentLine++
}
TL;DR: What is the easiest way to do this using c++ libraries?
As Sam Varshavchik already mentioned, the easiest way would be separating input with , then separate on of them with / again.
Thanks to this famous question I'm using following approach to split string :
template<typename Out>
void split(const std::string &s, char delim, Out result)
{
std::stringstream ss(s);
std::string item;
while(std::getline(ss, item, delim))
{
*(result++) = item;
}
}
std::vector<std::string> split(const std::string &s, char delim)
{
std::vector<std::string> elems;
split(s, delim, std::back_inserter(elems));
return elems;
}
assuming that this is your structure :
struct info
{
std::string firstName;
std::string lastName;
std::string birthMonth;
std::string birthDay;
std::string birthYear;
std::string tempSSN;
std::string role;
std::string tempSalary;
std::string zip;
std::string phoneNum;
};
I would implement your needed function like this :
void parser(std::string fileName, std::vector<info> &inf)
{
std::string line;
std::ifstream infile(fileName);
int index = inf.size();
while(std::getline(infile, line))
{
inf.push_back({});
std::vector<std::string> comma_seprated_vec = split(line, ',');
inf.at(index).firstName = comma_seprated_vec.at(0);
inf.at(index).lastName = comma_seprated_vec.at(1);
inf.at(index).tempSSN = comma_seprated_vec.at(3);
inf.at(index).role = comma_seprated_vec.at(4);
inf.at(index).tempSalary = comma_seprated_vec.at(5);
inf.at(index).zip = comma_seprated_vec.at(6);
inf.at(index).phoneNum = comma_seprated_vec.at(7);
std::vector<std::string> slash_seprated_vec = split(comma_seprated_vec.at(2), '/');
inf.at(index).birthMonth = slash_seprated_vec.at(0);
inf.at(index).birthDay = slash_seprated_vec.at(1);
inf.at(index).birthYear = slash_seprated_vec.at(2);
++index;
}
}
Then you can use it like this :
int main()
{
std::vector<info> information;
parser("some file", information);
return 0;
}
There you go, your information are presented in information variable.

Pairs and stringstream

In a previous question I have asked how I could read and comfortably transform a vector of strings into a vector of integers or doubles.
Now I am expanding that vector of simple types to vector of pairs of types (consider int, double, or even std::string, all stream-enabled types) separated by colons.
Example:
time:5, length:10
Should be read as a std::pair<std::string, unsigned int>, with something like this:
// Here declare what to parse: a string and an uint, separated by a colon
get_vector<std::pair<std::string, unsigned int>>(s);
How can I write a std::stringstream operator that does the trick?
I cannot figure how (or even if) I could play with getline (my code follows). I'd really like to leave the get_vector function as it is, if possible.
Thanks!
template <class F, class S>
std::stringstream& operator >> (std::stringstream& in, std::pair<F, S> &out)
{
in >> out.first >> out.second;
return in;
}
template <class T>
auto get_vector(std::string s) -> std::vector<T>
{
std::vector<T> v;
// Vector of strings
auto tmp = split(s);
// Magic
std::transform(tmp.begin(), tmp.end(), std::back_inserter(v),
[](const std::string& elem) -> T
{
std::stringstream ss(elem);
T value;
ss >> value;
return value;
});
return v;
}
Your operator>> receives a std::stringstream which in that moment contains a string like "time:5". The operator needs to parse that string by splitting it up into two tokens, then convert the first token to an F and the second to an S.
One way to do this is to use std::getline with a custom delimiter. You will end up with two std::string tokens. These can be converted, for example, with two internal std::istringstreams.
Here is some code you can try:
template <class F, class S>
std::stringstream& operator >> (std::stringstream& in, std::pair<F, S> &out)
{
std::string first_token;
std::string second_token;
std::getline(in, first_token, ':');
std::getline(in, second_token);
std::istringstream first_converter(first_token);
std::istringstream second_converter(second_token);
first_converter >> out.first;
second_converter >> out.second;
return in;
}
You may want to add some error handling for the case when there is no ':' in the string.
Note that you should not use std::stringstream if you do not want to combine input and output. Most of the times, you will want either std::istringstream or std::ostringstream.
Because I am new in English I am not sure to understand what you want.
I jest seeing that #Christian Hackl do what. So if this answer does not
relate to what you want please tell me and I will delete it.
I assume you need to extract a string from string or stream and then put them into a vector that contains a std::pair< std::string, std::size_t >
Say you have this string that can be an input-stream, too:
std::string string( "one:1, two:20, three:300, four:40000, five:500000" );
There is more than one way to do it, but I say it in this way!
So first you need to split this string and then put what has split, in to vector of pair.
std::string string( "one:1, two:20, three:300, four:40000, five:500000" );
std::stringstream io_stream;
std::basic_regex< char > regex( ", " );
std::regex_token_iterator< std::string::const_iterator > last;
std::regex_token_iterator< std::string::const_iterator > first( string.begin(), string.end(), regex, -1 );
std::vector< std::pair< std::string, std::size_t > > vector_of_pair;
std::string str_pair( "" );
std::size_t ui_pair( 0 );
while( first != last ){
io_stream.clear();
io_stream.seekg( 0 );
io_stream.seekp( 0 );
io_stream << *first << '\n';
std::getline( io_stream, str_pair, ':' );
// error:
// no match function to call: stream and int
// std::getline( io_stream, ui_pair);
io_stream >> ui_pair;
vector_of_pair.emplace_back( str_pair, ui_pair );
++first;
}
test
for( std::size_t index = 0; index < vector_of_pair.size(); ++index ){
std::cout << vector_of_pair[ index ].first << " and " << vector_of_pair[ index ].second << '\n';
}
output
one and 1
two and 20
three and 300
four and 40000
five and 500000
how it work
There is no difficult code to understand. It just by std::regex_token_iterator splits the string, then in a while loop send the value of it to is_stream, then read the line and send it to str_pair, but here because std::getline has not function to extracts int type, I used >> of stringstream itself, then emplace_back to vector and that's it.
NOTE
For:
How can I write a std::stringstream operator that does the trick?
I think now you can do it by yourself. Since I was not sure, I have not written the code with operator>>.

Matching a string at the beginning of a inputstream

I have implemented a simple inputstream manipulator to match the next n chars in an inputstream against a given string. However, I am not sure if this is the best way to do this. Any hints?
class MatchString {
private:
std::string mString;
public:
MatchString(const std::string &str) {
mString = str;
}
std::istream& operator()(std::istream& is) const {
// Allocate a string buffer, ...
char *buffer = new char[mString.length()];
// ... read next n chars into the buffer ...
is.read(buffer, mString.length());
// ... and compare them with given string.
if(strncmp(buffer, mString.c_str(), mString.length())) {
throw MismatchException(mString);
}
delete[] buffer;
return is;
}
};
inline MatchString match(const std::string &str) {
return MatchString(str);
}
inline std::istream& operator>>(std::istream& is, const MatchString& matchStr) {
return matchStr(is);
}
EDIT:
A solution consuming the matched chars could be implemented based on the suggestion of user673679:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
std::for_each(mString.begin(), mString.end(),
[&](const char c) {
if(is.get() != c) {
throw MismatchException(mString);
}
});
return is;
}
};
How would I implement this if I don't want to consume the chars?
EDIT II:
Here another solution mentioned by fjardon:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
if(std::mismatch(mString.begin(), mString.end(),
std::istreambuf_iterator<char>(is)).first != mString.end()) {
throw MismatchException(mString);
}
return is;
}
};
EDIT III:
Finally got a working function, that will revert consumption, if string doesn't match:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
std::streampos oldPos = is.tellg();
if(std::mismatch(mString.begin(), mString.end(),
std::istreambuf_iterator<char>(is)).first != mString.end()) {
is.seekg(oldPos);
throw MismatchException(mString);
}
return is;
}
};
Instead of allocating and copying the whole string from the stream, you could just check one character at a time and avoid allocating the buffer completely:
#include <iostream>
#include <sstream>
#include <string>
auto mString = std::string("foobar");
std::istream& match(std::istream& is) {
for (auto c : mString)
if (c != is.get())
throw std::runtime_error("nope");
return is;
}
int main()
{
auto input = "foobarbaz";
auto stream = std::istringstream(input);
match(stream);
std::cout << "done!" << std::endl;
}
You should also add error checking for is.get() (or .read() in your original code).

Can you specify what ISN'T a delimiter in std::getline?

I want it to consider anything that isn't an alphabet character to be a delimiter. How can I do this?
You can't. The default delimiter is \n:
while (std::getline (std::cin, str) // '\n' is implicit
For other delimiters, pass them:
while (std::getline (std::cin, str, ' ') // splits at a single whitespace
However, the delimiter is of type char, thus you can only use one "split-character", but not what not to match.
If your input already happens to be inside a container like std::string, you can use find_first_not_of or find_last_not_of.
In your other question, are you sure you have considered all answers? One uses istream::operator>>(std::istream&, <string>), which will match a sequence of non-whitespace characters.
You don't. getline is a simple tool for a simple job. If you need something more complex, then you need to use a more complex tool, like RegEx's or something.
You can't do what you want using std::getline(), but you can roll your own. Here's a getline variant that let's you specify a predicate (function, functor, lambda if it's C++11) to indicate if a character is a delimiter along with a couple overloads that let you pass in a string of delimiter characters (kind of like strtok()):
#include <functional>
#include <iostream>
#include <string>
using namespace std;
template <typename Predicate>
istream& getline_until( istream& is, string& str, Predicate pred)
{
bool changed = false;
istream::sentry k(is,true);
if (bool(k)) {
streambuf& rdbuf(*is.rdbuf());
str.erase();
istream::traits_type::int_type ch = rdbuf.sgetc(); // get next char, but don't move stream position
for (;;ch = rdbuf.sgetc()) {
if (istream::traits_type::eof() == ch) {
is.setstate(ios_base::eofbit);
break;
}
changed = true;
rdbuf.sbumpc(); // move stream position to consume char
if (pred(istream::traits_type::to_char_type(ch))) {
break;
}
str.append(1,istream::traits_type::to_char_type(ch));
if (str.size() == str.max_size()) {
is.setstate(ios_base::failbit);
break;
}
}
if (!changed) {
is.setstate(ios_base::failbit);
}
}
return is;
}
// a couple of overloads (along with a predicate) that allow you
// to pass in a string that contains a set of delimiter characters
struct in_delim_set : unary_function<char,bool>
{
in_delim_set( char const* delim_set) : delims(delim_set) {};
in_delim_set( string const& delim_set) : delims(delim_set) {};
bool operator()(char ch) {
return (delims.find(ch) != string::npos);
};
private:
string delims;
};
istream& getline_until( istream& is, string& str, char const* delim_set)
{
return getline_until( is, str, in_delim_set(delim_set));
}
istream& getline_until( istream& is, string& str, string const& delim_set)
{
return getline_until( is, str, in_delim_set(delim_set));
}
// a simple example predicate functor
struct is_digit : unary_function<char,bool>
{
public:
bool operator()(char c) const {
return ('0' <= c) && (c <= '9');
}
};
int main(int argc, char* argv[]) {
string test;
// treat anything that's not a digit as end-of-line
while (getline_until( cin, test, not1(is_digit()))) {
cout << test << endl;
}
return 0;
}