Esta técnica utiliza algunos de los métodos anteriores (iterators
, std::copy
), añadiendo la posibilidad de particularizar cual es el separador y extendiendo el uso de la técnca a streams multilínea que contienen otras cosas que no son std::string
.
Por supuesto el std::copy
se puede hacer a un std::vector
por simplicidad y afinidad con la pregunta original, se dirige todo a std::cout
.
Primero la respuesta directa a la pregunta:
#include <iostream>
#include <iterator>
#include <limits>
#include <sstream>
#include <string>
struct SeparatorReader: std::ctype<char>
{
template<typename T>
SeparatorReader(const T &seps): std::ctype<char>(get_table(seps), true) {}
template<typename T>
std::ctype_base::mask const *get_table(const T &seps) {
auto &&rc = new std::ctype_base::mask[std::ctype<char>::table_size]();
for(auto &&sep: seps)
rc[static_cast<unsigned char>(sep)] = std::ctype_base::space;
return &rc[0];
}
};
int
main(int argc, char *argv[])
{
std::string str("Texto para dividir");
std::istringstream stream(str);
// This says whitespace is only ' '
stream.imbue(std::locale(stream.getloc(), new SeparatorReader(" ")));
auto first = std::istream_iterator<std::string>(stream);
auto last = std::istream_iterator<std::string>();
auto out = std::ostream_iterator<std::string>(std::cout, "\n");
std::copy(first, last, out);
return 0;
}
Con el siguiente resultado:
Texto
para
dividir
Nada nuevo bajo el sol, salvo por la introducción del SeparatorReader
. Esta subclase de std::ctype
acepta una lista de caracteres (que sea iterable, puede incluso ser un vector de caracteres) y los marca como std::ctype_base::space
, es decir como los caracteres a considerar como espacio en blanco.
En el código anterior hemos aplicado " "
porque es el separador de la cadena de texto de la pregunta (Nota: '\n' no está incluído como espacio en blanco en el ejemplo)
Una vez introducido el concepto podemos trabajar con otro separador como por ejemplo :
.
int
main(int argc, char *argv[])
{
std::string str("Texto:para:dividir");
std::istringstream stream(str);
// This says whitespace is only ':'
stream.imbue(std::locale(stream.getloc(), new SeparatorReader(":")));
auto first = std::istream_iterator<std::string>(stream);
auto last = std::istream_iterator<std::string>();
auto out = std::ostream_iterator<std::string>(std::cout, "\n");
std::copy(first, last, out);
return 0;
}
Con el mismo resultado de antes a pesar de haber cambiado los " "
por ":"
.
Texto
para
dividir
La técnica se vuelve más interesante si la utilizamos para aplicársela la tokenización y conversión de otros tipos como p.ej. int
y nos apoyamos en los errores de conversión para detectar hitos mayores.
En el siguiente ejemplo simulamos la lectura de un archivo csv
que contiene enteros. En lugar de un std::isstringstream
podría tratarse de un std:istream
vulgar y corriente.
Como SeparatorReader
no ve \n
como espacio en blanco, lo que producirá un error de conversión que permite detectar el final de línea, que tendremos que ignorar (pasándolo p.ej a un char
desechable)
int
main(int argc, char *argv[])
{
std::string str("1,2, 3\n4, 5,6\n 7,8,9");
std::istringstream stream(str);
// Only ' ' and ',' are whitespace, '\n' will stop int conversion
stream.imbue(std::locale(stream.getloc(), new SeparatorReader(" ,")));
auto last = std::istream_iterator<int>();
auto out = std::ostream_iterator<int>(std::cout, "-");
while(stream) {
auto first = std::istream_iterator<int>(stream); // redo after error needed
std::copy(first, last, out);
std::cout << std::endl; // separate lines
// Either eof or eol - try to skip eol or else re-meet eof
stream.clear(); char skip; stream >> skip;
}
return 0;
}
Con el siguiente resultado:
1-2-3-
4-5-6-
7-8-9-