Jaki jest najlepszy sposób przycinania std :: string?

812

Obecnie używam następującego kodu do przycięcia wszystkich std::stringsprogramów w moich programach:

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

Działa dobrze, ale zastanawiam się, czy są jakieś przypadki końcowe, w których może się nie powieść?

Oczywiście mile widziane są odpowiedzi z eleganckimi alternatywami, a także lewe wykończenia.

Milan Babuškov
źródło
549
Odpowiedzi na to pytanie są świadectwem braku standardowej biblioteki C ++.
Idan K
83
@IdanK I wciąż nie ma tej funkcji w C ++ 11.
kwantowy
44
@IdanK: Świetnie, prawda? Spójrz na wszystkich konkurencyjnych opcji mamy teraz do naszej dyspozycji, wolny od idei pojedynczej osoby z „ w sposób, który musimy to zrobić”!
Wyścigi lekkości na orbicie
59
@LightnessRacesinOrbit funkcjonalność w obrębie typu, cóż, to jest decyzja projektowa, a dodanie funkcji przycinania do łańcucha może (przynajmniej w c ++) i tak nie być najlepszym rozwiązaniem - ale nie zapewnia żadnego standardowego sposobu na zrobienie tego, pozwalając wszystkim się martwić te same drobne problemy
wciąż na
27
Możesz zapytać, dlaczego funkcje przycinania nie są wbudowane w std::stringklasę, kiedy to takie funkcje sprawiają, że inne języki są tak przyjemne w użyciu (na przykład Python).
HelloGoodbye,

Odpowiedzi:

648

EDYCJA Od wersji c ++ 17 niektóre części standardowej biblioteki zostały usunięte. Na szczęście, począwszy od c ++ 11, mamy lambdy, które są doskonałym rozwiązaniem.

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

Dzięki https://stackoverflow.com/a/44973498/524503 za wprowadzenie nowoczesnego rozwiązania.

Oryginalna odpowiedź:

Zwykle używam jednego z tych 3 do moich potrzeb przycinania:

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

Są dość zrozumiałe i działają bardzo dobrze.

EDYCJA : BTW, mam std::ptr_funtam pomóc std::isspacew jednoznacznym ustaleniu, ponieważ tak naprawdę istnieje druga definicja, która obsługuje ustawienia narodowe. To mogła być obsada tak samo, ale ja lubię to bardziej.

EDYCJA : Aby odnieść się do niektórych komentarzy na temat akceptowania parametru przez odniesienie, modyfikowanie i zwracanie go. Zgadzam się. Implementacją, którą prawdopodobnie wolałbym, byłyby dwa zestawy funkcji, jeden na miejscu i jeden, który tworzy kopię. Lepszym zestawem przykładów byłoby:

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

Zachowuję jednak pierwotną odpowiedź powyżej w kontekście i w celu utrzymania wysokiej głosowanej odpowiedzi nadal dostępnej.

Evan Teran
źródło
28
Ten kod zawodził w niektórych międzynarodowych ciągach znaków (shift-jis w moim przypadku, przechowywany w std :: string); Skończyło się boost::trimna rozwiązaniu problemu.
Tom
5
Używałbym wskaźników zamiast odniesień, aby z punktu wywoławczego łatwiej było zrozumieć, że te funkcje edytują ciąg w miejscu, zamiast tworzyć kopię.
Marco Leogrande,
3
Zauważ, że dzięki isspace możesz łatwo uzyskać niezdefiniowane zachowanie ze stosami
R. Martinho Fernandes
10
Dlaczego statyczny? Czy właśnie tam preferowana byłaby anonimowa przestrzeń nazw?
Trevor Hickey,
3
@TrevorHickey, na pewno możesz użyć anonimowej przestrzeni nazw, jeśli wolisz.
Evan Teran
417

Użycie algorytmów ciągu Boost byłoby najłatwiejsze:

#include <boost/algorithm/string.hpp>

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

strjest teraz "hello world!". Jest też trim_lefti trim, która przycina obie strony.


Jeśli dodasz _copysufiks do dowolnej z powyższych nazw funkcji, np trim_copy. Funkcja zwróci przyciętą kopię ciągu zamiast modyfikować ją przez odwołanie.

Jeśli dodasz _ifsufiks do dowolnej z powyższych nazw funkcji, np. trim_copy_ifMożesz przyciąć wszystkie znaki spełniające Twój predykat, w przeciwieństwie do samych białych znaków.

Leon Timmermans
źródło
7
To zależy od regionu. Moje domyślne ustawienia narodowe (VS2005, en) oznaczają, że tabulatory, spacje, znaki powrotu karetki, znaki nowej linii, tabulatory pionowe i wysuw strony są przycięte.
MattyT
117
Boost to taki masywny młot na tak mały problem.
Casey Rodarmor
143
@rodarmor: Boost rozwiązuje wiele drobnych problemów. To ogromny młot, który wiele rozwiązuje.
Nicol Bolas
123
Boost to zestaw młotków o różnych rozmiarach, rozwiązujących wiele różnych problemów.
Ibrahim
11
@rodarmor Mówisz, że jakby Boost był monolitem „wszystko albo nic”, w którym umieszczenie jednego z jego nagłówków w jakiś sposób narzuca całą sprawę programowi. Co oczywiście nie jest prawdą. Btw, nigdy nie korzystałem z Boost, fwiw.
underscore_d
61

Użyj następującego kodu, aby wyrównać (końcowe) spacje i znaki tabulacji w std::strings( ideone ):

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

I żeby to zrównoważyć, dołączę również lewy kod wykończenia ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
Bill jaszczurka
źródło
4
Nie wykryje to innych form białych znaków ... w szczególności znak nowej linii, przesunięcie wiersza, powrót karetki.
Tom
1
Dobrze. Musisz dostosować go do białych znaków, które chcesz przyciąć. Moja konkretna aplikacja spodziewała się tylko spacji i tabulatorów, ale możesz dodać \ n \ r, aby złapać inne.
Bill the Lizard
5
str.substr(...).swap(str)jest lepiej. Zapisz zadanie.
updogliu
4
@updogliu Nie użyjesz przeniesienia basic_string& operator= (basic_string&& str) noexcept;?
nurettin
8
Ta odpowiedź nie zmienia ciągów zawierających WSZYSTKIE spacje. Co jest porażką.
Tom Andersen
56

To, co robisz, jest dobre i solidne. Długo używałem tej samej metody i jeszcze nie znalazłem szybszej metody:

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

Dostarczając znaki, które mają zostać przycięte, masz elastyczność przycinania znaków spoza białych i wydajność przycinania tylko tych znaków, które chcesz przyciąć.

Galik
źródło
jeśli zmienisz kolejność trim, tzn. rtrim(ltrim(s, t), t)
sprawisz,
1
@CITBL Funkcja wewnętrzna jest wykonywana jako pierwsza, więc będzie przycinana z lewej strony przed przycinaniem z prawej. Myślę, że to byłoby mniej wydajne, prawda?
Galik
Dokładnie. Mój błąd
CITBL
jeśli używasz Basic_string i szablonu na CharT, możesz to zrobić dla wszystkich łańcuchów, po prostu użyj zmiennej szablonu dla białych znaków, aby używać jej jak ws <CharT>. technicznie w tym momencie możesz przygotować go do wersji c ++ 20 i oznaczyć go jako constexpr, ponieważ to sugeruje inline
Beached
@Beached Indeed. Trochę skomplikowane jest jednak udzielenie tutaj odpowiedzi. Napisałem do tego funkcje szablonów i jest to z pewnością dość zaangażowane. Wypróbowałem kilka różnych podejść i nadal nie jestem pewien, który jest najlepszy.
Galik,
55

Trochę późno na imprezę, ale nieważne. Teraz jest już C ++ 11, mamy lambdy i zmienne automatyczne. Tak więc moja wersja, która obsługuje również białe znaki i puste ciągi, to:

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

Moglibyśmy zrobić iterator do tyłu wsfronti użyć go jako warunku zakończenia w drugim, find_if_notale jest to przydatne tylko w przypadku łańcucha całkowicie białych znaków, a gcc 4.8 przynajmniej nie jest wystarczająco inteligentny, aby wywnioskować rodzaj iteratora do tyłu ( std::string::const_reverse_iterator) z auto. Nie wiem, jak drogie jest tworzenie iteratora wstecznego, więc tutaj YMMV. Po tej zmianie kod wygląda następująco:

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());
}
David G.
źródło
9
Miły. +1 ode mnie Szkoda, że ​​C ++ 11 nie wprowadził trim () do std :: string i ułatwił wszystkim życie.
Milan Babuškov
3
Zawsze chcę jednego wywołania funkcji, aby przyciąć ciąg, zamiast go implementować
linquize
22
Do tego, co jest warte, nie ma potrzeby używania tej lambda. Możesz po prostu przejść std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob
4
+1 za prawdopodobnie jedyną odpowiedź z implementacją, która wykonuje tylko jedną kopię ciągu O (N).
Alexei Averchenko
4
Kompilatory @vmrob niekoniecznie są tak inteligentne. robienie tego, co mówisz, jest dwuznaczne:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers,
42

Wypróbuj to, działa dla mnie.

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;
}
user818330
źródło
12
Jeśli Twój ciąg nie zawiera spacji, spowoduje to skasowanie, zaczynając od npos + 1 == 0, i usuniesz cały ciąg.
msmsm
3
@rgove Proszę wyjaśnić. str.find_last_not_of(x)zwraca pozycję pierwszego znaku różną od x. Zwraca npos tylko wtedy, gdy żadne znaki nie pasują do x. W tym przykładzie, jeśli nie ma spacji, zwróci równowartość str.length() - 1, dając w zasadzie str.erase((str.length() - 1) + 1).To znaczy, chyba że się mylę.
Travis,
5
Powinno to zwrócić wartość std :: string &, aby uniknąć niepotrzebnego wywoływania konstruktora kopiowania.
heksesang
7
Jestem zdezorientowany, dlaczego to zwraca kopię po zmodyfikowaniu parametru return?
Galik,
3
@MiloDC Moje zamieszanie powoduje, że zwracam kopię zamiast odwołania. Bardziej sensowne jest dla mnie powrót std::string&.
Galik
25

Podoba mi się rozwiązanie tzamana, jedynym problemem jest to, że nie przycina ciągu zawierającego tylko spacje.

Aby poprawić tę 1 wadę, dodaj str.clear () pomiędzy 2 liniami trymera

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Michaël Schoonbrood
źródło
Fajnie :) Problem z obydwoma naszymi rozwiązaniami polega na tym, że przycinają oba końce; nie mogę zrobić ltrimani rtrimtak.
tzaman
44
Dobrze, ale nie radzi sobie z łańcuchem z wewnętrznymi białymi znakami. np. trim (abc def ") -> abc, pozostało tylko abc.
liheyuan
Dobre rozwiązanie, jeśli wiesz, że nie będzie żadnych białych znaków!
Elliot Gorokhovsky
Jest to przyjemne i łatwe, ale jest również dość powolne, ponieważ ciąg jest kopiowany do i z 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());
}
Pushkoff
źródło
1
Eleganckie rozwiązanie dla podstawowego wykończenia przestrzeni w końcu ... :)
jave.web
Jak to działa: Jest to rozwiązanie podobne do kopiowania - znajduje pozycję pierwszego znaku, który nie jest spacją ( it), i odwrotnie: pozycję znaku, po której są tylko spacje ( rit) - po tym zwraca nowo utworzony ciąg == kopia części oryginalnego ciągu - część oparta na tych iteratorach ...
jave.web
Dziękuję, pracował dla mnie: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche,
15

W przypadku pustego ciągu kod zakłada, że ​​dodanie 1 do wartości string::npos0. string::nposjest typu string::size_type, który nie jest podpisany. Dlatego polegasz na przepełnieniu dodawania.

Greg Hewgill
źródło
23
Frazujesz to tak, jakby to było złe. Podpisane zachowanie przepełnienia liczb całkowitych jest złe.
MSalters,
2
Dodanie 1do std::string::npos musi dać 0zgodnie z C++ Standard. Jest to więc dobre założenie, na którym można absolutnie polegać.
Galik
13

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

Działa to również w przypadku wartości zerowej. :-)

Paul Nathan
źródło
4
To po prostu rtrimnieltrim
ub3rst4r
1
^ czy masz coś przeciwko użyciu find_first_not_of? Jest to stosunkowo łatwe do zmodyfikowania.
Abhinav Gauniyal
13

W C ++ 17 możesz używać basic_string_view :: remove_prefix i basic_string_view :: remove_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;
}

Ładna alternatywa:

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));
}
Phidelux
źródło
Nie jestem pewien, co testujesz , ale w twoim przykładzie std :: find_first_not_of zwróci std :: string :: npos i std :: string_view :: size zwróci 4. Minimum to oczywiście cztery, liczba elementów do usunięte przez std :: string_view :: remove_prefix . Zarówno gcc 9.2, jak i clang 9.0 obsługują to poprawnie: godbolt.org/z/DcZbFH
Phidelux
1
Dzięki! Dla mnie wygląda dobrze.
Contango
11

Moje rozwiązanie oparte na odpowiedzi @Bill the Lizard .

Zauważ, że te funkcje zwrócą pusty ciąg, jeśli ciąg wejściowy nie zawiera nic oprócz białych znaków.

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);
}
DavidRR
źródło
9

Moja odpowiedź jest poprawą w stosunku do górnej odpowiedzi dla tego postu, która przycina znaki kontrolne oraz spacje (0-32 i 127 w tabeli ASCII ).

std::isgraphokreśla, czy znak ma reprezentację graficzną, więc możesz użyć tego do zmiany odpowiedzi Evana, aby usunąć dowolny znak, który nie ma reprezentacji graficznej z żadnej strony łańcucha. Rezultatem jest znacznie bardziej eleganckie rozwiązanie:

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

Uwaga: Alternatywnie powinieneś być w stanie użyć, std::iswgraphjeśli potrzebujesz obsługi szerokich znaków, ale będziesz musiał również edytować ten kod, aby umożliwić std::wstringmanipulację, co jest czymś, czego nie przetestowałem (zobacz stronę referencyjną, std::basic_stringaby zbadać tę opcję) .

Clay Freeman
źródło
3
std :: ptr_fun Jest przestarzałe
johnbakers,
8

Wraz z C ++ 11 pojawiło się również wyrażenie regularne moduł , który oczywiście może być użyty do przycinania spacji wiodących lub końcowych.

Może coś takiego:

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));
}
Jakiś koleś programisty
źródło
8

Tego używam. Po prostu usuwaj przestrzeń z przodu, a następnie, jeśli coś pozostało, zrób to samo z tyłu.

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
}
synaptik
źródło
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
źródło
2
Byłoby to nieco bardziej wydajne, jeśli wykonasz je w odwrotnej kolejności i przycinasz najpierw od prawej, zanim wywołasz zmianę, przycinając lewą.
Galik
7

Dla tego, co jest warte, oto implementacja wykończenia z myślą o wydajności. Jest znacznie szybszy niż wiele innych procedur przycinania, które widziałem w pobliżu. Zamiast używać iteratorów i std :: find, używa surowych ciągów c i indeksów. Optymalizuje następujące przypadki specjalne: łańcuch o rozmiarze 0 (nic nie rób), łańcuch bez białych znaków do przycinania (nic nie rób), łańcuch z tylko końcowymi białymi znakami do przycinania (wystarczy zmienić rozmiar łańcucha), łańcuch całkowicie białą spacją (wystarczy wyczyścić łańcuch) . I wreszcie, w najgorszym przypadku (łańcuch z wiodącymi białymi znakami), robi wszystko, aby wykonać wydajną konstrukcję kopii, wykonując tylko 1 kopię, a następnie przenosząc tę ​​kopię w miejsce oryginalnego ciągu.

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));
    }
}
Mbgda
źródło
@bmgda może teoretycznie najszybsza wersja może mieć ten podpis: extern "C" void string_trim (char ** begin_, char ** end_) ... Catch my drift?
6

Może to być elegancki sposób na zrobienie tego

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

Funkcje pomocnicze są realizowane jako:

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

A kiedy już to wszystko umieścisz, możesz również napisać:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
jha-G
źródło
6

Implementacja Trim C ++ 11:

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());
}
GutiMac
źródło
5

Myślę, że jeśli zaczniesz pytać o „najlepszy sposób” przycinania łańcucha, powiedziałbym, że dobrym rozwiązaniem byłoby:

  1. Nie przydziela tymczasowych ciągów
  2. Ma przeciążenia do przycinania na miejscu i kopiowania przycinania
  3. Może być łatwo dostosowany do akceptowania różnych sekwencji / logiki sprawdzania poprawności

Oczywiście istnieje zbyt wiele różnych sposobów podejścia do tego i na pewno zależy to od tego, czego naprawdę potrzebujesz. Jednak biblioteka standardowa C nadal ma bardzo przydatne funkcje w <string.h>, takie jak memchr. Jest powód, dla którego C jest nadal uważany za najlepszy język dla IO - jego stdlib to czysta wydajność.

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;
}
Jorma Rebane
źródło
3

Nie jestem pewien, czy twoje środowisko jest takie samo, ale w moim przypadku pusty ciąg znaków spowoduje przerwanie programu. Chciałbym owinąć to połączenie kasowania poleceniem if (! S.empty ()) lub użyć Boosta, jak już wspomniano.

Steve
źródło
3

Oto, co wymyśliłem:

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

Ekstrakcja strumienia automatycznie eliminuje białe spacje, więc działa to jak urok.
Całkiem czysty i elegancki, jeśli sam to powiem. ;)

tzaman
źródło
15
Hmm; zakłada to, że łańcuch nie ma wewnętrznych spacji (np. spacji). OP powiedział tylko, że chce przyciąć białe znaki po lewej lub prawej stronie.
SuperElectric,
3

Przyczyniając się do rozwiązania mojego problemu z hałasem. trimdomyślnie tworzy nowy ciąg i zwraca zmodyfikowany trim_in_placeciąg, jednocześnie modyfikując przekazany ciąg. Ta trimfunkcja obsługuje semantykę przenoszenia c ++ 11.

#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");  
}
vmrob
źródło
3

Można to zrobić prościej w C ++ 11 dzięki dodaniu back()i pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
nobar
źródło
Podejście zaproponowane przez OP nie jest też złe - jest tylko trochę trudniejsze do zastosowania.
nobar
3

Oto moja wersja:

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);
światło nocne
źródło
Brakuje ostatniej postaci. +1 długości rozwiązuje ten problem
galinette
2

Powyższe metody są świetne, ale czasami chcesz użyć kombinacji funkcji do tego, co rutyna uważa za spacje. W takim przypadku używanie funktorów do łączenia operacji może być nieporządne, dlatego wolę prostą pętlę, którą mogę zmodyfikować dla wykończenia. Oto nieco zmodyfikowana funkcja przycinania skopiowana z wersji C tutaj na SO. W tym przykładzie przycinam znaki nie alfanumeryczne.

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);
}
Corwin Joy
źródło
2

Oto prosta implementacja. Do tak prostej operacji prawdopodobnie nie powinieneś używać żadnych specjalnych konstrukcji. Wbudowana funkcja isspace () zajmuje się różnymi formami białych znaków, dlatego powinniśmy z niej skorzystać. Musisz również wziąć pod uwagę specjalne przypadki, w których łańcuch jest pusty lub po prostu kilka spacji. Przycinanie w lewo lub w prawo można uzyskać z następującego kodu.

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);
}
Kemin Zhou
źródło
2

Oto rozwiązanie łatwe do zrozumienia dla początkujących, którzy nie używają pisma std::wszędzie i jeszcze nie znają constpoprawności, iterators, STL algorithmitp.

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

Mam nadzieję, że to pomoże...

cute_ptr
źródło
1

Ta wersja przycina wewnętrzne białe znaki i znaki niealfanumeryczne:

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;
}
Brian
źródło
1

Jeszcze inna opcja - usuwa jedną lub więcej postaci z obu końców.

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);
}
Brian W.
źródło