2009-06-30 3 views
1

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

В настоящее время мой код сканирования требует обозначающая размер вектора в качестве первого элемента в файле:

7 : 1 3 6 8 -9 .123 1.1 

Что беспокоит меня, потому что «7» может быть определена путем проверки белого пространства.

Я пробовал различные формы fscanf(), strtok() и т. Д., Но все они кажутся грубыми forcish. Не прибегая к lex/yacc (не доступно), кто-то может предложить что-то более элегантное, чем следующее?

typedef struct vector_tag 
{ 
    int Length; 
    double * value; 
} vector; 

vector v; 

char buf[BIG_ENOUGH], key[BIG_ENOUGH], val[BIG_ENOUGH]; 

void scan_vector(FILE * fh) 
{ 
    int i, length; 
    double * data; 
    char * tok; 

    do { 
     if (feof(fh)) return; 
     fgets(buf, sizeof buf, fh);  
    } while (2 != sscanf(buf,"%[^:]:%[^\n\r]",key,val)); 

    length  = 
    v.Length = strtol(key,NULL,10); 
    data  = 
    v.value  = malloc(length * sizeof(double)); 

    tok = strtok(val, " "); /* I'd prefer tokenizing on whitespace */ 
    for (i = 0; i++ < v.Length;) { 
     * data++ = strtod(tok,NULL);; 
     tok = strtok(NULL, " "); /* Again, tokenize on whitespace */ 
    } 
} 

Решение: Благодаря проверенному ответ, я реализовал:

static int scan_vector(FILE * fh, vector * v) 
{ 
    if (1 == fscanf(fh,"%d:",& v->length)) 
    { 
     int   i; 

     v->value = malloc(v->Length * sizeof(double)); 

     assert (NULL != v->value); 

     for (i = 0; i < v->Length; i++) 
     { 
      if (fscanf(fh,"%lf",v->value + i) != 1) return(0); 
     } 
     return(1); 
    } 
    return(0); 
} /* scan_vector() */ 
+0

Если у вас есть контроль над форматированием входных данных, вы можете попытаться обойтись без длины вектора. Это удаляет специальный корпус для длины вектора и перемещает вас прямо в токенизацию. Если вы можете надежно узнать свою длинную векторную строку, вы можете проанализировать ее в массиве и затем распределить вектор vector.value на основе количества значений, обозначенных как токенизация. Sscanf заставляет меня съеживаться, но каждый из них. – Erik

ответ

1

, что случилось что-то вроде:

int scan_vector(FILE *fh) 
{ 
    char pad[2]; 
    int i; 
    if (fscanf(fh,"%d %1[:]", &v.Length, &pad) != 2) 
     return -1; 
    v.value = malloc(v.Length * sizeof(double)); 
    for (i = 0; i < v.Length; i++) { 
     if (fscanf(fh, "%lf", &v.value[i]) != 1) 
      return -1; 
    } 
    return 0; 
} 

Это пытается прочитать вектор с зсапЕ, и возвращает -1 код ошибки, если возникла проблема.

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

+0

Легкие оценки. Это было прямо передо мной, спасибо за то, что я проник в мой вопрос. – Jamie

+0

(Помимо «scanf» -> 'fscanf' typo) +1 – Jamie

0

Если вы используете realloc() вы всегда можете обратиться за дополнительной памяти, если не выделить достаточно с начальным malloc(). Общая стратегия заключается в том, чтобы начать произвольное задание n. Всякий раз, когда вы выбегаете из пространства, вы удваиваете n и изменяете размер буфера.

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

+0

Это больше в сканировании файла, чем управление памятью, за которым я работаю; хотя мне, конечно, понадобилось бы временное пространство для сканирования, если бы я не знал apriori, сколько переменных мне нужно будет сканировать. – Jamie

+0

Хорошо, я перестану быть сродни. Я намекал (oops), что вы можете читать числа в одном за раз и realloc() по мере необходимости. Это позволяет читать в каждой строке за один проход без маркера длины. –

0

Насколько велики ваши векторы?
Один способ пойти,

  • сканировать строку в локальный буфер (это один вектор данных я полагаю)
  • сканирование в течение этого локального буфера для подсчета пробельные разделители (довольно легко кода)
  • затем сделать правильное распределение
  • и инициализировать вектор

Как вы заметили, размерность '7' не обязательно должны быть частью ввода.
Вам нужен только один локальный буфер, достаточно большой для самой длинной линии.
И некоторые обработки ошибок для этого :-)

0

Вот версия, которая не нуждается размер вектора, как первая запись в файле:

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

#define LINE_MAX 256 
#define VECTOR_SIZE_MAX 32 

struct vector 
{ 
    size_t size; 
    double *values; 
}; 

// returns 1 on error 
_Bool scan_vector(FILE *file, struct vector *v) 
{ 
    char buffer[LINE_MAX]; 
    if(!fgets(buffer, sizeof(buffer), file)) 
     return 1; 

    double values[VECTOR_SIZE_MAX]; 

    size_t size = 0; 
    errno = 0; 

    for(char *head = buffer, *tail = NULL;; ++size, head = tail) 
    { 
     while(isspace(*head)) ++head; 
     if(!*head) break; 

     if(size >= VECTOR_SIZE_MAX) 
      return 1; 

     values[size] = strtod(head, &tail); 
     if(errno || head == tail) 
      return 1; 
    } 

    v->size = size; 
    v->values = malloc(sizeof(double) * size); 
    if(!v->values) return 1; 

    memcpy(v->values, values, sizeof(double) * size); 

    return 0; 
} 

int main(void) 
{ 
    struct vector v; 
    while(!scan_vector(stdin, &v)) 
    { 
     printf("value count: %u\n", (unsigned)v.size); 
     free(v.values); 
    } 

    return 0; 
} 

Максимальный размер строки и число записей фиксированы из соображений производительности и лени.