Usuwanie spacji wiodących i końcowych z ciągu


92

Jak usunąć spacje z obiektu ciągu w C ++.
Na przykład, jak usunąć spacje wiodące i końcowe z poniższego obiektu ciągu.

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

O ile wiem, klasa string nie zapewnia żadnych metod usuwania spacji wiodących i końcowych.

Aby dodać do problemu, jak rozszerzyć to formatowanie, aby przetwarzać dodatkowe spacje między słowami ciągu. Na przykład,

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

Korzystając z metod łańcuchowych wymienionych w rozwiązaniu, mogę pomyśleć o wykonaniu tych operacji w dwóch krokach.

  1. Usuń spacje wiodące i końcowe.
  2. Użyj find_first_of, find_last_of, find_first_not_of, find_last_not_of i substr , wielokrotnie na granicach słowa, aby uzyskać pożądane formatowanie.

Odpowiedzi:


128

Nazywa się to przycinaniem. Jeśli możesz użyć Boost , polecam to.

W przeciwnym razie użyj, find_first_not_ofaby pobrać indeks pierwszego niebiałego znaku, a następnie find_last_not_ofpobrać indeks od końca, który nie jest białą spacją. Z nimi użyj, substraby uzyskać podciąg bez otaczających białych znaków.

W odpowiedzi na twoją edycję nie znam terminu, ale zgaduję, że jest to coś w rodzaju „zmniejsz”, więc tak to nazwałem. :) (Uwaga, odstępy zostały zmienione na parametr, aby zwiększyć elastyczność)

#include <iostream>
#include <string>

std::string trim(const std::string& str,
                 const std::string& whitespace = " \t")
{
    const auto strBegin = str.find_first_not_of(whitespace);
    if (strBegin == std::string::npos)
        return ""; // no content

    const auto strEnd = str.find_last_not_of(whitespace);
    const auto strRange = strEnd - strBegin + 1;

    return str.substr(strBegin, strRange);
}

std::string reduce(const std::string& str,
                   const std::string& fill = " ",
                   const std::string& whitespace = " \t")
{
    // trim first
    auto result = trim(str, whitespace);

    // replace sub ranges
    auto beginSpace = result.find_first_of(whitespace);
    while (beginSpace != std::string::npos)
    {
        const auto endSpace = result.find_first_not_of(whitespace, beginSpace);
        const auto range = endSpace - beginSpace;

        result.replace(beginSpace, range, fill);

        const auto newStart = beginSpace + fill.length();
        beginSpace = result.find_first_of(whitespace, newStart);
    }

    return result;
}

int main(void)
{
    const std::string foo = "    too much\t   \tspace\t\t\t  ";
    const std::string bar = "one\ntwo";

    std::cout << "[" << trim(foo) << "]" << std::endl;
    std::cout << "[" << reduce(foo) << "]" << std::endl;
    std::cout << "[" << reduce(foo, "-") << "]" << std::endl;

    std::cout << "[" << trim(bar) << "]" << std::endl;
}

Wynik:

[too much               space]  
[too much space]  
[too-much-space]  
[one  
two]  

Zakładam, że miałeś na myśli „size_t”. a na podłańcuchu znajduje się jeden po drugim, powinien być substr (beginStr, endStr - beginStr + 1);
goldPseudo

Powinien site_tbyć size_t? I myślę, że miejsce, w którym masz komentarz, no whitespaceoznacza, że ​​cały ciąg jest spacji lub pusty.
Fred Larson,

Dzięki, size_tpoprawiłem literówkę i pojedynczo w edycji, ale nie zauważyłem, że mój komentarz został odwrócony, dzięki.
GManNickG,

@GMan bardzo eleganckie rozwiązanie. Dzięki.
Ankur

Błąd: spróbuj uruchomić "one \ ttwo" przez trim (). Wynik to pusty ciąg. Musisz również przetestować endStr względem std :: string :: npos.
dlchambers

48

Łatwe usuwanie początkowych, końcowych i dodatkowych spacji ze std :: string w jednej linii

value = std::regex_replace(value, std::regex("^ +| +$|( ) +"), "$1");

usuwanie tylko wiodących spacji

value.erase(value.begin(), std::find_if(value.begin(), value.end(), std::bind1st(std::not_equal_to<char>(), ' ')));

lub

value = std::regex_replace(value, std::regex("^ +"), "");

usuwanie tylko spacji końcowych

value.erase(std::find_if(value.rbegin(), value.rend(), std::bind1st(std::not_equal_to<char>(), ' ')).base(), value.end());

lub

value = std::regex_replace(value, std::regex(" +$"), "");

usuwanie tylko dodatkowych spacji

value = regex_replace(value, std::regex(" +"), " ");

3
Niezłe. Przydałoby się podać trochę informacji o tym, co się tutaj dzieje, ponieważ trudno jest zrozumieć te kody.
Marcin

Działa jednak tylko w C ++ 11.
Martin Pecka

7
Nie usuwa zakładek, ale można to naprawić. To, czego nie można naprawić, to to, że jest strasznie powolny (~ 100 razy wolniejszy niż odpowiedzi z substrlub erase).
4LegsDrivenCat

dla optymalizacji prędkości wyrażenie regularne nie jest optymalnym rozwiązaniem, ale można je poprawić, tworząc raz wystąpienie wyrażenia regularnego
Evgeny Karpov

40

Obecnie używam tych funkcji:

// trim from left
inline std::string& ltrim(std::string& s, const char* t = " \t\n\r\f\v")
{
    s.erase(0, s.find_first_not_of(t));
    return s;
}

// trim from right
inline std::string& rtrim(std::string& s, const char* t = " \t\n\r\f\v")
{
    s.erase(s.find_last_not_of(t) + 1);
    return s;
}

// trim from left & right
inline std::string& trim(std::string& s, const char* t = " \t\n\r\f\v")
{
    return ltrim(rtrim(s, t), t);
}

// copying versions

inline std::string ltrim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return ltrim(s, t);
}

inline std::string rtrim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return rtrim(s, t);
}

inline std::string trim_copy(std::string s, const char* t = " \t\n\r\f\v")
{
    return trim(s, t);
}


9

To jest moje rozwiązanie do usuwania spacji wiodącej i końcowej ...

std::string stripString = "  Plamen     ";
while(!stripString.empty() && std::isspace(*stripString.begin()))
    stripString.erase(stripString.begin());

while(!stripString.empty() && std::isspace(*stripString.rbegin()))
    stripString.erase(stripString.length()-1);

Wynik to „Plamen”


8

Oto jak możesz to zrobić:

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

Funkcje pomocnicze są realizowane jako:

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

A kiedy już to wszystko na miejscu, możesz również napisać to:

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

Spróbuj tego


7

Przykład przycinania wiodących i końcowych spacji zgodnie z sugestią Jon-Hansona, aby użyć wzmocnienia (usuwa tylko końcowe i oczekujące spacje):

#include <boost/algorithm/string/trim.hpp>

std::string str = "   t e s t    ";

boost::algorithm::trim ( str );

Prowadzi do "t e s t"

Jest również

  • trim_left prowadzi do "t e s t "
  • trim_right prowadzi do " t e s t"

5
/// strip a string, remove leading and trailing spaces
void strip(const string& in, string& out)
{
    string::const_iterator b = in.begin(), e = in.end();

    // skipping leading spaces
    while (isSpace(*b)){
        ++b;
    }

    if (b != e){
        // skipping trailing spaces
        while (isSpace(*(e-1))){
            --e;
        }
    }

    out.assign(b, e);
}

W powyższym kodzie funkcja isSpace () jest funkcją logiczną, która mówi, czy znak jest odstępem, możesz zaimplementować tę funkcję, aby odzwierciedlić swoje potrzeby, lub po prostu wywołać funkcję isspace () z „ctype.h”, jeśli chcesz .


4

Przykład przycinania spacji wiodących i końcowych

std::string aString("    This is a string to be trimmed   ");
auto start = aString.find_first_not_of(' ');
auto end = aString.find_last_not_of(' ');
std::string trimmedString;
trimmedString = aString.substr(start, (end - start) + 1);

LUB

trimmedSring = aString.substr(aString.find_first_not_of(' '), (aString.find_last_not_of(' ') - aString.find_first_not_of(' ')) + 1);

3
Ludzie nie lubią zaglądać do 10 stron kodu, aby dowiedzieć się, jak przycinać ciąg.
Thinkal VB

2
jest zepsuty, jeśli łańcuch zawiera tylko spacje
DAG

3

Korzystanie z biblioteki standardowej ma wiele zalet, ale należy pamiętać o pewnych specjalnych przypadkach, które powodują wyjątki. Na przykład żadna z odpowiedzi nie dotyczyła przypadku, w którym ciąg w C ++ ma kilka znaków Unicode. W takim przypadku, jeśli użyjesz funkcji isspace , zostanie zgłoszony wyjątek.

Używałem następującego kodu do przycinania ciągów i innych operacji, które mogą się przydać. Główne zalety tego kodu to: jest naprawdę szybki (szybszy niż jakikolwiek kod, który kiedykolwiek testowałem), używa tylko standardowej biblioteki i nigdy nie powoduje wyjątku:

#include <string>
#include <algorithm>
#include <functional>
#include <locale>
#include <iostream>

typedef unsigned char BYTE;

std::string strTrim(std::string s, char option = 0)
{
    // convert all whitespace characters to a standard space
    std::replace_if(s.begin(), s.end(), (std::function<int(BYTE)>)::isspace, ' ');

    // remove leading and trailing spaces
    size_t f = s.find_first_not_of(' ');
    if (f == std::string::npos) return "";
    s = s.substr(f, s.find_last_not_of(' ') - f + 1);

    // remove consecutive spaces
    s = std::string(s.begin(), std::unique(s.begin(), s.end(),
        [](BYTE l, BYTE r){ return l == ' ' && r == ' '; }));

    switch (option)
    {
    case 'l':  // convert to lowercase
        std::transform(s.begin(), s.end(), s.begin(), ::tolower);
        return s;
    case 'U':  // convert to uppercase
        std::transform(s.begin(), s.end(), s.begin(), ::toupper);
        return s;
    case 'n':  // remove all spaces
        s.erase(std::remove(s.begin(), s.end(), ' '), s.end());
        return s;
    default: // just trim
        return s;
    }
}

3

To może być najprostsze ze wszystkich.

Możesz użyć string::findi, string::rfindaby znaleźć białe znaki z obu stron i zredukować ciąg.

void TrimWord(std::string& word)
{
    if (word.empty()) return;

    // Trim spaces from left side
    while (word.find(" ") == 0)
    {
        word.erase(0, 1);
    }

    // Trim spaces from right side
    size_t len = word.size();
    while (word.rfind(" ") == --len)
    {
        word.erase(len, len + 1);
    }
}

2

Przetestowałem to, wszystko działa. Więc ta metoda processInput po prostu poprosi użytkownika o wpisanie czegoś. Zwróci łańcuch, który nie ma wewnętrznych spacji ani dodatkowych spacji na początku lub na końcu. Mam nadzieję że to pomoże. (umieść także stos komentarzy, aby ułatwić zrozumienie).

możesz zobaczyć, jak to zaimplementować w main () na dole

#include <string>
#include <iostream>

string processInput() {
  char inputChar[256];
  string output = "";
  int outputLength = 0;
  bool space = false;
  // user inputs a string.. well a char array
  cin.getline(inputChar,256);
  output = inputChar;
       string outputToLower = "";
  // put characters to lower and reduce spaces
  for(int i = 0; i < output.length(); i++){
    // if it's caps put it to lowercase
    output[i] = tolower(output[i]);
    // make sure we do not include tabs or line returns or weird symbol for null entry array thingy
    if (output[i] != '\t' && output[i] != '\n' && output[i] != 'Ì') {
      if (space) {
        // if the previous space was a space but this one is not, then space now is false and add char
        if (output[i] != ' ') {
          space = false;
          // add the char
          outputToLower+=output[i];
        }
      } else {
        // if space is false, make it true if the char is a space
        if (output[i] == ' ') {
          space = true;
        }
        // add the char
        outputToLower+=output[i];
      }
    }
  }
  // trim leading and tailing space
  string trimmedOutput = "";
  for(int i = 0; i < outputToLower.length(); i++){
    // if it's the last character and it's not a space, then add it
    // if it's the first character and it's not a space, then add it
    // if it's not the first or the last then add it
    if (i == outputToLower.length() - 1 && outputToLower[i] != ' ' || 
      i == 0 && outputToLower[i] != ' ' || 
      i > 0 && i < outputToLower.length() - 1) {
      trimmedOutput += outputToLower[i];
    } 
  }
  // return
  output = trimmedOutput;
  return output;
}

int main() {
  cout << "Username: ";
  string userName = processInput();
  cout << "\nModified Input = " << userName << endl;
}

2

Po co komplikować?

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

Działa to nawet jeśli boost miał się nie powieść, bez wyrażenia regularnego, bez dziwnych rzeczy ani bibliotek.

EDYCJA: Naprawiono komentarz MM.


Jest to nieco nieefektywne w porównaniu z obliczaniem długości białych znaków i użyciem jednego wywołania kasowania dla każdego końca
MM

1

Wprowadzono C ++ 17 std::basic_string_view, szablon klasy, który odwołuje się do stałej, ciągłej sekwencji obiektów typu char, czyli widoku łańcucha. Oprócz bardzo podobnego interfejsu do std::basic_string, ma dwie dodatkowe funkcje remove_prefix():, która zmniejsza widok, przesuwając jego początek do przodu; i remove_suffix(), która zmniejsza widok, przesuwając jego koniec do tyłu. Można ich użyć do przycięcia spacji wiodącej i końcowej:

#include <string_view>
#include <string>

std::string_view ltrim(std::string_view str)
{
    const auto pos(str.find_first_not_of(" \t"));
    str.remove_prefix(pos);
    return str;
}

std::string_view rtrim(std::string_view str)
{
    const auto pos(str.find_last_not_of(" \t"));
    str.remove_suffix(str.length() - pos - 1);
    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
    auto s1{ sv1 };
    auto s2{ sv2 };
    auto s3{ sv3 };
}

Uwaga: nie std::string_viewjest to odniesienie, które nie jest właścicielem, więc jest ważne tylko tak długo, jak długo istnieje oryginalny ciąg.


0
    char *str = (char*) malloc(50 * sizeof(char));
    strcpy(str, "    some random string (<50 chars)  ");

    while(*str == ' ' || *str == '\t' || *str == '\n')
            str++;

    int len = strlen(str);

    while(len >= 0 && 
            (str[len - 1] == ' ' || str[len - 1] == '\t' || *str == '\n')
    {
            *(str + len - 1) = '\0';
            len--;
    }

    printf(":%s:\n", str);

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

0
string trim(const string & sStr)
{
    int nSize = sStr.size();
    int nSPos = 0, nEPos = 1, i;
    for(i = 0; i< nSize; ++i) {
        if( !isspace( sStr[i] ) ) {
            nSPos = i ;
            break;
        }
    }
    for(i = nSize -1 ; i >= 0 ; --i) {
        if( !isspace( sStr[i] ) ) {
            nEPos = i;
            break;
        }
    }
    return string(sStr, nSPos, nEPos - nSPos + 1);
}

0

W przypadku spacji wiodących i końcowych, co powiesz na:

string string_trim(const string& in) {

    stringstream ss;
    string out;
    ss << in;
    ss >> out;
    return out;

}

Albo o zdanie:

string trim_words(const string& sentence) {
    stringstream ss;
    ss << sentence;
    string s;
    string out;

    while(ss >> s) {

        out+=(s+' ');
    }
    return out.substr(0, out.length()-1);
}

0

schludnie i czysto

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

0

Nie boost, nie regex, tylko stringbiblioteka. To takie proste.

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

1
... i uniknąłeś wprowadzania 2 mln plików nagłówkowych do kompilacji!
Larry_C

0

Aby dodać do problemu, jak rozszerzyć to formatowanie, aby przetwarzać dodatkowe spacje między słowami ciągu.

W rzeczywistości jest to prostszy przypadek niż uwzględnienie wielu wiodących i końcowych znaków odstępu. Wszystko, co musisz zrobić, to usunąć zduplikowane sąsiednie znaki odstępu z całego ciągu.

Predykat dla sąsiednich białych znaków wyglądałby po prostu:

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

a następnie możesz pozbyć się tych zduplikowanych sąsiednich znaków białych za pomocą std::uniquei idiomu erase-remove:

// 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 "  

To potencjalnie pozostawia dodatkowy znak odstępu z przodu i / lub z tyłu. Można to dość łatwo usunąć:

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

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

Oto demo .


-1

Moje rozwiązanie tego problemu nie używa żadnych metod STL, ale tylko własne metody C ++ stringów jest następujące :

void processString(string &s) {
    if ( s.empty() ) return;

    //delete leading and trailing spaces of the input string
    int notSpaceStartPos = 0, notSpaceEndPos = s.length() - 1;
    while ( s[notSpaceStartPos] == ' ' ) ++notSpaceStartPos;
    while ( s[notSpaceEndPos] == ' ' ) --notSpaceEndPos;
    if ( notSpaceStartPos > notSpaceEndPos ) { s = ""; return; }
    s = s.substr(notSpaceStartPos, notSpaceEndPos - notSpaceStartPos + 1);

    //reduce multiple spaces between two words to a single space 
    string temp;
    for ( int i = 0; i < s.length(); i++ ) {
        if ( i > 0 && s[i] == ' ' && s[i-1] == ' ' ) continue;
        temp.push_back(s[i]);
    }
    s = temp;
}

Użyłem tej metody do przekazania problemu LeetCode Reverse Words in a String


-1
void TrimWhitespaces(std::wstring& str)
{
    if (str.empty())
        return;

    const std::wstring& whitespace = L" \t";
    std::wstring::size_type strBegin = str.find_first_not_of(whitespace);
    std::wstring::size_type strEnd = str.find_last_not_of(whitespace);

    if (strBegin != std::wstring::npos || strEnd != std::wstring::npos)
    {
        strBegin == std::wstring::npos ? 0 : strBegin;
        strEnd == std::wstring::npos ? str.size() : 0;

        const auto strRange = strEnd - strBegin + 1;
        str.substr(strBegin, strRange).swap(str);
    }
    else if (str[0] == ' ' || str[0] == '\t')   // handles non-empty spaces-only or tabs-only
    {
        str = L"";
    }
}

void TrimWhitespacesTest()
{
    std::wstring EmptyStr = L"";
    std::wstring SpacesOnlyStr = L"    ";
    std::wstring TabsOnlyStr = L"           ";
    std::wstring RightSpacesStr = L"12345     ";
    std::wstring LeftSpacesStr = L"     12345";
    std::wstring NoSpacesStr = L"12345";

    TrimWhitespaces(EmptyStr);
    TrimWhitespaces(SpacesOnlyStr);
    TrimWhitespaces(TabsOnlyStr);
    TrimWhitespaces(RightSpacesStr);
    TrimWhitespaces(LeftSpacesStr);
    TrimWhitespaces(NoSpacesStr);

    assert(EmptyStr == L"");
    assert(SpacesOnlyStr == L"");
    assert(TabsOnlyStr == L"");
    assert(RightSpacesStr == L"12345");
    assert(LeftSpacesStr == L"12345");
    assert(NoSpacesStr == L"12345");
}

-2

A co z idiomem kasuj-usuń ?

std::string s("...");
s.erase( std::remove(s.begin(), s.end(), ' '), s.end() );

Przepraszam. Za późno zauważyłem, że nie chcesz usuwać wszystkich białych znaków.


Cześć, teraz, gdy wiesz, że odpowiedź jest błędna, możesz ją usunąć, jeśli chcesz. W ten sposób odzyskasz utraconą reputację od DV na tej odpowiedzi :)
cigien
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.