¿Cuál es la mejor manera de recortar std :: string?

812

Actualmente estoy usando el siguiente código para recortar a la derecha todos los std::stringsprogramas:

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

Funciona bien, pero me pregunto si hay algunos casos finales donde podría fallar.

Por supuesto, las respuestas con alternativas elegantes y también la solución de ajuste izquierdo son bienvenidas.

Milan Babuškov
fuente
549
Las respuestas a esta pregunta son un testimonio de la falta de la biblioteca estándar de C ++.
Idan K
83
@IdanK Y todavía no tiene esta función en C ++ 11.
Quantum
44
@IdanK: Genial, ¿no? ¡Mire todas las opciones competitivas que ahora tenemos a nuestra disposición, sin la idea de una sola persona de " la forma en que debemos hacerlo"!
Carreras de ligereza en órbita
59
La funcionalidad @LightnessRacesinOrbit dentro de un tipo, bueno, esa es una decisión de diseño, y agregar una función de recorte a una cadena podría (al menos bajo c ++) no ser la mejor solución de todos modos, pero no proporciona ninguna forma estándar de hacerlo, sino dejar que todos se preocupen los mismos problemas tan pequeños una y otra vez, ciertamente tampoco ayudan a nadie
codeling
27
Puede preguntarse por qué las funciones de recorte no están integradas en la std::stringclase, cuando son funciones como estas las que hacen que otros lenguajes sean tan agradables de usar (Python, por ejemplo).
HelloGoodbye

Respuestas:

648

EDITAR Desde c ++ 17, se eliminaron algunas partes de la biblioteca estándar. Afortunadamente, comenzando con c ++ 11, tenemos lambdas que son una solución superior.

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

Gracias a https://stackoverflow.com/a/44973498/524503 por presentar la solución moderna.

Respuesta original:

Tiendo a usar uno de estos 3 para mis necesidades de recorte:

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

Se explican por sí mismas y funcionan muy bien.

EDITAR : Por cierto, tengo std::ptr_funque ayudar a desambiguar std::isspaceporque en realidad hay una segunda definición que admite configuraciones regionales. Podría haber sido un elenco igual, pero me gusta más.

EDITAR : para abordar algunos comentarios acerca de aceptar un parámetro por referencia, modificarlo y devolverlo. Estoy de acuerdo. Una implementación que probablemente preferiría sería dos conjuntos de funciones, una para el lugar y otra que hace una copia. Un mejor conjunto de ejemplos sería:

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

Sin embargo, mantengo la respuesta original anterior para el contexto y en interés de mantener la respuesta votada aún disponible.

Evan Teran
fuente
28
Este código fallaba en algunas cadenas internacionales (shift-jis en mi caso, almacenadas en un std :: string); Terminé usando boost::trimpara resolver el problema.
Tom
55
Usaría punteros en lugar de referencias, para que desde el punto de llamada sea mucho más fácil entender que estas funciones editan la cadena en su lugar, en lugar de crear una copia.
Marco Leogrande
3
Tenga en cuenta que con isspace puede obtener fácilmente un comportamiento indefinido con caracteres no ASCII stacked-crooked.com/view?id=49bf8b0759f0dd36dffdad47663ac69f
R. Martinho Fernandes
10
¿Por qué la estática? ¿Es aquí donde se preferiría un espacio de nombres anónimo?
Trevor Hickey
3
@TrevorHickey, seguro, puedes usar un espacio de nombres anónimo si lo prefieres.
Evan Teran
417

Usar los algoritmos de cadena de Boost sería más fácil:

#include <boost/algorithm/string.hpp>

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

stres ahora "hello world!". También hay trim_lefty trim, que recorta ambos lados.


Si agrega _copysufijo a cualquiera de los nombres de funciones anteriores trim_copy, por ejemplo , la función devolverá una copia recortada de la cadena en lugar de modificarla a través de una referencia.

Si agrega _ifsufijo a cualquiera de los nombres de funciones anteriores trim_copy_if, por ejemplo , puede recortar todos los caracteres que satisfagan su predicado personalizado, en lugar de solo espacios en blanco.

Leon Timmermans
fuente
77
Depende de la localidad. Mi configuración regional predeterminada (VS2005, en) significa que se recortan las pestañas, los espacios, los retornos de carro, las nuevas líneas, las pestañas verticales y los feeds de formularios.
MattyT
117
Boost es un martillo tan grande para un problema tan pequeño.
Casey Rodarmor
143
@rodarmor: Boost resuelve muchos pequeños problemas. Es un martillo masivo que resuelve mucho.
Nicol Bolas
123
Boost es un conjunto de martillos de muchos tamaños diferentes que resuelven muchos problemas diferentes.
Ibrahim
11
@rodarmor Dices eso como si Boost fuera un monolito de todo o nada, donde incluir uno de sus encabezados de alguna manera inflige todo en el programa. Lo cual claramente no es el caso. Por cierto, nunca he usado Boost, fwiw.
underscore_d
61

Use el siguiente código para recortar a la derecha los espacios (finales) y los caracteres de tabulación de 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));
}

Y solo para equilibrar las cosas, también incluiré el código de ajuste izquierdo ( ideone ):

// trim leading spaces
size_t startpos = str.find_first_not_of(" \t");
if( string::npos != startpos )
{
    str = str.substr( startpos );
}
Bill el lagarto
fuente
44
Esto no detectará otras formas de espacios en blanco ... nueva línea, avance de línea, retorno de carro en particular.
Tom
1
Derecha. Debe personalizarlo para el espacio en blanco que desea recortar. Mi aplicación particular solo esperaba espacios y pestañas, pero puede agregar \ n \ r para atrapar a los demás.
Bill the Lizard
55
str.substr(...).swap(str)es mejor. Guarda una tarea.
updogliu
44
@updogliu ¿No usará la asignación de movimiento basic_string& operator= (basic_string&& str) noexcept;?
nurettin
8
Esta respuesta no altera las cadenas que son TODOS los espacios. Lo cual es un fracaso.
Tom Andersen
56

Lo que estás haciendo es bueno y robusto. He usado el mismo método durante mucho tiempo y todavía tengo que encontrar un método más rápido:

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

Al proporcionar los caracteres que se van a recortar, tiene la flexibilidad para recortar caracteres que no sean espacios en blanco y la eficiencia para recortar solo los caracteres que desea recortar.

Galik
fuente
si cambia el orden trim, es decir, haga que rtrim(ltrim(s, t), t)sea ​​un poco más eficiente
CITBL
1
@CITBL La función interna se realiza primero, por lo que se recortará desde la izquierda antes de recortar desde la derecha. Creo que sería menos eficiente, ¿no?
Galik
Exactamente. Mi error
CITBL
si usa basic_string y template en el CharT, puede hacer esto para todas las cadenas, solo use una variable de plantilla para el espacio en blanco para que lo use como ws <CharT>. técnicamente en ese punto, podría prepararlo para c ++ 20 y marcarlo como constexpr también, ya que esto implica en línea
Beached
@Beached hecho. Sin embargo, es un poco complicado poner una respuesta aquí. He escrito funciones de plantilla para esto y ciertamente es bastante complicado. He intentado un montón de enfoques diferentes y todavía no estoy seguro de cuál es el mejor.
Galik
55

Un poco tarde para la fiesta, pero no importa. Ahora C ++ 11 está aquí, tenemos lambdas y variables automáticas. Entonces, mi versión, que también maneja espacios en blanco y cadenas vacías, es:

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

Podríamos hacer un iterador inverso wsfronty usarlo como la condición de terminación en el segundo, find_if_notpero eso solo es útil en el caso de una cadena de espacios en blanco, y gcc 4.8 al menos no es lo suficientemente inteligente como para inferir el tipo del iterador inverso ( std::string::const_reverse_iterator) con auto. No sé lo costoso que es construir un iterador inverso, así que YMMV aquí. Con esta alteración, el código se ve así:

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
fuente
99
Agradable. +1 de mi parte Lástima que C ++ 11 no introdujo trim () en std :: string y facilitó la vida de todos.
Milan Babuškov
3
Siempre quiero una llamada de función para recortar la cadena, en lugar de implementarla
linquize
22
Por lo que vale, no hay necesidad de usar esa lambda. Puede pasar std::isspace:auto wsfront=std::find_if_not(s.begin(),s.end(),std::isspace);
vmrob
44
+1 para probablemente la única respuesta con la implementación que solo hace una copia de cadena O (N).
Alexei Averchenko
44
Los compiladores de @vmrob no son necesariamente tan inteligentes. hacer lo que dices es ambigua:candidate template ignored: couldn't infer template argument '_Predicate' find_if_not(_InputIterator __first, _InputIterator __last, _Predicate __pred)
johnbakers
42

Prueba esto, funciona para mí.

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;
}
usuario818330
fuente
12
Si su cadena no contiene espacios de sufijo, esto se borrará comenzando en npos + 1 == 0, y eliminará toda la cadena.
mhsmith
3
@rgove Por favor explique. str.find_last_not_of(x)Devuelve la posición del primer carácter que no es igual a x. Solo devuelve npos si los caracteres no coinciden con x. En el ejemplo, si no hay espacios de sufijos, devolverá el equivalente de str.length() - 1, produciendo esencialmente str.erase((str.length() - 1) + 1).Eso, a menos que esté terriblemente equivocado.
Travis
55
Esto debería devolver std :: string & para evitar invocar innecesariamente el constructor de la copia.
heksesang
77
Estoy confundido por qué esto devuelve una copia después de modificar el parámetro de retorno?
Galik
3
@MiloDC Mi confusión es por qué devolver una copia en lugar de una referencia. Tiene más sentido para mí regresar std::string&.
Galik
25

Me gusta la solución de tzaman, el único problema es que no recorta una cadena que contiene solo espacios.

Para corregir ese 1 defecto, agregue un str.clear () entre las 2 líneas de recorte

std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Michaël Schoonbrood
fuente
Agradable :) el problema con nuestras dos soluciones, sin embargo, es que recortarán ambos extremos; No puedo hacer un ltrimo rtrimcomo este.
tzaman
44
Bien, pero no puede manejar cadenas con espacios en blanco internos. p.ej. trim (abc def ") -> abc, solo queda abc.
liheyuan
¡Una buena solución si sabe que no habrá espacios en blanco internos!
Elliot Gorokhovsky
Esto es agradable y fácil, pero también es bastante lento ya que la cadena se copia dentro y fuera del 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
fuente
1
Solución elegante para recortar espacio básico por fin ... :)
jave.web
Cómo funciona: esta es una solución similar a una copia: encuentra la posición del primer carácter que no es espacio ( it) y viceversa: la posición del carácter después de la cual solo hay espacios ( rit); después de eso, devuelve una cadena recién creada == una copia de la parte de la cadena original - una parte basada en esos iteradores ...
jave.web
Gracias, trabajó para mí: std: string s = "Oh noez: space \ r \ n"; std :: string clean = trim (s);
Alexx Roche
15

En el caso de una cadena vacía, su código asume que agregar 1 para string::nposdar 0. string::nposes de tipo string::size_type, que no está firmado. Por lo tanto, confía en el comportamiento de desbordamiento de la suma.

Greg Hewgill
fuente
23
Lo estás expresando como si fuera malo. El comportamiento de desbordamiento de entero firmado es malo.
MSalters
2
Agregar 1a std::string::npos debe dar de 0acuerdo con el C++ Standard. Por lo tanto, es una buena suposición en la que se puede confiar absolutamente.
Galik
13

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

Esto también funciona para el caso nulo. :-)

Paul Nathan
fuente
44
Esto es justo rtrim, noltrim
ub3rst4r
1
^ ¿te importa usar find_first_not_of? Es relativamente fácil modificarlo.
Abhinav Gauniyal
13

Con C ++ 17 puede usar basic_string_view :: remove_prefix y 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;
}

Una buena alternativa:

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
fuente
No estoy seguro de lo que está probando, pero en su ejemplo std :: find_first_not_of devolverá std :: string :: npos y std :: string_view :: size devolverá 4. El mínimo es obviamente cuatro, el número de elementos a ser eliminado por std :: string_view :: remove_prefix . Tanto gcc 9.2 como clang 9.0 manejan esto correctamente: godbolt.org/z/DcZbFH
Phidelux
1
¡Gracias! Me parece bien.
Contango
11

Mi solución basada en la respuesta de @Bill the Lizard .

Tenga en cuenta que estas funciones devolverán la cadena vacía si la cadena de entrada contiene nada más que espacios en blanco.

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
fuente
9

Mi respuesta es una mejora con respecto a la respuesta principal para esta publicación que recorta los caracteres de control y los espacios (0-32 y 127 en la tabla ASCII ).

std::isgraphdetermina si un personaje tiene una representación gráfica, por lo que puede usar esto para alterar la respuesta de Evan para eliminar cualquier personaje que no tenga una representación gráfica a cada lado de una cadena. El resultado es una solución mucho más elegante:

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

Nota: Alternativamente, debería poder usarlo std::iswgraphsi necesita soporte para caracteres anchos, pero también tendrá que editar este código para habilitar la std::wstringmanipulación, que es algo que no he probado (consulte la página de referencia para std::basic_stringexplorar esta opción) .

Clay Freeman
fuente
3
std :: ptr_fun está en desuso
johnbakers
8

Con C ++ 11 también vino un módulo de expresión regular , que por supuesto se puede usar para recortar espacios iniciales o finales.

Tal vez algo como esto:

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));
}
Algún tipo programador
fuente
8

Esto es lo que yo uso. Simplemente siga eliminando espacio desde el frente y luego, si queda algo, haga lo mismo desde atrás.

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
fuente
8
s.erase(0, s.find_first_not_of(" \n\r\t"));                                                                                               
s.erase(s.find_last_not_of(" \n\r\t")+1);   
freeboy1015
fuente
2
Sería un poco más eficiente si los realiza en el orden opuesto y recorta desde la derecha primero antes de invocar un cambio recortando la izquierda.
Galik
7

Para lo que vale, aquí hay una implementación optimizada con miras al rendimiento. Es mucho más rápido que muchas otras rutinas de recorte que he visto. En lugar de usar iteradores y std :: find, usa cadenas e índices en bruto. Optimiza los siguientes casos especiales: cadena de tamaño 0 (no hacer nada), cadena sin espacios en blanco para recortar (no hacer nada), cadena con solo espacios en blanco al final para recortar (solo cambiar el tamaño de la cadena), cadena que es completamente un espacio en blanco (solo borrar la cadena) . Y finalmente, en el peor de los casos (cadena con espacios en blanco iniciales), hace todo lo posible para realizar una construcción de copia eficiente, realizando solo 1 copia y luego moviendo esa copia en lugar de la cadena original.

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
fuente
@bmgda quizás teóricamente la versión más rápida podría tener esta firma: extern "C" void string_trim (char ** begin_, char ** end_) ... ¿Me entiendes?
6

Una forma elegante de hacerlo puede ser como

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

Y las funciones de apoyo se implementan como:

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

Y una vez que tenga todo esto en su lugar, puede escribir esto también:

std::string trim_copy(std::string const & str)
{
   auto s = str;
   return ltrim(rtrim(s));
}
jha-G
fuente
6

Recorte la implementación de 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
fuente
5

Supongo que si comienzas a pedir la "mejor manera" de recortar una cadena, diría que una buena implementación sería aquella que:

  1. No asigna cadenas temporales
  2. Tiene sobrecargas para recortes en el lugar y recortes de copia
  3. Se puede personalizar fácilmente para aceptar diferentes secuencias de validación / lógica

Obviamente, hay muchas maneras diferentes de abordar esto y definitivamente depende de lo que realmente necesita. Sin embargo, la biblioteca estándar de C todavía tiene algunas funciones muy útiles en <string.h>, como memchr. Hay una razón por la cual C sigue siendo considerado como el mejor lenguaje para IO: su stdlib es pura eficiencia.

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
fuente
3

No estoy seguro de si su entorno es el mismo, pero en el mío, el caso de cadena vacía hará que el programa se cancele. Envolvería esa llamada de borrado con un if (! S.empty ()) o usaría Boost como ya se mencionó.

Steve
fuente
3

Esto es lo que se me ocurrió:

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

La extracción de flujo elimina los espacios en blanco automáticamente, por lo que esto funciona de maravilla.
Bastante limpio y elegante también, si lo digo yo mismo. ;)

tzaman
fuente
15
Hmm; esto supone que la cadena no tiene espacios en blanco internos (por ejemplo, espacios). El OP solo dijo que quería recortar espacios en blanco a la izquierda o derecha.
SuperElectric el
3

Aportando mi solución al ruido. trimpor defecto crea una nueva cadena y devuelve la modificada mientras trim_in_placemodifica la cadena que se le pasa. La trimfunción admite semántica de movimiento de 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
fuente
3

Esto se puede hacer de manera más simple en C ++ 11 debido a la adición de back()y pop_back().

while ( !s.empty() && isspace(s.back()) ) s.pop_back();
sin bar
fuente
El enfoque sugerido por el OP tampoco es malo, solo un poco más difícil de seguir.
nobar
3

Aquí está mi versión:

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);
nulleight
fuente
Te estás perdiendo el último personaje. Un +1 en la longitud resuelve esto
galinette
2

Los métodos anteriores son geniales, pero a veces desea utilizar una combinación de funciones para lo que su rutina considera espacios en blanco. En este caso, el uso de functores para combinar operaciones puede ser complicado, así que prefiero un bucle simple que pueda modificar para el recorte. Aquí hay una función de recorte ligeramente modificada copiada de la versión C aquí en SO. En este ejemplo, estoy recortando caracteres no alfanuméricos.

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
fuente
2

Aquí hay una implementación sencilla. Para una operación tan simple, probablemente no deberías estar usando construcciones especiales. La función isspace () incorporada se encarga de varias formas de caracteres blancos, por lo que debemos aprovecharla. También debe considerar casos especiales donde la cadena está vacía o simplemente un montón de espacios. El recorte hacia la izquierda o hacia la derecha podría derivarse del siguiente código.

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
fuente
2

Aquí hay una solución fácil de entender para principiantes que no están acostumbrados a escribir en std::todas partes y que aún no están familiarizados con la constcorrección, iterators, STL algorithms, etc.

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

Espero eso ayude...

cute_ptr
fuente
1

Esta versión recorta espacios en blanco internos y no alfanuméricos:

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
fuente
1

Otra opción más: elimina uno o más caracteres de ambos extremos.

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.
fuente