Возникли проблемы при чтении из текстового файла в массив структур - PullRequest
0 голосов
/ 04 октября 2018

Я недавно начал в университете с программирования C (курс для начинающих), и теперь мы проводим наш заключительный экзамен, который касается базы данных пациентов.

Мне необходимо прочитать данные из текстового файла в массив struct (размер 10000).Файл содержит 2 строковых массива (личную идентификационную строку (10 чисел, разделенных символом «-») и строку имени), 1 массив int, содержащий ссылки на фотографии, и 1 целое число, содержащее количество ссылок на фотографии на пациента.Я пробовал fscanf, но программа просто зависает всякий раз, когда я пытаюсь читать, когда я использую fgets, она читает всю строку и сохраняет целые числа из массива ссылок на фотографии в массив имен (средний).Мне интересно, как мне поступить, я потратил несколько дней, пытаясь найти решение, но, похоже, ничего не работает.Вот как выглядит мой текстовый файл:

123456-1234   Name Name     [1, 2, 3, 4]
234567-2345   Name2 Name2   [1, 2]
345678-3456   Name3 Name3   []

И это моя функция write_to_file, которая записывает в файл при выходе из программы:

void write_to_file(Patient reg[], int *pNr_of_patients){
FILE *fp;
fp=fopen("file.txt","w");
if(*pNr_of_patients>0){
    int i,j;
    for(i=0;i<*pNr_of_patients;i++){
        fprintf(fp,"%s\t%s\t[",reg[i].pers_nr,reg[i].name);
        for(j=0;j<reg[i].nr_of_ref-1;j++){
            fprintf(fp,"%d, ",reg[i].photo_ref[j]);
        }
        if(reg[i].photo_ref[j]==0){
            fprintf(fp,"]");
        }else{
            fprintf(fp,"%d]",reg[i].photo_ref[j]);
        }
        fprintf(fp,"\n");
    }
    fclose(fp);
}
}

Это моя функция read_from_file, этоотсутствует код для чтения значений массива int в конце:

Редактирование: я добавил цикл for для удаления символов, начинающихся с "[", из строки имени, теперь мне просто нужно знать, как читать массивзначения в конце в ссылочном массиве структуры.

void read_from_file(Patient reg[],int *pNr_of_patients){
FILE *fp;
fp=fopen("file.txt","r");
if(fp!=NULL){
    reg[*pNr_of_patients].nr_of_ref=0;
    int i=0, pos;
    while(fgets(reg[*pNr_of_patients].pers_nr,13,fp)!=NULL){
        reg[*pNr_of_patients].pers_nr[strlen(reg[*pNr_of_patients].pers_nr)-1]='\0';
        fgets(reg[*pNr_of_patients].name,31,fp);
        reg[*pNr_of_patients].name[strlen(reg[*pNr_of_patients].name)-1]='\0';
        for(pos=0;pos<30;pos++){
            if(reg[*pNr_of_patients].name[pos]=='['){
                reg[*pNr_of_patients].name[pos]='\0';
            }
        }
        (*pNr_of_patients)++;
    }
    fclose(fp);
}else{
  printf("File does not exist\n");  
}
}

Вот как выглядит моя структура Patient:

struct patient{
char pers_nr[12], name[30];
int photo_ref[10], nr_of_ref;
};
typedef struct patient Patient;

Вызов read_from_file в main:

int main(void){
Patient patient_register[10000];
int nr_of_patients=0;
read_from_file(patient_register,&nr_of_patients);
database_management(patient_register,&nr_of_patients); //this is where I fill all the data into the array before writing to the file at the end
write_to_file(patient_register,&nr_of_patients);
return 0;

}

Ответы [ 4 ]

0 голосов
/ 05 октября 2018

Уже есть несколько хороших ответов, но большинство из них пытаются использовать один метод для анализа всех элементов строки.Сначала я прочитал бы целые строки в буфер, затем использовал sscanf() для разбора номера и имени пациента, но использовал strtok(), чтобы разбить массив на отдельные компоненты:

void read_from_file(Patient reg[], int *pNr_of_patients) {
    FILE *fp = fopen("file.txt", "r");
    if (!fp) {
        fprintf(stderr, "Error opening file: %s\n", strerror(errno));
        *pNr_of_patients = 0;
        return;
    }

    char line[1024];
    int i = 0;

    while (fgets(line, sizeof line, fp)) {
        int offset = 0;
        int refs = 0;

        sscanf(line, "%11s %29[^[] [%n", &reg[i].pers_nr, &reg[i].name, &offset);

        for (char *tok = strtok(line + offset, ","); tok && refs < 10; tok = strtok(NULL, ",")) {
            if (*tok != ']')
                reg[i].photo_ref[refs++] = atoi(tok);
        }
        reg[i].nr_of_ref = refs;
        i++;
    }

    *pNr_of_patients = i;
}
0 голосов
/ 04 октября 2018

Это был комментарий, но он слишком длинный, поэтому я набираю его здесь.

read_from_file () выглядит слишком сложным.Возможно, вы захотите вернуться к fscanf, прочитав ссылки на фотографии как целую строку, а затем проанализировав целые числа, которые можно назначить массиву photo_ref.(Хотя приведенный ниже код может скомпилироваться, я не проверял, работает ли он. Это просто идея того, как можно действовать.)

void read_from_file (Patient reg[], int *pNr_of_patients)
{
  FILE *fp;
  fp = fopen ("file.txt", "r");
  if (fp != NULL)
    {
      int n;
      int i = 0;        // position in photo_ref
      char refs[30];
      *pNr_of_patients = 0;
      while (EOF !=
         (n =
          fscanf (fp, "%s %[^[]%[^]]]", reg[*pNr_of_patients].pers_nr,
              reg[*pNr_of_patients].name, refs)))
    {
      // btw, reg[*pNr_of_patients].name may contain terminating blanks. right trim it. that's easy enough.
      if (n > 2)
        { /* found photo refs.Now split the string into integers */
          char *s = refs + 1;   //skip '['
          char *p;
          while (*s && i<10){       // scan for the integers, 10 of them
            while (*s && *s == ' ')
              s++;  // skip blanks
            p = s;  // mark start of number
            while (*p && *p != ',')
              p++;
            if (*p == ',')
              *p = 0;
            reg[*pNr_of_patients].photo_ref[i++] = atoi (s);    //tip: use strtol(3), verify that `i' isnt larger than size of the array
            s = p + 1;  // skip ','. Must Do: verify that `s' hasnt yet moved past the end of `ref'!!
          }
        }
      (*pNr_of_patients)++;
    }
      fclose (fp);
    }
  else
    {
      printf ("File does not exist\n");
    }
}
0 голосов
/ 05 октября 2018

Разделяй и властвуй

Разбейте это на шаги.Сделайте функцию, которая заполняет 1 Patient.

Ниже приведен непроверенный код.Считайте это отправной точкой.Цель устройства состоит в том, чтобы сделать функцию, которая читает 1 строку в 1 Patient.


Читать в 1 всю строку

// return 1: success, 0: failure EOF:end-of-file
int read_once_from_file(FILE *stream, Patient *pat_ptr) {
  Patient pat = { 0 };
  char buffer[100 + 30*13];
  if (fgets(buffer, sizeof buffer, stream) == NULL) {
    return EOF;
  }

Разобрать первую часть.Используйте "%n", который записывает смещение разбора.Используйте ограничения ширины при вводе строки.

  int n = 0;
  if (sscanf(buffer, " %11[^\t] %29[^\t] [ %n", pat.pers_nr, pat.name) != 2) {
    return 0; // improper formatted input
  }
  char *p = buffer + n;

Теперь ищите ']' и photo_ref

  if (*p != ']') {
    for (pat.nr_of_ref=0; ; pat.nr_of_ref++) {
      if (sscanf(p, "%d %n", &pat.photo_ref[i], &n) != 1) {
        return 0; // improper formatted input
      }
      p += n;
      if (*p == ']') {
        pat.nr_of_ref++;
        break;
      }
      if (*p != ',' || pat.nr_of_ref + 1 == 10) {
        return 0; // improper formatted input
      }
      p++;
    }
  }

Сохранить результат

  *pat_ptr = pat;
  return 1;
}

Вызовread_once_from_file() при необходимости

void read_from_file(Patient reg[],int *pNr_of_patients){
  *pNr_of_patients = 0;
  FILE *fp = fopen("file.txt","r");
  if(fp){
    for (int i = 0; i<10000; i++) {
      int count = read_once_from_file(fp, &reg[i]);
      if (count ==  EOF) {
        break;
      }
      if (count != 1) {
        // error
        fprintf(stderr, "Input error\n"); 
        break;
      }
    } 
    *pNr_of_patients = i;
    fclose(fp);
  }
}
0 голосов
/ 04 октября 2018

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

Первое решение читает один Patient из строки.Он не использует sscanf, единственный стандартный вызов, который устанавливает errno, это strtol, который используется для преобразования чисел.
Вторая функция использует sscanf и некоторую сумасшедшую конструкцию строки формата для защиты от переполнения буфера..
Все сводится к тому, как устроен входной поток и насколько вы ему доверяете.

#include <stdio.h>
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <limits.h>

struct patient{
    char pers_nr[12];
    char name[30];
    int photo_ref[10];
    size_t nr_of_ref;
};

typedef struct patient Patient;

int patient_read_from_line_1(const char line[], Patient *p)
{
    assert(line != NULL);
    assert(p != NULL);

    // check the first 12 characters ----------
    // first 6 chars must be numbers
    for (int i = 0; i < 6; ++i) {
        if (!isdigit(line[i])) {
            return -__LINE__;
        }
    }
    // followed by a single '-'
    if (line[6] != '-') {
        return -__LINE__;
    }
    // followed by 4 numbers
    for (int i = 7; i < 7 + 4; ++i) {
        if (!isdigit(line[i])) {
            return -__LINE__;
        }
    }
    // followed by a space
    if (line[7 + 4] != ' ') {
        return -__LINE__;
    }
    // read up first field ---------------------
    // cool first field checks out
    memcpy(p->pers_nr, line, 11);
    p->pers_nr[11] = '\0';

    line += 12;
    // let's omit spaces
    while (line[0] == ' ') {
        line++;
    }

    // read up second field --------------------------
    // now we should read a two strings separated by a space
    // so we should read up until a second space
    if (!isalpha(*line)) {
        return -__LINE__;
    }
    const char *pnt_first_space = strchr(line, ' ');
    if (pnt_first_space == NULL) {
        return -__LINE__;
    }
    const char *pnt_another_space = strchr(pnt_first_space + 1, ' ');
    if (pnt_another_space == NULL) {
        return -__LINE__;
    }
    const size_t name_to_read_length = pnt_another_space - line;
    if (name_to_read_length > sizeof(p->name)) {
        return -__LINE__;
    }
    memcpy(p->name, line, name_to_read_length);
    p->name[name_to_read_length] = '\0';

    // buh two fields done, now the array
    line += name_to_read_length;
    // let's omit the spaces
    while (line[0] == ' ') {
        line++;
    }

    // read up array -----------------------------------
    // array
    if (line[0] != '[') {
        return -__LINE__;
    }
    line++;
    for (size_t numscnt = 0;; ++numscnt) {
        if (numscnt >= sizeof(p->photo_ref)/sizeof(*p->photo_ref)) {
            return -__LINE__;
        }
        char *pnt;
        errno = 0;
        long num = strtol(line, &pnt, 10);
        if (errno) {
            return -__LINE__;
        }
        if (!(INT_MIN < num && num < INT_MAX)) {
            return -__LINE__;
        }
        p->photo_ref[numscnt] = num;

        line = pnt;
        // omit spaces
        while (*line == ' ') line++;
        // now we should get a comma
        if (line[0] != ',') {
            // if don't get a comma, we need to get a ]
            if (line[0] == ']') {
                // cool
                ++line;
                // but remember to save the count
                p->nr_of_ref = numscnt + 1;
                // cool
                break;
            }
            return -__LINE__;
        }
        ++line;
        // omit spaces
        while (*line == ' ') line++;
        // start again
    }
    // this needs to be end of line or newline
    if (line[0] != '\0' && line[0] != '\n') {
        return -__LINE__;
    }
    // success!
    return 0;
}

// ok, ok, ok, let's use sscanf
int patient_read_from_line_2(const char line[], Patient *p)
{
    assert(line != NULL);
    assert(p != NULL);
    int ret;
    int pos;

    // read up first fiedl and half of the second ------------------
    ret = sscanf(line, "%12s %30[^ ] %n", p->pers_nr, p->name, &pos);
    if (ret != 2) {
        return -__LINE__;
    }
    line += pos;

    // read up another half of the second field -------------------
    const size_t cur_name_len = strlen(p->name);
    p->name[cur_name_len] = ' ';
    char tmp[20];
    ret = snprintf(tmp, 20, "%%%d[^ ] [%%n", (int)(sizeof(p->name) - cur_name_len - 1));
    if (ret < 0) {
        return -__LINE__;
    }
    ret = sscanf(line, tmp, &p->name[cur_name_len + 1], &pos);
    if (ret != 1) {
        return -__LINE__;
    }
    line += pos;

    // read up array *sigh* ------------------------------------------- 
    for (p->nr_of_ref = 0;; ++p->nr_of_ref) {
        if (p->nr_of_ref >= sizeof(p->photo_ref)/sizeof(*p->photo_ref)) {
            return -__LINE__;
        }

        ret = sscanf(line, " %d%1s%n", &p->photo_ref[p->nr_of_ref], tmp, &pos);
        if (ret == 0) {
            // hm...
            if (line[0] == ']') {
                // ach all ok, empty numbers list;
                line++;
                p->nr_of_ref++;
                break;
            }
            return -__LINE__;
        }
        if (ret != 2) {
            return -__LINE__;
        }
        line += pos;
        if (tmp[0] != ',') {
            if (tmp[0] == ']') {
                // whoa!  success
                p->nr_of_ref++;
                // cool
                break;
            }
            return -__LINE__;
        }
    }

    // so what's left? - EOF or newline
    if (line[0] != '\0' && line[0] != '\n') {
        return -__LINE__;
    }

    // success!
    return 0;
}

long patient_read_from_file(FILE *fp, Patient patients[], size_t patients_len)
{
    size_t patients_cnt = 0;

    char line[256];
    // for each line in file
    while (fgets(line, sizeof(line), fp) != NULL) {

        const int ret = patient_read_from_line_2(line, &patients[patients_cnt]);
        if (ret < 0) {
            // hanle reading error 
            return ret;
        }

        patients_cnt++;
        if (patients_cnt > patients_len) {
            // no more memory in patients left
            return -__LINE__;
        }

    }

    return patients_cnt;
}

void patient_fprintln(FILE *f, const Patient *p)
{
    fprintf(f, "%s %s [", p->pers_nr, p->name);
    for (size_t i = 0; i < p->nr_of_ref; ++i) {
        fprintf(f, "%d", p->photo_ref[i]);
        if (i + 1 != p->nr_of_ref) {
            fprintf(f, ",");
        }
    }
    fprintf(f, "]\n");
}

int main()
{
    FILE *fp;
    fp = stdin; // fopen("file.txt","r");
    if (fp == NULL) {
        return -__LINE__;
    }

    Patient patients[3];
    const long patients_cnt = patient_read_from_file(fp, patients, sizeof(patients)/sizeof(*patients));
    if (patients_cnt < 0) {
        fprintf(stderr, "patient_read_from_file error %ld\n", patients_cnt);
        return patients_cnt;
    }

    fclose(fp);

    printf("Readed %d patients:\n", patients_cnt);
    for (size_t i = 0; i < patients_cnt; ++i) {
        patient_fprintln(stdout, &patients[i]);
    }

    return 0;
}

Живая версия доступна на onlinedbg .

Это можно упростить на 100%.Это имеет ошибки на 100%.Это просто для того, чтобы показать, какие методы (strtol, memcpy, sscanf, isdigit, isalpha) иногда используются людьми для чтения из ввода.Также я указываю модификатор длины для scanf (sscanf(..., "%12s") для обработки переполнений (надеюсь).Старайтесь всегда проверять возвращаемые значения из scanf и других стандартных функций (возможно, проверка snprintf возвращаемого значения - это слишком много, но, эй, давайте будем последовательными).Следует помнить, что на некоторых платформах модификатор scanf %n не работает.Также это может быть построено для использования динамического выделения, используя malloc, realloc и free, оба при чтении строки (в основном это равносильно написанию пользовательской версии GNU getline), чтение строк из ввода, чтение массива int извходные и динамические распределения пациентов.

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