Использование C / C ++ для эффективной десериализации строки, состоящей из чисел с плавающей запятой, токенов и пустых строк - PullRequest
4 голосов
/ 14 января 2010

У меня есть большие строки, которые похожи на следующие ...

some_text_token

24.325973 -20.638823  

-1.964366 0.753947  
-1.290811 -3.547422  
0.813014 -3.547227  

0.472015 3.723311  
-0.719116 3.676793  

other_text_token  

24.325973 20.638823  

-1.964366 0.753947  
-1.290811 -3.547422  
-1.996611 -2.877422  
0.813014 -3.547227  

1.632365 2.083673  
0.472015 3.723311  
-0.719116 3.676793  

...

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

  1. текстовые токены
  2. значения с плавающей точкой
  3. пустые строки

... но у меня проблемы.

Я попробовал strtod и успешно извлек плавающие числа из строки, но я не могу получить цикл с использованием strtod, чтобы сообщить мне чередующиеся текстовые токены и пустые строки. Я не уверен на 100%, что strtod - это "правильный путь", учитывая чередующиеся токены и пустые строки, которые меня также интересуют.

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

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

Есть указатели? Меня очень интересует , почему различные подходы функционируют более или менее эффективно. Спасибо !!!

Ответы [ 3 ]

5 голосов
/ 14 января 2010

Используя C, я бы сделал что-то вроде этого (не проверено):

#include <stdio.h>

#define MAX 128

char buf[MAX];
while (fgets(buf, sizeof buf, fp) != NULL) {
    double d1, d2;
    if (buf[0] == '\n') {
        /* saw blank line */
    } else if (sscanf(buf, "%lf%lf", &d1, &d2) != 2) {
        /* buf has the next text token, including '\n' */
    } else {
        /* use the two doubles, d1, and d2 */
    }
}

Проверка на пустую строку - первая, потому что она относительно недорогая. В зависимости от ваших потребностей:

  1. вам может потребоваться увеличить / изменить MAX,
  2. вам может потребоваться проверить, заканчивается ли buf новой строкой, если нет, то строка была слишком длинной (в этом случае перейдите к 1 или 3),
  3. вам может понадобиться функция, которая читает полные строки из файла, используя malloc() и realloc() для динамического выделения буфера (подробнее см. this ),
  4. вы можете позаботиться об особых случаях, таких как одно значение с плавающей запятой в строке (что, я полагаю, не произойдет). sscanf() возвращает количество элементов ввода, успешно сопоставленных и назначенных.

Я также предполагаю, что пустые строки действительно пустые (только символ новой строки). Если нет, вам нужно будет пропустить ведущий пробел. isspace() в ctype.h полезно в этом случае.

fp является действительным FILE * объектом, возвращаемым fopen().

4 голосов
/ 14 января 2010

Ух ты, я больше не пишу много парсеров на C

Это было проверено на входе ОП

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef enum {
  scan_blank, scan_label, scan_float
} tokens;

double f1, f2;

char line[512], string_token[sizeof line];

tokens scan(void) {
  char *s;
  for(s = line; *s; ++s) {
    switch(*s) {
      case ' ':
      case '\t':
        continue;
      case '\n':
        return scan_blank;
      case '0': case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8': case '9':
      case '.': case '-':
        sscanf(line, " %lf %lf", &f1, &f2);
        return scan_float;
      default:
        sscanf(line, " %s", string_token);
        return scan_label;
    }
    abort();
  }
  abort();
}

int main(void) {
  int n;
  for(n = 1;; ++n) {
    if (fgets(line, sizeof line, stdin) == NULL)
      return 0;
    printf("%2d %-40.*s", n, (int)strlen(line)-1, line);
    switch(scan()) {
      case scan_blank:
        printf("blank\n");
        break;
      case scan_label:
        printf("label [%s]\n", string_token);
        break;
      case scan_float:
        printf("floats [%lf %lf]\n", f1, f2);
        break;
    }
  }
}
1 голос
/ 14 января 2010

Это немного грубо и непроверено, но общая идея состоит в том, чтобы попытаться проанализировать каждую строку и посмотреть, что там есть:

while (!feof (stdin))
{
    char buf [100];
    (!fgets (buf, sizeof buf, stdin))
        break;  // end of file or error

    // skip leading whitespace
    char *cp = buf;
    while (isspace (*cp))
         ++cp;

    if (*cp == '\000')  // blank line?
    {
        do_whatever_for_a_blank_line ();
        continue;
    }

    // try reading a float
    double v1, v2;
    char *ep = NULL;
    v1 = strtod (cp, &ep);
    if (ep == cp)   // if nothing parsed
    {
        do_whatever_for_a_text_token (cp);
        continue;
    }

    while (isspace (*cp))
       ++cp;
    ep = NULL;
    v2 = strtod (cp, &ep);
    if (ep == cp)   // if no float parsed
    {
         handle_single_floating_value (v1);
         continue;
    }
    handle_two_floats (v1, v2);  
 }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...