Простой анализ строки с C ++ - PullRequest
33 голосов
/ 21 мая 2010

Я использую C ++ уже довольно давно, но, тем не менее, я склонен прибегать к scanf, когда мне приходится анализировать простые текстовые файлы. Например, с учетом такой конфигурации (также предполагается, что порядок полей может отличаться):

foo: [3 4 5]
baz: 3.0

Я бы написал что-то вроде:

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

Какой самый краткий способ добиться этого в C ++? Всякий раз, когда я пытаюсь это сделать, я получаю много кода для строительных лесов.

Ответы [ 6 ]

29 голосов
/ 21 мая 2010

Это попытка использовать только стандартный C ++.

Большую часть времени я использую комбинацию std :: istringstream и std :: getline (которая может работать для разделения слов), чтобы получить то, что я хочу.И если я могу, я могу сделать так, чтобы мои конфигурационные файлы выглядели так:

foo = 1,2,3,4

, что облегчает процесс.

текстовый файл выглядит так:

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

И вы разбираете это так:

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" )
            {
               //...
    }
}
19 голосов
/ 02 января 2011

Библиотека C ++ String Toolkit (StrTk) имеет следующее решение вашей проблемы:

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

Больше примеров можно найти Здесь

5 голосов
/ 24 февраля 2011

Boost.Spirit не зарезервирован для анализа сложной структуры. Он также хорош в микропарсинге и почти соответствует компактности фрагмента 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;
   }
}

(Почему они не добавили «контейнерную» версию, мне не подходит, было бы гораздо удобнее, если бы мы могли просто написать:

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

Но это правда, что:

  • Это добавляет много времени на компиляцию.
  • Сообщения об ошибках являются жестокими. Если вы допустите небольшую ошибку с помощью scanf, вы просто запустите свою программу и сразу получите segfault или абсурдное проанализированное значение. Сделайте небольшую ошибку с помощью Spirit, и вы получите безнадежные гигантские сообщения об ошибках от компилятора, а для их понимания требуется много практики с boost.spirit.

Так что, в конечном счете, для простого анализа я использую scanf, как и все остальные ...

3 голосов
/ 16 августа 2012

Регулярные выражения часто можно использовать для разбора строк. Используйте capture groups (круглые скобки), чтобы получить различные части анализируемой строки.

Например, для разбора выражения типа foo: [3 4 56] используйте регулярное выражение (.*): \[(\d+) (\d+) (\d+)\]. Первая группа захвата будет содержать «foo», вторая, третья и четвертая будут содержать цифры 3, 4 и 56.

Если существует несколько возможных форматов строк, которые необходимо проанализировать, как в примере, приведенном OP, либо примените отдельные регулярные выражения одно за другим и посмотрите, какой из них соответствует, или напишите регулярное выражение, которое соответствует всем возможным вариантам обычно с использованием оператора | (set union).

Регулярные выражения очень гибкие, поэтому выражение можно расширить, чтобы разрешить больше вариаций, например, произвольное количество пробелов и других пробелов после : в примере. Или только для того, чтобы числа содержали определенное количество цифр.

В качестве дополнительного бонуса регулярные выражения обеспечивают неявную проверку, поскольку они требуют идеального соответствия. Например, если номер 56 в приведенном выше примере был заменен на 56x, сопоставление не будет выполнено. Это также может упростить код, так как в приведенном выше примере группы, содержащие числа, могут быть безопасно преобразованы в целые числа без какой-либо дополнительной проверки после успешного сопоставления.

Регулярные выражения обычно работают с хорошей производительностью, и есть много хороших библиотек на выбор. Например, Boost.Regex .

1 голос
/ 21 мая 2010

Я чувствую твою боль. Я регулярно работаю с файлами с полями фиксированной ширины (вывод через код Fortran77), поэтому всегда интересно пытаться загрузить их с минимумом суеты. Лично я хотел бы, чтобы boost::format предоставил реализацию scanf. Но, исключая реализацию, я делаю что-то похожее на @Nikko, используя boost::tokenizer со смещенными разделителями и лексическое приведение для преобразования. Например,

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

Примечание: boost::lexical_cast плохо справляется с начальным пробелом (он выбрасывает), поэтому я рекомендую обрезать пробел для всего, что вы передаете.

1 голос
/ 21 мая 2010

Я думаю, что Boost.Spirit - хороший способ описать грамматику прямо в вашем коде C ++. Требуется некоторое время, чтобы привыкнуть к Boost.Spirit, но после этого его довольно легко использовать. Это может быть не так кратко, как вы, возможно, хотите, но я думаю, что это удобный способ обработки простых грамматик. Его производительность может быть проблемой, поэтому вполне вероятно, что в ситуациях, когда вам нужна скорость, это может быть не лучшим выбором.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...