jammy
jammy

Reputation: 977

parsing a string when there are two delimiters

Parsing the given string to be able to construct a graph . I have been given this string

string str ="abc,def,0.9;ghi,jkl,109;mno,par,155";

I need to parse this string such that i can construct an edge between abc and def, ghi and jkl, mno and pqr with weights 0.9, 109, 155.

I am stuck only in parsing part .

stringstream ss(str);
    for(int i =0 ;i<str.size();i++){
         string substr;
        getline(ss, substr, ',');
       
        cout <<substr<<endl;
    }

But this gives 0.9;ghi on one line.

Any suggestions will be highly appreciated.

Upvotes: 0

Views: 322

Answers (3)

Ted Lyngmo
Ted Lyngmo

Reputation: 117258

An alternative without using stringstreams could be using std::string_views and std::from_chars

Example:

#include <charconv>
#include <iostream>
#include <string>
#include <string_view>

struct edge {
    std::string a, b;
    double weight;

    // a function to parse a string_view and to return the unconsumed part of it
    std::string_view parse(std::string_view str) {
        auto curr = str.substr(0, str.find(';'));
        auto len = curr.size();

        auto f = [](std::string_view& curr, std::string& out) {
            auto comma = curr.find(',');
            if (comma == std::string_view::npos) return false;
            out = curr.substr(0, comma);
            curr = curr.substr(comma + 1);
            return true;
        };
        // if not both `a` and `b` be populated, return that nothing was consumed
        if (!f(curr, a) || !f(curr, b)) return str;  // nothing consumed

        // extract the weight
        auto [ptr, ec] =
            std::from_chars(curr.data(), curr.data() + curr.size(), weight);
        if (ec != std::errc{}) return str;  // nothing consumed

        // return everything but the consumed part of `str`:
        return len == str.size() ? std::string_view{} : str.substr(len + 1);
    }

    // reuse the `parse` function when reading from a stream:
    friend std::istream& operator>>(std::istream& is, edge& e) {
        if (std::string full; std::getline(is, full, ';')) {
            // if `parse` returns a string_view of the same size given as input,
            // nothing was consumed, so set the failbit:
            if (e.parse(full).size() == full.size())
                is.setstate(std::ios::failbit);
        }
        return is;
    }

    friend std::ostream& operator<<(std::ostream& os, const edge& e) {
        return os << e.a << ',' << e.b << ',' << e.weight;
    }
};

Then, if you already have a std::string, you can simply call parse in a loop:

int main() {
    std::string str = "abc,def,0.9;ghi,jkl,109;mno,par,155";
    
    edge x;
    for(std::string_view sv = str, newsv; 
        (newsv = x.parse(sv)).size() != sv.size(); sv = newsv)
    {
        std::cout << x << '\n';
    }
}

Output:

abc,def,0.9
ghi,jkl,109
mno,par,155

If you are reading from a std::istream:

int main() {
    // std::ifstream is("some_file");

    for(edge x; is >> x;) {     // loop while parsing successfully
        std::cout << x << '\n';
    }
}

Upvotes: 1

Vlad from Moscow
Vlad from Moscow

Reputation: 310930

You can use just one more string stream as for example

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    std::string str ="abc,def,0.9;ghi,jkl,109;mno,par,155";

    std::istringstream iss1( str );
    std::string line;

    while ( std::getline( iss1, line, ';' ) )
    {
        std::istringstream iss2( line );
        std::string item;
        while ( std::getline( iss2, item, ',' ) )
        {
            std::cout << item << ' ';
        }
        std::cout << '\n';
    }
}

The program output is

abc def 0.9 
ghi jkl 109 
mno par 155 

To get double values you can use standard function std:stod.

Upvotes: 2

Pepijn Kramer
Pepijn Kramer

Reputation: 12849

There is probably a library that can do this too. Here is an approach that uses a vector of string_view

#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <set>

auto get_substring_views(const std::string& input, const std::set<char>&& delimiters)
{
    std::vector<std::string_view> views;

    auto begin_of_word = input.begin();
    auto end_of_word = begin_of_word;

    while (end_of_word != input.end())
    {
        // check if input character can be found in delimiter
        while (end_of_word != input.end() && (delimiters.find(*end_of_word) == delimiters.end())) ++end_of_word;

        // double delimiter will result in empty view being added (should be fine)
        views.emplace_back(begin_of_word, end_of_word);

        // next word starts one after delimiter
        if ( end_of_word != input.end() ) begin_of_word = ++end_of_word;
    }

    return views;
}

int main()
{
    std::string input{ "abc,def,0.9;ghi,jkl,109;mno,par,155" };

    // note will return views on input so input must stay in scope
    auto substrings = get_substring_views(input, { ',',';' });

    for (const auto& substring : substrings)
    {
        std::cout << substring << "\n";
    }

    return 0;
}

Upvotes: 0

Related Questions