Reputation: 1078
I know there are several ways to do this in Java and C that are nice, but in C++ I can't seem to find a way to easily implement a string trimming function.
This is what I currently have:
string trim(string& str)
{
size_t first = str.find_first_not_of(' ');
size_t last = str.find_last_not_of(' ');
return str.substr(first, (last-first+1));
}
but whenever I try and call
trim(myString);
I get the compiler error
/tmp/ccZZKSEq.o: In function `song::Read(std::basic_ifstream<char,
std::char_traits<char> >&, std::basic_ifstream<char, std::char_traits<char> >&, char const*, char const*)':
song.cpp:(.text+0x31c): undefined reference to `song::trim(std::string&)'
collect2: error: ld returned 1 exit status
I am trying to find a simple and standard way of trimming leading and trailing whitespace from a string without it taking up 100 lines of code, and I tried using regex, but could not get that to work as well.
I also cannot use Boost.
Upvotes: 28
Views: 142436
Reputation: 9782
Using a regex
#include <regex>
#include <string>
string trim(string s) {
regex e("^\\s+|\\s+$"); // remove leading and trailing spaces
return regex_replace(s, e, "");
}
// if you prefer the namespaced version
std::string trim(std::string s) {
std::regex e("^\\s+|\\s+$"); // remove leading and trailing spaces
return std::regex_replace(s, e, "");
}
Credit to: https://www.regular-expressions.info/examples.html for the regex
As @o_oTurtle mentioned - regexes are very slow.
An alternate approach, which includes additional whitespace characters:
std::string trim(const std::string& str, const std::string REMOVE = " \n\r\t")
{
size_t first = str.find_first_not_of(REMOVE);
if (std::string::npos == first)
{
return str;
}
size_t last = str.find_last_not_of(REMOVE);
return str.substr(first, (last - first + 1));
}
Upvotes: 4
Reputation: 2216
It is somewhat surprising that none of the answers here provide a test function to demonstrate how trim
behaves in corner cases. The empty string and strings composed entirely of spaces can both be troublesome.
Here is such a function:
#include <iomanip>
#include <iostream>
#include <string>
void test(std::string const& s)
{
auto const quote{ '\"' };
auto const q{ quote + s + quote };
auto const t{ quote + trim(s) + quote };
std::streamsize const w{ 6 };
std::cout << std::left
<< "s = " << std::setw(w) << q
<< " : trim(s) = " << std::setw(w) << t
<< '\n';
}
int main()
{
for (std::string s : {"", " ", " ", "a", " a", "a ", " a "})
test(s);
}
When I ran this on 2023-Aug-05 against the accepted answer, I was disappointed with how it treated strings composed entirely of spaces. I expected them to be trimmed to the empty string. Instead, they were returned unchanged. The if-statement is the reason why.
// Accepted solution by @Anthony Kong. Copied on 2023-Aug-05.
using namespace std;
string trim(const string& str)
{
size_t first = str.find_first_not_of(' ');
if (string::npos == first)
{
return str;
}
size_t last = str.find_last_not_of(' ');
return str.substr(first, (last - first + 1));
}
Here is the output from my test:
s = "" : trim(s) = ""
s = " " : trim(s) = " "
s = " " : trim(s) = " "
s = "a" : trim(s) = "a"
s = " a" : trim(s) = "a"
s = "a " : trim(s) = "a"
s = " a " : trim(s) = "a"
To make it work the way I wanted, I changed the if-statement.
While I was at it, I got rid of using namespace std;
, and changed the parameter name to s
. You know, because I could.
// "New and improved" version of trim. Lol.
std::string trim(std::string const& s)
{
auto const first{ s.find_first_not_of(' ') };
if (first == std::string::npos)
return {};
auto const last{ s.find_last_not_of(' ') };
return s.substr(first, (last - first + 1));
}
Now the test routine produces the output I like:
s = "" : trim(s) = ""
s = " " : trim(s) = ""
s = " " : trim(s) = ""
s = "a" : trim(s) = "a"
s = " a" : trim(s) = "a"
s = "a " : trim(s) = "a"
s = " a " : trim(s) = "a"
Upvotes: 0
Reputation: 3582
In addition to answer of @gjha:
inline std::string ltrim_copy(const std::string& str)
{
auto it = std::find_if(str.cbegin(), str.cend(),
[](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
return std::string(it, str.cend());
}
inline std::string rtrim_copy(const std::string& str)
{
auto it = std::find_if(str.crbegin(), str.crend(),
[](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
return it == str.crend() ? std::string() : std::string(str.cbegin(), ++it.base());
}
inline std::string trim_copy(const std::string& str)
{
auto it1 = std::find_if(str.cbegin(), str.cend(),
[](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
if (it1 == str.cend()) {
return std::string();
}
auto it2 = std::find_if(str.crbegin(), str.crend(),
[](char ch) { return !std::isspace<char>(ch, std::locale::classic()); });
return it2 == str.crend() ? std::string(it1, str.cend()) : std::string(it1, ++it2.base());
}
Upvotes: 0
Reputation: 71
I think that substr() throws an exception if str only contains the whitespace.
I would modify it to the following code:
string trim(string& str)
{
size_t first = str.find_first_not_of(' ');
if (first == std::string::npos)
return "";
size_t last = str.find_last_not_of(' ');
return str.substr(first, (last-first+1));
}
Upvotes: 7
Reputation: 1
#include <vector>
#include <numeric>
#include <sstream>
#include <iterator>
void Trim(std::string& inputString)
{
std::istringstream stringStream(inputString);
std::vector<std::string> tokens((std::istream_iterator<std::string>(stringStream)), std::istream_iterator<std::string>());
inputString = std::accumulate(std::next(tokens.begin()), tokens.end(),
tokens[0], // start with first element
[](std::string a, std::string b) { return a + " " + b; });
}
Upvotes: 0
Reputation: 40874
Your code is fine. What you are seeing is a linker issue.
If you put your code in a single file like this:
#include <iostream>
#include <string>
using namespace std;
string trim(const string& str)
{
size_t first = str.find_first_not_of(' ');
if (string::npos == first)
{
return str;
}
size_t last = str.find_last_not_of(' ');
return str.substr(first, (last - first + 1));
}
int main() {
string s = "abc ";
cout << trim(s);
}
then do g++ test.cc
and run a.out, you will see it works.
You should check if the file that contains the trim
function is included in the link stage of your compilation process.
Upvotes: 43
Reputation: 2210
Here is how you can do it:
std::string & trim(std::string & str)
{
return ltrim(rtrim(str));
}
And the supportive functions are implemeted as:
std::string & ltrim(std::string & str)
{
auto it2 = std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
str.erase( str.begin() , it2);
return str;
}
std::string & rtrim(std::string & str)
{
auto it1 = std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
str.erase( it1.base() , str.end() );
return str;
}
And once you've all these in place, you can write this as well:
std::string trim_copy(std::string const & str)
{
auto s = str;
return ltrim(rtrim(s));
}
Try this
Upvotes: 29