Removing leading and trailing spaces from a string

Viewed 239198

How to remove spaces from a string object in C++.
For example, how to remove leading and trailing spaces from the below string object.

//Original string: "         This is a sample string                    "
//Desired string: "This is a sample string"

The string class, as far as I know, doesn't provide any methods to remove leading and trailing spaces.

To add to the problem, how to extend this formatting to process extra spaces between words of the string. For example,

// Original string: "          This       is         a sample   string    " 
// Desired string:  "This is a sample string"  

Using the string methods mentioned in the solution, I can think of doing these operations in two steps.

  1. Remove leading and trailing spaces.
  2. Use find_first_of, find_last_of, find_first_not_of, find_last_not_of and substr, repeatedly at word boundaries to get desired formatting.
25 Answers

C++17 introduced std::basic_string_view, a class template that refers to a constant contiguous sequence of char-like objects, i.e. a view of the string. Apart from having a very similar interface to std::basic_string, it has two additional functions: remove_prefix(), which shrinks the view by moving its start forward; and remove_suffix(), which shrinks the view by moving its end backward. These can be used to trim leading and trailing space:

#include <string_view>
#include <string>

std::string_view ltrim(std::string_view str)
{
    const auto pos(str.find_first_not_of(" \t\n\r\f\v"));
    str.remove_prefix(std::min(pos, str.length()));
    return str;
}

std::string_view rtrim(std::string_view str)
{
    const auto pos(str.find_last_not_of(" \t\n\r\f\v"));
    str.remove_suffix(std::min(str.length() - pos - 1, str.length()));
    return str;
}

std::string_view trim(std::string_view str)
{
    str = ltrim(str);
    str = rtrim(str);
    return str;
}

int main()
{
    std::string str = "   hello world   ";
    auto sv1{ ltrim(str) };  // "hello world   "
    auto sv2{ rtrim(str) };  // "   hello world"
    auto sv3{ trim(str) };   // "hello world"

    //If you want, you can create std::string objects from std::string_view objects
    std::string s1{ sv1 };
    std::string s2{ sv2 };
    std::string s3{ sv3 };
}

Note: the use of std::min to ensure pos is not greater than size(), which happens when all characters in the string are whitespace and find_first_not_of returns npos. Also, std::string_view is a non-owning reference, so it's only valid as long as the original string still exists. Trimming the string view has no effect on the string it is based on.

To add to the problem, how to extend this formatting to process extra spaces between words of the string.

Actually, this is a simpler case than accounting for multiple leading and trailing white-space characters. All you need to do is remove duplicate adjacent white-space characters from the entire string.

The predicate for adjacent white space would simply be:

auto by_space = [](unsigned char a, unsigned char b) {
    return std::isspace(a) and std::isspace(b);
};

and then you can get rid of those duplicate adjacent white-space characters with std::unique, and the erase-remove idiom:

// s = "       This       is       a sample   string     "  
s.erase(std::unique(std::begin(s), std::end(s), by_space), 
        std::end(s));
// s = " This is a sample string "  

This does potentially leave an extra white-space character at the front and/or the back. This can be removed quite easily:

if (std::size(s) && std::isspace(s.back()))
    s.pop_back();

if (std::size(s) && std::isspace(s.front()))
    s.erase(0, 1);

Here's a demo.

Why complicate?

std::string removeSpaces(std::string x){
    if(x[0] == ' ') { x.erase(0, 1); return removeSpaces(x); }
    if(x[x.length() - 1] == ' ') { x.erase(x.length() - 1, x.length()); return removeSpaces(x); }
    else return x;
}

This works even if boost was to fail, no regex, no weird stuff nor libraries.

EDIT: Fix for M.M.'s comment.

No boost, no regex, just the string library. It's that simple.

string trim(const string s) { // removes whitespace characters from beginnig and end of string s
    const int l = (int)s.length();
    int a=0, b=l-1;
    char c;
    while(a<l && ((c=s.at(a))==' '||c=='\t'||c=='\n'||c=='\v'||c=='\f'||c=='\r'||c=='\0')) a++;
    while(b>a && ((c=s.at(b))==' '||c=='\t'||c=='\n'||c=='\v'||c=='\f'||c=='\r'||c=='\0')) b--;
    return s.substr(a, 1+b-a);
}

The constant time and space complexity for removing leading and trailing spaces can be achieved by using pop_back() function in the string. Code looks as follows:

void trimTrailingSpaces(string& s) {
    while (s.size() > 0 && s.back() == ' ') {
        s.pop_back();
    }
}

void trimSpaces(string& s) {
    //trim trailing spaces.
    trimTrailingSpaces(s);
    //trim leading spaces
    //To reduce complexity, reversing and removing trailing spaces 
    //and again reversing back
    reverse(s.begin(), s.end());
    trimTrailingSpaces(s);
    reverse(s.begin(), s.end());
}
void removeSpaces(string& str)
{
    /* remove multiple spaces */
    int k=0;
    for (int j=0; j<str.size(); ++j)
    {
            if ( (str[j] != ' ') || (str[j] == ' ' && str[j+1] != ' ' ))
            {
                    str [k] = str [j];
                    ++k;
            }

    }
    str.resize(k);

    /* remove space at the end */   
    if (str [k-1] == ' ')
            str.erase(str.end()-1);
    /* remove space at the begin */
    if (str [0] == ' ')
            str.erase(str.begin());
}

neat and clean

 void trimLeftTrailingSpaces(string &input) {
        input.erase(input.begin(), find_if(input.begin(), input.end(), [](int ch) {
            return !isspace(ch);
        }));
    }

    void trimRightTrailingSpaces(string &input) {
        input.erase(find_if(input.rbegin(), input.rend(), [](int ch) {
            return !isspace(ch);
        }).base(), input.end());
    }
Related