Hoe trim je een std::string?

Ik gebruik momenteel de volgende code om alle std::stringsin mijn programma’s rechts bij te snijden:

std::string s;
s.erase(s.find_last_not_of(" \n\r\t")+1);

Het werkt prima, maar ik vraag me af of er enkele eindgevallen zijn waarin het zou kunnen mislukken?

Natuurlijk zijn antwoorden met elegante alternatieven en ook linksafgewerkte oplossingen welkom.


Antwoord 1, autoriteit 100%

EDITSinds c++17 zijn sommige delen van de standaardbibliotheek verwijderd. Gelukkig hebben we vanaf c++11 lambda’s die een superieure oplossing zijn.

#include <algorithm> 
#include <cctype>
#include <locale>
// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
        return !std::isspace(ch);
    }));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
        return !std::isspace(ch);
    }).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Met dank aan https://stackoverflow.com/a/44973498/524503voor het naar voren brengen van de moderne oplossing.

Origineel antwoord:

Ik heb de neiging om een ​​van deze 3 te gebruiken voor mijn trimbehoeften:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>
// trim from start
static inline std::string &ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
    return s;
}
// trim from end
static inline std::string &rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
    return s;
}
// trim from both ends
static inline std::string &trim(std::string &s) {
    return ltrim(rtrim(s));
}

Ze zijn vrij vanzelfsprekend en werken heel goed.

bewerken : btw, ik heb std::ptr_fundaar om te helpen bij het verwijderen van std::isspaceomdat er eigenlijk een tweede definitie is die locales ondersteunt. Dit had precies hetzelfde kunnen zijn, maar ik heb de neiging om dit beter leuk te vinden.

Bewerken : om enkele opmerkingen over het accepteren van een parameter door verwijzing aan te pakken, te wijzigen en te retourneren. Daar ben ik het mee eens. Een implementatie die ik waarschijnlijk verkiest, zou twee sets functies zijn, één voor op zijn plaats en een die een kopie maakt. Een betere set voorbeelden zou zijn:

#include <algorithm> 
#include <functional> 
#include <cctype>
#include <locale>
// trim from start (in place)
static inline void ltrim(std::string &s) {
    s.erase(s.begin(), std::find_if(s.begin(), s.end(),
            std::not1(std::ptr_fun<int, int>(std::isspace))));
}
// trim from end (in place)
static inline void rtrim(std::string &s) {
    s.erase(std::find_if(s.rbegin(), s.rend(),
            std::not1(std::ptr_fun<int, int>(std::isspace))).base(), s.end());
}
// trim from both ends (in place)
static inline void trim(std::string &s) {
    ltrim(s);
    rtrim(s);
}
// trim from start (copying)
static inline std::string ltrim_copy(std::string s) {
    ltrim(s);
    return s;
}
// trim from end (copying)
static inline std::string rtrim_copy(std::string s) {
    rtrim(s);
    return s;
}
// trim from both ends (copying)
static inline std::string trim_copy(std::string s) {
    trim(s);
    return s;
}

Ik behoud het oorspronkelijke antwoord hierboven echter voor de context en in het belang van het beschikbaar houden van het hooggestemde antwoord.


Antwoord 2, autoriteit 60%

Gebruik maken van Boost’s string-algoritmenzou het gemakkelijkst zijn:

#include <boost/algorithm/string.hpp>
std::string str("hello world! ");
boost::trim_right(str);

stris nu "hello world!". Er zijn ook trim_leften trim, die beide zijden bijsnijden.


Als u het achtervoegsel _copytoevoegt aan een van de bovenstaande functienamen, b.v. trim_copy, de functie retourneert een bijgesneden kopie van de tekenreeks in plaats van deze te wijzigen via een verwijzing.

Als u het achtervoegsel _iftoevoegt aan een van bovenstaande functienamen, b.v. trim_copy_if, je kunt alle tekens bijsnijden die voldoen aan je eigen predikaat, in plaats van alleen spaties.


Antwoord 3, autoriteit 9%

Wat je doet is prima en robuust. Ik gebruik al heel lang dezelfde methode en heb nog geen snellere methode gevonden:

const char* ws = " \t\n\r\f\v";
// trim from end of string (right)
inline std::string& rtrim(std::string& s, const char* t = ws)
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}
// trim from beginning of string (left)
inline std::string& ltrim(std::string& s, const char* t = ws)
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}
// trim from both ends of string (right then left)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

Door de tekens op te geven die moeten worden bijgesneden, hebt u de flexibiliteit om niet-witruimtetekens bij te snijden en de efficiëntie om alleen de tekens bij te snijden die u wilt bijsnijden.


Antwoord 4, autoriteit 8%

Gebruik de volgende code om spaties en tabtekens rechts bij te snijden uit std::strings(idee):

// trim trailing spaces
size_t endpos = str.find_last_not_of(" \t");
size_t startpos = str.find_first_not_of(" \t");
if( std::string::npos != endpos )
{
    str = str.substr( 0, endpos+1 );
    str = str.substr( startpos );
}
else {
    str.erase(std::remove(std::begin(str), std::end(str), ' '), std::end(str));
}

En om alles in evenwicht te brengen, zal ik ook de linker trimcode toevoegen (ideone):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}

Antwoord 5, autoriteit 8%

Beetje laat op het feest, maar dat geeft niet. Nu is C++11 hier, we hebben lambda’s en autovariabelen. Dus mijn versie, die ook alle witruimte en lege tekenreeksen verwerkt, is:

#include <cctype>
#include <string>
#include <algorithm>
inline std::string trim(const std::string &s)
{
   auto wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   auto wsback=std::find_if_not(s.rbegin(),s.rend(),[](int c){return std::isspace(c);}).base();
   return (wsback<=wsfront ? std::string() : std::string(wsfront,wsback));
}

We zouden een omgekeerde iterator kunnen maken van wsfronten dat gebruiken als de beëindigingsvoorwaarde in de tweede find_if_notmaar dat is alleen nuttig in het geval van een string met alleen witruimte, en gcc 4.8 is in ieder geval niet slim genoeg om het type van de omgekeerde iterator (std::string::const_reverse_iterator) af te leiden met auto. Ik weet niet hoe duur het maken van een omgekeerde iterator is, dus YMMV hier. Met deze wijziging ziet de code er als volgt uit:

inline std::string trim(const std::string &s)
{
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}

Antwoord 6, autoriteit 6%

Probeer dit, het werkt voor mij.

inline std::string trim(std::string& str)
{
    str.erase(0, str.find_first_not_of(' '));       //prefixing spaces
    str.erase(str.find_last_not_of(' ')+1);         //surfixing spaces
    return str;
}

7, Autoriteit 3%

Ik hou van de oplossing van Tzaman, het enige probleem daarmee is dat het geen tekenreeks trimt die alleen spaties bevat.

Om die 1 fout te corrigeren, voeg dan een str.clear () toe tussen de 2 trimmerlijnen

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;

8, Autoriteit 3%

http://ideone.com/nfvteo

std::string trim(const std::string &s)
{
    std::string::const_iterator it = s.begin();
    while (it != s.end() && isspace(*it))
        it++;
    std::string::const_reverse_iterator rit = s.rbegin();
    while (rit.base() != it && isspace(*rit))
        rit++;
    return std::string(it, rit.base());
}

9, Autoriteit 2%

Met C++ 17 kunt u basic_string_view :: verwijderen_prefix verwijderen en basic_string_view :: verwijder_suffix :

std::string_view trim(std::string_view s)
{
    s.remove_prefix(std::min(s.find_first_not_of(" \t\r\v\n"), s.size()));
    s.remove_suffix(std::min(s.size() - s.find_last_not_of(" \t\r\v\n") - 1, s.size()));
    return s;
}

Een leuk alternatief:

std::string_view ltrim(std::string_view s)
{
    s.remove_prefix(std::distance(s.cbegin(), std::find_if(s.cbegin(), s.cend(),
         [](int c) {return !std::isspace(c);})));
    return s;
}
std::string_view rtrim(std::string_view s)
{
    s.remove_suffix(std::distance(s.crbegin(), std::find_if(s.crbegin(), s.crend(),
        [](int c) {return !std::isspace(c);})));
    return s;
}
std::string_view trim(std::string_view s)
{
    return ltrim(rtrim(s));
}

Antwoord 10, autoriteit 2%

In het geval van een lege string, gaat je code ervan uit dat het toevoegen van 1 aan string::npos0 geeft. string::nposis van het type string::size_type, die niet ondertekend is. U vertrouwt dus op het overloopgedrag van optellen.


Antwoord 11, autoriteit 2%

Gehackt van Cplusplus.com

std::string choppa(const std::string &t, const std::string &ws)
{
    std::string str = t;
    size_t found;
    found = str.find_last_not_of(ws);
    if (found != std::string::npos)
        str.erase(found+1);
    else
        str.clear();            // str is all whitespace
    return str;
}

Dit werkt ook in het geval van nul. 🙂


Antwoord 12, autoriteit 2%

Mijn oplossing gebaseerd op het antwoord van @Bill the Lizard.

Merk op dat deze functies de lege tekenreeks teruggeven als de invoertekenreeks niets anders dan witruimte bevat.

const std::string StringUtils::WHITESPACE = " \n\r\t";
std::string StringUtils::Trim(const std::string& s)
{
    return TrimRight(TrimLeft(s));
}
std::string StringUtils::TrimLeft(const std::string& s)
{
    size_t startpos = s.find_first_not_of(StringUtils::WHITESPACE);
    return (startpos == std::string::npos) ? "" : s.substr(startpos);
}
std::string StringUtils::TrimRight(const std::string& s)
{
    size_t endpos = s.find_last_not_of(StringUtils::WHITESPACE);
    return (endpos == std::string::npos) ? "" : s.substr(0, endpos+1);
}

13

Met C++ 11 kwam ook een reguliere expressie module, wat natuurlijk kan worden gebruikt om leidende of achterliggende ruimtes te trimmen.

Misschien zoiets:

std::string ltrim(const std::string& s)
{
    static const std::regex lws{"^[[:space:]]*", std::regex_constants::extended};
    return std::regex_replace(s, lws, "");
}
std::string rtrim(const std::string& s)
{
    static const std::regex tws{"[[:space:]]*$", std::regex_constants::extended};
    return std::regex_replace(s, tws, "");
}
std::string trim(const std::string& s)
{
    return ltrim(rtrim(s));
}

14

Mijn antwoord is een verbetering op de topantwoord voor dit bericht dat regelpersonages en spaties (0- 32 en 127 op de ASCII tafel ).

std::isgraphBepaalt of een personage een grafische weergave heeft, zodat u dit kunt gebruiken om het antwoord van Evan te wijzigen om elk teken te verwijderen dat geen grafische weergave van elke kant van een tekenreeks heeft. Het resultaat is een veel elegante oplossing:

#include <algorithm>
#include <functional>
#include <string>
/**
 * @brief Left Trim
 *
 * Trims whitespace from the left end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& ltrim(std::string& s) {
  s.erase(s.begin(), std::find_if(s.begin(), s.end(),
    std::ptr_fun<int, int>(std::isgraph)));
  return s;
}
/**
 * @brief Right Trim
 *
 * Trims whitespace from the right end of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& rtrim(std::string& s) {
  s.erase(std::find_if(s.rbegin(), s.rend(),
    std::ptr_fun<int, int>(std::isgraph)).base(), s.end());
  return s;
}
/**
 * @brief Trim
 *
 * Trims whitespace from both ends of the provided std::string
 *
 * @param[out] s The std::string to trim
 *
 * @return The modified std::string&
 */
std::string& trim(std::string& s) {
  return ltrim(rtrim(s));
}

Opmerking:u kunt ook std::iswgraphals je ondersteuning nodig hebt voor brede tekens, maar je moet deze code ook bewerken om std::wstringmanipulatie in te schakelen, wat iets dat ik niet heb getest (zie de referentiepagina voor std::basic_stringom deze optie te verkennen).


15

Dit is wat ik gebruik. Blijf gewoon ruimte van de voorkant verwijderen en dan, als er iets is over, doe dan hetzelfde van de achterkant.

void trim(string& s) {
    while(s.compare(0,1," ")==0)
        s.erase(s.begin()); // remove leading whitespaces
    while(s.size()>0 && s.compare(s.size()-1,1," ")==0)
        s.erase(s.end()-1); // remove trailing whitespaces
}

16

Voor wat het waard is, is hier een trimimplementatie met het oog op de prestaties. Het is veel sneller dan vele andere trimroutines die ik heb gezien. In plaats van het gebruik van iterators en STD :: Vinden, gebruikt het RAW C-snaren en indices. Het optimaliseert de volgende speciale gevallen: maat 0 string (doe niets), string zonder witruimte om te trimmen (doe niets), string met alleen trailing witruimte om te trimmen (gewoon het formaat van de tekenreeks), string die volledig wit is) . En ten slotte, in het ergste geval (string met toonaangevende witruimte), het is het beste om een ​​efficiënte kopieerconstructie uit te voeren, slechts 1 kopie uit te voeren en vervolgens die kopie in plaats van de originele string te verplaatsen.

void TrimString(std::string & str)
{ 
    if(str.empty())
        return;
    const auto pStr = str.c_str();
    size_t front = 0;
    while(front < str.length() && std::isspace(int(pStr[front]))) {++front;}
    size_t back = str.length();
    while(back > front && std::isspace(int(pStr[back-1]))) { --back;}
    if(0 == front)
    {
        if(back < str.length())
        {
            str.resize(back - front);
        }
    }
    else if(back <= front)
    {
        str.clear();
    }
    else
    {
        str = std::move(std::string(str.begin()+front, str.begin()+back));
    }
}

Antwoord 17

Ik denk dat als je begint te vragen naar de “beste manier” om een string in te korten, ik zou zeggen dat een goede implementatie er een zou zijn die:

  1. Wijst geen tijdelijke tekenreeksen toe
  2. Heeft overbelasting voor ter plaatse bijsnijden en bijsnijden van kopieën
  3. Kan eenvoudig worden aangepast om verschillende validatiesequenties / logica te accepteren

Er zijn natuurlijk te veel verschillende manieren om dit aan te pakken en het hangt er zeker van af wat je echt nodig hebt. De C-standaardbibliotheek heeft echter nog steeds enkele zeer nuttige functies in <string.h>, zoals memchr. Er is een reden waarom C nog steeds wordt beschouwd als de beste taal voor IO – de stdlib is pure efficiëntie.

inline const char* trim_start(const char* str)
{
    while (memchr(" \t\n\r", *str, 4))  ++str;
    return str;
}
inline const char* trim_end(const char* end)
{
    while (memchr(" \t\n\r", end[-1], 4)) --end;
    return end;
}
inline std::string trim(const char* buffer, int len) // trim a buffer (input?)
{
    return std::string(trim_start(buffer), trim_end(buffer + len));
}
inline void trim_inplace(std::string& str)
{
    str.assign(trim_start(str.c_str()),
        trim_end(str.c_str() + str.length()));
}
int main()
{
    char str [] = "\t \nhello\r \t \n";
    string trimmed = trim(str, strlen(str));
    cout << "'" << trimmed << "'" << endl;
    system("pause");
    return 0;
}

Antwoord 18

Een elegante manier om het te doen kan zijn als

std::string & trim(std::string & str)
{
   return ltrim(rtrim(str));
}

en de ondersteunende functies worden geïmplementeerd als:

std::string & ltrim(std::string & str)
{
  auto it =  std::find_if( str.begin() , str.end() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( str.begin() , it);
  return str;   
}
std::string & rtrim(std::string & str)
{
  auto it =  std::find_if( str.rbegin() , str.rend() , [](char ch){ return !std::isspace<char>(ch , std::locale::classic() ) ; } );
  str.erase( it.base() , str.end() );
  return str;   
}

En zodra u al deze op zijn plaats bent, kunt u dit ook schrijven:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}

19

Trim C++ 11 implementatie:

static void trim(std::string &s) {
     s.erase(s.begin(), std::find_if_not(s.begin(), s.end(), [](char c){ return std::isspace(c); }));
     s.erase(std::find_if_not(s.rbegin(), s.rend(), [](char c){ return std::isspace(c); }).base(), s.end());
}

20

Dit kan eenvoudiger in C++ 11 worden gedaan vanwege de toevoeging van back()en pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();

Antwoord 21

Ik weet niet zeker of jouw omgeving hetzelfde is, maar in de mijne zorgt de lege string ervoor dat het programma wordt afgebroken. Ik zou ofwel die wisaanroep inpakken met een if(!s.empty()) of Boost gebruiken zoals eerder vermeld.


Antwoord 22

Dit is wat ik bedacht:

std::stringstream trimmer;
trimmer << str;
trimmer >> str;

Streamextractie elimineert automatisch witruimte, dus dit werkt als een tierelier.
Behoorlijk clean en elegant ook, al zeg ik het zelf. 😉


Antwoord 23

Mijn oplossing bijdragen aan het lawaai. trimmaakt standaard een nieuwe string aan en retourneert de gewijzigde, terwijl trim_in_placede string die eraan wordt doorgegeven aanpast. De functie trimondersteunt c++11 move-semantiek.

#include <string>
// modifies input string, returns input
std::string& trim_left_in_place(std::string& str) {
    size_t i = 0;
    while(i < str.size() && isspace(str[i])) { ++i; };
    return str.erase(0, i);
}
std::string& trim_right_in_place(std::string& str) {
    size_t i = str.size();
    while(i > 0 && isspace(str[i - 1])) { --i; };
    return str.erase(i, str.size());
}
std::string& trim_in_place(std::string& str) {
    return trim_left_in_place(trim_right_in_place(str));
}
// returns newly created strings
std::string trim_right(std::string str) {
    return trim_right_in_place(str);
}
std::string trim_left(std::string str) {
    return trim_left_in_place(str);
}
std::string trim(std::string str) {
    return trim_left_in_place(trim_right_in_place(str));
}
#include <cassert>
int main() {
    std::string s1(" \t\r\n  ");
    std::string s2("  \r\nc");
    std::string s3("c \t");
    std::string s4("  \rc ");
    assert(trim(s1) == "");
    assert(trim(s2) == "c");
    assert(trim(s3) == "c");
    assert(trim(s4) == "c");
    assert(s1 == " \t\r\n  ");
    assert(s2 == "  \r\nc");
    assert(s3 == "c \t");
    assert(s4 == "  \rc ");
    assert(trim_in_place(s1) == "");
    assert(trim_in_place(s2) == "c");
    assert(trim_in_place(s3) == "c");
    assert(trim_in_place(s4) == "c");
    assert(s1 == "");
    assert(s2 == "c");
    assert(s3 == "c");
    assert(s4 == "c");  
}

24

Hier is een oplossing die gemakkelijk te begrijpen is voor beginners die niet worden gebruikt om std::overal te schrijven en nog niet bekend met const-correctness, iteratorS, STL algorithmS, ETC …

#include <string>
#include <cctype> // for isspace
using namespace std;
// Left trim the given string ("  hello!  " --> "hello!  ")
string left_trim(string str) {
    int numStartSpaces = 0;
    for (int i = 0; i < str.length(); i++) {
        if (!isspace(str[i])) break;
        numStartSpaces++;
    }
    return str.substr(numStartSpaces);
}
// Right trim the given string ("  hello!  " --> "  hello!")
string right_trim(string str) {
    int numEndSpaces = 0;
    for (int i = str.length() - 1; i >= 0; i--) {
        if (!isspace(str[i])) break;
        numEndSpaces++;
    }
    return str.substr(0, str.length() - numEndSpaces);
}
// Left and right trim the given string ("  hello!  " --> "hello!")
string trim(string str) {
    return right_trim(left_trim(str));
}

Hopelijk helpt het…


Antwoord 25

Hier is een oplossing voor trimmen met regex

#include <string>
#include <regex>
string trim(string str){
    return regex_replace(str, regex("(^[ ]+)|([ ]+$)"),"");
}

Antwoord 26

str.erase(0, str.find_first_not_of("\t\n\v\f\r ")); // left trim
str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1); // right trim

Antwoord 27

De bovenstaande methoden zijn geweldig, maar soms wil je een combinatie van functies gebruiken voor wat je routine beschouwt als witruimte. In dit geval kan het gebruik van functors om bewerkingen te combineren rommelig worden, dus ik geef de voorkeur aan een eenvoudige lus die ik kan aanpassen voor de trim. Hier is een licht gewijzigde trimfunctie gekopieerd van de C-versie hier op SO. In dit voorbeeld knip ik niet-alfanumerieke tekens bij.

string trim(char const *str)
{
  // Trim leading non-letters
  while(!isalnum(*str)) str++;
  // Trim trailing non-letters
  end = str + strlen(str) - 1;
  while(end > str && !isalnum(*end)) end--;
  return string(str, end+1);
}

Antwoord 28

En dit…?

#include <iostream>
#include <string>
#include <regex>
std::string ltrim( std::string str ) {
    return std::regex_replace( str, std::regex("^\\s+"), std::string("") );
}
std::string rtrim( std::string str ) {
    return std::regex_replace( str, std::regex("\\s+$"), std::string("") );
}
std::string trim( std::string str ) {
    return ltrim( rtrim( str ) );
}
int main() {
    std::string str = "   \t  this is a test string  \n   ";
    std::cout << "-" << trim( str ) << "-\n";
    return 0;
}

Opmerking: ik ben nog relatief nieuw in C++, dus vergeef me als ik hier niet op mijn plek ben.

Other episodes