c++ - usar - leer cadena de caracteres en c




¿Cómo itero sobre las palabras de una cadena? (20)

Estoy tratando de iterar sobre las palabras de una cuerda.

Se puede suponer que la cadena está compuesta de palabras separadas por espacios en blanco.

Tenga en cuenta que no estoy interesado en las funciones de cadena C o en ese tipo de manipulación / acceso de caracteres. Además, por favor, dé prioridad a la elegancia sobre la eficiencia en su respuesta.

La mejor solución que tengo ahora es:

#include <iostream>
#include <sstream>
#include <string>

using namespace std;

int main()
{
    string s = "Somewhere down the road";
    istringstream iss(s);

    do
    {
        string subs;
        iss >> subs;
        cout << "Substring: " << subs << endl;
    } while (iss);
}

¿Hay una manera más elegante de hacer esto?


Aquí hay otra solución. Es compacto y razonablemente eficiente:

std::vector<std::string> split(const std::string &text, char sep) {
  std::vector<std::string> tokens;
  std::size_t start = 0, end = 0;
  while ((end = text.find(sep, start)) != std::string::npos) {
    tokens.push_back(text.substr(start, end - start));
    start = end + 1;
  }
  tokens.push_back(text.substr(start));
  return tokens;
}

Se puede templar fácilmente para manejar separadores de cuerdas, cuerdas anchas, etc.

Tenga en cuenta que la división "" da como resultado una única cadena vacía y la división "," (es decir, sep) da como resultado dos cadenas vacías.

También se puede expandir fácilmente para omitir fichas vacías:

std::vector<std::string> split(const std::string &text, char sep) {
    std::vector<std::string> tokens;
    std::size_t start = 0, end = 0;
    while ((end = text.find(sep, start)) != std::string::npos) {
        if (end != start) {
          tokens.push_back(text.substr(start, end - start));
        }
        start = end + 1;
    }
    if (end != start) {
       tokens.push_back(text.substr(start));
    }
    return tokens;
}

Si se desea dividir una cadena en múltiples delimitadores mientras se omiten tokens vacíos, se puede usar esta versión:

std::vector<std::string> split(const std::string& text, const std::string& delims)
{
    std::vector<std::string> tokens;
    std::size_t start = text.find_first_not_of(delims), end = 0;

    while((end = text.find_first_of(delims, start)) != std::string::npos)
    {
        tokens.push_back(text.substr(start, end - start));
        start = text.find_first_not_of(delims, end);
    }
    if(start != std::string::npos)
        tokens.push_back(text.substr(start));

    return tokens;
}

Aquí hay una función dividida que:

  • es genérico
  • utiliza C ++ estándar (sin impulso)
  • acepta múltiples delimitadores
  • ignora las fichas vacías (se pueden cambiar fácilmente)

    template<typename T>
    vector<T> 
    split(const T & str, const T & delimiters) {
        vector<T> v;
        typename T::size_type start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != T::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }
    

Ejemplo de uso:

    vector<string> v = split<string>("Hello, there; World", ";,");
    vector<wstring> v = split<wstring>(L"Hello, there; World", L";,");

Aquí hay una solución simple que usa solo la biblioteca estándar de expresiones regulares

#include <regex>
#include <string>
#include <vector>

std::vector<string> Tokenize( const string str, const std::regex regex )
{
    using namespace std;

    std::vector<string> result;

    sregex_token_iterator it( str.begin(), str.end(), regex, -1 );
    sregex_token_iterator reg_end;

    for ( ; it != reg_end; ++it ) {
        if ( !it->str().empty() ) //token could be empty:check
            result.emplace_back( it->str() );
    }

    return result;
}

El argumento regex permite verificar múltiples argumentos (espacios, comas, etc.)

Por lo general, solo selecciono para dividir en espacios y comas, por lo que también tengo esta función predeterminada:

std::vector<string> TokenizeDefault( const string str )
{
    using namespace std;

    regex re( "[\\s,]+" );

    return Tokenize( str, re );
}

El "[\\s,]+" verifica espacios ( \\s ) y comas ( , ).

Tenga en cuenta, si desea dividir wstring lugar de string ,

  • cambiar todos std::regex a std::wregex
  • cambia todo sregex_token_iterator a wsregex_token_iterator

Tenga en cuenta que es posible que también desee tomar el argumento de cadena por referencia, dependiendo de su compilador.


El STL no tiene un método disponible ya.

Sin embargo, puede usar la strtok() std::string::c_str() C usando el miembro std::string::c_str() , o puede escribir el suyo propio. Aquí hay un ejemplo de código que encontré después de una búsqueda rápida en Google ( "división de cadena STL" ):

void Tokenize(const string& str,
              vector<string>& tokens,
              const string& delimiters = " ")
{
    // Skip delimiters at beginning.
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);
    // Find first "non-delimiter".
    string::size_type pos     = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos)
    {
        // Found a token, add it to the vector.
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters.  Note the "not_of"
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next "non-delimiter"
        pos = str.find_first_of(delimiters, lastPos);
    }
}

Tomado de: http://oopweb.com/CPP/Documents/CPPHOWTO/Volume/C++Programming-HOWTO-7.html

Si tiene preguntas sobre el ejemplo de código, deje un comentario y se lo explicaré.

Y solo porque no implementa un typedef llamado iterador o sobrecarga, el operador << no significa que sea un código incorrecto. Yo uso las funciones de C con bastante frecuencia. Por ejemplo, printf y scanf son más rápidos que std::cin y std::cout (significativamente), la sintaxis de fopen es mucho más amigable para los tipos binarios, y también tienden a producir EXEs más pequeños.

No se venda en este acuerdo de "Elegancia sobre rendimiento" .


Esto es similar a la pregunta de desbordamiento de pila ¿ Cómo tokenize una cadena en C ++? .

#include <iostream>
#include <string>
#include <boost/tokenizer.hpp>

using namespace std;
using namespace boost;

int main(int argc, char** argv)
{
    string text = "token  test\tstring";

    char_separator<char> sep(" \t");
    tokenizer<char_separator<char>> tokens(text, sep);
    for (const string& t : tokens)
    {
        cout << t << "." << endl;
    }
}

Hasta ahora utilicé el de Boost , pero necesitaba algo que no dependiera de él, así que llegué a esto:

static void Split(std::vector<std::string>& lst, const std::string& input, const std::string& separators, bool remove_empty = true)
{
    std::ostringstream word;
    for (size_t n = 0; n < input.size(); ++n)
    {
        if (std::string::npos == separators.find(input[n]))
            word << input[n];
        else
        {
            if (!word.str().empty() || !remove_empty)
                lst.push_back(word.str());
            word.str("");
        }
    }
    if (!word.str().empty() || !remove_empty)
        lst.push_back(word.str());
}

Un buen punto es que en los separators puedes pasar más de un carácter.


Hice rodar mi propio uso de strtok y utilicé boost para dividir una cadena. El mejor método que he encontrado es la biblioteca de C ++ String Toolkit . Es increíblemente flexible y rápido.

#include <iostream>
#include <vector>
#include <string>
#include <strtk.hpp>

const char *whitespace  = " \t\r\n\f";
const char *whitespace_and_punctuation  = " \t\r\n\f;,=";

int main()
{
    {   // normal parsing of a string into a vector of strings
        std::string s("Somewhere down the road");
        std::vector<std::string> result;
        if( strtk::parse( s, whitespace, result ) )
        {
            for(size_t i = 0; i < result.size(); ++i )
                std::cout << result[i] << std::endl;
        }
    }

    {  // parsing a string into a vector of floats with other separators
        // besides spaces

        std::string s("3.0, 3.14; 4.0");
        std::vector<float> values;
        if( strtk::parse( s, whitespace_and_punctuation, values ) )
        {
            for(size_t i = 0; i < values.size(); ++i )
                std::cout << values[i] << std::endl;
        }
    }

    {  // parsing a string into specific variables

        std::string s("angle = 45; radius = 9.9");
        std::string w1, w2;
        float v1, v2;
        if( strtk::parse( s, whitespace_and_punctuation, w1, v1, w2, v2) )
        {
            std::cout << "word " << w1 << ", value " << v1 << std::endl;
            std::cout << "word " << w2 << ", value " << v2 << std::endl;
        }
    }

    return 0;
}

El kit de herramientas tiene mucha más flexibilidad de lo que muestra este simple ejemplo, pero su utilidad para analizar una cadena en elementos útiles es increíble.


La stringstream puede ser conveniente si necesita analizar la cadena con símbolos que no son espacios:

string s = "Name:JAck; Spouse:Susan; ...";
string dummy, name, spouse;

istringstream iss(s);
getline(iss, dummy, ':');
getline(iss, name, ';');
getline(iss, dummy, ':');
getline(iss, spouse, ';')

Otra forma más flexible y rápida.

template<typename Operator>
void tokenize(Operator& op, const char* input, const char* delimiters) {
  const char* s = input;
  const char* e = s;
  while (*e != 0) {
    e = s;
    while (*e != 0 && strchr(delimiters, *e) == 0) ++e;
    if (e - s > 0) {
      op(s, e - s);
    }
    s = e + 1;
  }
}

Para usarlo con un vector de cadenas (Editar: Dado que alguien señaló que no hereda las clases STL ... hrmf;)):

template<class ContainerType>
class Appender {
public:
  Appender(ContainerType& container) : container_(container) {;}
  void operator() (const char* s, unsigned length) { 
    container_.push_back(std::string(s,length));
  }
private:
  ContainerType& container_;
};

std::vector<std::string> strVector;
Appender v(strVector);
tokenize(v, "A number of words to be tokenized", " \t");

¡Eso es! Y esa es solo una forma de usar el tokenizador, por ejemplo, cómo contar palabras:

class WordCounter {
public:
  WordCounter() : noOfWords(0) {}
  void operator() (const char*, unsigned) {
    ++noOfWords;
  }
  unsigned noOfWords;
};

WordCounter wc;
tokenize(wc, "A number of words to be counted", " \t"); 
ASSERT( wc.noOfWords == 7 );

Limitado por la imaginación;)


Para aquellos con quienes no se siente bien sacrificar toda la eficiencia por el tamaño del código y ver "eficiente" como un tipo de elegancia, lo siguiente debería ser un punto dulce (y creo que la clase de contenedor de plantillas es una adición increíblemente elegante):

template < class ContainerT >
void tokenize(const std::string& str, ContainerT& tokens,
              const std::string& delimiters = " ", bool trimEmpty = false)
{
   std::string::size_type pos, lastPos = 0, length = str.length();

   using value_type = typename ContainerT::value_type;
   using size_type  = typename ContainerT::size_type;

   while(lastPos < length + 1)
   {
      pos = str.find_first_of(delimiters, lastPos);
      if(pos == std::string::npos)
      {
         pos = length;
      }

      if(pos != lastPos || !trimEmpty)
         tokens.push_back(value_type(str.data()+lastPos,
               (size_type)pos-lastPos ));

      lastPos = pos + 1;
   }
}

Por lo general, elijo usar los tipos std::vector<std::string> como mi segundo parámetro ( ContainerT ) ... pero list<> es mucho más rápido que vector<> para cuando no se necesita acceso directo, e incluso puedes crear tu propia clase de cadena y usa algo como std::list<subString> donde subString no hace ninguna copia para aumentos de velocidad increíbles.

Es más del doble de rápido que el tokenize más rápido en esta página y casi 5 veces más rápido que otros. También con los tipos de parámetros perfectos puede eliminar todas las copias de cadenas y listas para aumentos de velocidad adicionales.

Además, no hace el retorno del resultado (extremadamente ineficiente), sino que pasa los tokens como referencia, lo que también le permite crear tokens utilizando múltiples llamadas si así lo desea.

Por último, le permite especificar si desea recortar tokens vacíos de los resultados a través de un último parámetro opcional.

Todo lo que necesita es std::string ... el resto son opcionales. No utiliza streams o la biblioteca boost, pero es lo suficientemente flexible como para poder aceptar algunos de estos tipos extranjeros de forma natural.


Si le gusta usar boost, pero desea usar una cadena completa como delimitador (en lugar de caracteres simples como en la mayoría de las soluciones propuestas anteriormente), puede usar boost_split_iterator .

Código de ejemplo que incluye una plantilla conveniente:

#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>

template<typename _OutputIterator>
inline void split(
    const std::string& str, 
    const std::string& delim, 
    _OutputIterator result)
{
    using namespace boost::algorithm;
    typedef split_iterator<std::string::const_iterator> It;

    for(It iter=make_split_iterator(str, first_finder(delim, is_equal()));
            iter!=It();
            ++iter)
    {
        *(result++) = boost::copy_range<std::string>(*iter);
    }
}

int main(int argc, char* argv[])
{
    using namespace std;

    vector<string> splitted;
    split("HelloFOOworldFOO!", "FOO", back_inserter(splitted));

    // or directly to console, for example
    split("HelloFOOworldFOO!", "FOO", ostream_iterator<string>(cout, "\n"));
    return 0;
}

Tengo una solución de 2 líneas para este problema:

char sep = ' ';
std::string s="1 This is an example";

for(size_t p=0, q=0; p!=s.npos; p=q)
  std::cout << s.substr(p+(p!=0), (q=s.find(sep, p+1))-p-(p!=0)) << std::endl;

Luego en lugar de imprimir puedes ponerlo en un vector.


Usar std::stringstream como funciona perfectamente, y hacer exactamente lo que quería. Sin embargo, si solo está buscando una forma diferente de hacer las cosas, puede usar std::find() / std::find_first_of() y std::string::substr() .

Aquí hay un ejemplo:

#include <iostream>
#include <string>

int main()
{
    std::string s("Somewhere down the road");
    std::string::size_type prev_pos = 0, pos = 0;

    while( (pos = s.find(' ', pos)) != std::string::npos )
    {
        std::string substring( s.substr(prev_pos, pos-prev_pos) );

        std::cout << substring << '\n';

        prev_pos = ++pos;
    }

    std::string substring( s.substr(prev_pos, pos-prev_pos) ); // Last word
    std::cout << substring << '\n';

    return 0;
}

Yo uso esto para dividir la cadena por un delimitador. El primero coloca los resultados en un vector preconstruido, el segundo devuelve un nuevo vector.

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

template<typename Out>
void split(const std::string &s, char delim, Out result) {
    std::stringstream ss(s);
    std::string item;
    while (std::getline(ss, item, delim)) {
        *(result++) = item;
    }
}

std::vector<std::string> split(const std::string &s, char delim) {
    std::vector<std::string> elems;
    split(s, delim, std::back_inserter(elems));
    return elems;
}

Tenga en cuenta que esta solución no omite tokens vacíos, por lo que los siguientes encontrarán 4 elementos, uno de los cuales está vacío:

std::vector<std::string> x = split("one:two::three", ':');

Aquí hay otra forma de hacerlo ..

void split_string(string text,vector<string>& words)
{
  int i=0;
  char ch;
  string word;

  while(ch=text[i++])
  {
    if (isspace(ch))
    {
      if (!word.empty())
      {
        words.push_back(word);
      }
      word = "";
    }
    else
    {
      word += ch;
    }
  }
  if (!word.empty())
  {
    words.push_back(word);
  }
}

Corto y elegante

#include <vector>
#include <string>
using namespace std;

vector<string> split(string data, string token)
{
    vector<string> output;
    size_t pos = string::npos; // size_t to avoid improbable overflow
    do
    {
        pos = data.find(token);
        output.push_back(data.substr(0, pos));
        if (string::npos != pos)
            data = data.substr(pos + token.size());
    } while (string::npos != pos);
    return output;
}

puede usar cualquier cadena como delimitador, también se puede usar con datos binarios (std :: string admite datos binarios, incluidos los nulos)

utilizando:

auto a = split("this!!is!!!example!string", "!!");

salida:

this
is
!example!string

El siguiente código se utiliza strtok()para dividir una cadena en tokens y almacena los tokens en un vector.

#include <iostream>
#include <algorithm>
#include <vector>
#include <string>

using namespace std;


char one_line_string[] = "hello hi how are you nice weather we are having ok then bye";
char seps[]   = " ,\t\n";
char *token;



int main()
{
   vector<string> vec_String_Lines;
   token = strtok( one_line_string, seps );

   cout << "Extracting and storing data in a vector..\n\n\n";

   while( token != NULL )
   {
      vec_String_Lines.push_back(token);
      token = strtok( NULL, seps );
   }
     cout << "Displaying end result in vector line storage..\n\n";

    for ( int i = 0; i < vec_String_Lines.size(); ++i)
    cout << vec_String_Lines[i] << "\n";
    cout << "\n\n\n";


return 0;
}

Esta respuesta toma la cadena y la coloca en un vector de cadenas. Utiliza la biblioteca boost.

#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));

Obtener Boost ! : -)

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <vector>

using namespace std;
using namespace boost;

int main(int argc, char**argv) {
    typedef vector < string > list_type;

    list_type list;
    string line;

    line = "Somewhere down the road";
    split(list, line, is_any_of(" "));

    for(int i = 0; i < list.size(); i++)
    {
        cout << list[i] << endl;
    }

    return 0;
}

Este ejemplo da la salida -

Somewhere
down
the
road

Recientemente tuve que dividir una palabra en camello en subpuntos. No hay delimitadores, solo caracteres superiores.

#include <string>
#include <list>
#include <locale> // std::isupper

template<class String>
const std::list<String> split_camel_case_string(const String &s)
{
    std::list<String> R;
    String w;

    for (String::const_iterator i = s.begin(); i < s.end(); ++i) {  {
        if (std::isupper(*i)) {
            if (w.length()) {
                R.push_back(w);
                w.clear();
            }
        }
        w += *i;
    }

    if (w.length())
        R.push_back(w);
    return R;
}

Por ejemplo, esto divide "AQueryTrades" en "A", "Consulta" y "Operaciones". La función funciona con cuerdas estrechas y anchas. Porque respeta la configuración regional actual, divide "RaumfahrtÜberwachungsVerordnung" en "Raumfahrt", "Überwachungs" y "Verordnung".

La nota std::upperdebe pasarse realmente como argumento de plantilla de función. Entonces, lo más generalizado de esta función puede dividirse en delimitadores como ",", ";"o " "también.





split