1

I would like to store data in an object array but I don't know how to split my string.

The result I would like to see is:

tab[0].username = "user1"
tab[0].ip = "192.168.0.1"
tab[1].username = "user2"
tab[1].ip = "192.168.0.2"
tab[2].username = "user3"
tab[2].ip = "192.168.0.3"

Here's how my string looks:

user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3

The code I currently have, which only allows you to split without managing the pipe :

void addInTab(std::vector<std::string> vec, client *tab, int total_user)
{

    for(int i = 0; i < 2; ++i) {
        if (i == 0)
            tab[total_user].username = vec[i];
        if (i == 1)
            tab[total_user].ip = vec[i];
    }
}

void split(std::string str, char delim)
{
    std::vector<std::string> vec;
    std::string::size_type tmp = str.find(delim);

    while(tmp != std::string::npos) {
        vec.push_back(str.substr(0, tmp));
        str = str.substr(tmp + 1);
        tmp = str.find(delim);
    }
    vec.push_back(str);
    addInTab(vec);
}

thank advance

Wolf
  • 9,679
  • 7
  • 62
  • 108
Testostas
  • 87
  • 1
  • 8
  • Have you tried using a regex? – Ayjay Sep 26 '19 at 12:33
  • 1) Please provide [mcve], as your example can't be compiled. 2) If you need to search for multiple delimiters, why aren't you using [`std::string::find_first_of`](https://en.cppreference.com/w/cpp/string/basic_string/find_first_of)? – Algirdas Preidžius Sep 26 '19 at 12:33
  • have you considered using find_if instead of find? that would allow you to use your own match checking function and have multiple characters checked – xception Sep 26 '19 at 12:34
  • I'd vote for de-duplication: this question is more specific (much better illustrates the actual problem) whereas the targeted question is *too broad* and not worth the up-votes it attracted. – Wolf Sep 26 '19 at 13:37

5 Answers5

1

I recommend that you create a more generalized version of the split function, that returns the vector instead of calling some special function.

Then you can call it first to split on the pipe character, and in a loop call it again to split each sub-string.

Something like this

std::vector<std::string> split(std::string str, char delim);

// ...

for (auto pair : split(original_input_with_pipes, '|'))
{
    // Here pair is a string containing values like "user1:192.168.0.1" etc.

    auto values = split(pair, ':');  // Split each pair

    // Now values[0] should be e.g. "user1"
    // and values[1] should be "192.168.0.1"
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
0

Just split twice, first with |, second (on each of the results) with : as the delimiter. Here is a quite efficient and compact split function

std::vector<std::string> split(const std::string& text, const char separator)
{
    std::vector<std::string> items;
    std::istringstream f(text);
    std::string s;
    while (getline(f, s, separator)) {
        items.push_back(s);
    }
    return items;
}

If you are sure about the fact that the delimiters alternate, you can build a specialized function, by swapping the delimiters, here a short demo:


#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <sstream>

int main()
{
    std::string text = "user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3";
    std::vector<std::pair<std::string, std::string> > pairs;
    std::istringstream f(text);
    std::string name, ip;
    while (getline(f, name, ':')) {
        if (getline(f, ip, '|')) {
            pairs.push_back(std::pair<std::string,std::string>(name, ip));
        } else {
            break;
        }
    }

    for (auto pair: pairs) {
        std::cout << pair.first << ", " << pair.second << std::endl;
    }

}
Wolf
  • 9,679
  • 7
  • 62
  • 108
0

If you have

You can just do:

std::vector<std::string> tokens;
boost::split(tokens, input, boost::is_any_of("|:."));    

(Then it's just a matter of filling the tokens into your struct)


Try it yourself ! :

#include <boost/algorithm/string.hpp>
#include <iostream>

int main(){
    auto input = "user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3";

    std::vector<std::string> tokens;
    boost::split(tokens, input, boost::is_any_of("|:."));    

    for (auto tok : tokens) {
      std::cout << tok << std::endl;
    }

    return 0;
}

If you don't have

A std::regex can do the same thing:

std::regex re("\\.;\\|");
std::sregex_token_iterator first{input.begin(), input.end(), re, -1}, last;//the '-1' is what makes it split
std::vector<std::string> tokens{first, last};
darune
  • 10,480
  • 2
  • 24
  • 62
0

Here you are.

#include <iostream>
#include <string>
#include <vector>

std::vector<std::string> splitStr( const std::string &s, 
                                   const std::string &delim = " \t" )
{
    std::vector<std::string> v;

    for ( std::string::size_type pos = 0, n = 0;
          ( pos = s.find_first_not_of( delim, pos ) ) != std::string::npos; 
          pos += n )
    {
        n = s.find_first_of( delim, pos );

        n = ( n == std::string::npos ? s.size() : n ) - pos;

        v.push_back( s.substr( pos, n ) );
    }

    return v;
}

int main() 
{
    const std::string s( "user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3 " );
    for ( const auto &item : splitStr( s, ":|" ) )
    {
        std::cout << item << std::endl;
    }

    return 0;
}

The program output is

user1
192.168.0.1
user2
192.168.0.2
user3
192.168.0.3 

That is you can use searching member functions find_first_of and find_first_not_of of the class std::string.

You can supply any set of delimiters to the function.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • This solution is too much boilerplate to be recommended - any would be simpler than this. – darune Sep 26 '19 at 13:04
  • @darune It is a simple solution that is clear and moreover useful for beginners. – Vlad from Moscow Sep 26 '19 at 13:04
  • It's too much "boilerplate" the way you wrote the split function. That is not good for beginners. So I guess I just disagree. – darune Sep 26 '19 at 13:07
  • @darune The function implementation is very good. Using regualr expressions will arise more questions. At least it is the best suggestion among presented here. – Vlad from Moscow Sep 26 '19 at 13:09
0

You can use a regex to do complicated string process. It's often easier to maintain and work with than processing it by hand.

#include <regex>
#include <string>
#include <iostream>
#include <iterator>
#include <algorithm>

int main() {
    const std::string s = "user1:192.168.0.1|user2:192.168.0.2|user3:192.168.0.3";
    std::regex r(R"_((user\d+)\:((?:\d{1,3}\.?){4}))_");

    struct record {
        std::string username;
        std::string ip;
    };
    std::vector<record> records;
    std::transform(
        std::sregex_iterator{ s.begin(), s.end(), r }, std::sregex_iterator{},
        std::back_inserter(records),
        [](auto& sm) -> record { return { sm[1],sm[2] }; }
    );

    std::transform(
        records.begin(), records.end(),
        std::ostream_iterator<std::string>{std::cout, "\n"},
        [](auto& rec) { return rec.username + ':' + rec.ip; }
    );
}
Ayjay
  • 3,413
  • 15
  • 20