stdlib - Análisis de cadena simple con C++
rand() stdlib (7)
He estado usando C ++ durante bastante tiempo pero, sin embargo, tiendo a recurrir a scanf
cuando tengo que analizar archivos de texto simples. Por ejemplo, dado una configuración como esta (también suponiendo que el orden de los campos podría variar):
foo: [3 4 5]
baz: 3.0
Escribiría algo como:
char line[SOME_SIZE];
while (fgets(line, SOME_SIZE, file)) {
int x, y, z;
if (3 == sscanf(line, "foo: [%d %d %d]", &x, &y, &z)) {
continue;
}
float w;
if (1 == sscanf(line, "baz: %f", &w)) {
continue;
}
}
¿Cuál es la forma más concisa de lograr esto en C ++? Cada vez que intento termino con un montón de código de andamio.
Boost.Spirit no está reservado para analizar estructura complicada. También es bastante bueno en micro-análisis, y casi coincide con la compacidad del fragmento C + scanf:
#include <boost/spirit/include/qi.hpp>
#include <string>
#include <sstream>
using namespace boost::spirit::qi;
int main()
{
std::string text = "foo: [3 4 5]/nbaz: 3.0";
std::istringstream iss(text);
std::string line;
while (std::getline(iss, line))
{
int x, y, z;
if(phrase_parse(line.begin(), line.end(), "foo: [">> int_ >> int_ >> int_ >> "]", space, x, y, z))
continue;
float w;
if(phrase_parse(line.begin(), line.end(), "baz: ">> float_, space , w))
continue;
}
}
(Por qué no agregaron una versión de "contenedor" me supera, sería mucho más conveniente si pudiéramos simplemente escribir:
if(phrase_parse(line, "foo: [">> int_ >> int_ >> int_ >> "]", space, x, y, z))
continue;
Pero es verdad que:
- Agrega mucho tiempo de compilación.
- Los mensajes de error son brutales. Si comete un pequeño error con scanf, simplemente ejecute su programa e inmediatamente obtenga un segfault o un absurdo valor analizado. Comete un pequeño error con el espíritu y obtendrá mensajes de error gigantescos sin esperanza del compilador y se necesita mucha práctica con boost.spirit para comprenderlos.
Así que, en última instancia, para el análisis simple utilizo scanf como todos los demás ...
Creo que Boost.Spirit es una buena forma de describir una gramática directamente en tu código C ++. Lleva un tiempo acostumbrarse a Boost.Spirit pero después es bastante fácil de usar. Puede que no sea tan conciso como probablemente quieras, pero creo que es una forma práctica de manejar gramáticas sencillas. Su rendimiento puede ser un problema, por lo que es probable que en situaciones donde necesites velocidad no sea una buena opción.
Este es un intento utilizando solo C ++ estándar.
La mayoría de las veces uso una combinación de std :: istringstream y std :: getline (que puede funcionar para separar palabras) para obtener lo que quiero. Y si puedo, puedo hacer que mis archivos de configuración se vean así:
foo = 1,2,3,4
que lo hace fácil
el archivo de texto es así:
foo=1,2,3,4
bar=0
Y lo analizas así:
int main()
{
std::ifstream file( "sample.txt" );
std::string line;
while( std::getline( file, line ) )
{
std::istringstream iss( line );
std::string result;
if( std::getline( iss, result , ''='') )
{
if( result == "foo" )
{
std::string token;
while( std::getline( iss, token, '','' ) )
{
std::cout << token << std::endl;
}
}
if( result == "bar" )
{
//...
}
}
La biblioteca C ++ String Toolkit (StrTk) tiene la siguiente solución a su problema:
#include <string>
#include <deque>
#include "strtk.hpp"
int main()
{
std::string file_name = "simple.txt";
strtk::for_each_line(file_name,
[](const std::string& line)
{
std::deque<std::string> token_list;
strtk::parse(line,"[]: ",token_list);
if (token_list.empty()) return;
const std::string& key = token_list[0];
if (key == "foo")
{
//do ''foo'' related thing with token_list[1]
//and token_list[2]
return;
}
if (key == "bar")
{
//do ''bar'' related thing with token_list[1]
return;
}
});
return 0;
}
Más ejemplos se pueden encontrar Here
Lamentamos no poder darle detalles, pero C ++ 11 tiene características de expresión regulares. Quizás puedas buscarlos. Parecen ser genéricos (como en usted puede hacer coincidir una secuencia de cualquier cosa, y no solo una secuencia de caracteres).
Tal vez haya una solución interesante para su problema allí.
Las expresiones regulares a menudo se pueden usar para analizar cadenas. Utilice capture groups
(paréntesis) para obtener las diversas partes de la línea que se analiza.
Por ejemplo, para analizar una expresión como foo: [3 4 56]
, use la expresión regular (.*): /[(/d+) (/d+) (/d+)/]
. El primer grupo de captura contendrá "foo", el segundo, el tercero y el cuarto contendrán los números 3, 4 y 56.
Si hay varios formatos de cadena posibles que necesitan ser analizados, como en el ejemplo dado por el OP, aplique expresiones regulares separadas una a una y vea cuál coincide, o escriba una expresión regular que coincida con todas las variaciones posibles, generalmente usando el |
(establecer unión) operador.
Las expresiones regulares son muy flexibles, por lo que la expresión se puede ampliar para permitir más variaciones, por ejemplo, un número arbitrario de espacios y otros espacios en blanco después de :
en el ejemplo. O solo permita que los números contengan una cierta cantidad de dígitos.
Como una ventaja adicional, las expresiones regulares proporcionan una validación implícita ya que requieren una combinación perfecta. Por ejemplo, si el número 56
en el ejemplo anterior fue reemplazado por 56x
, la coincidencia fallaría. Esto también puede simplificar el código ya que, en el ejemplo anterior, los grupos que contienen los números se pueden convertir de manera segura en números enteros sin que se requiera ninguna verificación adicional después de una coincidencia exitosa.
Las expresiones regulares generalmente se ejecutan con buen rendimiento y hay muchas buenas bibliotecas para elegir. Por ejemplo, Boost.Regex .
Siento tu dolor. Trato regularmente archivos que tienen campos de ancho fijo (salida a través del código Fortran77), por lo que siempre es entretenido intentar cargarlos con el mínimo de alboroto. Personalmente, me gustaría ver que el suministro de boost::format
una implementación de scanf. Pero, salvo que lo implemente yo mismo, hago algo similar a @Nikko usando boost::tokenizer
con separadores de desplazamiento y conversión léxica para la conversión. Por ejemplo,
typedef boost::token_iterator_generator<
boost::char_separator<char> >::type tokenizer;
boost::char_separator<char> sep("=,");
std::string line;
std::getline( file_istream, line );
tokenizer tok = boost::make_token_iterator< std::string > (
line.begin(), line.end() sep );
std::string var = *tok; // need to check for tok.at_end() here
++tok;
std::vector< int > vals;
for(;!tok.at_end();++tok){
vals.push_back( boost::lexical_cast< int >( trimws( *tok ) );
}
Nota: boost::lexical_cast
no funciona bien con los espacios en blanco boost::lexical_cast
(arroja), por lo que recomiendo recortar el espacio en blanco de todo lo que pase.