c++ - separar - manejo de archivos csv en c
Analizando una std:: string delimitada por comas (17)
Algo menos detallado, estándar y toma cualquier cosa separada por una coma.
stringstream ss( "1,1,1,1, or something else ,1,1,1,0" );
vector<string> result;
while( ss.good() )
{
string substr;
getline( ss, substr, '','' );
result.push_back( substr );
}
Si tengo una std :: cadena que contiene una lista de números separados por comas, ¿cuál es la forma más simple de analizar los números y ponerlos en una matriz de enteros?
No quiero generalizar esto para analizar nada más. Solo una simple cadena de números enteros separados por comas como "1,1,1,1,2,1,1,1,0".
Función simple Copiar / Pegar, basada en el Boost.Tokenizer .
void strToIntArray(std::string string, int* array, int array_len) {
boost::tokenizer<> tok(string);
int i = 0;
for(boost::tokenizer<>::iterator beg=tok.begin(); beg!=tok.end();++beg){
if(i < array_len)
array[i] = atoi(beg->c_str());
i++;
}
La biblioteca de C ++ String Toolkit (Strtk) tiene la siguiente solución a su problema:
#include <string>
#include <deque>
#include <vector>
#include "strtk.hpp"
int main()
{
std::string int_string = "1,2,3,4,5,6,7,8,9,10,11,12,13,14,15";
std::vector<int> int_list;
strtk::parse(int_string,",",int_list);
std::string double_string = "123.456|789.012|345.678|901.234|567.890";
std::deque<double> double_list;
strtk::parse(double_string,"|",double_list);
return 0;
}
Más ejemplos se pueden encontrar Here
Me sorprende que nadie haya propuesto una solución usando std::regex
todavía:
#include <string>
#include <algorithm>
#include <vector>
#include <regex>
void parse_csint( const std::string& str, std::vector<int>& result ) {
typedef std::regex_iterator<std::string::const_iterator> re_iterator;
typedef re_iterator::value_type re_iterated;
std::regex re("(//d+)");
re_iterator rit( str.begin(), str.end(), re );
re_iterator rend;
std::transform( rit, rend, std::back_inserter(result),
[]( const re_iterated& it ){ return std::stoi(it[1]); } );
}
Esta función inserta todos los enteros en la parte posterior del vector de entrada. Puede ajustar la expresión regular para incluir números enteros negativos, números de coma flotante, etc.
Muchas respuestas bastante terribles aquí, así que agregaré las mías (incluido el programa de prueba):
#include <string>
#include <iostream>
#include <cstddef>
template<typename StringFunction>
void splitString(const std::string &str, char delimiter, StringFunction f) {
std::size_t from = 0;
for (std::size_t i = 0; i < str.size(); ++i) {
if (str[i] == delimiter) {
f(str, from, i);
from = i + 1;
}
}
if (from <= str.size())
f(str, from, str.size());
}
int main(int argc, char* argv[]) {
if (argc != 2)
return 1;
splitString(argv[1], '','', [](const std::string &s, std::size_t from, std::size_t to) {
std::cout << "`" << s.substr(from, to - from) << "`/n";
});
return 0;
}
Buenas propiedades:
- Sin dependencias (por ejemplo, impulsar)
- No es un one-liner loco
- Fácil de entender (espero)
- Maneja espacios perfectamente bien
- No asigna divisiones si no desea, por ejemplo, puede procesarlas con una lambda como se muestra.
- No agrega caracteres de a uno por vez, debe ser rápido.
- Si usa C ++ 17, puede cambiarlo para usar
std::stringview
y luego no hará ninguna asignación y será extremadamente rápido.
Algunas elecciones de diseño que quizás desee cambiar:
- Las entradas vacías no son ignoradas.
- Una cadena vacía llamará a f () una vez.
Ejemplo de entradas y salidas:
"" -> {""}
"," -> {"", ""}
"1," -> {"1", ""}
"1" -> {"1"}
" " -> {" "}
"1, 2," -> {"1", " 2", ""}
" ,, " -> {" ", "", " "}
Otro enfoque más bien diferente: use una configuración regional especial que trate las comas como espacios en blanco:
#include <locale>
#include <vector>
struct csv_reader: std::ctype<char> {
csv_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());
rc['',''] = std::ctype_base::space;
rc[''/n''] = std::ctype_base::space;
rc['' ''] = std::ctype_base::space;
return &rc[0];
}
};
Para usar esto, imbue()
una secuencia con una configuración regional que incluye esta faceta. Una vez que haya hecho eso, puede leer los números como si las comas no estuvieran allí. Solo por ejemplo, leeremos los números delimitados por comas de la entrada, y escribiremos una línea por cada salida estándar:
#include <algorithm>
#include <iterator>
#include <iostream>
int main() {
std::cin.imbue(std::locale(std::locale(), new csv_reader()));
std::copy(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(),
std::ostream_iterator<int>(std::cout, "/n"));
return 0;
}
Solución alternativa usando algoritmos genéricos y Boost.Tokenizer :
struct ToInt
{
int operator()(string const &str) { return atoi(str.c_str()); }
};
string values = "1,2,3,4,5,9,8,7,6";
vector<int> ints;
tokenizer<> tok(values);
transform(tok.begin(), tok.end(), back_inserter(ints), ToInt());
También podría usar la siguiente función.
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.
lastPos = str.find_first_not_of(delimiters, pos);
// Find next non-delimiter.
pos = str.find_first_of(delimiters, lastPos);
}
}
Todavía no puedo comentar (comencé en el sitio) pero agregué una versión más genérica de la clase derivada del fantástico tipo de Jerry Coffin a su publicación.
Gracias Jerry por la súper idea.
(Debido a que debe ser revisado por pares, agregándolo aquí también temporalmente)
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];
}
};
estructura simple, fácil de adaptar, mantenimiento fácil.
std::string stringIn = "my,csv,,is 10233478,separated,by commas";
std::vector<std::string> commaSeparated(1);
int commaCounter = 0;
for (int i=0; i<stringIn.size(); i++) {
if (stringIn[i] == ",") {
commaSeparated.push_back("");
commaCounter++;
} else {
commaSeparated.at(commaCounter) += stringIn[i];
}
}
al final tendrá un vector de cadenas con cada elemento de la oración separado por espacios. las cadenas vacías se guardan como elementos separados.
#include <sstream>
#include <vector>
const char *input = "1,1,1,1,2,1,1,1,0";
int main() {
std::stringstream ss(input);
std::vector<int> output;
int i;
while (ss >> i) {
output.push_back(i);
ss.ignore(1);
}
}
Una mala entrada (por ejemplo, separadores consecutivos) lo estropeará, pero dijiste simple.
#include <sstream>
#include <vector>
#include <algorithm>
#include <iterator>
const char *input = ",,29870,1,abc,2,1,1,1,0";
int main()
{
std::stringstream ss(input);
std::vector<int> output;
int i;
while ( !ss.eof() )
{
int c = ss.peek() ;
if ( c < ''0'' || c > ''9'' )
{
ss.ignore(1);
continue;
}
if (ss >> i)
{
output.push_back(i);
}
}
std::copy(output.begin(), output.end(), std::ostream_iterator<int> (std::cout, " ") );
return 0;
}
#include <vector>
#include <string>
#include <sstream>
#include <iostream>
int main()
{
std::string str = "1,2,3,4,5,6";
std::vector<int> vect;
std::stringstream ss(str);
int i;
while (ss >> i)
{
vect.push_back(i);
if (ss.peek() == '','')
ss.ignore();
}
for (i=0; i< vect.size(); i++)
std::cout << vect.at(i)<<std::endl;
}
bool GetList (const std::string& src, std::vector<int>& res)
{
using boost::lexical_cast;
using boost::bad_lexical_cast;
bool success = true;
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
boost::char_separator<char> sepa(",");
tokenizer tokens(src, sepa);
for (tokenizer::iterator tok_iter = tokens.begin();
tok_iter != tokens.end(); ++tok_iter) {
try {
res.push_back(lexical_cast<int>(*tok_iter));
}
catch (bad_lexical_cast &) {
success = false;
}
}
return success;
}
std::string input="1,1,1,1,2,1,1,1,0";
std::vector<long> output;
for(std::string::size_type p0=0,p1=input.find('','');
p1!=std::string::npos || p0!=std::string::npos;
(p0=(p1==std::string::npos)?p1:++p1),p1=input.find('','',p0) )
output.push_back( strtol(input.c_str()+p0,NULL,0) );
Sería una buena idea verificar los errores de conversión en strtol()
, por supuesto. Quizás el código también se pueda beneficiar de algunas otras comprobaciones de errores.
string exp = "token1 token2 token3";
char delimiter = '' '';
vector<string> str;
string acc = "";
for(int i = 0; i < exp.size(); i++)
{
if(exp[i] == delimiter)
{
str.push_back(acc);
acc = "";
}
else
acc += exp[i];
}
void ExplodeString( const std::string& string, const char separator, std::list<int>& result ) {
if( string.size() ) {
std::string::const_iterator last = string.begin();
for( std::string::const_iterator i=string.begin(); i!=string.end(); ++i ) {
if( *i == separator ) {
const std::string str(last,i);
int id = atoi(str.c_str());
result.push_back(id);
last = i;
++ last;
}
}
if( last != string.end() ) result.push_back( atoi(&*last) );
}
}