danijar
danijar

Reputation: 34185

How to let std::istringstream treat a given character as white space?

Using std::istringstream it is easy to read words separated by white space. But to parse the following line, I need the character / to be treated like white space.

f 104/387/104 495/574/495 497/573/497

How can I read values separated by either slash or white space?

Upvotes: 1

Views: 2565

Answers (4)

Jordan Harris
Jordan Harris

Reputation: 390

I'm sure this isn't the best way at all, but I was working on an exercise in the book Programming Principles and Practice Using C++ 2nd Ed. by Bjarne Stroustrup and I came up with a solution that might work for you. I searched around to see how others were doing it (which is how I found this thread) but I really didn't find anything.

First of all, here's the exercise from the book:

Write a function vector<string> split(const string& s, const string& w) that returns a vector of whitespace-separated substrings from the argument s, where whitespace is defined as "ordinary whitespace" plus the characters in w.

Here's the solution that I came up with, which seems to work well. I tried commenting it to make it more clear. Just want to mention I'm pretty new to C++ (which is why I'm reading this book), so don't go too hard on me. :)

// split a string into its whitespace-separated substrings and store
// each string in a vector<string>. Whitespace can be defined in argument
// w as a string (e.g. ".;,?-'")
vector<string> split(const string& s, const string& w)
{
    string temp{ s };
    // go through each char in temp (or s)
    for (char& ch : temp) {     
        // check if any characters in temp (s) are whitespace defined in w
        for (char white : w) {  
            if (ch == white)
                ch = ' ';       // if so, replace them with a space char ('')
        }
    }

    vector<string> substrings;
    stringstream ss{ temp };

    for (string buffer; ss >> buffer;) {
        substrings.push_back(buffer);
    }
    return substrings;
}

Then you can do something like this to use it:

cout << "Enter a string and substrings will be printed on new lines:\n";
string str;
getline(cin, str);
vector<string> substrings = split(str, ".;,?-'");

cout << "\nSubstrings:\n";
for (string s : substrings)
    cout << s << '\n';

I know you aren't wanting to split strings, but this is just an example of how you can treat other characters as whitespace. Basically, I'm just replacing those characters with ' ' so they literally do become whitespace. When using that with a stream, it works pretty well. The for loop(s) might be the relevant code for your case.

Upvotes: 0

Olaf Dietsche
Olaf Dietsche

Reputation: 74028

If you know when to split by either slash or whitespace, you can use std::getline

std::istringstream is("f 104/387/104 495/574/495 497/573/497");
std::string f, i, j, k;
std::getline(is, f, ' ');
std::getline(is, i, '/');
std::getline(is, j, '/');
std::getline(is, k, ' ');

Alternatively, you can use formatted input and discard the slashes manually

std::string f;
int i, j, k;
char slash;
is >> f >> i >> slash >> j >> slash >> k;

Upvotes: 2

Jerry Coffin
Jerry Coffin

Reputation: 490138

One way is to define a ctype facet that classifies / as white-space:

class my_ctype : public std::ctype<char> {
public:
    mask const *get_table() { 
        static std::vector<std::ctype<char>::mask> 
            table(classic_table(), classic_table()+table_size);
        table['/'] = (mask)space;
        return &table[0];
    }
    my_ctype(size_t refs=0) : std::ctype<char>(get_table(), false, refs) { }
};

From there, imbue the stream with a locale using that ctype facet, then read words:

int main() { 
    std::string input("f 104/387/104 495/574/495 497/573/497");
    std::istringstream s(input);
    s.imbue(std::locale(std::locale(), new my_ctype));

    std::copy(std::istream_iterator<std::string>(s),
              std::istream_iterator<std::string>(),
              std::ostream_iterator<std::string>(std::cout, "\n"));
}

Upvotes: 9

hmjd
hmjd

Reputation: 121971

If boost is available, then boost::split() would be a possible solution. Populate a std::string using std::getline() and then split the line:

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

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

int main()
{
    std::vector<std::string> tokens;
    std::string line("f 104/387/104 495/574/495 497/573/497");
    boost::split(tokens, line, boost::is_any_of("/ "));

    for (auto& token: tokens) std::cout << token << "\n";

    return 0;
}

Output:

f
104
387
104
495
574
495
497
573
497

Upvotes: 4

Related Questions