0

I have a text file that I'm scanning in. The first number in the file is the number of rows in the matrix, the second number is the number of columns in the matrix.

"Text file"

4    
10
3.000000,1.000000,1180.000000,1955.000000,221900.000000
3.000000,2.250000,2570.000000,1951.000000,538000.000000
2.000000,1.000000,770.000000,1933.000000,180000.000000
.
.
.

n (4,10)Matrix

I'm using fscanf to store read the array and two loops to read the values received in a two d array.

double hold;
fscanf(fpointer,"%d",&value);//gets me 4
fscanf(fpointer,"%d",&lastvalue);/*gets me 10*/
for (i=0; i<value; i++)
{
for (j=0; j<lastvalue; j++)    //Supposed to input the other values
{
  fscanf(fpointer,"%lf",&hold); array[i][j]=hold;

I'm printing the array contents through two for loops.

for(i=0;i<value;i++){
        for(j=0;j<lastvalue;j++){

  printf("%lf\t", array[i][j]); 
                            }
  printf("\n");

Yet I receive the first index of the loop repeating as an output.

3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    
3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    3.000000    

I've spent quite some time on this and I'm not sure if my logic is incorrect or something I'm not understanding about fscanf. I initially thought fscanf was having trouble with the comma. I tried using the comma as a delimiter as I saw on another post(fscanf(...,"%lf[^ ,]"...).I received the same output. Please let me know what's wrong and how to fix it.

5
  • how is array defined? Commented Oct 16, 2017 at 2:07
  • 4
    I initially thought fscanf was having trouble with the comma: Yup. You're right. Discard the comma by using fscanf(fpointer,"%lf,",&hold); Commented Oct 16, 2017 at 2:07
  • 1
    check return code of fscanf. returns 0 => could not read data Commented Oct 16, 2017 at 2:08
  • Thank you,fixing the comma solved my issue. Commented Oct 16, 2017 at 2:34
  • See also Using fscanf() to read comma-delimited doubles. Commented Oct 16, 2017 at 4:01

2 Answers 2

1

There are a number of approaches you can take. The key here is you know you will have 2 integers (one in each of the first two lines) representing the number of rows and cols worth of data to follow. Then you will read rows number of cols into your matrix. Your first decision will be "How do I handle storage?" (allocate dynamically, or is the data small enough it won't StackOverflow if I use a variable length array (VLA) for the matrix?)

Using a VLA eliminates the need to allocate, track and free memory dynamically, but you must know you won't need to store more double values than can fit on your stack. The following presumes you have less than 100,000 or so values making a VLA a valid option.

First, how are you going to pick-off (read) the row and col values from the file? While fgets is by far the preferred manner of doing line oriented input, you can actually get both row and col in one call to fprintf with the added benefit of consuming all remaining whitespace following the col value up to the first value for your matrix. For example, the following will work:

/* read row & col and consume all whitespace to first value */
if (fscanf (fp, "%d %d ", &row, &col) != 2) {
    fprintf (stderr, "error: failed to read row and col.\n");
    return 1;
}

(note: the space following the last conversion specifier in "%d %d ")

Having the row and col values, you can now size a buffer to read each of the remaining lines in the file with fgets. After each line is read into a buffer, each column value will be parsed from the buffer using strtod and the number of values parsed validated against the col value to insure a complete row of you matrix is filled. The line buffer is a VLA as well sized 32-chars per col value to be read (which should be more than enough by 50%). Sizing the buffer and your matrix with VLA could be done as follows:

    bufsz = col * 32;       /* set read buffer size based on col */
    char buf [bufsz];       /* VLA for read buffer */
    double mtrx[row][col];  /* VLA for matrix */
    memset (mtrx, 0, row * col * sizeof **mtrx);    /* zero matrix */

Next is simply reading each line with fgets, and then using a pointer to test whether the current character is one of [+-0-9]. If it is, a conversion to double is performed and validated, and the pointer is advance to the next character beyond the ending character in the conversion (as provided by strtod itself).

If the current character isn't one you are interested, just do nothing with it and get the next character (that is an easy way to skip the ','s)

After all values in a line are converted, compare the number of successful conversion against col to insure a full row in you matrix was filled, if not handle error. Then just read the next line and repeat until row number of lines have been read and converted. You can accomplish that will something similar to the following:

    /* read each remaining line up to row lines */
    while (ridx < row && fgets (buf, bufsz, fp)) {
        int cidx = 0;                   /* column index */
        char *p = buf, *ep = NULL;      /* pointer & end pointer for strtod */
        while (cidx < col && *p && *p != '\n') {    /* for each character */
            /* if '+-' or '0-9' convert number with strtod */
            if (*p == '+' || *p == '-' || ('0' <= *p && *p <= '9')) {
                errno = 0;                      /* set errno for strtod */
                double tmp = strtod (p, &ep);   /* convert string to value */
                if (errno) {    /* if errno set, conversion failed */
                    fprintf (stderr, "error: failed conversion mtrx[%d][%d].\n",
                            row, col);
                    return 1;
                }
                mtrx[ridx][cidx++] = tmp;   /* set matrix value, inc. cidx */
                p = ++ep;                   /* set new p to one past ep */
            }
            else    /* if not '+-' or '0-9', just get next char */
                p++;
        }
        if (cidx != col) {  /* validate that col values contained in line */
            fprintf (stderr, "error: row '%d' has only '%d' values.\n",
                    ridx, cidx);
            return 1;
        }
        ridx++;     /* row done and values validated, read next row */
    }

After you have read row rows worth of col values, you can use your matrix as required. The following just puts the above together in a short example to read and output the example data you provided:

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

int main (int argc, char **argv) {

    int bufsz, col, row, ridx = 0;  /* buffer size, col, row, row index */
    FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;

    if (!fp) {  /* validate file open for reading */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        return 1;
    }

    /* read row & col and consume all whitespace to first value */
    if (fscanf (fp, "%d %d ", &row, &col) != 2) {
        fprintf (stderr, "error: failed to read row and col.\n");
        return 1;
    }

    bufsz = col * 32;       /* set read buffer size based on col */
    char buf [bufsz];       /* VLA for read buffer */
    double mtrx[row][col];  /* VLA for matrix */
    memset (mtrx, 0, row * col * sizeof **mtrx);    /* zero matrix */

    /* read each remaining line up to row lines */
    while (ridx < row && fgets (buf, bufsz, fp)) {
        int cidx = 0;                   /* column index */
        char *p = buf, *ep = NULL;      /* pointer & end pointer for strtod */
        while (cidx < col && *p && *p != '\n') {    /* for each character */
            /* if '+-' or '0-9' convert number with strtod */
            if (*p == '+' || *p == '-' || ('0' <= *p && *p <= '9')) {
                errno = 0;                      /* set errno for strtod */
                double tmp = strtod (p, &ep);   /* convert string to value */
                if (errno) {    /* if errno set, conversion failed */
                    fprintf (stderr, "error: failed conversion mtrx[%d][%d].\n",
                            row, col);
                    return 1;
                }
                mtrx[ridx][cidx++] = tmp;   /* set matrix value, inc. cidx */
                p = ++ep;                   /* set new p to one past ep */
            }
            else    /* if not '+-' or '0-9', just get next char */
                p++;
        }
        if (cidx != col) {  /* validate that col values contained in line */
            fprintf (stderr, "error: row '%d' has only '%d' values.\n", 
                    ridx, cidx);
            return 1;
        }
        ridx++;     /* row done and values validated, read next row */
    }

    if (fp != stdin) fclose (fp);       /* close file if not stdin */

    if (ridx != row) {  /* validate that row rows read from file */
        fprintf (stderr, "error: file has only row '%d' rows.\n", ridx);
        return 1;
    }

    for (int i = 0; i < row; i++) {     /* output the matrix */
        for (int j = 0; j < col; j++)
            printf (" %9.2f", mtrx[i][j]);
        putchar ('\n');
    }

    return 0;
}

Example Use/Output

$ ./bin/readmtrx dat/matrix.txt
      3.00      1.00   1180.00   1955.00 221900.00
      3.00      2.25   2570.00   1951.00 538000.00
      2.00      1.00    770.00   1933.00 180000.00

Look things over and let me know if you have further questions. If you don't have the VLA extension provided by your compiler, dynamic memory allocation is your other option as explained in the other answer.

Sign up to request clarification or add additional context in comments.

Comments

1

You need to deal with the commas. Here's one way:

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


void die(char *msg) {
  fprintf(stderr, "Error: %s\n", msg);
  exit(1); 
}

double **read_matrix(FILE *f) {
  int m, n;
  if (fscanf(f, "%d%d", &m, &n) != 2) die("Couldn't scan matrix size");
  if (m <= 0 || n <= 0) die("Bad matrix size");
  double **matrix = malloc(m * sizeof(double*));
  if (!matrix) die("Couldn't allocate matrix spine");
  for (int i = 0; i < m; ++i) {
    matrix[i] = malloc(n * sizeof(double));
    if (!matrix[i]) die("Couldn't allocate matrix row");
    // Read the first column. No comma.
    if (fscanf(f, "%lf", &matrix[i][0]) != 1) die("Couldn't read matrix (1)");
    // Read the other columns. Skip preceding commas.
    for (int j = 1; j < n; ++j)
      if (fscanf(f, ",%lf", &matrix[i][j]) != 1) die("Couldn't read matrix (2)");
  }
  return matrix;
}

int main(void) {
  double **matrix = read_matrix(stdin);
  for (int i = 0; i < m; ++i) {
    for (int j = 0; j < n; ++j) printf(" %lf", matrix[i][j]);
    printf("\n");
  } 
  return 0;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.