【问题标题】:Reading data from a .csv file to append into a 2D array in c从 .csv 文件中读取数据以追加到 c 中的二维数组中
【发布时间】:2018-02-16 23:45:42
【问题描述】:
for (int i = 0; i < 4; i++) {
    for (int j = 0; j <4; i++) {
        while (c != EOF)
            token = strtok((fgets(token,5,fp)), delim);
    }
}

大家好,我是 C 新手,我有一个项目来获取一个 csv 文件并计算每列中值的平均数。现在我正在尝试用逗号解析这些行。我找到了函数 [strtok],但我肯定没有正确地实现它。我有 csv 文件中的行数和列数,我只需要帮助弄清楚如何用“”解析每一行并将这些值放入二维数组中。以上是我将用来将值附加到数组的当前代码,但我不断收到“分段错误”。任何帮助将不胜感激。

这是函数的完整代码。我包括 stdio.h 和 stdlib.h:

void main() {
    char *strcat(char *dest, const char *src);
    char *strtok(char *str, const char *delim);

    char *file_name = "test.txt";

    FILE *fp = fopen(file_name, "r");

    int array[4][4];
    //int array1 [2] = {1, 3};

    int counter = 0;
    char *token = " ";
    const char *delim = (const char *)',';

    char c = fgetc(fp);

    for (int i = 0; i < 4; i++) {
        token = "";
        for (int j = 0; j <4; i++) {
            while (c != EOF)
                token = strtok((fgets(token,5,fp)), delim);
        }
    }
    fclose(fp);
}

示例输入如下所示:

10,20,30,60
40,50,60,70
70,80,90,80
100,110,120,70

【问题讨论】:

  • 你能举一个你的输入文件的例子吗?你的整个代码也会很好。
  • 尚不清楚c 何时会变成EOF
  • @MustacheMoses 我已经更新了帖子。如果您能提供帮助,请告诉我!
  • 相信@Pablo已经给了你很好的答案。

标签: c


【解决方案1】:

是的,你是对的,你错误地使用了strtok

我要做的第一件事是读取每一行并使用解析该行 strtok,像这样:

char line[1024];

const char *delim=",\n";

while(fgets(line, sizeof line, fp))
{
    char *token = strtok(line, delim);

    do {
        printf("token: %s\n", token);
    } while(token = strtok(NULL, delim));
}

strtok 要求strtok 的所有后续调用必须使用 NULL。当找不到更多令牌时,strtok 将返回 NULL,通常是 已到达行尾。请注意,我在 分隔符参数。当目标缓冲区足够大时fgets 写入 换行符也是如此。将换行符放在分隔符列表中是个好技巧 因为strtok 会为你去掉换行符。

上面的代码为您提供了一种将 csv 的每个单元格作为字符串获取的方法。你 必须自己转换值。这是棘手的一点,如果 csv 包含空格、引号等,您需要不同的策略来解析 单元格的正确值。您可以使用像strtol 和朋友这样的功能 允许您从错误中恢复,但它们不是防弹的,会有 失败的情况也是如此。

一个简单的例子是:

char line[1024];

const char *delim=",\n";

while(fgets(line, sizeof line, fp))
{
    char *token = strtok(line, delim);

    do {
        int val;
        if(sscanf(token, "%d", &n) != 1)
            fprintf(stderr, "'%s' is not a number!\n", token);
        else
            printf("number found: %d\n", val);
    } while(token = strtok(NULL, delim));
}

请注意,这并不涵盖所有情况,例如引号中的单元格。

最后要做的就是存储这些值。一种方法是 为指向 int 数组的指针分配内存并为 每个细胞。这里的问题再次出在 csv 文件中,有时他们有 格式错误,有些行会是空的,或者有些行会有更多或更少 列而不是其他行,这可能很棘手。在这一点上,这将是一个很好的 使用库来解析 csv 的想法。

以下代码将假定 csv 格式正确,并且 列在所有行中始终相同,并且没有行长于 1023 长字符。当*cols 为0时,我计算列数基于 第一行。如果其他行的列数较少,则所有剩余值为 0 (因为 calloc 将新分配的内存设置为 0)。如果还有更多 colmuns 比第一行,此列将被忽略:

int **parse_csv(const char *filename, size_t *rows, size_t *cols)
{
    if(filename == NULL || rows == NULL || cols == NULL)
        return NULL;

    FILE *fp = fopen(filename, "r");

    if(fp == NULL)
        return NULL;

    int **csv = NULL, **tmp;

    *rows = 0;
    *cols = 0;

    char line[1024];
    char *token;
    char *delim = ",\n";

    while(fgets(line, sizeof line, fp))
    {
        tmp = realloc(csv, (*rows + 1) * sizeof *csv);
        if(tmp == NULL)
            return csv; // return all parsed rows so far

        csv = tmp;

        if(*cols == 0)
        {
            // calculating number of rows
            char copy[1024];
            strcpy(copy, line);

            token = strtok(copy, delim);

            do {
                (*cols)++;
            } while((token = strtok(NULL, delim)));
        }

        int *row = calloc(*cols, sizeof *row);

        if(row == NULL)
        {
            if(*rows == 0)
            {
                free(csv);
                return NULL;
            }

            return csv; // return all parsed rows so far
        }

        // increment rows count
        (*rows)++;

        size_t idx = 0;

        token = strtok(line, delim);

        do {
            if(sscanf(token, "%d", row + idx) != 1)
                row[idx] = 0; // in case the conversion fails,
                              // just to make sure to have a defined value
                              // in the cell

            idx++;
        } while((token = strtok(NULL, delim)) && idx < *cols);

        csv[*rows - 1] = row;
    }

    fclose(fp);
    return csv;
}

void free_csv(int **csv, size_t rows)
{
    if(csv == NULL)
        return;

    for(size_t i = 0; i < rows; ++i)
        free(csv[i]);

    free(csv);
}

现在你可以像这样解析它:

size_t cols, rows;
int **csv = parse_csv("file.csv", &rows, &cols);

if(csv == NULL)
{
    // error handling...
    // do not continue
}

...

free_csv(csv, rows);

现在csv[3][4] 将为您提供第 3 行第 4 列的单元格(从 0 开始)。


编辑

我从你的代码中注意到的事情:

void main() 是错误的。 main 应该只有以下原型之一:

  • int main(void);
  • int main(int argc, char **argv);
  • int main(int argc, char *argv[]);

另一个:

int main(void)
{
    char *strcat(char *dest, const char *src);
    char *strtok(char *str, const char *delim);
    ...

}

不要把那个放在main函数里面,放在外面,也有标准的 为此的头文件。在这种情况下包括string.h

#include <string.h>

int main(void)
{
    ...
}

另一个

const char *delim = (const char *)',';

这是错误的,这就像试图出售一个苹果并称其为橙子。 ','char 类型的单个字符。它的值为 44。它与 正在做:

const char *delim = (const char*) 44;

您正在设置delim 应指向的地址 44。

你必须使用双引号:

const char *delim = ",";

请注意,'x'"x" 并不相同。 'x' 是 120(参见 ASCII),它是 一个字符。 "x" 是一个字符串文字,它返回一个指向开始的指针 以'\0'-终止字节结尾的字符序列,又名 细绳。这些在 C 中是完全不同的东西。

【讨论】:

    猜你喜欢
    • 2015-03-19
    • 1970-01-01
    • 2016-01-05
    • 2016-08-11
    • 1970-01-01
    • 2017-08-23
    • 2012-05-21
    • 2015-05-16
    • 1970-01-01
    相关资源
    最近更新 更多