Видалення провідних та кінцевих пробілів із рядка


91

Як видалити пробіли з рядкового об’єкта в C ++.
Наприклад, як видалити пробіли, що ведуть і завершують, із наведеного нижче рядкового об’єкта.

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

Клас рядків, наскільки мені відомо, не передбачає жодних методів для видалення пробілів, що ведуть і завершують.

Щоб додати проблему, як розширити це форматування для обробки зайвих пробілів між словами рядка. Наприклад,

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

Використовуючи рядкові методи, згадані у рішенні, я можу подумати про те, щоб зробити ці операції у два етапи.

  1. Видаліть провідні та кінцеві пробіли.
  2. Використовуйте find_first_of, find_last_of, find_first_not_of, find_last_not_of і substr , кілька разів на межі слів, щоб отримати бажане форматування.

Відповіді:


127

Це називається обрізанням. Якщо ви можете використовувати Boost , я б рекомендував його.

В іншому випадку використовуйте, find_first_not_ofщоб отримати індекс першого непробільного символу, а потім find_last_not_ofотримати індекс з кінця, який не є пробілами. З ними використовуйте, substrщоб отримати підрядок без пробілів навколо.

У відповідь на вашу редакцію я не знаю термін, але я б здогадався про щось на зразок "зменшити", тож саме так я його і назвав. :) (Примітка, я змінив пробіли як параметр для гнучкості)

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

Результат:

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

Я припускаю, ви мали на увазі "size_t". і у вас є поодинці для підрядка, має бути substr (beginStr, endStr - beginStr + 1);
goldPseudo

Має site_tбути size_t? І я думаю, що якщо у вас є коментар, це no whitespaceозначає, що рядок є пробілом або порожнім.
Фред Ларсон,

Дякую, виправили size_tпомилку та одиничну помилку в редагуванні, але не помітили, що мій коментар був інвертований, дякую.
GManNickG

@GMan дуже елегантне рішення. Дякую.
Анкур

Помилка: спробуйте запустити "один \ dtwo" через trim (). Результат - це порожній рядок. Вам також потрібно протестувати endStr щодо std :: string :: npos.
dlchambers

48

Легке видалення провідних, кінцевих та зайвих пробілів зі рядка std :: в один рядок

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

видалення лише пробілів

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

або

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

видалення лише кінцевих пробілів

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

або

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

видалення лише зайвих пробілів

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

3
Хороший. Було б корисно надати деяку інформацію про те, що тут відбувається, оскільки важко зрозуміти ці коди.
Марсін

Працює лише в C ++ 11.
Martin Pecka

7
Він не видаляє вкладки, але це можна виправити. Що неможливо виправити, це те, що це надзвичайно повільно (~ 100 разів повільніше, ніж відповіді з substrабо erase).
4LegsDrivenCat

для оптимізації швидкості регулярний вираз не є оптимальним рішенням, але його можна покращити, створивши екземпляр регулярного виразу один раз
Євген Карпов

40

Зараз я використовую такі функції:

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

Це моє рішення для зачищення провідних та кінцевих просторів ...

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

Результат - "Пламен"


8

Ось як це можна зробити:

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

А допоміжні функції реалізуються як:

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

І коли ви все це встановите на місці, ви можете написати і це:

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

Спробуйте це


7

Приклад обрізання пробілів, що ведуть, і кінцевих, після пропозиції Джон-Хансона використовувати підсилення (видаляє лише кінцеві та очікувані пробіли):

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

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

boost::algorithm::trim ( str );

Призводить до "t e s t"

Є також

  • trim_left призводить до "t e s t "
  • trim_right призводить до " 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);
}

У наведеному вище коді функція isSpace () є логічною функцією, яка повідомляє, чи є символ пробілом, ви можете реалізувати цю функцію, щоб відобразити ваші потреби, або просто викликати isspace () з "ctype.h", якщо хочете .


4

Приклад для обрізання пробілів, що ведуть та задні

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

АБО

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

3
Людям не сподобається розглядати 10 сторінок коду, щоб навчитися обрізати рядок.
Thinkal VB

2
він порушений, якщо рядок має лише пробіли
DAG

3

Використання стандартної бібліотеки має багато переваг, але слід пам’ятати про деякі особливі випадки, що спричиняють винятки. Наприклад, жодна з відповідей не охоплювала випадок, коли рядок C ++ має деякі символи Unicode. У цьому випадку, якщо ви використовуєте функцію isspace , буде видано виняток.

Я використовую наступний код для обрізки рядків та деяких інших операцій, які можуть стати в нагоді. Основними перевагами цього коду є: він справді швидкий (швидший за будь-який код, який я коли-небудь тестував), він використовує лише стандартну бібліотеку і ніколи не викликає винятків:

#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

Це може бути найпростішим з усіх.

Ви можете використовувати string::findі, string::rfindщоб знайти пробіл з обох сторін і зменшити рядок.

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

Я перевірив це, все працює. Отже, цей метод processInput просто попросить користувача ввести щось. Він поверне рядок, який не має зайвих пробілів усередині, а також зайвих пробілів на початку або в кінці. Сподіваюся, це допомагає. (також додайте купу коментарів, щоб зробити це зрозумілим).

Ви можете побачити, як це реалізувати, в main () внизу

#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

Навіщо ускладнювати?

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

Це працює, навіть якщо підсилення повинно було провалитися, відсутність регулярного виразу, ніяких дивних матеріалів та бібліотек.

EDIT: Виправлено коментар М.М.


Це дещо неефективно в порівнянні з обчисленням довжини пробілів та використанням одного виклику стирання для кожного кінця
ММ

1

Введено C ++ 17 std::basic_string_view, шаблон класу, який посилається на постійну суміжну послідовність символів, подібних до символів, тобто вигляд рядка. Окрім того std::basic_string, що він має дуже подібний інтерфейс , він має дві додаткові функції:, remove_prefix()яка стискає вигляд, рухаючи його пуск вперед; і remove_suffix(), який стискає вигляд, рухаючи його кінець назад. Вони можуть бути використані для обрізки провідної та кінцевої площі:

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

Примітка: the std::string_viewє невласницьким посиланням, тому воно дійсне лише до тих пір, поки оригінальний рядок все ще існує.


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

Що стосується пробілів, що ведуть та закінчуються, як щодо:

string string_trim(const string& in) {

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

}

Або для речення:

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

акуратно і чисто

 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

Ні boost, ні regex, просто stringбібліотека. Це так просто.

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
... і ви уникали залучення 2M файлів заголовків у свою збірку!
Larry_C

0

Щоб додати проблему, як розширити це форматування для обробки зайвих пробілів між словами рядка.

Насправді це простіший випадок, ніж облік кількох провідних та кінцевих пробілів. Все, що вам потрібно зробити, це видалити повторювані сусідні пробіли з усього рядка.

Присудком для сусіднього пробілу буде просто:

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

а потім ви можете позбутися цих повторюваних сусідніх символів пробілу за допомогою std::uniqueідіоми видалення-видалення:

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

Це потенційно може залишити додатковий пробіл спереду та / або ззаду. Це можна видалити досить просто:

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

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

Ось демонстрація .


-1

Моє рішення цієї проблеми, що не використовує жодних методів STL, а лише власні методи рядка C ++, є наступним:

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

Я використав цей метод, щоб передати в рядку проблему LeetCode Reverse Words


-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

А як щодо ідіоми видалення-видалення ?

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

Вибачте. Я пізно побачив, що ви не хочете видаляти всі пробіли.


Привіт, тепер, коли ви усвідомлюєте, що відповідь неправильна, можете видалити її, якщо хочете. Таким чином, ви отримаєте репутацію, яку ви втратили від ДВ за цією відповіддю :)
cigien
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.