【问题标题】:Read a .txt file and save the data as a matrix in C读取 .txt 文件并将数据保存为 C 中的矩阵
【发布时间】:2015-12-30 15:02:19
【问题描述】:

我有兴趣阅读 .txt 文件并将其中的数据保存在 C 中的矩阵中。

dist.txt is the following:
Distance    Amsterdam   Antwerp Athens  Barcelona   Berlin
Amsterdam   -   160 3082    1639    649
Antwerp 160 -   2766    1465    723
Athens  3082    2766    -   3312    2552
Barcelona   1639    1465    3312    -   1899
Berlin  649 723 2552    1899    -

事实上它有更多的城市,但没关系。

我想阅读这份文件并记录距离。我试过以下代码:

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

#define rows 6
#define cols 6

int main()
{
    FILE *nansa;
    char *buffer;
    int ret,row=0,i,j;

    char delims[]=" \t";
    char *result=NULL;

    double **mat=malloc( rows*sizeof(double*) );
    for(i=0; i<rows; i++)
    {
        mat[i]=malloc( cols*sizeof(double) ); 
    }

    if ((nansa=fopen("dist.txt","r"))==NULL)
    {
        fprintf(stdout, "Error\n"); 
        return -1;
    }
    while(!feof(nansa))
    {
        buffer=malloc( sizeof(char)*4096 );
        memset(buffer,0,4096);
        ret=fscanf(nansa, "%4095[^\n]\n", buffer);
        if(ret != EOF) 
        {
            int field=0;
            result=strtok(buffer,delims);
            while(result != NULL)
            {
                if(field>4) break;
                mat[row][field]=atof(result);
                result=strtok(NULL,delims);
                field++;
            }
            ++row;
        }
        free(buffer);
    }
    fclose(nansa);
    for(i=0; i<rows; i++)
    {
        for(j=0; j<cols; j++)
        {
            printf("%g%s", mat[i][j], j<cols-1 ? "\t" : "\n");
            free(mat[i]);
        }
    }
    free(mat);
    return 0;
}

但我没有得到我想要的...而且我不知道如何区分名称和距离(字符和整数)。如果有人可以帮助我,我将不胜感激!

【问题讨论】:

  • 什么文字或谁建议使用while(!feof(...
  • ... 看到这篇文章:stackoverflow.com/questions/5431941/…
  • buffer = malloc(4096); + memset(buffer, 0, 4096); + free(buffer); 都在同一个循环中?为什么不分配一次缓冲区,只分配memset 它,然后在循环外释放。或者更好:buffer[0] = '\0'; 和/或使用fgets?
  • 我建议通过使用while(fgets(...) != NULL) 读取每一行然后使用strtok 从输入字符串中提取字段来避免错误使用feof。但是当你有一个像Los Angeles 这样的多词城市时要小心。您最好重新考虑使用的字段分隔符,例如逗号或制表符。
  • 发帖时说“如果有人能帮助我,我将不胜感激!”,在没有回复的情况下离开帖子一个小时,这看起来像是一种片面的帮助。

标签: c matrix


【解决方案1】:

虽然使用fgets 读取每一行很诱人(feof 是错误的),但问题只是少数城市的示例:可能有 10000 个。所以我假设任何城市的名称都小于 64(仅供输入)。保留的内存对于名称的实际长度是正确的。

行和列将是相同的,因此定义不同没有意义:实际上我只定义了城市的数量。我对城市名称(与向下相同)和距离使用单独的数组。

为简单起见,我进行了错误检查,但没有消息就中止了。但是需要修改的地方是当城市是一个多词名称时,例如洛杉矶(%s 停在任何空白处)。那么您将需要一种不同的方法,或者可能使用下划线来分隔 city_name。

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

#define cities 5

int main(void){

    FILE *nansa;
    char buffer[64];
    char distname[64];                      // just to save work
    char *city[cities];                     // city names
    int *dist[cities];                      // distance array
    int i, j, len, wid = 0;

    if((nansa = fopen("dist.txt","r")) == NULL)
        exit(1);                            // file open fault

    // read the headings
    if(fscanf(nansa, "%63s", buffer) != 1)  // read the word for "distance"
        exit(1);                            // fscanf fault
    strcpy(distname, buffer);

    for(i=0; i<cities; i++) {               // read the city names
        if(fscanf(nansa, "%63s", buffer) != 1)
            exit(1);                        // fscanf fault
        len = strlen(buffer) + 1;
        if (wid < len)
            wid = len;                      // column width
        if((city[i] = malloc(len)) == NULL) // memory for city name
            exit(1);                        // malloc fault
        strcpy(city[i], buffer);
    }

    // read the data
    for(j=0; j<cities; j++) {               // read each table line
        if((dist[j] = malloc(cities * sizeof(int))) == NULL)    // memory for distance chart
            exit(1);                        // malloc fault
        if(fscanf(nansa, "%s", buffer) != 1)   // skip the city name
            exit(1);                        // fscanf fault
        for(i=0; i<cities; i++) {           // read each table line
            if(fscanf(nansa, "%63s", buffer) != 1)  // read the distance
                exit(1);                    // fscanf fault
            dist[j][i] = atoi(buffer);
        }
    }

    fclose(nansa);

    // display the table headings
    printf("%-*s", wid, distname);          // use the terminology in the file
    for(i=0; i<cities; i++)                 // each city name
        printf("%-*s", wid, city[i]);
    printf("\n");

    // display each line
    for(j=0; j<cities; j++) {
        printf("%-*s", wid, city[j]);       // start with city name
        for(i=0; i<cities; i++) {           // each table data
            if(dist[j][i])
                printf("%-*d", wid, dist[j][i]);
            else
                printf("%-*c", wid, '-');
        }
        printf("\n");

    }

    // free the memory
    for(i=0; i<cities; i++) {
        free (city[i]);
        free (dist[i]);
    }
    return 0;
}

程序输出:

Distance  Amsterdam Antwerp   Athens    Barcelona Berlin
Amsterdam -         160       3082      1639      649
Antwerp   160       -         2766      1465      723
Athens    3082      2766      -         3312      2552
Barcelona 1639      1465      3312      -         1899
Berlin    649       723       2552      1899      -

【讨论】:

  • 我真的不喜欢您的编码风格(例如:小写宏?),但是您的代码非常好。一个建议是在scanf() 中使用"%n" 修饰符,这样您就可以避免使用strlen()。它将提高非常大文件的性能。此外,如果城市名称中包含空格,您的解决方案将不起作用。但该计划的意图非常明确。
  • @iharob 我在代码上方评论了一个两个单词的城市名称。除了“真的不喜欢”之外,对我的风格有什么有用的建议吗?
  • 小写宏 - mybad,我通常使用大写但复制的 OP 样式。
  • 另外,您的空白使用率不一致。
  • @iharob 我之前注意到你的风格有所不同。我喜欢在运算符的每一侧都有一个空格,除了在for 语句中(我之前看到你编辑过它们!)在我看来它流动得更好,这只是我的风格。我也喜欢分隔函数参数的空间。
【解决方案2】:

这似乎是引发许多从头开始重写的新解决方案的问题之一。这是一个允许任意数量的城市达到一定最大值并强制匹配城市名称。

核心是一个自制函数getcell,它类似于scanf("%s", ...),但在读取一个或多个换行符时会发出一个特殊的返回值。这允许正确地获取行和列,而无需读取可能很长的整行。

因为函数直接从文件中读取,并且必须同时查看空格和标记,所以会消耗第一个不匹配的字符。为避免这种情况,使用ungetc,但不要超过一次。我不认为这是特别好的风格,但我保持原样。 (当您使用字符串和指针时,这种风格很容易,但不适用于文件。)

读取距离的代码会积极检查行、列和城市之间的一致性,但会跳过对文件 I/O 和分配的检查,以免代码更加混乱。

城市名称必须是单个单词(LeMansLos_Angeles)并存储在一个单独的、固定大小的数组中。 (这个固定大小是有最大城市数量的原因。)距离存储在动态分配的 doubles 数组中。

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

#define MAX_CITY 256        // Max. number of cities
#define MAX_NAME 24         // Buffer allocated for a name
#define NEWLINE -2          // Special token: end of line was read

/*
 *      Short-cut acro for string comparison
 */
#define is(a, b) (strcmp(a, b) == 0)

/*
 *      Quick-and-dirty exit macro with message
 */
#define die(...) exit((printf(__VA_ARGS__), putchar('\n'), 1))



/*
 *      Read a cell of at most (max - 1) characters and return its length.
 *      When the end of input is read, return the special value EOF; when
 *      one ore more new-line characters are read, return the special
 *      value NEWLINE. On EOF and NEWLINE, the contents of buf are
 *      undefined.
 */
int getcell(FILE *f, char *buf, size_t max)
{
    size_t len = 0;
    int nl = 0;
    int c;

    /*
     *      Skip leading whitespace and account for newlines
     */
    for (;;) {
        c = fgetc(f);

        if (c == EOF) {
            if (nl) break;
            return EOF;
        }
        if (!isspace(c)) break;
        if (c == '\n') nl++;
    }

    ungetc(c, f);
    if (nl) return NEWLINE;

    /*
     *      Store the token proper
     */
    for (;;) {
        c = fgetc(f);

        if (c == EOF || isspace(c)) break;
        if (len + 1 < max) buf[len++] = c;
    }

    ungetc(c, f);
    buf[len] = '\0';

    return len;
}

int main()
{
    FILE *f = fopen("dist.txt", "r");
    int nrow = -1;
    int ncol = -1;

    char city[MAX_CITY][MAX_NAME];
    int ncity = 0;

    double *data;           // contiguous data block
    double **dist;          // Pointers into that block

    for (;;) {
        char buf[MAX_NAME];
        int len = getcell(f, buf, sizeof(buf));

        if (len == EOF) break;

        if (len == NEWLINE) {
            if (nrow >= 0 && ncol < ncity) {
                die("Insufficient data for %s.", city[nrow]);
            }

            nrow++;
            ncol = -1;

            continue;
        }

        if (nrow < 0) {
            if (ncol < 0) {
                if (!is(buf, "Distance")) die("Wrong file format");
            } else {
                if (ncol >= MAX_CITY) {
                    die("Can have at most %d cities", MAX_CITY);
                }
                strcpy(city[ncity++], buf);
            }

            ncol++;
            continue;
        }

        if (ncol < 0) {
            if (nrow > ncity) {
                die("Too many rows, expected only %d.", ncity);
            }

            if (!is(buf, city[nrow])) {
                die("Expected '%s' in row %d.", city[nrow], nrow);
            }

            if (nrow == 0) {
                // First-touch allocation
                data = malloc(ncity * ncity * sizeof(*data));
                dist = malloc(ncity * sizeof(*dist));

                for (int i = 0; i < ncity; i++) {
                    dist[i] = &data[i * ncity];
                }
            }
        } else {
            if (nrow == ncol) {
                if (!is(buf, "-")) {
                    die("Distance of %s to itself isn't '-'.", city[nrow]);
                }

                dist[nrow][ncol] = 0.0;
            } else {
                double d = strtod(buf, NULL);

                if (ncol >= ncity) {
                    die("Too many columns for %s.", city[nrow]);
                }
                dist[nrow][ncol] = d;
            }
        }

        ncol++;
    }

    if (nrow < ncity) die("Got only %d rows, expected %d.", nrow, ncity);

    /*
     *      Print distance matrix
     */

    printf("Distance");
    for (ncol = 0; ncol < ncity; ncol++) {
        printf(", %s", city[ncol]);
    }
    puts("");

    for (nrow = 0; nrow < ncity; nrow++) {
        printf("%s", city[nrow]);

        for (ncol = 0; ncol < ncity; ncol++) {
            printf(", %g", dist[nrow][ncol]);
        }
        puts("");
    }

    free(dist);
    free(data);

    return 0;
}

【讨论】:

  • 不错的方法。尤其是像您如何使用 getcell() 函数拆分整体 main()
【解决方案3】:

我认为的解决方案是完全忽略标题行并从行中提取城市名称,然后在找到第一个 digit- 之后开始扫描strtod() 的值。我刚刚写的这个解决方案还远未完成。它需要更多的结构(使用函数会有所帮助)和完整性检查(例如,每行中的列数不一定相同)。但我认为它会带你走向正确的方向。

为什么要忽略标题行?因为不清楚是什么字符将一个城市名称与另一个城市名称分开,而且城市名称中通常包含空格,例如“Los Angeles”就是这样。无论分隔符如何以及城市名称是否包含空格,此方法都将起作用。

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

int main()
{
    char *pointer;
    FILE *nansa;
    char buffer[1024];
    char **cities;
    double **distances;
    size_t rows;
    nansa = fopen("dist.txt", "r");
    if (nansa == NULL)
        return -1;
    if (fgets(buffer, sizeof(buffer), nansa) == NULL)
        return -1; // Skip the header line.
    rows = 0;
    distances = NULL;
    cities = NULL;
    while (fgets(buffer, sizeof(buffer), nansa) != NULL)
    {
        char next;
        double value;
        void *aux;
        ptrdiff_t length;
        size_t column;

        pointer = buffer;
        next = *pointer;        
        while ((isdigit((unsigned char) next) == 0) && (next != '-'))
            next = *pointer++;            
        aux = realloc(cities, (rows + 1) * sizeof(*cities));
        if (aux == NULL)
            return -1; // allocation error ABORT
        length = pointer - buffer - 1;
        cities = aux;
        cities[rows] = malloc(length + 1);
        if (cities[rows] == NULL)
            return -1; // allocation error ABORT                        
        memcpy(cities[rows], buffer, length);
        // Remove trailing spaces
        while ((length > 0) && (isspace((unsigned char) cities[rows][length - 1]) != 0))
            --length;
        cities[rows][length] = '\0';
        if (isspace(next) == 0)
            pointer--;
        aux = realloc(distances, (rows + 1) * sizeof(*distances));
        if (aux == NULL)
            return -1;
        distances = aux;
        column = 0;
        distances[rows] = NULL;
        while ((*pointer != '\0') && (*pointer != '\n')) 
        {
            char *endptr;
            aux = realloc(distances[rows], (column + 1) * sizeof(**distances));
            if (aux == NULL)
                return -1;
            distances[rows] = aux;
            value = strtod(pointer, &endptr);
            if (*endptr == '-')
                distances[rows][column] = -1.0;
            else
                distances[rows][column] = value;
            while ((*endptr != '\0') && (isspace((unsigned char) *(endptr + 1)) != 0))
                ++endptr;
            pointer = ++endptr;
            column += 1;
        }
        rows += 1;
    }

    fprintf(stdout, "%-15s|", "Distance");
    for (size_t i = 0 ; i < rows ; ++i)
        fprintf(stdout, " %-14s|", cities[i]);
    fputc('\n', stdout);
    for (size_t i = 0 ; i < rows ; ++i)
    {
        fprintf(stdout, "%-15s|", cities[i]);
        for (size_t j = 0 ; j < rows ; ++j)
        {
            if (distances[i][j] < 0.0) // Invalid distance
                fprintf(stdout, "%15s|", "-");
            else
                fprintf(stdout, "%15.2f|", distances[i][j]);
        }
        free(distances[i]);
        free(cities[i]);
        fputc('\n', stdout);
    }
    free(distances);
    free(cities);

    fclose(nansa);
    return 0;
}

【讨论】:

    【解决方案4】:

    起初,你有一个错误的免费,你可以看看下面:

    for(i=0; i<rows; i++)
    {
        for(j=0; j<cols; j++)
        {
            printf("%g%s", mat[i][j], j<cols-1 ? "\t" : "\n");
            /*free(mat[i]); this will be executed several time and the program will crash*/ 
        }
        free(mat[i]);
    }
    free(mat);
    

    我已更新您的代码以显示所有所需数据:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <malloc.h>
    
    #define rows 6
    #define cols 6
    
    int main()
    {
        FILE *nansa;
        char *buffer;
        int ret,row=0,i,j,len=0,maxlen=0;
    
        char delims[]=" \t";
        char *result=NULL;
    
        double **mat=malloc( rows*sizeof(double*) );
        for(i=0; i<rows; i++)
        {
            mat[i]=malloc( cols*sizeof(double) ); 
        }
    
        char **cities = (char **)malloc( rows*sizeof(char *) );
        for(i=0; i<rows; i++)
        {
            cities[i]=(char *)malloc(sizeof(char)*4095); 
        }
    
        if ((nansa=fopen("dist.txt","r"))==NULL)
        {
            fprintf(stdout, "Error\n"); 
            return -1;
        }
        while(!feof(nansa))
        {
            buffer=malloc( sizeof(char)*4096 );
            memset(buffer,0,4096);
            ret=fscanf(nansa, "%4095[^\n]\n", buffer);
            if(ret != EOF) 
            {
                int field=0;
                result=strtok(buffer,delims);
                while(result != NULL)
                {
                    if(field>5) break;
                    if(field == 0)
                    {
                        strcpy(cities[row], result);
                        len = strlen(result);
                        if(len>maxlen)
                            maxlen=len;
                    }
                    mat[row][field]=atof(result);
                    result=strtok(NULL,delims);
                    field++;
                }
                ++row;
            }
            free(buffer);
        }
        fclose(nansa);
        for(i=0; i<cols; i++)
        {
            printf("%-*s%s", maxlen, cities[i], (i<cols-1) ? " " : "\n");
        }
        for(i=1; i<rows; i++)
        {
            printf("%-*s ", maxlen, cities[i]);
            for(j=1; j<cols; j++)
            {
                printf("%-*g%s", maxlen, mat[i][j], (j<cols-1) ? " " : "\n");
            }
        }
        for(i=0; i<rows; i++)
        {
            free(cities[i]);
            free(mat[i]);
        }
        free(mat);
        return 0;
    }
    

    结果会是这样的:

    Distance  Amsterdam Antwerp   Athens    Barcelona Berlin   
    Amsterdam 0         160       3082      1639      649      
    Antwerp   160       0         2766      1465      723      
    Athens    3082      2766      0         3312      2552     
    Barcelona 1639      1465      3312      0         1899     
    Berlin    649       723       2552      1899      0        
    

    【讨论】:

    • 这可能是真的,但它没有回答问题
    • 城市名称在哪里?
    • 他只想显示mat[i][j],所以无法显示,因为mat是double**和他的代码,替换为0
    • 感谢您的回答!!
    • @S.Proa - upclick 是 Stackoverflow 中 Thank you 的受欢迎替代品。如果您喜欢开发者的答案,请点击该答案左上角的向上小箭头。
    【解决方案5】:

    ...而且我不知道如何区分名称和距离(字符和整数)...

    只关注文件读取数据解析数据存储...

    识别文本文件的特征是帮助您决定将parsing数据转换为变量的方法的重要步骤。

    您的文本文件可以分为以下几部分:

    • 第一行是标题
    • 每增加一行都包含数据(城市和距离)
    • 城市仅在第一列(仅非数字(字符串))
    • 其余列包含距离(数字和非数字)

    虽然数据全部存储在文本文件中,并且最初以字符串形式读取,但您表示希望将它们存储为字符串和数字。城市名称是字符串,距离是整数。但距离部分还包含非数字数据:“-”。

    可以使用struct 来存储多种数据类型。以下代码说明了如何解析然后使用结构分别存储数字和字符串。

    注意:以下示例旨在说明如何将名称和距离与文本文件分开。错误检查/处理很少。

    与打印数据相反,我将使用struct 的数组留下一个图像,显示数据如何存储的内存段。 (支持您将其中的数据保存在 C 中的矩阵中的请求)。

    enum {
        AM,
        AN,
        AT,
        BA,
        BE,
        MAX_CITY
    };
    
    typedef struct {//create a way to store both strings and numeric data
        char city[20];
        int dist[MAX_CITY];
    }DIST;
    
    DIST dist[MAX_CITY];//array (matrix) of struct DIST for storing results.
    
    int main(void)
    {
        int i;
        FILE *fp = {0};
        char *tok = {0};
        char line[260];
        fp = fopen(".\\dist.txt", "r");
        if(fp)
        {
            i = 0;
            fgets(line, 260, fp); //consume first line - header information
            while(fgets(line, 260, fp))
            {
                tok = strtok(line, " \t\n");
                if(tok)
                {
                    strcpy(dist[i].city, tok);//get city    
                }
                tok = strtok(NULL, " \t\n");
                if(tok)
                {
                    if(strstr(tok, "-")) dist[i].dist[0] = 0;
                    else dist[i].dist[0] = atoi(tok);//get city 1 dist  
                }
                tok = strtok(NULL, " \t\n");
                if(tok)
                {
                    if(strstr(tok, "-")) dist[i].dist[1] = 0;
                    else dist[i].dist[1] = atoi(tok);//get city 2 dist  
                }
                tok = strtok(NULL, " \t\n");
                if(tok)
                {
                    if(strstr(tok, "-")) dist[i].dist[2] = 0;
                    else dist[i].dist[2] = atoi(tok);//get city 3 dist  
                }
                tok = strtok(NULL, " \t\n");
                if(tok)
                {
                    if(strstr(tok, "-")) dist[i].dist[3] = 0;
                    else dist[i].dist[3] = atoi(tok);//get city 4 dist  
                }
                tok = strtok(NULL, " \t\n");
                if(tok)
                {
                    if(strstr(tok, "-")) dist[i].dist[4] = 0;
                    else dist[i].dist[4] = atoi(tok);//get city 5 dist  
                }
                i++;
            }
            fclose(fp);
        }
    
        return 0;
    }
    

    结果片段(执行后结构的内容)

    【讨论】:

    • 嗯,程序可以读取文件的第一行来判断有多少个城市,然后在内存中分配正确的空间,并在嵌套循环中读取每个距离(我不是down选民顺便说一句)。
    • 是的,我想有一百万种改进方法。所述意图仅限于解决有关分隔字符串和数字的问题。分配内存和额外的循环会使这个简单的部分变得模糊不清。谢谢。
    • 利用距离矩阵的对称性也很好(我可以在 C++ 中更轻松地做到这一点)
    • @Bob__ - 关于使用 C++ 来利用对称性,我很感激,我相信你可以。但是有两件事:首先,OP 已将此帖子标记为 C,因此我觉得将其保留在 C 中是有限制的。其次,该答案的重点已明确说明。我希望它有助于 OP 理解将不同的数据类型解析为 C 数组。 (或如OP所说的矩阵)。其他一些答案更广泛地解决了这篇文章的答案,并且在展示许多其他技术方面做得很好。感谢您的评论!
    【解决方案6】:

    查看strtok()的用法!!

    您可以使用strtok() 将它们分开。您将所有内容都作为字符串,但随后您使用以下命令与int 保持距离:-'0'

    【讨论】:

    • 因为您没有提供如何使用它。这个问题与解析多行文本有关。这类似于说如果你想解一个数学方程,你将使用勾股定理......但你没有提到如何使用它。看看帖子中的其他答案,并将答案的质量与您的答案进行比较......您是否觉得您的答案充分解决了问题?
    猜你喜欢
    • 2015-02-01
    • 2022-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-12
    • 1970-01-01
    相关资源
    最近更新 更多