Analizar (dividir) una cadena en C ++ usando delimitador de cadena (C ++ estándar)

364

Estoy analizando una cadena en C ++ usando lo siguiente:

using namespace std;

string parsed,input="text to be parsed";
stringstream input_stringstream(input);

if (getline(input_stringstream,parsed,' '))
{
     // do some processing.
}

Analizar con un solo delimitador de caracteres está bien. Pero, ¿y si quiero usar una cadena como delimitador?

Ejemplo: quiero dividir:

scott>=tiger

con >=como delimitador para que pueda obtener Scott y Tiger.

TheCrazyProgrammer
fuente

Respuestas:

577

Puede usar la std::string::find()función para encontrar la posición de su delimitador de cadena, luego usar std::string::substr()para obtener un token.

Ejemplo:

std::string s = "scott>=tiger";
std::string delimiter = ">=";
std::string token = s.substr(0, s.find(delimiter)); // token is "scott"
  • La find(const string& str, size_t pos = 0)función devuelve la posición de la primera aparición de stren la cadena, o npossi no se encuentra la cadena.

  • La substr(size_t pos = 0, size_t n = npos)función devuelve una subcadena del objeto, comenzando en la posición posy de longitud npos.


Si tiene varios delimitadores, después de extraer un token, puede eliminarlo (incluido el delimitador) para continuar con las extracciones posteriores (si desea preservar la cadena original, simplemente use s = s.substr(pos + delimiter.length());):

s.erase(0, s.find(delimiter) + delimiter.length());

De esta manera, puedes recorrer fácilmente para obtener cada token.

Ejemplo completo

std::string s = "scott>=tiger>=mushroom";
std::string delimiter = ">=";

size_t pos = 0;
std::string token;
while ((pos = s.find(delimiter)) != std::string::npos) {
    token = s.substr(0, pos);
    std::cout << token << std::endl;
    s.erase(0, pos + delimiter.length());
}
std::cout << s << std::endl;

Salida:

scott
tiger
mushroom
Vincenzo Pii
fuente
66
Para aquellos que no quieren modificar la cadena de entrada, hagasize_t last = 0; size_t next = 0; while ((next = s.find(delimiter, last)) != string::npos) { cout << s.substr(last, next-last) << endl; last = next + 1; } cout << s.substr(last) << endl;
hayk.mart
30
NOTA: mushroomsalidas fuera del bucle, es decirs = mushroom
Don Larynx
1
Esas muestras no extraen el último token de la cadena. Una muestra mía extrayendo un IpV4 de una cadena: <code> size_t last = 0; size_t next = 0; int index = 0; while (index <4) {next = str.find (delimitador, último); número automático = str.substr (último, siguiente - último); IPv4 [índice ++] = atoi (número.c_str ()); último = siguiente + 1; } </code>
rfog
2
@ hayk.mart Solo una nota, eso sería lo siguiente, necesita agregar 2 no 1 debido al tamaño del delimitador que es de 2 caracteres :): std :: string s = "scott> = tiger> = mushroom"; std :: string delimiter = "> ="; size_t last = 0; size_t next = 0; while ((next = s.find (delimitador, last))! = std :: string :: npos) {std :: cout << s.substr (last, next-last) << std :: endl; último = siguiente + 2; } std :: cout << s.substr (último) << std :: endl;
ervinbosenbacher
Para obtener "tigre", use std::string token = s.substr(s.find(delimiter) + 1);, si está seguro de que existe (uso +1 en la longitud) ...
gsamaras
64

Este método utiliza std::string::findsin mutar la cadena original al recordar el comienzo y el final del token de subcadena anterior.

#include <iostream>
#include <string>

int main()
{
    std::string s = "scott>=tiger";
    std::string delim = ">=";

    auto start = 0U;
    auto end = s.find(delim);
    while (end != std::string::npos)
    {
        std::cout << s.substr(start, end - start) << std::endl;
        start = end + delim.length();
        end = s.find(delim, start);
    }

    std::cout << s.substr(start, end);
}
Moswald
fuente
34

Puede usar la siguiente función para dividir cadenas:

vector<string> split(const string& str, const string& delim)
{
    vector<string> tokens;
    size_t prev = 0, pos = 0;
    do
    {
        pos = str.find(delim, prev);
        if (pos == string::npos) pos = str.length();
        string token = str.substr(prev, pos-prev);
        if (!token.empty()) tokens.push_back(token);
        prev = pos + delim.length();
    }
    while (pos < str.length() && prev < str.length());
    return tokens;
}
Sviatoslav
fuente
55
En mi opinión, no funciona como se esperaba: split("abc","a")devolverá un vector o una sola cadena "bc", donde creo que tendría más sentido si hubiera devuelto un vector de elementos ["", "bc"]. Al usar str.split()Python, fue intuitivo para mí que debería devolver una cadena vacía en caso de que delimse encontrara al principio o al final, pero esa es solo mi opinión. De todos modos, creo que debería mencionarse
kyriakosSt
1
Recomiendo encarecidamente eliminar la if (!token.empty()) prevención del problema mencionado por @kyriakosSt, así como otros problemas relacionados con delimitadores consecutivos.
Steve
1
Quitaría mi voto a favor si pudiera, pero SO no me lo permite. El problema planteado por @kyriakosSt es un problema, y ​​la eliminación if (!token.empty())no parece ser suficiente para solucionarlo.
bhaller
1
@bhaller este fragmento fue diseñado exactamente para omitir fragmentos vacíos. Si necesita mantener los vacíos, me temo que necesita escribir otra implementación dividida. Le sugiero que lo publique aquí por el bien de la comunidad.
Sviatoslav
32

Para delimitador de cadena

División de cadena basada en un delimitador de cadena . Como dividir la cadena en "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih"función del delimitador de cadena "-+", la salida será{"adsf", "qwret", "nvfkbdsj", "orthdfjgh", "dfjrleih"}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

// for string delimiter
vector<string> split (string s, string delimiter) {
    size_t pos_start = 0, pos_end, delim_len = delimiter.length();
    string token;
    vector<string> res;

    while ((pos_end = s.find (delimiter, pos_start)) != string::npos) {
        token = s.substr (pos_start, pos_end - pos_start);
        pos_start = pos_end + delim_len;
        res.push_back (token);
    }

    res.push_back (s.substr (pos_start));
    return res;
}

int main() {
    string str = "adsf-+qwret-+nvfkbdsj-+orthdfjgh-+dfjrleih";
    string delimiter = "-+";
    vector<string> v = split (str, delimiter);

    for (auto i : v) cout << i << endl;

    return 0;
}


Salida

adsf
qwret
nvfkbdsj
orthdfjgh
dfjrleih




Para delimitador de un solo carácter

División de cadena basada en un delimitador de caracteres. Como dividir una cadena "adsf+qwer+poui+fdgh"con delimitador "+"generará{"adsf", "qwer", "poui", "fdg"h}

#include <iostream>
#include <sstream>
#include <vector>

using namespace std;

vector<string> split (const string &s, char delim) {
    vector<string> result;
    stringstream ss (s);
    string item;

    while (getline (ss, item, delim)) {
        result.push_back (item);
    }

    return result;
}

int main() {
    string str = "adsf+qwer+poui+fdgh";
    vector<string> v = split (str, '+');

    for (auto i : v) cout << i << endl;

    return 0;
}


Salida

adsf
qwer
poui
fdgh
Arafat Hasan
fuente
Estás volviendo vector<string>, creo que se llamará constructor de copia.
Mayur
2
Cada referencia que he visto muestra que la llamada al constructor de copias se elimina en ese contexto.
David dado el
Con los compiladores "modernos" (C ++ 03?) Creo que esto es correcto, RVO y / o mover semántica eliminarán el constructor de copia.
Kevin
Intenté el delimitador de un solo carácter, y si la cadena termina en un delimitador (es decir, una columna csv vacía al final de la línea), no devuelve la cadena vacía. Simplemente devuelve una cadena menos. Por ejemplo: 1,2,3,4 \ nA, B, C,
kounoupis
También probé el delimitador de cadena, y si la cadena termina en un delimitador, el último delimitador se convierte en parte de la última cadena extraída.
kounoupis
20

Este código divide las líneas del texto y agrega a todos a un vector.

vector<string> split(char *phrase, string delimiter){
    vector<string> list;
    string s = string(phrase);
    size_t pos = 0;
    string token;
    while ((pos = s.find(delimiter)) != string::npos) {
        token = s.substr(0, pos);
        list.push_back(token);
        s.erase(0, pos + delimiter.length());
    }
    list.push_back(s);
    return list;
}

Llamado por:

vector<string> listFilesMax = split(buffer, "\n");
William Cuervo
fuente
está funcionando genial! He agregado list.push_back (s); porque faltaba
Stoica Mircea
1
se pierde la última parte de la cadena. Una vez que finaliza el ciclo while, necesitamos agregar el resto de s como un nuevo token.
whihathac
He realizado una edición en el ejemplo de código para corregir el push_back faltante.
traste
1
Será más agradablevector<string> split(char *phrase, const string delimiter="\n")
Mayur
15

strtok le permite pasar múltiples caracteres como delimitadores. Apuesto a que si pasa "> =" su cadena de ejemplo se dividirá correctamente (a pesar de que> y = se cuentan como delimitadores individuales).

EDITAR si no desea usar c_str()para convertir de string a char *, puede usar substr y find_first_of para tokenizar.

string token, mystring("scott>=tiger");
while(token != mystring){
  token = mystring.substr(0,mystring.find_first_of(">="));
  mystring = mystring.substr(mystring.find_first_of(">=") + 1);
  printf("%s ",token.c_str());
}
ryanbwork
fuente
3
Gracias. Pero quiero usar solo C ++ y no ninguna función de C, strtok()ya que requeriría que use char array en lugar de string.
TheCrazyProgrammer
2
@TheCrazyProgrammer Entonces? Si una función C hace lo que necesita, úsela. Este no es un mundo donde las funciones de C no están disponibles en C ++ (de hecho, tienen que estarlo). .c_str()También es barato y fácil.
Qix - MONICA FUE MALTRATADA el
1
La comprobación de if (token! = Mystring) da resultados incorrectos si tiene elementos repetidos en su cadena. Usé su código para hacer una versión que no tiene esto. Tiene muchos cambios que cambian la respuesta fundamentalmente, así que escribí mi propia respuesta en lugar de editar. Compruébalo a continuación.
Amber Elferink
5

Aquí está mi opinión sobre esto. Maneja los casos extremos y toma un parámetro opcional para eliminar entradas vacías de los resultados.

bool endsWith(const std::string& s, const std::string& suffix)
{
    return s.size() >= suffix.size() &&
           s.substr(s.size() - suffix.size()) == suffix;
}

std::vector<std::string> split(const std::string& s, const std::string& delimiter, const bool& removeEmptyEntries = false)
{
    std::vector<std::string> tokens;

    for (size_t start = 0, end; start < s.length(); start = end + delimiter.length())
    {
         size_t position = s.find(delimiter, start);
         end = position != string::npos ? position : s.length();

         std::string token = s.substr(start, end - start);
         if (!removeEmptyEntries || !token.empty())
         {
             tokens.push_back(token);
         }
    }

    if (!removeEmptyEntries &&
        (s.empty() || endsWith(s, delimiter)))
    {
        tokens.push_back("");
    }

    return tokens;
}

Ejemplos

split("a-b-c", "-"); // [3]("a","b","c")

split("a--c", "-"); // [3]("a","","c")

split("-b-", "-"); // [3]("","b","")

split("--c--", "-"); // [5]("","","c","","")

split("--c--", "-", true); // [1]("c")

split("a", "-"); // [1]("a")

split("", "-"); // [1]("")

split("", "-", true); // [0]()
Beder Acosta Borges
fuente
4

Esto debería funcionar perfectamente para delimitadores de cadena (o caracteres únicos). No te olvides de incluir #include <sstream>.

std::string input = "Alfa=,+Bravo=,+Charlie=,+Delta";
std::string delimiter = "=,+"; 
std::istringstream ss(input);
std::string token;
std::string::iterator it;

while(std::getline(ss, token, *(it = delimiter.begin()))) {
    while(*(++it)) ss.get();
    std::cout << token << " " << '\n';
}

El primer bucle while extrae un token utilizando el primer carácter del delimitador de cadena. El segundo bucle while omite el resto del delimitador y se detiene al comienzo del siguiente token.

hmofrad
fuente
3

Me gustaría utilizar boost::tokenizer. Aquí hay documentación que explica cómo hacer una función apropiada de tokenizer: http://www.boost.org/doc/libs/1_52_0/libs/tokenizer/tokenizerfunction.htm

Aquí hay uno que funciona para su caso.

struct my_tokenizer_func
{
    template<typename It>
    bool operator()(It& next, It end, std::string & tok)
    {
        if (next == end)
            return false;
        char const * del = ">=";
        auto pos = std::search(next, end, del, del + 2);
        tok.assign(next, pos);
        next = pos;
        if (next != end)
            std::advance(next, 2);
        return true;
    }

    void reset() {}
};

int main()
{
    std::string to_be_parsed = "1) one>=2) two>=3) three>=4) four";
    for (auto i : boost::tokenizer<my_tokenizer_func>(to_be_parsed))
        std::cout << i << '\n';
}
Benjamin Lindley
fuente
3
Gracias. Pero quiero desear solo C ++ estándar y no una biblioteca de terceros.
TheCrazyProgrammer
@TheCrazyProgrammer: De acuerdo, cuando leí "C ++ estándar", pensé que eso significaba que no había extensiones no estándar, no que no se pudieran usar bibliotecas de terceros que cumplan con los estándares.
Benjamin Lindley
3

La respuesta ya está allí, pero la respuesta seleccionada utiliza la función de borrado que es muy costosa, piense en una cadena muy grande (en MB). Por lo tanto, uso la siguiente función.

vector<string> split(const string& i_str, const string& i_delim)
{
    vector<string> result;

    size_t found = i_str.find(i_delim);
    size_t startIndex = 0;

    while(found != string::npos)
    {
        string temp(i_str.begin()+startIndex, i_str.begin()+found);
        result.push_back(temp);
        startIndex = found + i_delim.size();
        found = i_str.find(i_delim, startIndex);
    }
    if(startIndex != i_str.size())
        result.push_back(string(i_str.begin()+startIndex, i_str.end()));
    return result;      
}
Shubham Agrawal
fuente
Probé esto, y funciona. ¡Gracias! En mi opinión, esta es la mejor respuesta porque, como dice el contestador original, esta solución reduce la sobrecarga de memoria y el resultado se almacena convenientemente en un vector. (replica el string.split()método Python .)
Robbie Capps
2

Este es un método completo que divide la cadena en cualquier delimitador y devuelve un vector de las cadenas cortadas.

Es una adaptación de la respuesta de ryanbwork. Sin embargo, su comprobación para: if(token != mystring)da resultados incorrectos si tiene elementos repetidos en su cadena. Esta es mi solución a ese problema.

vector<string> Split(string mystring, string delimiter)
{
    vector<string> subStringList;
    string token;
    while (true)
    {
        size_t findfirst = mystring.find_first_of(delimiter);
        if (findfirst == string::npos) //find_first_of returns npos if it couldn't find the delimiter anymore
        {
            subStringList.push_back(mystring); //push back the final piece of mystring
            return subStringList;
        }
        token = mystring.substr(0, mystring.find_first_of(delimiter));
        mystring = mystring.substr(mystring.find_first_of(delimiter) + 1);
        subStringList.push_back(token);
    }
    return subStringList;
}
Amber Elferink
fuente
1
Algo así while (true)suele dar miedo ver en un código como este. Personalmente, recomendaría reescribir esto para que la comparación con std::string::npos(o, respectivamente, un cheque en contra mystring.size()) haga while (true)obsoleto.
Joel Bodenmann
1

Si no desea modificar la cadena (como en la respuesta de Vincenzo Pii) y desea generar también el último token, puede utilizar este enfoque:

inline std::vector<std::string> splitString( const std::string &s, const std::string &delimiter ){
    std::vector<std::string> ret;
    size_t start = 0;
    size_t end = 0;
    size_t len = 0;
    std::string token;
    do{ end = s.find(delimiter,start); 
        len = end - start;
        token = s.substr(start, len);
        ret.emplace_back( token );
        start += len + delimiter.length();
        std::cout << token << std::endl;
    }while ( end != std::string::npos );
    return ret;
}
user2366975
fuente
0
#include<iostream>
#include<algorithm>
using namespace std;

int split_count(string str,char delimit){
return count(str.begin(),str.end(),delimit);
}

void split(string str,char delimit,string res[]){
int a=0,i=0;
while(a<str.size()){
res[i]=str.substr(a,str.find(delimit));
a+=res[i].size()+1;
i++;
}
}

int main(){

string a="abc.xyz.mno.def";
int x=split_count(a,'.')+1;
string res[x];
split(a,'.',res);

for(int i=0;i<x;i++)
cout<<res[i]<<endl;
  return 0;
}

PD: funciona solo si las longitudes de las cadenas después de dividir son iguales


fuente
Esto usa la extensión GCC - matriz de longitud variable.
user202729
0

Función:

std::vector<std::string> WSJCppCore::split(const std::string& sWhat, const std::string& sDelim) {
    std::vector<std::string> vRet;
    int nPos = 0;
    int nLen = sWhat.length();
    int nDelimLen = sDelim.length();
    while (nPos < nLen) {
        std::size_t nFoundPos = sWhat.find(sDelim, nPos);
        if (nFoundPos != std::string::npos) {
            std::string sToken = sWhat.substr(nPos, nFoundPos - nPos);
            vRet.push_back(sToken);
            nPos = nFoundPos + nDelimLen;
            if (nFoundPos + nDelimLen == nLen) { // last delimiter
                vRet.push_back("");
            }
        } else {
            std::string sToken = sWhat.substr(nPos, nLen - nPos);
            vRet.push_back(sToken);
            break;
        }
    }
    return vRet;
}

Pruebas unitarias:

bool UnitTestSplit::run() {
bool bTestSuccess = true;

    struct LTest {
        LTest(
            const std::string &sStr,
            const std::string &sDelim,
            const std::vector<std::string> &vExpectedVector
        ) {
            this->sStr = sStr;
            this->sDelim = sDelim;
            this->vExpectedVector = vExpectedVector;
        };
        std::string sStr;
        std::string sDelim;
        std::vector<std::string> vExpectedVector;
    };
    std::vector<LTest> tests;
    tests.push_back(LTest("1 2 3 4 5", " ", {"1", "2", "3", "4", "5"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|2", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", "2"}));
    tests.push_back(LTest("|1f|2п|3%^|44354|5kdasjfdre|", "|", {"", "1f", "2п", "3%^", "44354", "5kdasjfdre", ""}));
    tests.push_back(LTest("some1 => some2 => some3", "=>", {"some1 ", " some2 ", " some3"}));
    tests.push_back(LTest("some1 => some2 => some3 =>", "=>", {"some1 ", " some2 ", " some3 ", ""}));

    for (int i = 0; i < tests.size(); i++) {
        LTest test = tests[i];
        std::string sPrefix = "test" + std::to_string(i) + "(\"" + test.sStr + "\")";
        std::vector<std::string> vSplitted = WSJCppCore::split(test.sStr, test.sDelim);
        compareN(bTestSuccess, sPrefix + ": size", vSplitted.size(), test.vExpectedVector.size());
        int nMin = std::min(vSplitted.size(), test.vExpectedVector.size());
        for (int n = 0; n < nMin; n++) {
            compareS(bTestSuccess, sPrefix + ", element: " + std::to_string(n), vSplitted[n], test.vExpectedVector[n]);
        }
    }

    return bTestSuccess;
}
kg de mar
fuente
0
std::vector<std::string> parse(std::string str,std::string delim){
    std::vector<std::string> tokens;
    char *str_c = strdup(str.c_str()); 
    char* token = NULL;

    token = strtok(str_c, delim.c_str()); 
    while (token != NULL) { 
        tokens.push_back(std::string(token));  
        token = strtok(NULL, delim.c_str()); 
    }

    delete[] str_c;

    return tokens;
}
XLVII
fuente
-4
std::vector<std::string> split(const std::string& s, char c) {
  std::vector<std::string> v;
  unsigned int ii = 0;
  unsigned int j = s.find(c);
  while (j < s.length()) {
    v.push_back(s.substr(i, j - i));
    i = ++j;
    j = s.find(c, j);
    if (j >= s.length()) {
      v.push_back(s.substr(i, s,length()));
      break;
    }
  }
  return v;
}
Yilei
fuente
1
Por favor sea más preciso. Su código no se compilará. Vea la declaración de "i" y la coma en lugar de un punto.
jstuardo