Eenvoudig ontleden van strings met C++

Ik gebruik C++ nu al een hele tijd, maar toch val ik vaak terug op scanfwanneer ik eenvoudige tekstbestanden moet ontleden. Bijvoorbeeld een configuratie als deze gegeven (ook in de veronderstelling dat de volgorde van de velden zou kunnen variëren):

foo: [3 4 5]
baz: 3.0

Ik zou zoiets schrijven als:

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;
    }
}

Wat is de meest beknopte manier om dit in C++ te bereiken? Telkens als ik het probeer, krijg ik veel steigercode.


Antwoord 1, autoriteit 100%

Dit is een poging met alleen standaard C++.

Meestal gebruik ik een combinatie van std::istringstream en std::getline (die kan werken om woorden te scheiden) om te krijgen wat ik wil. En als ik kan, laat ik mijn configuratiebestanden er als volgt uitzien:

foo=1,2,3,4

wat het gemakkelijk maakt.

tekstbestand ziet er als volgt uit:

foo=1,2,3,4
bar=0

En je analyseert het als volgt:

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" )
            {
               //...
    }
}

Antwoord 2, autoriteit 63%

De C++ String Toolkit Library (StrTk)heeft de volgende oplossing voor uw probleem:

#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;
}

Meer voorbeelden vindt u Hier


Antwoord 3, autoriteit 20%

Boost.Spirit is niet gereserveerd voor het ontleden van ingewikkelde structuren. Het is ook behoorlijk goed in micro-parsing en komt bijna overeen met de compactheid van het C + scanf-fragment:

#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;
   }
}

(Waarom ze geen “container”-versie hebben toegevoegd, is mij een raadsel, het zou veel handiger zijn als we gewoon konden schrijven:

if(phrase_parse(line, "foo: [">> int_ >> int_ >> int_ >> "]", space, x, y, z))
   continue;

Maar het is waar dat:

  • Het voegt veel overhead toe aan het compileren.
  • Foutberichten zijn brutaal. Als je een klein foutje maakt met scanf, voer je gewoon je programma uit en krijg je meteen een segfault of een absurde ontlede waarde. Maak een klein foutje met spirit en je krijgt hopeloos gigantische foutmeldingen van de compiler en het vergt VEEL oefening met boost.spirit om ze te begrijpen.

Dus uiteindelijk gebruik ik voor eenvoudige ontleding scanf zoals iedereen…


4, Autoriteit 7%

Ik voel je pijn. Ik werk regelmatig met bestanden die velden met een vaste breedte hebben (uitvoer via Fortran77-code), dus het is altijd leuk om te proberen ze met een minimum aan gedoe te laden. Persoonlijk zou ik graag zien dat boost::formateen scanf-implementatie levert. Maar, tenzij ik het zelf implementeer, doe ik iets dat lijkt op @Nikko met behulp van boost::tokenizermet offset-scheidingstekens en lexicale castvoor conversie. Bijvoorbeeld,

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 ) );
}

Opmerking: boost::lexical_castkan niet goed omgaan met voorloopwitruimte (het gooit), dus ik raad aan om de witruimte bij te knippen van alles wat je erdoor geeft.


Antwoord 5, autoriteit 3%

Ik denk dat Boost.Spirit een goede manier is om een grammatica te beschrijven in uw C++ -code. Het kost wat tijd om te wennen aan Boost.Spirit, maar nadat het vrij eenvoudig is om het te gebruiken. Het is misschien niet zo beknopt als waarschijnlijk je wilt, maar ik denk dat het een handige manier is om eenvoudige grammatica’s te hanteren. De prestaties kunnen een probleem zijn, dus het is waarschijnlijk dat het in situaties is waar je snelheid nodig hebt.


6

Ik denk niet dat een van de ingediende antwoorden beknopt zijn dan wat het opleverde oplaat. Bovendien maken de ingediende antwoorden de code minder draagbaar, langzamer om te compileren en moeilijker te lezen. Als er andere doelen zijn (bijvoorbeeld een specifieke veiligheids- of snelheidsvereiste), kunnen de ingediende antwoorden meer geschikt zijn, maar gezien de doelstellingen die zijn vermeld in de vraag met sscanfis nog steeds het antwoord op Beat.

Other episodes