How to implement split function to split words from a line?

Viewed 87

I would like to implement a function, that takes string as a line of input (via getline()) and separate (split) words in a vector. I have tried this:

vector<string> split(const string &s)
{
    vector<string> ret;
    int j = 0, i = 0; // j=="start word boundary", i=="end word boundary"
    while (i != s.size())
    {
        //get words
        while (i != s.size() && !isspace(s[i]))
        {
            i++;
        }
        //at least one word found (so the 'i' index is not at the beginning of string)
        if (!i)
        {
            ret.push_back(s.substr(j, i - j));
        }
        //now look for blanks
        j = i;
        //ignore blanks
        while (j != s.size() && isspace(s[j]))
        {
            j++;
        }
        //get position for next words back
        i = j;
    }
    return ret;
}

And then tried to see the results:

int main()
{
    string tmp;
    while (getline(cin, tmp))
    {
        vector<string> vec = split(tmp);
        for (string s : vec)
        {
            cout << s << endl;
        }
    }
}

But cannot see anything. Why?

2 Answers

You have a typo here:

if (!i)
{
   ret.push_back(s.substr(j, i - j));
}

You are only adding a substring if the end index is 0, but this means you never add a substring to the vector.

Instead, you need to add a substring if the end index is not 0:

if (i)   // if (i != 0)
{
   ret.push_back(s.substr(j, i - j));
}

Here's a demo.

Try something more like this instead:

vector<string> split(const string &s)
{
    vector<string> ret;
    size_t size = s.size(), start = 0, end;
    do
    {
        // skip leading whitespace
        while (start < size && isspace(s[start])){
            ++start;
        }
        if (start == size) break;

        //at least one word found
       
        // find the next whitespace, or end-of-string, whichever comes first
        end = start + 1;
        while (end < size && !isspace(s[end])){
            ++end;
        }

        //get word
        ret.push_back(s.substr(start, end - start));
        
        start = end;
    }
    while (start < size);

    return ret;
}

Which can then be simplified a bit by using std::string::find_first(_not)_of(), eg:

static const char *ws = " \t";

vector<string> split(const string &s)
{
    vector<string> ret;

    size_t size = s.size(), start = 0, end;
    while ((start = s.find_first_not_of(ws, end)) != string::npos)
    {
        if ((end = s.find_first_of(ws, start+1)) == string::npos){
            ret.push_back(s.substr(start));
            break;
        }
        ret.push_back(s.substr(start, end-start));
        start = end;
    }

    return ret;
}

Though, using std::istringstream with operator>> would be even simpler:

...
#include <sstream>

vector<string> split(const string &s)
{
    vector<string> ret;
    istringstream iss(s);
    string word;
    while (iss >> word){
        ret.push_back(word);
    }
    return ret;
}

Alternatively, instead of using a manual loop:

...
#include <sstream>
#include <iterator>
#include <algorithm>

vector<string> split(const string &s)
{
    vector<string> ret;
    istringstream iss(s);
    std::copy(istream_iterator<string>(iss), istream_iterator<string>(), back_inserter(ret));
    return ret;
}
Related