user6131790
user6131790

Reputation: 39

Read a matrix from a file

This is my function to read a matrix from a file. In the text file I have on the first line 2 is n and 3 is m and on the next two lines is the matrix. I don't have erros, but my program "stop working" and I don't know why. Thank you! In main I have: readmatrix(n,m,a, "text.txt");

int readmatrix(int* n,int *m, int a[*n][*m], char* filename){
    FILE *pf;
    int i,j;
    pf = fopen (filename, "rt");
    if (pf == NULL)
    return 0;
    fscanf (pf, "%d",n); 
    for (i=0;i<*n;i++) 
    {
        for(j=0;j<*m;j++)
        fscanf (pf, "%d", &a[i][j]);
    }
    fclose (pf); 
    return 1; 
    }

Upvotes: 3

Views: 20887

Answers (2)

Zsombor Szommer
Zsombor Szommer

Reputation: 101

If you're not afraid of using goto, an easy-to-read, robust way to read a matrix looks like this:

typedef struct Matrix
{
    size_t width, height;
    double **data;
} Matrix;

Matrix read_matrix(char const *filename)
{
    Matrix matrix;
    FILE *file;

    if ((file = fopen(filename, "rt")) == NULL)
        goto error;

    if (fscanf(file, "%zu %zu", &matrix.width, &matrix.height) != 2)
        goto error_file;

    if ((matrix.data = (double **)calloc(matrix.height, sizeof(double *))) == NULL)
        goto error_file;

    for (size_t y = 0; y < matrix.height; ++y)
        if ((matrix.data[y] = (double *)malloc(matrix.width * sizeof(double))) == NULL)
            goto error_matrix;

    for (size_t y = 0; y < matrix.height; ++y)
        for (size_t x = 0; x < matrix.width; ++x)
            if (fscanf(file, "%lf", &matrix.data[y][x]) != 1)
                goto error_matrix;

    fclose(file);
    return matrix;

error_matrix:
    for (size_t y = 0; y < matrix.height; ++y)
        free(matrix.data[y]);
    free(matrix.data);

error_file:
    fclose(file);

error:
    return (Matrix){0, 0, NULL};
}

Upvotes: 0

Pablo
Pablo

Reputation: 13580

If your compiler supports VLAs or you are using C99, then you can do this:

#include <stdio.h>

int readmatrix(size_t rows, size_t cols, int (*a)[cols], const char* filename)
{

    FILE *pf;
    pf = fopen (filename, "r");
    if (pf == NULL)
        return 0;

    for(size_t i = 0; i < rows; ++i)
    {
        for(size_t j = 0; j < cols; ++j)
            fscanf(pf, "%d", a[i] + j);
    }


    fclose (pf); 
    return 1; 
}

int main(void)
{
    int matrix[2][3];

    readmatrix(2, 3, matrix, "file.dat");

    for(size_t i = 0; i < 2; ++i)
    {
        for(size_t j = 0; j < 3; ++j)
            printf("%-3d ", matrix[i][j]);
        puts("");
    }

    return 0;
}

file.dat looks like this:

1 2 3
4 5 6

and the output of my program is

$ ./a 
1   2   3   
4   5   6   

Note that this is a basic example, you should always check the return value of fscanf. If file.dat had one row only, then you would get in trouble. Also there are not numbers in the file, you would get also undefined values in the matrix.

I'd advice to read the whole line with fgets and then parse the line using sscanf or some other function like strtok, then it would be easier to react to errors in the input file.


edit

A more robust way of reading a file like this would be:

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


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

    *rows = 0;
    *cols = 0;

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

    if(fp == NULL)
    {
        fprintf(stderr, "could not open %s: %s\n", filename, strerror(errno));
        return NULL;
    }

    int **matrix = NULL, **tmp;

    char line[1024];

    while(fgets(line, sizeof line, fp))
    {
        if(*cols == 0)
        {
            // determine the size of the columns based on
            // the first row
            char *scan = line;
            int dummy;
            int offset = 0;
            while(sscanf(scan, "%d%n", &dummy, &offset) == 1)
            {
                scan += offset;
                (*cols)++;
            }
        }

        tmp = realloc(matrix, (*rows + 1) * sizeof *matrix);

        if(tmp == NULL)
        {
            fclose(fp);
            return matrix; // return all you've parsed so far
        }

        matrix = tmp;

        matrix[*rows] = calloc(*cols, sizeof *matrix[*rows]);

        if(matrix[*rows] == NULL)
        {
            fclose(fp);
            if(*rows == 0) // failed in the first row, free everything
            {
                fclose(fp);
                free(matrix);
                return NULL;
            }

            return matrix; // return all you've parsed so far
        }

        int offset = 0;
        char *scan = line;
        for(size_t j = 0; j < *cols; ++j)
        {
            if(sscanf(scan, "%d%n", matrix[*rows] + j, &offset) == 1)
                scan += offset;
            else
                matrix[*rows][j] = 0; // could not read, set cell to 0
        }

        // incrementing rows
        (*rows)++;
    }

    fclose(fp);

    return matrix;
}

int main(void)
{

    size_t cols, rows;
    int **matrix = readmatrix(&rows, &cols, "file.dat");

    if(matrix == NULL)
    {
        fprintf(stderr, "could not read matrix\n");
        return 1;
    }


    for(size_t i = 0; i < rows; ++i)
    {
        for(size_t j = 0; j < cols; ++j)
            printf("%-3d ", matrix[i][j]);
        puts("");

    }


    // freeing memory
    for(size_t i = 0; i < rows; ++i)
        free(matrix[i]);
    free(matrix);

    return 0;
}

Now file.dat looks like this:

1 2 3 4 
4 5 6 5 
9 8 8 7 
5 5 5 5 
1 1 1 1 

And the output is

1   2   3   4   
4   5   6   5   
9   8   8   7   
5   5   5   5   
1   1   1   1   

In this example I calculate the number of columns only for the first column and use that number for all other columns. If the input file has rows with less columns that the first row, then the missing values are stored with 0. If it has rows with more columns than the row will be trimmed.

I calculate the number of rows like this:

            while(sscanf(scan, "%d%n", &dummy, &offset) == 1)
            {
                scan += offset;
                (*cols)++;
            }

First I declare a pointer scan to point to line, so that I can modify the pointer without losing the original line. The %n in sscanf is not counted in the number of successfull conversions, this returns the position of scan where it stopped reading. I used that to loop the sscanf. I explicitly check that sscanf returns 1 and if that's the case, I increment the number of columns and I update scan to update to the point where sscanf stoppped reading. This allows me to continue scanning until the end of the line is reached. I use a similar technique to parse all the integers.

Upvotes: 5

Related Questions