¿Cómo itero sobre las palabras de una cadena?
¿Cómo itero sobre las palabras de una cadena compuesta de palabras separadas por espacios en blanco?
Tenga en cuenta que no estoy interesado en las funciones de cadena C ni en ese tipo de manipulación/acceso a caracteres. Prefiero la elegancia a la eficiencia. Mi solución actual:
#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);
}
Lo uso para dividir una 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::istringstream iss(s);
std::string item;
while (std::getline(iss, 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 los tokens vacíos, por lo que a continuación encontrará 4 elementos, uno de los cuales está vacío:
std::vector<std::string> x = split("one:two::three", ':');
Por si sirve de algo, aquí hay otra forma de extraer tokens de una cadena de entrada, confiando únicamente en las instalaciones estándar de la biblioteca. Es un ejemplo del poder y la elegancia detrás del diseño del STL.
#include <iostream>
#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
int main() {
using namespace std;
string sentence = "And I feel fine...";
istringstream iss(sentence);
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
ostream_iterator<string>(cout, "\n"));
}
En lugar de copiar los tokens extraídos a un flujo de salida, se podrían insertar en un contenedor, utilizando el mismo copy
algoritmo genérico.
vector<string> tokens;
copy(istream_iterator<string>(iss),
istream_iterator<string>(),
back_inserter(tokens));
... o crear vector
directamente:
vector<string> tokens{istream_iterator<string>{iss},
istream_iterator<string>{}};
Una posible solución usando Boost podría ser:
#include <boost/algorithm/string.hpp>
std::vector<std::string> strs;
boost::split(strs, "string to split", boost::is_any_of("\t "));
Este enfoque podría ser incluso más rápido que el stringstream
enfoque. Y como se trata de una función de plantilla genérica, se puede utilizar para dividir otros tipos de cadenas (wchar, etc. o UTF-8) utilizando todo tipo de delimitadores.
Consulte la documentación para obtener más detalles.
#include <vector>
#include <string>
#include <sstream>
int main()
{
std::string str("Split me by whitespaces");
std::string buf; // Have a buffer string
std::stringstream ss(str); // Insert the string into a stream
std::vector<std::string> tokens; // Create vector to hold our words
while (ss >> buf)
tokens.push_back(buf);
return 0;
}
Una solución eficiente, pequeña y elegante que utiliza una función de plantilla:
template <class ContainerT>
void split(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.emplace_back(value_type(str.data() + lastPos,
(size_type)pos - lastPos));
lastPos = pos + 1;
}
}
Por lo general, elijo usar std::vector<std::string>
tipos como mi segundo parámetro ( ContainerT
)... pero list<...>
a veces es posible que sea preferible a vector<...>
.
También le permite especificar si desea recortar tokens vacíos de los resultados mediante un último parámetro opcional.
Todo lo que requiere se std::string
incluye a través de <string>
. No utiliza transmisiones ni la biblioteca boost explícitamente, pero podrá aceptar algunos de estos tipos.
Además, desde C++-17 puedes usar, std::vector<std::string_view>
que es mucho más rápido y eficiente en memoria que usar std::string
. Aquí hay una versión revisada que también admite el contenedor como tipo de devolución:
#include <vector>
#include <string_view>
#include <utility>
template < typename StringT,
typename DelimiterT = char,
typename ContainerT = std::vector<std::string_view> >
ContainerT split(StringT const& str, DelimiterT const& delimiters = ' ', bool trimEmpty = true, ContainerT&& tokens = {})
{
typename StringT::size_type pos, lastPos = 0, length = str.length();
while (lastPos < length + 1)
{
pos = str.find_first_of(delimiters, lastPos);
if (pos == StringT::npos)
pos = length;
if (pos != lastPos || !trimEmpty)
tokens.emplace_back(str.data() + lastPos, pos - lastPos);
lastPos = pos + 1;
}
return std::forward<ContainerT>(tokens);
}
Se ha tenido cuidado de no hacer copias innecesarias.
Esto permitirá:
for (auto const& line : split(str, '\n'))
O:
auto& lines = split(str, '\n');
Ambos devuelven el tipo de contenedor de plantilla predeterminado de std::vector<std::string_view>
.
Para recuperar un tipo de contenedor específico, o para pasar un contenedor existente, use el tokens
parámetro de entrada con un contenedor inicial escrito o una variable de contenedor existente:
auto& lines = split(str, '\n', false, std::vector<std::string>());
O:
std::vector<std::string> lines;
split(str, '\n', false, lines);
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 crear fácilmente una plantilla 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 ampliar fácilmente para omitir tokens vacíos:
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 desea dividir una cadena en múltiples delimitadores y omitir 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;
}