c++ - русском - программа для нарезки видео с ютуба




Каков наилучший способ обрезать std:: string? (20)

C ++ 11:

int i{};
string s = " h e ll \t\n  o";
string trim = " \n\t";

while ((i = s.find_first_of(trim)) != -1)
    s.erase(i,1);

cout << s;

выход:

hello

отлично работает и с пустыми строками

В настоящее время я использую следующий код для выравнивания всех std::strings в моих программах:

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

Он отлично работает, но мне интересно, есть ли какие-то конечные случаи, когда он может потерпеть неудачу?

Разумеется, приветствуются ответы с изящными альтернативами, а также с левым отделением.


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

В случае пустой строки ваш код предполагает, что добавление 1 в string::npos дает 0. string::npos имеет тип string::size_type , который не имеет знака. Таким образом, вы полагаетесь на поведение переполнения добавления.


Внесение моего решения в шум. trim умолчанию создает новую строку и возвращает измененную, а trim_in_place изменяет переданную ей строку. Функция trim поддерживает семантику перемещения 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");  
}

Вот что я придумал:

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

Извлечение потока автоматически удаляет пробелы, поэтому это работает как шарм.
Довольно чистый и элегантный тоже, если я так говорю сам. ;)


Вышеупомянутые методы являются большими, но иногда вы хотите использовать комбинацию функций для того, что ваша рутина считает пробелом. В этом случае использование функторов для объединения операций может стать беспорядочным, поэтому я предпочитаю простой цикл, который я могу изменить для обрезки. Вот слегка модифицированная функция trim, скопированная из версии C здесь, на SO. В этом примере я обрезаю не буквенно-цифровые символы.

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

Еще один вариант - удаление одного или нескольких символов с обоих концов.

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

Использование алгоритмов строки Boost было бы проще всего:

#include <boost/algorithm/string.hpp>

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

str теперь "hello world!" , Там также trim_left и trim , которая обрезает обе стороны.

Если вы добавите суффикс _copy к любому из названных имен функций, например trim_copy , функция вернет обрезанную копию строки вместо изменения ее посредством ссылки.

Если вы добавите суффикс _if к любому из названных имен функций, например trim_copy_if , вы можете обрезать все символы, удовлетворяющие вашему пользовательскому предикату, в отличие от просто пробелов.


Как насчет этого...?

#include <iostream>
#include <string>
#include <regex>

std::string ltrim( std::string str ) {
    return std::regex_replace( str, std::regex("^\\s+"), std::string("") );
}

std::string rtrim( std::string str ) {
    return std::regex_replace( str, std::regex("\\s+$"), std::string("") );
}

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

int main() {

    std::string str = "   \t  this is a test string  \n   ";
    std::cout << "-" << trim( str ) << "-\n";
    return 0;

}

Примечание. Я все еще относительно новичок в C ++, поэтому, пожалуйста, простите меня, если я здесь не нахожусь.


Мне нравится решение tzaman, единственная проблема заключается в том, что он не обрезает строку, содержащую только пробелы.

Чтобы исправить этот недостаток, добавьте str.clear () между двумя линиями триммера

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

Мой ответ является улучшением верхнего ответа на этот пост, который обрезает управляющие символы, а также пробелы (0-32 и 127 в таблице ASCII ).

std::isgraph определяет, имеет ли символ графическое представление, поэтому вы можете использовать это, чтобы изменить ответ Эвана, чтобы удалить любой символ, который не имеет графического представления с любой стороны строки. В результате получается гораздо более элегантное решение:

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

Примечание. Также вы можете использовать std::iswgraph если вам нужна поддержка широких символов, но вам также придется отредактировать этот код, чтобы включить манипуляцию std::wstring , что я не тестировал (см. эталонная страница для std::basic_string чтобы изучить эту опцию).


Попытайтесь, это работает для меня.

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

С C ++ 11 также появился модуль регулярных выражений , который, конечно, можно использовать для обрезки ведущих или конечных пробелов.

Может быть, что-то вроде этого:

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

То, что вы делаете, прекрасно и надежно. Я использовал тот же метод в течение длительного времени, и мне еще предстоит найти более быстрый метод:

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 (left & right)
inline std::string& trim(std::string& s, const char* t = ws)
{
    return ltrim(rtrim(s, t), t);
}

Поставляя символы, которые нужно обрезать, вы можете гибко обрезать символы без пробелов и эффективность, чтобы обрезать только те персонажи, которые вы хотите обрезать.


Эта версия обрезает внутренние пробелы и не-буквенно-цифровые символы:

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

Это проще сделать в C ++ 11 из-за добавления back() и pop_back() .

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

Я думаю, если вы начнете просить «лучший способ» обрезать строку, я бы сказал, что хорошая реализация будет такой:

  1. Не выделяет временные строки
  2. Имеет перегрузки для внутренней отделки и копирования
  3. Может быть легко настроена для принятия различных последовательностей проверки / логики

Очевидно, что существует слишком много разных способов приблизиться к этому, и это определенно зависит от того, что вам действительно нужно. Однако в стандартной библиотеке C все еще есть некоторые очень полезные функции в <string.h>, например memchr. Есть причина, по которой C по-прежнему считается лучшим языком для IO - его stdlib - это чистая эффективность.

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

Я не уверен, что ваша среда такая же, но в моем случае пустая строка приведет к прерыванию программы. Я бы либо обернул этот стирающий вызов с if (! S.empty ()), либо использовал Boost, как уже упоминалось.


Cplusplus.com с Cplusplus.com

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

    return str;
}

Это работает и для нулевого случая. :-)


EDIT Начиная с c ++ 17, некоторые части стандартной библиотеки были удалены. К счастью, начиная с c ++ 11, у нас есть lambdas, которые являются превосходным решением.

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

Благодаря https://.com/a/44973498/524503 для создания современного решения.

Оригинальный ответ:

Я, как правило, использую один из этих 3 для моих нужд обрезки:

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

Они достаточно понятны и работают очень хорошо.

EDIT : BTW, у меня есть std::ptr_fun чтобы помочь disambiguate std::isspace потому что на самом деле есть второе определение, которое поддерживает локали. Это может быть приведение точно так же, но мне нравится это лучше.

EDIT : для рассмотрения некоторых комментариев о принятии параметра по ссылке, изменении и возврате его. Согласен. Реализация, которую я, скорее всего, предпочла бы, - это два набора функций: один на месте и один, который делает копию. Лучший пример:

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

Я придерживаюсь первоначального ответа выше, хотя для контекста и в интересах сохранения высокого голосованного ответа по-прежнему доступны.





stdstring