0

I have written a code in c that reads numbers from a text file and puts them into a matrix format in an array. The thing is I have made my code work for a matrix of definite size 3x3. If I have to increase the size, I have to directly change it in code. How can I change my code so that it automatically creates an array of the required size and reads the file irrespective of its length (it is a given that the input file will contain just enough numbers to form a square matrix)? For example, I can give it an input file which contains numbers separated by a comma for an nxn matrix, and my code should give me an nxn matrix containing those numbers. Here is my code,

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

//Declaring Matrices
float A[3][3];

//Function to read the matrices from a file
int ReadMatrix(void){
    FILE * f_pointer;
    f_pointer = fopen("Input Matrix.txt","r");
    if(f_pointer==NULL){
        printf("Error 404:- File not found");
        return 1;
    }
    
    fscanf(f_pointer,"%f, %f, %f\n%f, %f, %f\n%f, %f, %f", &A[0][0], &A[0][1], &A[0][2], &A[1][0], &A[1][1], &A[1][2], &A[2][0], &A[2][1], &A[2][2]);
    return 0;
}

2 Answers2

2

Probably the easiest single-pass approach is simple to read the floats in the file into an allocated block of memory, that can be expanded as needed with realloc until all values are read. Then you can simply loop and determine if the number of values read will form a square matrix -- handle the error if they won't. You then have an efficiently stored block of floats that with simple arithmetic can be manipulated as a 2D array. For example, if you read 9 values, and your square size is 3x3, you can access your i, j row and column values as:

array [i * 3 + j];

(note: another option -- you can allocate n pointers and n additional blocks of n - floats if you want true 2D indexing, e.g. array [i][j] -- up to you)

A simple implementation that does just that could be:

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

#define NELEM 16    /* initial number of floats to allocate */

/** realloc 'ptr' of 'nelem' of 'psz' to 'nelem * 2' of 'psz'.
 *  if *nelem == 0, initial allocation of NELEM psz block,
 *  returns pointer to reallocated block of memory with new
 *  memory initialized to 0/NULL. return must be assigned to
 *  original pointer in caller.
 */
void *xrealloc2 (void *ptr, size_t psz, size_t *nelem)
{
    void *memptr = NULL;
    if (!*nelem)
        *nelem = NELEM < 2 ? 2 : NELEM / 2;
    memptr = realloc (ptr, *nelem * 2 * psz);
    if (!memptr) {
        perror ("realloc(): virtual memory exhausted.");
        exit (EXIT_FAILURE);
        /* return NULL; */
    }   /* zero new memory (optional) */
    memset ((char *)memptr + *nelem * psz, 0, *nelem * psz);
    *nelem *= 2;
    return memptr;
}

int main (int argc, char **argv) {
    
    float *arr = NULL;      /* pointer to allocate block of floats */
    size_t  n = 0,          /* number of floats read */
            nelem = 0,      /* number allocated */
            sqsize = 0;     /* square size of 2D matrix */
    /* 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;
    }
    
    for (;;) {              /* loop continually reading float from file */
        if (n == nelem)     /* is realloc needed? */
            arr = xrealloc2 (arr, sizeof *arr, &nelem); /* realloc 2x current */
        if (fscanf (fp, "%f", &arr[n]) != 1) {          /* read/validate float */
            if (n)          /* if values stored, break loop */
                break;
            else            /* otherwise, error or EOF before values read */
                exit (EXIT_FAILURE);
        }
        n++;
    }
    
    if (fp != stdin)   /* close file if not stdin */
        fclose (fp);
    
    while (sqsize * sqsize < n)     /* determine size of square matrix */
        sqsize++;
    
    if (sqsize * sqsize != n) {     /* validate only that number read */
        fputs ("error: n not a square.\n", stderr);
        exit (EXIT_FAILURE);
    }
    
    for (size_t i = 0; i < sqsize; i++) {           /* output matrix */
        for (size_t j = 0; j < sqsize; j++)
            printf (" %2g", arr[i * sqsize + j]);
        putchar ('\n');
    }
    
    free (arr);     /* free allocated memory */
}

Since you are reading one float at a time, it doesn't matter whether the numbers are all in one line, or one per-line, or arranged in square-matrix form, fscanf() skips whitespace whether that be a space or newline.

Exampe Use/Output

2x2:

$ echo "1.1 2.2 3.3 4.4" | ./bin/array_1d2d_square_float
 1.1 2.2
 3.3 4.4

non-square:

$ echo "1.1 2.2 3.3 4.4 5.5" | ./bin/array_1d2d_square_float
error: n not a square.

3x3:

$ echo "1.1 2.2 3.3 4.4 5.5 6.6 7.7 8.8 9.9" | ./bin/array_1d2d_square_float
 1.1 2.2 3.3
 4.4 5.5 6.6
 7.7 8.8 9.9

9x9 from file with 1-81:

$ ./bin/array_1d2d_square_float dat/81int.txt
  1  2  3  4  5  6  7  8  9
 10 11 12 13 14 15 16 17 18
 19 20 21 22 23 24 25 26 27
 28 29 30 31 32 33 34 35 36
 37 38 39 40 41 42 43 44 45
 46 47 48 49 50 51 52 53 54
 55 56 57 58 59 60 61 62 63
 64 65 66 67 68 69 70 71 72
 73 74 75 76 77 78 79 80 81
David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Alternative: I'd use `size_t nelem = 0` and `arr = malloc (...` --> `arr =NULL;` and adjust `xrealloc2()` to handle `*nelem == 0`. – chux - Reinstate Monica Aug 30 '20 at 05:58
  • Yes, I like that approach, a single call. I generally stay away from it when allocating within main in answers because it inherently leads to and `if ... else` or `ternary` which is confusing to new users, but if I moved that to the function I could do the same thing without cluttering `main()` with that additional logic. Give me a sec. – David C. Rankin Aug 30 '20 at 06:08
  • @chux-ReinstateMonica - I updated `xrealloc2` to handle `*nelem == 0` and eliminated the initial allocation from `main()`. Since I want the initial allocation to match the `NELEM` const, the original allocation with `*nelem = 0` makes `*nelem = 2` if `NELEM < 2` or `*nelem = NELEM / 2` otherwise. You see a cleaner way to make that come out? (we could have used `1`, but since we are looking for square matricies, `4` for a 2x2 is about as small as makes sense) – David C. Rankin Aug 30 '20 at 06:25
0

I had also some fun and created a program too, it's probably a little bit easier to understand. :-)

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

/* Program to read a square matrix from a file */
/* Syntax: <programname> <filename> */
int main(int argc, char *argv[])
{
    FILE *f_pointer = NULL;
    f_pointer = fopen(argv[1],"r");

    if(f_pointer==NULL)
    {
        printf("File not found\n");
        return 1;
    }
    else
    {
        char floatbuffer[32] = "";
        float matrix_member[256]; /* supports up to a 16x16 square matrix */
        int matrix_entries = 0;
        int matrix_columns_n_rows = 0;
        
        for(int i = 0; !feof(f_pointer); i = fgetc(f_pointer))
        {
            if(i=='\n'||i==' ')
            {
                /* do nothing */
            }
            else if(i==',')
            {
                /* read matrix member */
                sscanf(floatbuffer, "%f", &matrix_member[matrix_entries]);                
                matrix_entries++;
                
                floatbuffer[0] = '\0'; /* reset floatbuffer */
            }
            else
            {
                char c[2] = {i&0b11111111, '\0'}; /* transform unsigned char to string */
                strcat(floatbuffer, c);
            }
        }
        /* read last matrix member too */
        sscanf(floatbuffer, "%f", &matrix_member[matrix_entries]);        
        matrix_entries++;
        
        /* close file */
        fclose(f_pointer);
        
        printf("total matrix entries %i\n", matrix_entries);
        for(int i = 0; i<matrix_entries; i++)
        {
            printf("entry #%i: %f\n", i+1, matrix_member[i]);
        }
        
        /* check for plausbility */
        for(int i = 1; i<17; i++)
        {
            if(i*i==matrix_entries)
            {
                matrix_columns_n_rows=i;
            }
        }
        
        if(matrix_columns_n_rows)
        {
            printf("this is a valid square matrix with %i columns and %i rows\n", matrix_columns_n_rows, matrix_columns_n_rows);
            return 0;
        }
        else
        {
            printf("this isn't a valid square matrix\n");
            return 2;
        }
    }
}

Output

total matrix entries 9
entry #1: 0.100009
entry #2: 0.200008
entry #3: 0.300007
entry #4: 0.400006
entry #5: 0.500005
entry #6: 0.600004
entry #7: 0.700003
entry #8: 0.800002
entry #9: 0.900001
this is a valid square matrix with 3 columns and 3 rows
paladin
  • 765
  • 6
  • 13