c++ - how - ¿Cuál es la mejor manera de recortar std:: string?
trim c++ example (30)
¿Qué hay de esto ...?
#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;
}
Nota: todavía soy relativamente nuevo en C ++, así que perdóneme si estoy fuera de la base aquí.
Actualmente estoy usando el siguiente código para recortar a la derecha todas las std::strings
en mis programas:
std::string s;
s.erase(s.find_last_not_of(" /n/r/t")+1);
Funciona bien, pero me pregunto si hay algunos casos finales en los que podría fallar.
Por supuesto, las respuestas con alternativas elegantes y también la solución de corte a la izquierda son bienvenidas.
Aportando mi solución al ruido. trim
valores predeterminados de trim
para crear una nueva cadena y devolver la modificada mientras que trim_in_place
modifica la cadena que se le pasa. La función de trim
admite c ++ 11 mover semántica.
#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");
}
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);
Como quería actualizar mi antigua función de ajuste de C ++ con un enfoque de C ++ 11, he probado muchas de las respuestas publicadas a la pregunta. ¡Mi conclusión es que mantengo mi vieja solución de C ++!
Es el más rápido por el grande, incluso agregar más caracteres para verificar (por ejemplo, / r / n No veo ningún caso de uso para / f / v) es aún más rápido que las soluciones que utilizan el algoritmo.
std::string & trimMe (std::string & str)
{
// right trim
while (str.length () > 0 && (str [str.length ()-1] == '' '' || str [str.length ()-1] == ''/t''))
str.erase (str.length ()-1, 1);
// left trim
while (str.length () > 0 && (str [0] == '' '' || str [0] == ''/t''))
str.erase (0, 1);
return str;
}
Con C ++ 11 también vino un módulo de expresión regular , que por supuesto puede usarse 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));
}
En el caso de una cadena vacía, su código asume que agregar 1 a string::npos
da 0. string::npos
es de tipo string::size_type
, que no está firmado. Por lo tanto, usted está confiando en el comportamiento de desbordamiento de la adición.
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;
}
Esto es lo que se me ocurrió:
std::stringstream trimmer;
trimmer << str;
trimmer >> str;
La extracción de la secuencia elimina el espacio en blanco automáticamente, por lo que funciona como un encanto.
Bastante limpio y elegante también, si lo digo yo mismo. ;)
Esto es lo que yo uso. Solo sigue quitando espacio desde el frente, y luego, si queda algo, haz 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
}
Esto se puede hacer más simplemente en C ++ 11 debido a la adición de back()
y pop_back()
.
while ( !s.empty() && isspace(s.back()) ) s.pop_back();
Hackeado de 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;
}
Esto funciona para el caso nulo también. :-)
Implementar recorte 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());
}
Intenta 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;
}
Lo que estás haciendo es fino 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 (left & right)
inline std::string& trim(std::string& s, const char* t = ws)
{
return ltrim(rtrim(s, t), t);
}
Al proporcionar los caracteres a recortar, tiene la flexibilidad de recortar caracteres que no sean espacios en blanco y la eficiencia de recortar solo los caracteres que desea recortar.
Los métodos anteriores son excelentes, pero a veces desea utilizar una combinación de funciones para lo que su rutina considera que son espacios en blanco. En este caso, el uso de functores para combinar operaciones puede causar problemas, por lo que prefiero un simple bucle que puedo modificar para el ajuste. 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);
}
Me gusta la solución de Tzaman, el único problema es que no recorta una cadena que contiene solo espacios.
Para corregir esa falla, agregue un str.clear () entre las 2 líneas de recorte
std::stringstream trimmer;
trimmer << str;
str.clear();
trimmer >> str;
Mi respuesta es una mejora en la respuesta principal para esta publicación que recorta los caracteres de control así como los espacios (0-32 y 127 en la tabla ASCII ).
std::isgraph
determina si un personaje tiene una representación gráfica, por lo que puede usar esto para alterar la respuesta de Evan para eliminar cualquier carácter que no tenga una representación gráfica de cualquiera de los lados 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: como alternativa, debería poder usar std::iswgraph
si necesita compatibilidad con caracteres anchos, pero también tendrá que editar este código para habilitar la manipulación std::wstring
, que es algo que no he probado (consulte la página de referencia para std::basic_string
para explorar esta opción).
Mi solución basada en la respuesta por @Bill the Lizard .
Tenga en cuenta que estas funciones devolverán la cadena vacía si la cadena de entrada no contiene 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);
}
No estoy seguro de si su entorno es el mismo, pero en mi caso, el caso de cadena vacía hará que el programa se cancele. Yo envolvería esa llamada de borrado con un if (! S.empty ()) o usaría Boost como ya se mencionó.
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);
}
Para lo que vale, aquí está una implementación de recorte con un ojo hacia el rendimiento. Es mucho más rápido que muchas otras rutinas de ajuste que he visto. En lugar de usar iteradores y hallazgos estándar, 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 finales para recortar (solo redimensionar la cadena), cadena que es completamente de espacios en blanco (solo borre 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));
}
}
Poco tarde a la fiesta, pero no importa. Ahora C ++ 11 está aquí, tenemos lambdas y variables automáticas. Así que mi versión, que también maneja espacios en blanco y en blanco, 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 crear un iterador inverso desde wsfront
y usarlo como condición de terminación en el segundo find_if_not
pero eso solo es útil en el caso de una cadena de todo el espacio en blanco, y gcc 4.8 al menos no es lo suficientemente inteligente como para inferir el tipo de inverso iterador ( std::string::const_reverse_iterator
) con auto
. No sé qué tan costoso 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());
}
Supongo que si empiezas a pedir la "mejor manera" de recortar una cadena, diría que una buena implementación sería una que:
- No asigna cadenas temporales
- Tiene sobrecargas para recorte en el lugar y recorte de copia
- Se puede personalizar fácilmente para aceptar diferentes secuencias de validación / lógica.
Obviamente, hay muchas formas diferentes de abordar esto y, definitivamente, depende de lo que realmente necesite. 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 que C sigue siendo considerado como el mejor lenguaje para IO: su estándar 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;
}
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 tengas todo esto en su lugar, puedes escribir esto también:
std::string trim_copy(std::string const & str)
{
auto s = str;
return ltrim(rtrim(s));
}
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);
str
es ahora "hello world!"
. También hay trim_left
y trim
, que recorta ambos lados.
Si agrega el sufijo _copy
a cualquiera de los nombres de funciones anteriores, por ejemplo, trim_copy
, la función devolverá una copia recortada de la cadena en lugar de modificarla mediante una referencia.
Si agrega el sufijo _if
a cualquiera de los nombres de funciones anteriores, por ejemplo, trim_copy_if
, puede recortar todos los caracteres que satisfacen su predicado personalizado, en lugar de solo espacios en blanco.
Utilice el siguiente código para recortar a la derecha los espacios 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 ideone código de recorte izquierdo ( ideone ):
// trim leading spaces
size_t startpos = str.find_first_not_of(" /t");
if( string::npos != startpos )
{
str = str.substr( startpos );
}
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;
salida:
hello
funciona bien también con cuerdas vacías
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());
}
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://.com/a/44973498/524503 por traer a la luz 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 <rim(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));
}
Son bastante autoexplicativos y funcionan muy bien.
EDIT : BTW, tengo std::ptr_fun
allí para ayudar a desambiguar std::isspace
porque en realidad hay una segunda definición que admite configuraciones regionales. Esto podría haber sido un elenco igual, pero me suele gustar 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 para hacer 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 con el interés de mantener la respuesta altamente votada aún disponible.
s.erase(0, s.find_first_not_of(" /n/r/t"));
s.erase(s.find_last_not_of(" /n/r/t")+1);