0

My exercise is to load 2 matrices from 2 files with unknown sizes and multiply them together if it is possible. I should also load each file once. I could do it with loading the file twice as you can see below, but how could I do with loading the file only once?

typedef struct matrix_ {
    int r, c;
    double* dat;
} matrix;

int rows(char* fn) {
    int lines = 1;
    int ch;
    FILE* fp = fopen(fn, "r");
    while(!feof(fp)) {
        ch = fgetc(fp);
        if(ch == '\n') {
            lines++;
        }
    }
    return lines;
}

matrix loadmatrix(char* fn) {
    FILE* file = fopen(fn, "r");
    int size = 5*5;
    matrix mat;
    mat.r = rows(fn);
    mat.dat = malloc(size*sizeof(double));
    double input;
    int i = 0;
    do {
        if (fscanf(file, "%lf", &input)==1) {
            if(i == size-1) {
                size = 4*size;
                mat.dat = realloc(mat.dat, size*sizeof(double));
            }
            mat.dat[i] = input;
            i+=1;
        }
    } while (!feof(file));
    mat.c = ((i+1)/(mat.r));
    return mat;
}
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
Botond
  • 204
  • 1
  • 8
  • 8
    [while (!feof(fp)) always wrong](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Antti Haapala -- Слава Україні Dec 14 '18 at 09:01
  • @AnttiHaapala Thank you, I will read it later because It will surely be useful later on. But is it helpful in my actual problem? – Botond Dec 14 '18 at 09:12
  • May be, it's worth to read it soon. I think it's part of your problem. Btw. you probably will find inspriation in the answers how to solve your issue. – Scheff's Cat Dec 14 '18 at 09:16
  • 1
    You could expose sample input. Imagine you have 6 values: Is it 6x1, 3x2, 2x3, or 1x6 matrix? The line ends might provide the necessary hint. In this case, you should read the files in a way that provides this info as well. (May be, read lines, then evaluate what is in line.) – Scheff's Cat Dec 14 '18 at 09:19
  • 1
    Or ... read all values into a 1-d matrix that is easier to reallocate, then calculate the indices with `x + y * row_length` – Antti Haapala -- Слава Україні Dec 14 '18 at 09:19
  • 1
    Try reading the file in line-by-line, which allows you to count rows. With each line, use `sscanf` to extract the values in the columns. Once you have read the first line, you know the number of columns in the matrix. Keep a counter to indicate which position into your `matrix.data` you write values, and to know when to increase the size of your matrix's memory. `realloc` when needed, increasing the size of `matrix.data` by one row (the number of columns) at a time. – Billy Brown Dec 14 '18 at 10:06

1 Answers1

2

Read the file line-by-line, and for each line, read in each value with sscanf.

Every line is a row and every value in it is in a column. On the first row, count the columns as you go, and count the rows at every newline. If you need more space in your data array, realloc one more row's worth of space (matrix.cols * sizeof(double)).

Using read_line for portably reading in a line, we have:

#define MIN_SIZE (5 * 5)

typedef struct matrix {
    size_t  rows;
    size_t  cols;
    double *data;
} matrix;

matrix loadmatrix(const char *filename)
{
    FILE *file = fopen(filename, "r");
    if (file == NULL) {
        fprintf(stderr, "could not open file '%s' for reading\n", filename);
        exit(1);
    }
    matrix mat = {
        .rows = 0,
        .cols = 0,
        .data = calloc(MIN_SIZE, sizeof(double)),
    };
    // You should check mat.data != NULL here, but I omit it to ease reading
    char *line = NULL;
    size_t index = 0;
    size_t data_size = MIN_SIZE;
    while (read_line(file, &line) > 0) {
        double value;
        // Keep reading values into this row
        size_t offset = 0;
        int read_chars = 0;
        while (sscanf(line + offset, "%lf%n", &value, &read_chars) == 1) {
            offset += read_chars;
            if (mat.rows == 0) {
                ++mat.cols;
            }
            // Increase the size of the matrix by one more row if more space is needed
            if (index >= data_size) {
                data_size += mat.cols;
                mat.data = realloc(mat.data, sizeof(double) * data_size);
                if (mat.data == NULL) {
                    fprintf(stderr,
                        "could not allocate more space for matrix: %zu\n",
                        data_size);
                    exit(1);
                }
            }
            mat.data[index++] = value;
        }
        ++mat.rows;
        free(line);
    }
    return mat;
}
Billy Brown
  • 2,272
  • 23
  • 25
  • @Botond I have fixed the `read_line` link to a gist that I wrote. Alternatively, you can use `POSIX`/`glibc`'s `getline` (`man getline`) if you have it available (it takes different parameters from what I used here). – Billy Brown Dec 14 '18 at 15:14
  • Thank you! The read_line is working now. But I have a problem what I can't solve: I tested it with 2 3x3 matrices, but it did not read their last element. I tried to look for the problem, but I could not find it. Did I mess something up? – Botond Dec 14 '18 at 15:37
  • It works well if I add an extra line at the end of the file, but I could not fix it. – Botond Dec 14 '18 at 15:59
  • @Botond What exactly do you mean by that? This solution expects the file to contain the matrix and only the matrix, with no extra lines or invalid data. – Billy Brown Dec 14 '18 at 16:45
  • 1
    I know. But if I make a file with 3 lines: "1 2 3" & "4 5 6" & "7 8 9", It will only read the first 8 elements i.e. it will miss the 9. But if I make a file with 4 lines: "1 2 3" & "4 5 6" & "7 8 9" & "" i.e. if I add an extra line, it will read all 9 elements. – Botond Dec 14 '18 at 17:14
  • @Botond I believe that this could be an issue with my `read_line` function, which expects the last character to be a `\n`, but your file does not end with one (while vim added one to my file). I have updated the `read_line` gist to fix this. Thanks for noticing it. – Billy Brown Dec 18 '18 at 09:10
  • Thank you for your help! – Botond Dec 18 '18 at 09:25