Считывание данных из файла с произвольной строки

Рейтинг: 0Ответов: 2Опубликовано: 21.12.2014

У меня есть следующий текстовый файл:

Input data:
 N         tn         t1         t2         tk         u1         u2
21        5.000     20.000     50.000     65.000    100.000     95.000

 i       Time        Uvx      Uvix
 1      5.000      0.000      0.000
 2      8.000     20.000     50.000
 3     11.000     40.000     50.000
 4     14.000     60.000     50.000
 5     17.000     80.000     50.000
 6     20.000    100.000     50.000
 7     23.000     99.500     50.000
 8     26.000     99.000     50.000
 9     29.000     98.500     50.000

Длительность переднего фронта:

  Uvx      Uvix
 9.000      0.000

Задача заключается в том, чтобы считать с этого файла все числовые значения, минуя при этом строки с названиями переменных и прочим текстом, а также пустые строки. Так вот как пропустить эти самые строки и начать считывать данные с произвольной строки? Язык - Си.

Ответы

▲ 6Принят

Чтобы избежать загрузки в память строк, которые всё равно будут проигнорированы и чтобы поддерживать произвольно большие входные строки, можно читать файл побайтно, используя fgetc().

Чтобы пропустить строки до тех пор пока не встретится строка, которая начинается с цифры (строка с численными данными), игнорируя возможные пробелы, можно использовать автомат с двумя состояниями:

  1. читаем до тех пор пока не прочитан символ новой строки (\n)
  2. переходим в состояние ожидания цифры (expect_digit=1): все пробелы и табы игнорируются, если прочитана цифра, то возвращаем её назад в поток и выходим из функции, в противном случае (не цифра) возвращаемся в начальное состояние 1.

#include <stdio.h>

// read until a line that starts with a digit (ignoring leading hor.space)
int skip_until_line_startswith_digit(FILE* file)
{
  int expect_digit = 1; // expect a digit as the next character
  for (int c; (c = fgetc(file)) != EOF; ) {
    switch (c) {
    case '\n': // newline
      expect_digit = 1;
      break;
    case ' ': // horizontal whitespace
    case '\t':
      // ignore
      break;
    case '0': // digit
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
      if (expect_digit) {
        if (ungetc(c, file) == EOF)
          return -2; // error
        return 0; // found digit
      }
      // unexpected digit: ignore
      break;
    default: // everything else
      expect_digit = 0;
    }
  }
  return feof(file) ? -1 : -2; // not found or an error
}

isdigit() может зависеть от локали на Винде, поэтому она здесь не используется.

Пример использования

FILE* file = stdin;
if (skip_until_line_startswith_digit(file) < 0)
  exit(EXIT_FAILURE);

// read 1st numeric block
size_t N;
double tn, t1, t2, tk, u1, u2;
errno = 0;
if (fscanf(file, "%zu %lf  %lf  %lf  %lf  %lf  %lf",
                  &N, &tn, &t1, &t2, &tk, &u1, &u2) != 7) {
  if (errno) perror("N tn t1 t2 tk u1 u2");
  exit(EXIT_FAILURE);
}
printf("%zu %f %f %f %f %f %f\n", N, tn, t1, t2, tk, u1, u2);

А по поводу зависимости isdigit от локали -- можно поподробнее (я думаю, это всем будет интересно)? В каких случаях это не будет работать для распознавания чисел (и почему только в винде)?

С стандарт говорит (например, см. сноску в 7.11.1.1 в n1570), что isdigit() не зависит от локали, но на Винде isdigit() может зависеть от локали, например, '\xb2' в cp1252 кодировке в Danish локале распознается как десятичная цифра, что не правильно.

Кстати, Unicode стандарт солидарен с С стандартом: верхние индексы такие как U+00B2 явно исключены see 4.6 Numerical Value (Unicode 6.2).

▲ 3

@Иван Кущёв, fscanf не пойдет, т.к. она читает не по строкам. Можно, например, так:

 int c, nl = 0, skip_lines = сколько строк пропустить;

 while ((c = fgetc(f)) != EOF)
   if ((c == '\n') && (++nl == skip_lines))
     break;
 if (feof(f))
   fatal("Bad data");

Если размер строк, точнее смещение интересующей строки в файле известно (или его можно вычислить), то см. man 3 fseek.

Обновление

@VladD, после getline free не нужна (а если вызывать free, тогда указатель опять надо обнулить).

Можно так (одной строкой):

 while (n-- && getline(&s, &len, file) > 0);
 if (n != -1)
   fatal(...);
 printf("last skipped line: %s", s);

только сожрет немного больше ресурсов (а вообще-то, я отвечая, просто и забыл про getline).

--

@Иван Кущёв, м.б. на практике для Вашей задачи лучше просто пропустить строки до начала данных?

 while (getline(&s, &len, file) > 0 && *s != 'i');

а дальше читать построчно и использовать sscanf(s, "%d %d %d %d", ...) для выборки данных.

Обновление

Ага, пусть так. Идея-то остается. Все равно ведь 3 разных блока данных.

Просто 3 раза while (getline(...) && ...); потом чтение (один раз в цикле).


А если у автора в libc нет getline, то пусть напишет свою версию (заодно потренируется, тем более что во втором блоке данных ему может понадобиться динамический массив структур).