Программа на C для преобразования текстового файла в файл CSV - PullRequest
0 голосов
/ 26 марта 2019

Вопрос в том, чтобы преобразовать текстовый файл в файл CSV с помощью программирования на Си Входной текстовый файл отформатирован следующим образом: JACK Maria Stephan Nora 20 34 45 28 London NewYork Toronto Berlin

Выходной CSV-файл должен выглядеть следующим образом:

Jack,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin

Следующий код - это то, что я пробовал до сих пор:

void  load_and_convert(const char* filename){
    FILE *fp1, *fp2;
    char ch;

    fp1=fopen(filename,"r");
    fp2=fopen("output.csv","w");

    for(int i=0;i<1000;i++){
         ch=fgetc(fp1);
         fprintf(fp2,"%c",ch);    
         if(ch==' '|| ch=='\n')
              fprintf(fp2,"%c,\n",ch);
}
    fclose(fp1);
    fclose(fp2);

}

Вывод из моего кода выглядит следующим образом:

Jack,
Maria,
Stephan,
Nora,
20,
34,
45,
28,
London,
NewYork,
Toronto,
Berlin,

Как мне изменить мой код, чтобы он работал правильно?

В чем идея рассматривать этот вопрос?

Ответы [ 2 ]

0 голосов
/ 26 марта 2019

То, что вам нужно сделать, является нетривиальным, если вы хотите подойти к проблеме с удержанием всех значений в памяти, когда вы преобразуете 3 строки с 4 полями в каждой строке, в формат 4 строк с 3- поля на строку Поэтому, когда у вас есть файл данных, содержащий:

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

$ cat dat/col2csv3x4.txt
JACK Maria Stephan Nora
20 34 45 28
London NewYork Toronto Berlin

Вы хотите прочитать каждую из трех строк и затем преобразовать столбцы в строки для вывода .csv. Это означает, что вы получите 4 строки по 3 поля CSV в каждом, например,

Ожидаемый результат программы

$ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin

Ничего сложного в этом нет, но он требует кропотливого внимания к обработке хранения объекта в памяти и распределению / перераспределению для обработки преобразования между 3 строками с 4 частями данных в 4 ряда с 3 частями данные.

Один из подходов заключается в чтении всех исходных строк в типичном указателе на указатель на установку символов Затем преобразуйте / транспонируйте столбцы в строки. Поскольку в следующий раз, возможно, может быть 100 строк с 500 полями, вам нужно подойти к преобразованию, используя индексы и счетчики, чтобы отслеживать ваши требования к выделению и перераспределению, чтобы ваш готовый код мог обрабатывать транспонирование общего числа строк и полей в fields-количество строк с таким количеством вейлов на строку, как у вас было исходных строк.

Вы можете разработать свой код, чтобы обеспечить преобразование в две основные функции. Первый для чтения и сохранения строк (say getlines`), а второй для преобразования этих строк в новый указатель на указатель на тип char, чтобы его можно было выводить как значения, разделенные запятыми

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

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

#define NPTR 2
#define NWRD 128
#define MAXC 1024

/** getlines allocates all storage required to read all lines from file.
 *  the pointers are doubled each time reallocation is needed and then
 *  realloc'ed a final time to exactly size to the number of lines. all
 *  lines are stored with the exact memory required.
 */
char **getlines (size_t *n, FILE *fp)
{
    size_t nptr = NPTR;     /* tracks number of allocated pointers */
    char buf[MAXC];         /* tmp buffer sufficient to hold each line */
    char **lines = calloc (nptr, sizeof *lines);

    if (!lines) {   /* validate EVERY allocaiton */
        perror ("calloc-lines");
        return NULL;
    }

    *n = 0;         /* pointer tracks no. of lines read */
    rewind (fp);    /* clears stream error state if set */

    while (fgets (buf, MAXC, fp)) { /* read each line o finput */
        size_t len;

        if (*n == nptr) {   /* check/realloc ptrs if required */
            void *tmp = realloc (lines, 2 * nptr * sizeof *lines);
            if (!tmp) {     /* validate reallocation */
                perror ("realloc-tmp");
                break;
            }
            lines = tmp;    /* assign new block, (opt, zero new mem below) */
            memset (lines + nptr, 0, nptr * sizeof *lines);
            nptr *= 2;      /* increment allocated pointer count */
        }

        buf[(len = strcspn(buf, "\r\n"))] = 0;  /* get line, remove '\n' */
        lines[*n] = malloc (len + 1);           /* allocate for line */
        if (!lines[*n]) {                       /* validate */
            perror ("malloc-lines[*n]");
            break;
        }
        memcpy (lines[(*n)++], buf, len + 1);   /* copy to line[*n] */
    }

    if (!*n) {          /* if no lines read */
        free (lines);   /* free pointers */
        return NULL;
    }

    /* optional final realloc to free unused pointers */
    void *tmp = realloc (lines, *n * sizeof *lines);
    if (!tmp) {
        perror ("final-realloc");
        return lines;
    }

    return (lines = tmp);   /* return ptr to exact no. of required ptrs */
}

/** free all pointers and n alocated arrays */
void freep2p (void *p2p, size_t n)
{
    for (size_t i = 0; i < n; i++)
        free (((char **)p2p)[i]);
    free (p2p);
}

/** transpose a file of n rows and a varying number of fields to an
 *  allocated pointer-to-pointer t0 char structure with a fields number 
 *  of rows and n csv values per row.
 */
char **transpose2csv (size_t *n, FILE *fp)
{
    char **l = NULL, **t = NULL;
    size_t  csvl = 0,       /* csv line count */
            ncsv = 0,       /* number of csv lines allocated */
            nchr = MAXC,    /* initial chars alloc for csv line */
            *offset,        /* array tracking read offsets in lines */
            *used;          /* array tracking write offset to csv lines */

    if (!(l = getlines (n, fp))) {  /* read all lines to l */
        fputs ("error: getlines failed.\n", stderr);
        return NULL;
    }
    ncsv = *n;
#ifdef DEBUG
    for (size_t i = 0; i < *n; i++)
        puts (l[i]);
#endif

    if (!(t = malloc (ncsv * sizeof *t))) { /* alloc ncsv ptrs for csv */
        perror ("malloc-t");
        freep2p (l, *n);        /* free everything else on failure */
        return NULL;
    }

    for (size_t i = 0; i < ncsv; i++)   /* alloc MAXC chars to csv ptrs */
        if (!(t[i] = malloc (nchr * sizeof *t[i]))) {
            perror ("malloc-t[i]");
            while (i--)         /* free everything else on failure */
                free (t[i]);
            free (t);
            freep2p (l, *n);
            return NULL;
        }

    if (!(offset = calloc (*n, sizeof *offset))) {  /* alloc offsets array */
        perror ("calloc-offsets");
        free (t);
        freep2p (l, *n);
        return NULL;
    }

    if (!(used = calloc (ncsv, sizeof *used))) {    /* alloc used array */
        perror ("calloc-used");
        free (t);
        free (offset);
        freep2p (l, *n);
        return NULL;
    }

    for (;;) {  /* loop continually transposing cols to csv rows */
        for (size_t i = 0; i < *n; i++) { /* read next word from each line */
            char word[NWRD];    /* tmp buffer for word */
            int off;            /* number of characters consumed in read */
            if (sscanf (l[i] + offset[i], "%s%n", word, &off) != 1)
                goto readdone;  /* break nested loops on read failure */
            size_t len = strlen (word);         /* get word length */
            offset[i] += off;                   /* increment read offset */
            if (csvl == ncsv) { /* check/realloc new csv row as required */
                size_t newsz = ncsv + 1;    /* allocate +1 row over *n */
                void *tmp = realloc (t, newsz * sizeof *t); /* realloc ptrs */
                if (!tmp) {
                    perror ("realloc-t");
                    freep2p (t, ncsv);
                    goto readdone;
                }
                t = tmp;
                t[ncsv] = NULL;     /* set new pointer NULL */

                /* allocate nchr chars to new pointer */
                if (!(t[ncsv] = malloc (nchr * sizeof *t[ncsv]))) {
                    perror ("malloc-t[i]");
                    while (ncsv--)   /* free everything else on failure */
                        free (t[ncsv]);
                    goto readdone;
                }

                tmp = realloc (used, newsz * sizeof *used); /* realloc used */
                if (!tmp) {
                    perror ("realloc-used");
                    freep2p (t, ncsv);
                    goto readdone;
                }
                used = tmp;
                used[ncsv] = 0;

                ncsv++;
            }
            if (nchr - used[csvl] - 2 < len) {  /* check word fits in line */
                /* realloc t[i] if required (left for you) */
                fputs ("realloc t[i] required.\n", stderr);
            }
            /* write word to csv line at end */
            sprintf (t[csvl] + used[csvl], used[csvl] ? ",%s" : "%s", word);
            t[csvl][used[csvl] ? used[csvl] + len + 1 : len] = 0;
            used[csvl] += used[csvl] ? len + 1 : len;
        }
        csvl++;
    }
    readdone:;

    freep2p (l, *n);
    free (offset);
    free (used);

    *n = csvl;

    return t;
}

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

    char **t;
    size_t n = 0;
    /* 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 (!(t = transpose2csv (&n, fp))) {
        fputs ("error: transpose2csv failed.\n", stderr);
        return 1;
    }

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < n; i++)
        if (t[i])
        puts (t[i]);

    freep2p (t, n);

    return 0;
}

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

$ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin

Использование памяти / проверка ошибок

В любом написанном вами коде, который динамически распределяет память, у вас есть 2 обязанностей в отношении любого выделенного блока памяти: (1) всегда сохраняет указатель на начальный адрес для блока памяти, так что, (2) он может быть освобожден , когда он больше не нужен.

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

Для Linux valgrind - нормальный выбор. Для каждой платформы есть похожие проверки памяти. Все они просты в использовании, просто запустите вашу программу через него.

$ valgrind ./bin/transpose2csv < dat/col2csv3x4.txt
==18604== Memcheck, a memory error detector
==18604== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==18604== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==18604== Command: ./bin/transpose2csv
==18604==
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
==18604==
==18604== HEAP SUMMARY:
==18604==     in use at exit: 0 bytes in 0 blocks
==18604==   total heap usage: 15 allocs, 15 frees, 4,371 bytes allocated
==18604==
==18604== All heap blocks were freed -- no leaks are possible
==18604==
==18604== For counts of detected and suppressed errors, rerun with: -v
==18604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

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

Посмотрите вещи и дайте мне знать, если у вас есть дополнительные вопросы.

0 голосов
/ 26 марта 2019

Так как у меня есть несколько раз, вот рабочее решение для вас (изо всех сил старался сделать решение настолько элегантным, насколько я могу):

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

#define MAX_STRING_LENGTH 50
#define MAX_NUMBER_OF_PEOPLE 50

typedef struct  
{  
  char name[MAX_STRING_LENGTH];
  int age;
  char city[MAX_STRING_LENGTH];
} Person;

void getName(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].name, ptr, MAX_STRING_LENGTH);
    ptr = strtok(NULL, delim);
    i++;
  }
}

void getAge(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    people[i].age = atoi(ptr);
    i++;
    ptr = strtok(NULL, delim);
  }
}

void getCity(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].city, ptr, MAX_STRING_LENGTH);
    i++;
    ptr = strtok(NULL, delim);
  }
}

int main(void)
{
  Person somebody[MAX_NUMBER_OF_PEOPLE];
  FILE *fp;
  char *line = NULL;
  size_t len = 0;
  ssize_t read;
  int ln = 0;

  fp = fopen("./test.txt", "r");
  if (fp == NULL)
      return -1;

  // Read every line, support first line is name, second line is age...
  while ((read = getline(&line, &len, fp)) != -1) {
    // remote trailing newline character
    line = strtok(line, "\n");
    if (ln == 0) {
      getName(line, " ", somebody);
    } else if (ln == 1) {
      getAge(line, " ", somebody);
    } else {
      getCity(line, " ", somebody);
    }
    ln++;
  }

  for (int j = 0; j < MAX_NUMBER_OF_PEOPLE; j++) {
      if (somebody[j].age == 0) 
        break;
      printf("%s, %d, %s\n", somebody[j].name, somebody[j].age, somebody[j].city);
  }

  fclose(fp);
  if (line)
      free(line);

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