Как прочитать CSV-файл в 2d массив структуры, где каждый элемент имеет определяющий символ (C, G, M) для сортировки в структуру? - PullRequest
0 голосов
/ 26 мая 2019

Я пытаюсь создать двумерную карту массива с помощью ввода csv

   5,4
   ,,,C 200
   ,G Vibranium Shield:hands:990,,C 50
   M Healing Potion:85,,M Defence Enchanment:360,
   ,,,
   ,,G Lighsaber:hands:850,5,4

Первая строка - это размер данного массива.

Проблема, с которой я столкнулся сейчаскак по-прежнему считать пустой список в CSV в качестве строки и столбца в массиве, таких как ",,,".Кроме того, как прочитать определяющий символ (C, G, M), чтобы сохранить элемент в структуре.Пример, G Vibranium Shield: hands: 990, G будет определяющим символом, хранящимся в типе символа, который затем я использую в качестве переключателя для сохранения другого элемента в соответствующей структуре.

Я пытался использовать fgets () strtok() но я не могу прочитать определяющий элемент отдельно от другого элемента в CSV.Как видно из другого примера, необходимо предварительное знание того, какой элемент будет находиться в строке и предопределять строку чтения, а не на основе определяющего символа в CSV.Таким образом, я использовал fscanf для чтения:

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

typedef struct map
{
char types;
char geartypes[100];
int coins;
int values;
char items[100];
}map;

struct map **m;

int main()
{
    FILE* mapz;
    int i,j,h;
    int width,height;
    char a;
    mapz=fopen("map.csv","r");

    if(mapz!=NULL)
    {
        fscanf(mapz,"%d,%d",&height,&width);
        map **m=(map **)malloc(height * sizeof(map *)); 
        for(i=0;i<height;i++)
        {
            m[i]=(map*)malloc(width * sizeof(map)); 
        }
        for(h=0;h<height;h++)
        {
            for(j=0;j<width;j++)
            {
                fscanf(mapz,"%c",&a);
                switch(a)
                {
                case('C'):
                    m[h][j].types=a;
                    fscanf(mapz,"%d",&m[h][j].coins);
                    break;
                case('G'):
                    m[h][j].types=a;
                    fscanf(mapz,"%[^,:]s",m[h][j].items);
                    fscanf(mapz,"%[^,:]s",m[h][j].geartypes);
                    fscanf(mapz,"%d",&m[h][j].values);
                    break;
                case('M'):
                    m[h][j].types=a;
                    fscanf(mapz,"%[^,:]s",m[h][j].items);
                    fscanf(mapz,"%d",&m[h][j].values);
                    break;
                }

            }
        }   
        for(h=0;h<height;h++)
        {
            for(j=0;j<width;j++)
            {
                switch(m[h][j].types)
                {
                case('C'):
                    printf("%c",m[h][j].types);
                    printf("%d\n",m[h][j].coins);
                    break;
                case('G'):
                    printf("%c",m[h][j].types);
                    printf("%s%s%d\n",m[h][j].items,m[h][j].geartypes,m[h][j].values);
                    break;
                case('M'):
                    printf("%c",m[h][j].types);
                    printf("%s%d\n",m[h][j].items,m[h][j].values);
                    break;
                }
            }
        }   
    }
    else
    {
        printf("No such file in directory");
    }
    fclose(mapz);
    return 0;

Я пытался использовать fscanf, но, похоже, он также читал ",", что испортило счетчик для.Когда я запустил код, он вышел пустым.

Ответы [ 2 ]

0 голосов
/ 27 мая 2019

Хотя у меня нет сомнений в ответе Дэвида С. Ранкина , есть другой подход, использующий регулярные выражения:

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

#include <sys/types.h>
#include <regex.h>

char line[4096];

int main( int argc, char *argv[] ) {
  if( !argv[1] )
    errx(EXIT_FAILURE, "missing input"); 

  FILE *input = fopen(argv[1], "r");
  if( !input  )
    err(EXIT_FAILURE, "could not open %s", argv[1]);

  if( NULL == fgets(line, sizeof(line), input) )
    err(EXIT_FAILURE, "could not read %s", argv[1]);

  int nr, nf, nfield;
  if( 2 != sscanf(line, "%d,%d", &nr, &nfield) ) 
    err(EXIT_FAILURE, "failed to parse first line");
  printf( "reading %d lines of %d fields each\n", nr, nfield );

  int erc;
  regex_t reg;
  const char fmt[] = "([^,\n]*)[,\n]";
  char *regex = calloc( nfield, 1 + strlen(fmt) );
  for( int i=0; i < nfield; i++ ) {
    strcat(regex, fmt);
  }

  int cflags = REG_EXTENDED;
  char errbuf[128];
  size_t len = sizeof(errbuf);
  const char *truncated = "";

  if( (erc = regcomp(&reg, regex, cflags)) != 0 ) {
    if( (len = regerror(erc, &reg, errbuf, len)) > sizeof(errbuf) ) 
      truncated = "(truncated)";
    errx(EXIT_FAILURE, "%s %s", errbuf, truncated);
  }

  for( int i=0; i < nr && NULL != fgets(line, sizeof(line), input); i++ ) {
    regmatch_t matches[1 + nfield];
    const int eflags = 0;

    printf("%s", line);

    if( (erc = regexec(&reg, line, 1 + nfield, matches, eflags)) != 0 ) {
      if( (len = regerror(erc, &reg, errbuf, len)) > sizeof(errbuf) ) 
        truncated = "(truncated)";
      errx(EXIT_FAILURE, "regex error: %s %s", errbuf, truncated);
    }

    for( nf=1; nf < nfield + 1 && matches[nf].rm_so != -1; nf++ ) {
      assert(matches[nf].rm_so <= matches[nf].rm_eo);
      printf( "%4d: '%.*s'\n",
          nf,
          (int)(matches[nf].rm_eo - matches[nf].rm_so),
          line + matches[nf].rm_so );
    }
  }

  return EXIT_SUCCESS;
}  

Это только немного дольше (в основном для обработки ошибок). Что мне нравится, так это то, что при вызове regexec (3) все поля настраиваются в массиве matches.

0 голосов
/ 26 мая 2019

Поскольку вы застряли в обработке пустых полей при токенизации каждой строки, давайте рассмотрим использование strsep, чтобы справиться с этим для вас. Есть несколько предостережений по поводу использования strsep. Сначала обратите внимание на тип первого параметра. Это char **. Это означает, что вы не можете прочитать каждую строку в массив фиксированных символов и передать адрес фиксированного массива (это будет не char**, а вместо char (*)[length]). Далее, поскольку strsep обновит указатель, предоставленный в качестве первого параметра, вы не можете просто дать ему адрес выделенного буфера, который вы используете для хранения каждой прочитанной строки (вы потеряете указатель на начало выделенного блока и быть не в состоянии free() памяти или прочитать более одной строки.

Итак, нижняя строка, вам нужен выделенный буфер для хранения текста, который вы собираетесь передать strsep, а затем вам нужно 2 указателя , один для захвата возврата из strsep и один для передачи адреса на strsep (чтобы сохранить исходный указатель буфера).

Имея это в виду, вы можете проанализировать CSV с пустыми полями, похожими на:

    while (fgets (buf, MAXC, fp)) { /* read each line in file */
        size_t i = 0;       /* counter */
        p = fields = buf;   /* initialize pointers to use with strsep */
        printf ("\nline %2zu:\n", n++ + 1);         /* output heading */
        while ((p = strsep (&fields, DELIM))) {     /* call strsep */
            p[strcspn(p, "\r\n")] = 0;              /* trim '\n' (last) */
            printf ("  field %2zu: '%s'\n", i++ + 1, p); /* output field */
        }
    }

Собрав воедино полный пример, используя ваши данные, вы можете сделать что-то похожее на:

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

#define MAXC  1024      /* if you need a constant, #define one (or more) */
#define DELIM ","       /* (numeric or string) */

int main (int argc, char **argv) {

    size_t n = 0, lines, nflds;
    char *buf, *fields, *p; /* must use 2 pointers for strsep */
    /* use filename provided as 1st argument (stdin by default) */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        perror ("file open failed");
        return 1;
    }

    if (!(buf = malloc (MAXC))) {   /* allocate storage for buffer */
        perror ("malloc-buf");      /* cannot be array with strsep */
        return 1;
    }

    if (!fgets (buf, MAXC, fp)) {   /* read/validate 1st line */
        fputs ("error: insufficient input line 1.\n", stderr);
        return 1;
    }   /* convert to lines and no. of fields (lines not needed) */
    if (sscanf (buf, "%zu,%zu", &lines, &nflds) != 2) {
        fputs ("error: invalid format line 1.\n", stderr);
        return 1;
    }

    while (fgets (buf, MAXC, fp)) { /* read each line in file */
        size_t i = 0;       /* counter */
        p = fields = buf;   /* initialize pointers to use with strsep */
        printf ("\nline %2zu:\n", n++ + 1);         /* output heading */
        while ((p = strsep (&fields, DELIM))) {     /* call strsep */
            p[strcspn(p, "\r\n")] = 0;              /* trim '\n' (last) */
            printf ("  field %2zu: '%s'\n", i++ + 1, p); /* output field */
        }
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */
    free (buf);  /* free allocated memory */

    return 0;
}

Пример входного файла

$ cat dat/emptyflds.csv
5,4
,,,C 200
,G Vibranium Shield:hands:990,,C 50
M Healing Potion:85,,M Defence Enchanment:360,
,,,
,,G Lighsaber:hands:850,5,4

Пример использования / Вывод

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

$ ./bin/strcspnsepcsv <dat/emptyflds.csv

line  1:
  field  1: ''
  field  2: ''
  field  3: ''
  field  4: 'C 200'

line  2:
  field  1: ''
  field  2: 'G Vibranium Shield:hands:990'
  field  3: ''
  field  4: 'C 50'

line  3:
  field  1: 'M Healing Potion:85'
  field  2: ''
  field  3: 'M Defence Enchanment:360'
  field  4: ''

line  4:
  field  1: ''
  field  2: ''
  field  3: ''
  field  4: ''

line  5:
  field  1: ''
  field  2: ''
  field  3: 'G Lighsaber:hands:850'
  field  4: '5'
  field  5: '4'

( примечание: строка 5 содержит пятое поле, которое превышает ожидаемое количество полей)

Для дальнейшего разделения внутри полей в ':' или в любом другом месте, вы можете позвонить strtok по указателю p в пределах поля токенизации while loop.

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