Was ist der beste Weg, um std :: string zu trimmen?


812

Ich verwende derzeit den folgenden Code, um alle Elemente std::stringsin meinen Programmen nach rechts zu kürzen :

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

Es funktioniert gut, aber ich frage mich, ob es einige Endfälle gibt, in denen es fehlschlagen könnte.

Antworten mit eleganten Alternativen und auch Links-Trimm-Lösungen sind natürlich willkommen.


549
Die Antworten auf diese Frage sind ein Beweis dafür, wie wenig die C ++ - Standardbibliothek vorhanden ist.
Idan K

83
@IdanK Und diese Funktion gibt es in C ++ 11 immer noch nicht.
Quanten

44
@IdanK: Großartig, nicht wahr? Sehen Sie alle konkurrierenden Optionen , die wir jetzt zur Verfügung haben, unbelastet von einer einzelnen Person Idee „ die Art und Weise , dass wir es tun müssen!“
Leichtigkeitsrennen im Orbit

59
@ LightnessRacesinOrbit-Funktionalität innerhalb eines Typs, nun, das ist eine Entwurfsentscheidung, und das Hinzufügen einer Trimmfunktion zu einem String ist möglicherweise (zumindest unter c ++) ohnehin nicht die beste Lösung - bietet jedoch keine Standardmethode, sondern lässt alle sich Sorgen machen Das gleiche so kleine
Problem

27
Sie können sich fragen, warum Trimmfunktionen nicht in die std::stringKlasse integriert sind, wenn es Funktionen wie diese sind, die die Verwendung anderer Sprachen so angenehm machen (z. B. Python).
Hallo Goodbye

Antworten:


648

BEARBEITEN Seit c ++ 17 wurden einige Teile der Standardbibliothek entfernt. Glücklicherweise haben wir ab c ++ 11 Lambdas, die eine überlegene Lösung darstellen.

#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(), [](int 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(), [](int 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;
}

Vielen Dank an https://stackoverflow.com/a/44973498/524503 für die Einführung der modernen Lösung.

Ursprüngliche Antwort:

Ich neige dazu, eine dieser 3 für meine Trimmbedürfnisse zu verwenden:

#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));
}

Sie sind ziemlich selbsterklärend und funktionieren sehr gut.

EDIT : Übrigens, ich habe std::ptr_funda drin, um bei der Disambiguierung zu helfen, std::isspaceda es tatsächlich eine zweite Definition gibt, die Gebietsschemas unterstützt. Dies hätte trotzdem eine Besetzung sein können, aber ich mag das eher besser.

BEARBEITEN : Um einige Kommentare zum Akzeptieren eines Parameters als Referenz, zum Ändern und Zurückgeben zu beantworten. Genau. Eine Implementierung, die ich wahrscheinlich bevorzugen würde, wären zwei Funktionssätze, einer für In-Place und einer, der eine Kopie erstellt. Eine bessere Reihe von Beispielen wäre:

#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;
}

Ich behalte die obige ursprüngliche Antwort jedoch für den Kontext und im Interesse, die hochstimmige Antwort weiterhin verfügbar zu halten.


28
Dieser Code ist bei einigen internationalen Zeichenfolgen fehlgeschlagen (in meinem Fall Shift-Jis, gespeichert in einer std :: Zeichenfolge). Am Ende habe ich boost::trimdas Problem gelöst.
Tom

5
Ich würde Zeiger anstelle von Referenzen verwenden, damit vom Aufrufpunkt aus leichter zu verstehen ist, dass diese Funktionen die Zeichenfolge an Ort und Stelle bearbeiten, anstatt eine Kopie zu erstellen.
Marco Leogrande

3
Beachten Sie, dass Sie mit isspace leicht undefiniertes Verhalten mit Nicht-ASCII-Zeichen erhalten können. Stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes

10
Warum die statische? Ist hier ein anonymer Namespace bevorzugt?
Trevor Hickey

3
@TrevorHickey, sicher, Sie könnten stattdessen einen anonymen Namespace verwenden, wenn Sie dies bevorzugen.
Evan Teran

417

Die Verwendung der String-Algorithmen von Boost wäre am einfachsten:

#include <boost/algorithm/string.hpp>

std::string str("hello world! ");
boost::trim_right(str);

strist jetzt "hello world!". Es gibt auch trim_leftund trim, die beide Seiten trimmt.


Wenn Sie _copyeinem der oben genannten Funktionsnamen ein Suffix hinzufügen , z. B. trim_copygibt die Funktion eine zugeschnittene Kopie der Zeichenfolge zurück, anstatt sie über eine Referenz zu ändern.

Wenn Sie _ifeinem der oben genannten Funktionsnamen ein Suffix hinzufügen , z. B. trim_copy_ifkönnen Sie alle Zeichen kürzen, die Ihrem benutzerdefinierten Prädikat entsprechen, und nicht nur Leerzeichen.


7
Dies hängt vom Gebietsschema ab. Mein Standardgebietsschema (VS2005, de) bedeutet, dass Tabulatoren, Leerzeichen, Wagenrückläufe, Zeilenumbrüche, vertikale Tabulatoren und Formularvorschübe gekürzt werden.
MattyT

117
Boost ist so ein massiver Hammer für so ein kleines Problem.
Casey Rodarmor

143
@rodarmor: Boost löst viele kleine Probleme. Es ist ein massiver Hammer, der viel löst.
Nicol Bolas

123
Boost ist eine Reihe von Hämmern in vielen verschiedenen Größen, die viele verschiedene Probleme lösen.
Ibrahim

11
@rodarmor Du sagst, dass Boost ein Alles-oder-Nichts-Monolith ist, bei dem das Einfügen eines seiner Header das gesamte Programm irgendwie beeinflusst. Was eindeutig nicht der Fall ist. Übrigens habe ich Boost noch nie benutzt, fwiw.
underscore_d

61

Verwenden Sie den folgenden Code, um Leerzeichen und Tabulatorzeichen von std::strings( ideone ) nach rechts zu kürzen :

// 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));
}

Und nur um die Dinge auszugleichen, werde ich auch den linken Trimmcode ( ideone ) einfügen :

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

4
Andere Formen von Leerzeichen werden dadurch nicht erkannt ... insbesondere Zeilenumbruch, Zeilenvorschub, Wagenrücklauf.
Tom

1
Recht. Sie müssen es an das Leerzeichen anpassen, das Sie trimmen möchten. Meine spezielle Anwendung erwartete nur Leerzeichen und Tabulatoren, aber Sie können \ n \ r hinzufügen, um die anderen zu fangen.
Bill the Lizard

5
str.substr(...).swap(str)ist besser. Speichern Sie eine Aufgabe.
updogliu

4
@updogliu Wird die Verschiebungszuweisung nicht verwendet basic_string& operator= (basic_string&& str) noexcept;?
Nurettin

8
Diese Antwort ändert keine Zeichenfolgen, die ALLE Leerzeichen sind. Welches ist ein Fehlschlag.
Tom Andersen

56

Was Sie tun, ist gut und robust. Ich habe lange Zeit dieselbe Methode angewendet und muss noch eine schnellere Methode finden:

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);
}

Durch die Angabe der zu beschneidenden Zeichen haben Sie die Flexibilität, Nicht-Leerzeichen zu beschneiden, und die Effizienz, nur die Zeichen zu beschneiden, die Sie beschneiden möchten.


Wenn Sie die Reihenfolge ändern trim, dh machen Sie rtrim(ltrim(s, t), t)es etwas effizienter
CITBL

1
@CITBL Die innere Funktion zuerst ausgeführt , so dass Ihre Art und Weise es von links trimmen , bevor von den rechten trimmen. Ich denke, das wäre weniger effizient, nicht wahr?
Galik

Genau. Mein Fehler
CITBL

Wenn Sie basic_string und template für CharT verwenden, können Sie dies für alle Zeichenfolgen tun. Verwenden Sie einfach eine Vorlagenvariable für das Leerzeichen, damit Sie sie wie ws <CharT> verwenden. Technisch gesehen könnten Sie es zu diesem Zeitpunkt für C ++ 20 fertig machen und es auch als constexpr markieren, da dies Inline impliziert
Beached

@Beached in der Tat. Es ist allerdings etwas kompliziert, hier eine Antwort zu geben. Ich habe dafür Vorlagenfunktionen geschrieben und es ist sicherlich ziemlich kompliziert. Ich habe verschiedene Ansätze ausprobiert und bin mir immer noch nicht sicher, welcher der beste ist.
Galik

55

Etwas spät zur Party, aber egal. Jetzt ist C ++ 11 da, wir haben Lambdas und Auto-Variablen. Meine Version, die auch Leerzeichen und leere Zeichenfolgen verarbeitet, lautet also:

#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));
}

Wir könnten einen Reverse-Iterator daraus machen wsfront und diesen als Beendigungsbedingung in der zweiten verwenden, find_if_notaber das ist nur im Fall einer All-Whitespace-Zeichenfolge nützlich, und gcc 4.8 ist zumindest nicht klug genug, um auf den Typ des Reverse-Iterators zu schließen ( std::string::const_reverse_iterator) mit auto. Ich weiß nicht, wie teuer das Erstellen eines Reverse-Iterators ist, also YMMV hier. Mit dieser Änderung sieht der Code folgendermaßen aus:

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());
}

9
Nett. +1 von mir. Schade, dass C ++ 11 trim () nicht in std :: string eingeführt hat und das Leben für alle einfacher gemacht hat.
Milan Babuškov

3
Ich möchte immer einen Funktionsaufruf, um die Zeichenfolge zu trimmen, anstatt sie zu implementieren
Linquize

22
Für das, was es wert ist, besteht keine Notwendigkeit, dieses Lambda zu verwenden. Sie können einfach bestehen std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob

4
+1 für wahrscheinlich die einzige Antwort mit der Implementierung, die nur eine O (N) -Stringkopie erstellt.
Alexei Averchenko

4
@ vmrob-Compiler sind nicht unbedingt so schlau. Das zu tun, was Sie sagen, ist nicht eindeutig:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
Johnbakers

42

Versuchen Sie dies, es funktioniert für mich.

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;
}

12
Wenn Ihre Zeichenfolge keine Suffix-Leerzeichen enthält, wird dies ab npos + 1 == 0 gelöscht und Sie löschen die gesamte Zeichenfolge.
Mhsmith

3
@rgove Bitte erklären Sie. str.find_last_not_of(x)Gibt die Position des ersten Zeichens ungleich x zurück. Es wird nur npos zurückgegeben, wenn keine Zeichen nicht mit x übereinstimmen. Wenn im Beispiel keine Suffix-Leerzeichen vorhanden sind, wird das Äquivalent von zurückgegeben str.length() - 1, was im Wesentlichen ergibt. str.erase((str.length() - 1) + 1).Das heißt, es sei denn, ich irre mich schrecklich.
Travis

5
Dies sollte std :: string & zurückgeben, um zu vermeiden, dass der Kopierkonstruktor unnötig aufgerufen wird.
Heksesang

7
Ich bin verwirrt, warum dies eine Kopie zurückgibt, nachdem der Rückgabeparameter geändert wurde.
Galik

3
@MiloDC Meine Verwirrung ist, warum eine Kopie anstelle einer Referenz zurückgegeben wird. Es macht für mich mehr Sinn, zurückzukehren std::string&.
Galik

25

Ich mag die Lösung von tzaman. Das einzige Problem dabei ist, dass eine Zeichenfolge, die nur Leerzeichen enthält, nicht abgeschnitten wird.

Um diesen Fehler zu korrigieren, fügen Sie zwischen den beiden Trimmerlinien ein str.clear () ein

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

Schön :) Das Problem bei unseren beiden Lösungen ist jedoch, dass sie beide Enden abschneiden. kann nicht machen ltrimoder rtrimso.
Zaman

44
Gut, kann aber nicht mit Zeichenfolgen mit internen Leerzeichen umgehen. zB trim (abc def ") -> abc, nur noch abc übrig.
liheyuan

Eine gute Lösung, wenn Sie wissen, dass es keine internen Leerzeichen gibt!
Elliot Gorokhovsky

Das ist schön und einfach, aber es ist auch ziemlich langsam, wenn die Zeichenfolge in die und aus der kopiert wird std::stringstream.
Galik

23

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());
}

1
Endlich elegante Lösung für die Grundausstattung ... :)
jave.web

So funktioniert das: Dies ist eine kopierähnliche Lösung - es findet die Position des ersten Zeichens, das kein Leerzeichen ist ( it), und umgekehrt: Position des Zeichens, nach der nur noch Leerzeichen ( rit) vorhanden sind - danach wird eine neu erstellte Zeichenfolge == zurückgegeben eine Kopie des Teils der ursprünglichen Zeichenfolge - ein Teil, der auf diesen Iteratoren basiert ...
jave.web

Danke, hat für mich gearbeitet: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche

15

Im Fall einer leeren Zeichenfolge geht Ihr Code davon aus, dass das Hinzufügen von 1 zu string::npos0 string::nposvom Typ ist string::size_type, der ohne Vorzeichen ist. Sie verlassen sich also auf das Überlaufverhalten der Addition.


23
Sie formulieren das so, als ob es schlecht wäre. Das vorzeichenbehaftete Ganzzahlüberlaufverhalten ist schlecht.
MSalters

2
Hinzufügen 1zu std::string::npos müssen geben 0nach dem C++ Standard. Es ist also eine gute Annahme, auf die man sich absolut verlassen kann.
Galik

13

Von Cplusplus.com gehackt

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;
}

Dies funktioniert auch für den Nullfall. :-)


4
Dies ist nur rtrimnichtltrim
ub3rst4r

1
^ Stört es Sie, find_first_not_of zu verwenden? Es ist relativ einfach, es zu ändern.
Abhinav Gauniyal

13

In C ++ 17 können Sie basic_string_view :: remove_prefix und basic_string_view :: remove_suffix verwenden :

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;
}

Eine schöne Alternative:

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));
}

Ich bin nicht sicher , was Sie testen, aber in Ihrem Beispiel std :: find_first_not_of zurückkehren std :: string :: npos und std :: string_view :: Größe 4. Die Mindest zurück ist offensichtlich vier, die Anzahl der Elemente zu sein entfernt von std :: string_view :: remove_prefix . Sowohl gcc 9.2 als auch clang 9.0 behandeln dies korrekt: godbolt.org/z/DcZbFH
Phidelux

1
Vielen Dank! Sieht gut für mich aus.
Contango

11

Meine Lösung basiert auf der Antwort von @Bill the Lizard .

Beachten Sie, dass diese Funktionen die leere Zeichenfolge zurückgeben, wenn die Eingabezeichenfolge nur Leerzeichen enthält.

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);
}

9

Meine Antwort ist eine Verbesserung gegenüber der Top-Antwort für diesen Beitrag, bei der sowohl Steuerzeichen als auch Leerzeichen (0-32 und 127 in der ASCII-Tabelle ) gekürzt werden .

std::isgraphLegt fest, ob ein Zeichen eine grafische Darstellung hat. Sie können dies verwenden, um Evans Antwort zu ändern und alle Zeichen zu entfernen, die keine grafische Darstellung von beiden Seiten einer Zeichenfolge haben. Das Ergebnis ist eine viel elegantere Lösung:

#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));
}

Hinweis: Alternativ sollten Sie in der Lage sein, zu verwendenstd::iswgraph wenn Sie Unterstützung für breite Zeichen benötigen. Sie müssen diesen Code jedoch auch bearbeiten, um die std::wstringManipulation zu aktivieren. Dies habe ich nicht getestet (Informationen zur std::basic_stringOption finden Sie auf der Referenzseite ). .


3
std :: ptr_fun ist veraltet
Johnbakers

8

Mit C ++ 11 kam auch ein regulärer Ausdruck Modul für , mit dem natürlich führende oder nachfolgende Leerzeichen abgeschnitten werden können.

Vielleicht so etwas:

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));
}

8

Das benutze ich. Entfernen Sie einfach weiter Platz von vorne und machen Sie dasselbe von hinten, wenn noch etwas übrig ist.

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
}

8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   

2
Es wäre etwas effizienter, wenn Sie diese in umgekehrter Reihenfolge ausführen und zuerst von rechts trimmen, bevor Sie eine Verschiebung durch Trimmen der linken Seite aufrufen.
Galik

7

Für das, was es wert ist, ist hier eine Trimmimplementierung mit Blick auf die Leistung. Es ist viel schneller als viele andere Trimmroutinen, die ich gesehen habe. Anstatt Iteratoren und std :: find zu verwenden, werden rohe c-Zeichenfolgen und -Indizes verwendet. Es optimiert die folgenden Sonderfälle: Zeichenfolge der Größe 0 (nichts tun), Zeichenfolge ohne Leerzeichen zum Trimmen (nichts tun), Zeichenfolge mit nur nachgestellten Leerzeichen zum Trimmen (Größe der Zeichenfolge ändern), Zeichenfolge, die vollständig Leerzeichen enthält (nur die Zeichenfolge löschen) . Und im schlimmsten Fall (Zeichenfolge mit führendem Leerzeichen) ist es am besten, eine effiziente Kopierkonstruktion durchzuführen, nur eine Kopie auszuführen und diese Kopie dann anstelle der ursprünglichen Zeichenfolge zu verschieben.

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));
    }
}

@bmgda Vielleicht hat theoretisch die schnellste Version diese Signatur: extern "C" void string_trim (char ** begin_, char ** end_) ... Fang meine Drift?

6

Eine elegante Art, es zu tun, kann so sein

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

Und die unterstützenden Funktionen werden implementiert 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;   
}

Und wenn Sie alle diese Voraussetzungen erfüllt haben, können Sie auch Folgendes schreiben:

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

6

Trim C ++ 11-Implementierung:

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());
}

5

Ich denke, wenn Sie nach dem "besten Weg" zum Trimmen eines Strings fragen, würde ich sagen, dass eine gute Implementierung eine wäre, die:

  1. Weist keine temporären Zeichenfolgen zu
  2. Hat Überlastungen für In-Place-Trimm und Kopier-Trimm
  3. Kann einfach angepasst werden, um unterschiedliche Validierungssequenzen / -logiken zu akzeptieren

Offensichtlich gibt es zu viele verschiedene Möglichkeiten, dies zu erreichen, und es hängt definitiv davon ab, was Sie tatsächlich benötigen. Die C-Standardbibliothek verfügt jedoch noch über einige sehr nützliche Funktionen in <string.h>, wie z. B. memchr. Es gibt einen Grund, warum C immer noch als die beste Sprache für IO angesehen wird - seine stdlib ist reine Effizienz.

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;
}

3

Ich bin nicht sicher, ob Ihre Umgebung dieselbe ist, aber in meiner führt der Fall einer leeren Zeichenfolge dazu, dass das Programm abgebrochen wird. Ich würde diesen Löschaufruf entweder mit einem if (! S.empty ()) abschließen oder Boost wie bereits erwähnt verwenden.


3

Folgendes habe ich mir ausgedacht:

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

Durch die Stream-Extraktion werden Leerzeichen automatisch entfernt, sodass dies wie ein Zauber wirkt.
Ziemlich sauber und elegant, wenn ich es selbst sage. ;)


15
Hmm; Dies setzt voraus, dass die Zeichenfolge keine internen Leerzeichen (z. B. Leerzeichen) enthält. Der OP sagte nur, er wolle links oder rechts Leerzeichen kürzen.
SuperElectric

3

Beitrag meiner Lösung zum Lärm. trimStandardmäßig wird eine neue Zeichenfolge erstellt und die geänderte zurückgegeben, während trim_in_placedie an sie übergebene Zeichenfolge geändert wird . Die trimFunktion unterstützt die C ++ 11-Verschiebungssemantik.

#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");  
}

3

Dies kann in C ++ 11 durch Hinzufügen von back()und einfacher erfolgen pop_back().

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

Der vom OP vorgeschlagene Ansatz ist auch nicht schlecht - nur etwas schwieriger zu befolgen.
Nobar

3

Hier ist meine Version:

size_t beg = s.find_first_not_of(" \r\n");
return (beg == string::npos) ? "" : in.substr(beg, s.find_last_not_of(" \r\n") - beg);

Ihnen fehlt das letzte Zeichen. Ein +1 in der Länge löst dieses
Galinette

2

Die oben genannten Methoden sind großartig, aber manchmal möchten Sie eine Kombination von Funktionen für das verwenden, was Ihre Routine als Leerzeichen betrachtet. In diesem Fall kann die Verwendung von Funktoren zum Kombinieren von Operationen unübersichtlich werden. Daher bevorzuge ich eine einfache Schleife, die ich für die Trimmung ändern kann. Hier ist eine leicht modifizierte Trimmfunktion, die aus der C-Version hier auf SO kopiert wurde. In diesem Beispiel schneide ich nicht alphanumerische Zeichen.

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);
}

2

Hier ist eine einfache Implementierung. Für eine so einfache Operation sollten Sie wahrscheinlich keine speziellen Konstrukte verwenden. Die eingebaute Funktion isspace () kümmert sich um verschiedene Formen weißer Zeichen, daher sollten wir sie nutzen. Sie müssen auch Sonderfälle berücksichtigen, in denen die Zeichenfolge leer ist oder nur eine Reihe von Leerzeichen. Das Trimmen nach links oder rechts kann aus dem folgenden Code abgeleitet werden.

string trimSpace(const string &str) {
   if (str.empty()) return str;
   string::size_type i,j;
   i=0;
   while (i<str.size() && isspace(str[i])) ++i;
   if (i == str.size())
      return string(); // empty string
   j = str.size() - 1;
   //while (j>0 && isspace(str[j])) --j; // the j>0 check is not needed
   while (isspace(str[j])) --j
   return str.substr(i, j-i+1);
}

2

Hier ist eine leicht verständliche Lösung für Anfänger, die nicht std::überall schreiben und noch nicht mit constKorrektheit, iterators, STLs algorithmusw. vertraut sind.

#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));
}

Ich hoffe es hilft...


1

Diese Version schneidet interne Leerzeichen und nicht alphanumerische Zeichen:

static inline std::string &trimAll(std::string &s)
{   
    if(s.size() == 0)
    {
        return s;
    }

    int val = 0;
    for (int cur = 0; cur < s.size(); cur++)
    {
        if(s[cur] != ' ' && std::isalnum(s[cur]))
        {
            s[val] = s[cur];
            val++;
        }
    }
    s.resize(val);
    return s;
}

1

Noch eine andere Option - entfernt ein oder mehrere Zeichen von beiden Enden.

string strip(const string& s, const string& chars=" ") {
    size_t begin = 0;
    size_t end = s.size()-1;
    for(; begin < s.size(); begin++)
        if(chars.find_first_of(s[begin]) == string::npos)
            break;
    for(; end > begin; end--)
        if(chars.find_first_of(s[end]) == string::npos)
            break;
    return s.substr(begin, end-begin+1);
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.