Dividir una cadena por un carácter

9 minutos de lectura

Dividir una cadena por un carácter
Ali

Sé que este es un problema bastante fácil, pero solo quiero resolverlo por mí mismo de una vez por todas.

Simplemente me gustaría dividir una cadena en una matriz usando un carácter como delimitador de división. (Muy parecido al famoso C# .Separar() función. Por supuesto, puedo aplicar el enfoque de fuerza bruta, pero me pregunto si hay algo mejor que eso.

Hasta ahora he buscado y probablemente el más cercano enfoque de solución es el uso de strtok(), sin embargo, debido a su inconveniente (convertir su cadena en una matriz de caracteres, etc.) no me gusta usarlo. ¿Hay alguna manera más fácil de implementar esto?

Nota: Quería enfatizar esto porque la gente podría preguntar “¿Cómo es que la fuerza bruta no funciona?”. Mi solución de fuerza bruta fue crear un bucle y usar el substr() función en el interior. Sin embargo, dado que requiere la punto de partida y la longitud, falla cuando quiero dividir una fecha. Porque el usuario puede ingresarlo como 12/7/2012 o 3/07/2011, donde realmente puedo saber la longitud antes de calcular la siguiente ubicación del delimitador “https://stackoverflow.com/”.

  • posible duplicado de Splitting String C++

    – Bo Person

    8 de abril de 2012 a las 6:56

  • ¿Responde esto a tu pregunta? ¿Cómo itero sobre las palabras de una cadena?

    – xskxzr

    28/07/2021 a las 10:00

Dividir una cadena por un carácter
desarrolladorperezoso

Uso de vectores, cadenas y stringstream. Un poco engorroso pero hace el truco.

#include <string>
#include <vector>
#include <sstream>

std::stringstream test("this_is_a_test_string");
std::string segment;
std::vector<std::string> seglist;

while(std::getline(test, segment, '_'))
{
   seglist.push_back(segment);
}

Lo que da como resultado un vector con el mismo contenido que

std::vector<std::string> seglist{ "this", "is", "a", "test", "string" };

  • En realidad, este tipo de enfoque es exactamente lo que estoy buscando. Bastante fácil de entender, sin uso de bibliotecas externas, simplemente muy sencillo. ¡Gracias @thelazydeveloper!

    – Alí

    7 abr 2012 a las 22:09

  • Si desea mejorar el rendimiento, puede agregar seglist.reserve(std::count_if(str.begin(), str.end(), [&](char c) { return c == splitChar; }) + (str.empty() ? 1 : 0)); Si la cadena original para dividir se almacena en str.

    – Jarek C.

    23 de mayo de 2018 a las 8:10


  • En lugar de while (std::getline(test, segment, '_')) podría ser mejor hacer while (!std::getline(test, segment, '_').eof()).

    – Makonede

    15 de enero a las 16:14

Dividir una cadena por un carácter
chrisaycock

Impulso tiene la separar() estas buscando en algorithm/string.hpp:

std::string sample = "07/3/2011";
std::vector<std::string> strs;
boost::split(strs, sample, boost::is_any_of("/"));

Otra forma (C++11/boost) para las personas a las que les gusta RegEx. Personalmente, soy un gran admirador de RegEx para este tipo de datos. En mi opinión, es mucho más poderoso que simplemente dividir cadenas usando un delimitador, ya que puede elegir ser mucho más inteligente sobre lo que constituye datos “válidos” si lo desea.

#include <string>
#include <algorithm>    // copy
#include <iterator>     // back_inserter
#include <regex>        // regex, sregex_token_iterator
#include <vector>

int main()
{
    std::string str = "08/04/2012";
    std::vector<std::string> tokens;
    std::regex re("\\d+");

    //start/end points of tokens in str
    std::sregex_token_iterator
        begin(str.begin(), str.end(), re),
        end;

    std::copy(begin, end, std::back_inserter(tokens));
}

  • Entonces, está incluyendo la totalidad de un comparador de expresiones regulares en su código solo para dividir una cadena. Triste…

    usuario6516765

    9 oct 2018 a las 11:28


  • @Dev No, incluido un comparador de expresiones regulares para ser más inteligente sobre lo que constituye datos válidos, por ejemplo, seleccionar números, y también permitir otros separadores como puntos o guiones

    – Ben Cottrell

    9 oct 2018 a las 13:53


  • Esto es malo tanto en términos de tamaño binario como de eficiencia general, pero dado que ambos no son motivo de preocupación en este caso, no continuaré.

    usuario6516765

    9 oct 2018 a las 15:23

  • @Dev Si uno tiene restricciones tan extremas sobre el tamaño binario, entonces debería reconsiderar incluso usar C++, o al menos sus bibliotecas estándar como string/vector/etc porque todas tendrán un efecto similar. En cuanto a la eficiencia, el mejor consejo sería el de Donald Knuth: “La optimización prematura es la raíz de todos los males”; en otras palabras, antes de realizar optimizaciones, la primera tarea es identificar si existe un problema y luego identificar la causa por medios objetivos, como la creación de perfiles, en lugar de perder el tiempo tratando de buscar todas las microoptimizaciones posibles.

    – Ben Cottrell

    9 oct 2018 a las 16:14


  • “ambos no son preocupaciones en absoluto en este caso” – yo mismo.

    usuario6516765

    9 oct 2018 a las 17:59

Otra posibilidad es imbuir una transmisión con una configuración regional que use un ctype faceta. Una transmisión usa la faceta ctype para determinar qué es un “espacio en blanco”, que trata como separadores. Con una faceta ctype que clasifica su carácter separador como espacio en blanco, la lectura puede ser bastante trivial. Aquí hay una forma de implementar la faceta:

struct field_reader: std::ctype<char> {

    field_reader(): std::ctype<char>(get_table()) {}

    static std::ctype_base::mask const* get_table() {
        static std::vector<std::ctype_base::mask> 
            rc(table_size, std::ctype_base::mask());

        // we'll assume dates are either a/b/c or a-b-c:
        rc["https://stackoverflow.com/"] = std::ctype_base::space;
        rc['-'] = std::ctype_base::space;
        return &rc[0];
    }
};

Usamos eso usando imbue para decirle a una transmisión que use una configuración regional que la incluya, luego lea los datos de esa transmisión:

std::istringstream in("07/3/2011");
in.imbue(std::locale(std::locale(), new field_reader);

Con eso en su lugar, la división se vuelve casi trivial: simplemente inicialice un vector usando un par de istream_iterators para leer las piezas de la cadena (que está incrustada en el istringstream):

std::vector<std::string>((std::istream_iterator<std::string>(in),
                          std::istream_iterator<std::string>());

Obviamente, esto tiende a exagerar si solo lo usa en un lugar. Sin embargo, si lo usa mucho, puede contribuir en gran medida a mantener el resto del código bastante limpio.

Dividir una cadena por un carácter
Compilador humano

Como nadie ha publicado esto todavía: la solución c ++ 20 es muy simple usando ranges. Puedes usar un std::ranges::views::split para dividir la entrada, y luego transformar la entrada en std::string o std::string_view elementos.

#include <ranges>


...

// The input to transform
const auto str = std::string{"Hello World"};

// Function to transform a range into a std::string
// Replace this with 'std::string_view' to make it a view instead.
auto to_string = [](auto&& r) -> std::string {
    const auto data = &*r.begin();
    const auto size = static_cast<std::size_t>(std::ranges::distance(r));

    return std::string{data, size};
};

const auto range = str | 
                   std::ranges::views::split(' ') | 
                   std::ranges::views::transform(to_string);

for (auto&& token : str | range) {
    // each 'token' is the split string
}

Este enfoque puede componer de manera realista casi cualquier cosa, incluso un simple split función que devuelve un std::vector<std::string>:

auto split(const std::string& str, char delimiter) -> std::vector<std::string>
{
    const auto range = str | 
                       std::ranges::views::split(delimiter) | 
                       std::ranges::views::transform(to_string);

    return {std::ranges::begin(range), std::ranges::end(range)};
}

Ejemplo en vivo

  • 1. ¿Por qué usas str | range en lugar de range? 2. es transform con to_string ¿necesario? Parece token se puede declarar como string_view así que eso transform es innecesario 3. split_view‘s begin y end las funciones no son constantes, por lo que parece que el programa está mal formado ya que el rango para el ciclo usa un rango constante.

    – xskxzr

    28 de julio de 2021 a las 9:39

  • Oh, para 2 veo, construyendo un string_view de un rango es una característica de C++23.

    – xskxzr

    28 de julio de 2021 a las 10:17

  • Esto es algo difícil de leer, no está nada claro en comparación con las otras respuestas.

    usuario8143588

    19 de diciembre de 2021 a las 4:37

Dividir una cadena por un carácter
alejandro bertinelli

inherentemente me desagrada stringstream, aunque no estoy seguro de por qué. Hoy, escribí esta función para permitir dividir un std::string por cualquier carácter arbitrario o cadena en un vector. Sé que esta pregunta es antigua, pero quería compartir una forma alternativa de dividir std::string.

Este código omite por completo la parte de la cadena por la que se dividió de los resultados, aunque podría modificarse fácilmente para incluirlos.

#include <string>
#include <vector>

void split(std::string str, std::string splitBy, std::vector<std::string>& tokens)
{
    /* Store the original string in the array, so we can loop the rest
     * of the algorithm. */
    tokens.push_back(str);

    // Store the split index in a 'size_t' (unsigned integer) type.
    size_t splitAt;
    // Store the size of what we're splicing out.
    size_t splitLen = splitBy.size();
    // Create a string for temporarily storing the fragment we're processing.
    std::string frag;
    // Loop infinitely - break is internal.
    while(true)
    {
        /* Store the last string in the vector, which is the only logical
         * candidate for processing. */
        frag = tokens.back();
        /* The index where the split is. */
        splitAt = frag.find(splitBy);
        // If we didn't find a new split point...
        if(splitAt == std::string::npos)
        {
            // Break the loop and (implicitly) return.
            break;
        }
        /* Put everything from the left side of the split where the string
         * being processed used to be. */
        tokens.back() = frag.substr(0, splitAt);
        /* Push everything from the right side of the split to the next empty
         * index in the vector. */
        tokens.push_back(frag.substr(splitAt+splitLen, frag.size()-(splitAt+splitLen)));
    }
}

Para usar, simplemente llame así…

std::string foo = "This is some string I want to split by spaces.";
std::vector<std::string> results;
split(foo, " ", results);

Ahora puede acceder a todos los resultados en el vector a voluntad. Tan simple como eso – no stringstreamsin bibliotecas de terceros, sin volver a C!

  • 1. ¿Por qué usas str | range en lugar de range? 2. es transform con to_string ¿necesario? Parece token se puede declarar como string_view así que eso transform es innecesario 3. split_view‘s begin y end las funciones no son constantes, por lo que parece que el programa está mal formado ya que el rango para el ciclo usa un rango constante.

    – xskxzr

    28 de julio de 2021 a las 9:39

  • Oh, para 2 veo, construyendo un string_view de un rango es una característica de C++23.

    – xskxzr

    28 de julio de 2021 a las 10:17

  • Esto es algo difícil de leer, no está nada claro en comparación con las otras respuestas.

    usuario8143588

    19 de diciembre de 2021 a las 4:37

Dividir una cadena por un carácter
Rafael Rawicki

Echa un vistazo a impulso::tokenizador

Si desea implementar su propio método, puede usar std::string::find() para determinar los puntos de división.

  • Gracias por la sugerencia de búsqueda de cadenas. Siempre me encanta escuchar estándar soluciones!

    – Alí

    7 abr 2012 a las 21:44

¿Ha sido útil esta solución?