0

I am tasked with reading a matrix from a text file and find out if it is a magic square, I also have to make sure that all the entries of the matrix are integers and if not I have to write to the standard output an ERROR message while also describing the position where this error occurred.

Now, I succeeded in reading all the elements of matrix but I have a problem with the verification if it is an integer or not.

I used fscanf() because I know that on success it returns the number of elements it succeeded reading correctly and returns EOF or 0 otherwise.

Here is the matrix:

3
12 3 87
78 a 9
45 0 23

first element is the number of rows and columns.

and here is my code ( the part I need help with at least )

while(!feof(file))
{
    while ( fgets(buffer, sizeof(buffer), file) )
    {

        for ( int j = 0; j < n; j++ )
        {
            if ( i == n )
            {
                break;
            }

            m = fscanf(file, "%d", &a[i][j]);
            printf("\n%d", m);

            if ( m == 0 || m == EOF )
            {
                printf("\nERROR, %d, %d, %d \n", i, j, a[i][j]);
            }

        }
        i++; 

    }



}

and here is my output ( right now I'm just trying to figure out this problem so this is not how it will look later on ):

1
1
1
1
0
ERROR, 1, 1, 0

0
ERROR, 1, 2, 48

1
1
1
12 3 87
78 0 48
45 0 23

When I replace 'a' with a number of type (float) for example it will display the ERROR message just for a[1][1] BUT it will replace 9 with 48.

When I have a character ( like in this case ) it will display an ERROR message both for a[1][1] ('a') and a[1][2] ('9') and replace it too.

And my question is why does this happen and what the hell happens between a[1][1] and a[1][2] and how to fix it.

EDIT:I know I could simply exit the program when I find 1 case but I'm really curious on why does this error happen.

fscanf(file, "%d", &n );//I read the first integer, which is also the maximum  capacity of my 2d array

int a[n][n];
The Virtuoso
  • 105
  • 1
  • 8
  • If scanf for a[i][j] fails, it will be left uninitialized. You shouldn't print its value on error. – hyde Feb 06 '19 at 05:22
  • I was just curious to see what happens I don't actually plan on printing it in the final program. – The Virtuoso Feb 06 '19 at 05:26
  • Hi @TheVirtuoso, can we see how exactly do you declare variable 'a'? – runwuf Feb 06 '19 at 05:35
  • [Why is “while ( !feof (file) )” always wrong?](https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong) – Swordfish Feb 06 '19 at 05:40
  • I declared it as a 2d array with integer entries with n lines and n columns, immediately after I read n I declare my array – The Virtuoso Feb 06 '19 at 05:44
  • @Swordfish -- and totally superfluous to the code presented -- the entire outer loop can be removed. You writing this one up? – David C. Rankin Feb 06 '19 at 05:49
  • @DavidC.Rankin *You writing this one up?* – Am I doing what? I'm just allergic to `while (!feof(file))` and I guess the OP would use it elsewhere too, so I left the link. – Swordfish Feb 06 '19 at 06:15
  • @TheVirtuoso Once you read with `fgets()` the file pointer moves to the next line. So the following `fscanf()` will never see the data consumed by `fgets()`. If you want to read the values from `buffer` use `sscanf()`. – Swordfish Feb 06 '19 at 06:23
  • I just hoped you were... – David C. Rankin Feb 06 '19 at 06:36
  • @DavidC.Rankin Hoped I were what!? Don't you understand that I don't get what you are trying to tell me?? – Swordfish Feb 06 '19 at 07:03
  • @Swordfish -- I was hoping you were going to write the answer so I could sit this one out `:)` – David C. Rankin Feb 06 '19 at 07:05
  • @DavidC.Rankin Sorry, I'm not much of a writer these days. – Swordfish Feb 06 '19 at 07:14

3 Answers3

3

First points:

while(!feof(file))
{

You will want to look at Why is while ( !feof (file) ) always wrong?. Further, the entire outer loop is superfluous to your code and can simply be removed.

With any of the scanf family of functions, when input does not match the conversion specifier used in the format string, a matching failure occurs, character extraction from the input stream stops at the point of failure, the offending characters causing the failure are left in the input stream (unread) just waiting to bite you on your next attempted read. Attempting to read 'a' as type int with scanf produces a matching failure.

How To Handle A Matching Failure?

Since you always validate all user input, as you note, you detect an input failure with scanf when the return is less than the number of conversions specified (or EOF). Reading an integer at a time with fscanf within your loops tells you exactly where the input failure happened from a row/col standpoint when reading a square matrix worth of data. Knowing where the failure occurred you can output the row/col where the invalid data was encountered.

To capture the invalid data for reporting purposes, you can simply scan forward with fgetc reading a character at a time and filling a buffer with the offending characters until the next valid digit or +/- (explicit sign for a digit) is encountered (you can use ungetc at that point to put the valid digit (or sign) back in the input stream if your wanted to continue reading additional values)

A Short Example

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

#define MAXC 1024

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

    int **m = NULL;
    unsigned dim = 0;
    /* 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;
    }

    if (fscanf (fp, "%u", &dim) != 1) { /* read square mtrx dim */
        fputs ("error: invalid format, no dimension.\n", stderr);
        return 1;
    }

    if (!(m = malloc (dim * sizeof *m))) {  /* allocate/validate dim ptrs */
        perror ("malloc-m");
        return 1;
    }

    for (unsigned i = 0; i < dim; i++)   /* allocate dim rows of dim int */
        if (!(m[i] = calloc (dim, sizeof *m[i]))) { /* zero mem w/calloc */
            perror ("calloc-m[i]");
            return 1;
        }

    for (unsigned i = 0; i < dim; i++)              /* for each row */
        for (unsigned j = 0; j < dim; j++) {        /* for each col */
            if (fscanf (fp, "%d", &m[i][j]) != 1) { /* read/validate int */
                char buf[MAXC], *p = buf;   /* buf and ptr to buf */
                int c;                      /* int for char */
                /* read while !EOF, not digit and not -/+ */
                while ((c = fgetc(fp)) != EOF && (c < '0' || '9' < c) &&
                        c != '-' && c != '+')
                    if (!isspace(c))    /* if not a space */
                        *p++ = c;       /* store offending char(s) */
                *p = 0;                 /* nul-terminate buf */
                if (c != EOF)           /* if c a char - put it back */
                    ungetc(c, fp);
                /* output location of invalid input */
                printf ("error: m[%d][%d] - invalid entry '%s'.\n",
                        i, j, buf);
                return 1;   /* and bail - or continue -- your choice */
            }
        }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (unsigned i = 0; i < dim; i++) {    /* for each row */
        for (unsigned j = 0; j < dim; j++)  /* for each col */
            printf (" %3d", m[i][j]);       /* output value */
        putchar ('\n');                     /* output newline */
        free (m[i]);                        /* free ints in row */
    }
    free (m);   /* free pointers */

    return 0;
}

(note: you didn't say how you allocated storage, so a simple pointer to pointer to int was used to allocate dim pointers and a block of dim integers was allocated and the starting address for each allocation assigned to each of the pointer to allow indexing as a 2D array)

Example Input Files

Using your example input with invalid int at [1][1]:

$ cat ~/tmpd/mtrx.txt
3
12 3 87
78 a 9
45 0 23

An example with good values:

$ cat ~/tmpd/mtrxgood.txt
3
12 3 87
78 8 9
45 0 23

Example Use/Output

With invalid int at [1][1]:

The program correctly reports the location and the offending character:

$ ./bin/readsquaremtrx ~/tmpd/mtrx.txt
error: m[1][1] - invalid entry 'a'.

With good values, all values are read and the matrix printed to the screen before all allocated memory is freed:

$ ./bin/readsquaremtrx ~/tmpd/mtrxgood.txt
  12   3  87
  78   8   9
  45   0  23

The code is commented to help you follow along. If you have any questions, just let me know.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
2

Key Issue of your Code: Your FILE pointer doesn't move ahead when fscanf encounters an error. You need to move it ahead using fseek.

Some of the issues you need to look at:

  • How to allocate an array of sufficient size to read all inputs correctly
  • File based error handling and how to move the FILE pointer ahead if it hits an error while reading using fscanf. My below code considers only the specific case of a single incorrect character being present, you will have to handle it appropriately if more than one character is present.
  • There is no need for you to do both fgets and fscanf. Only one is sufficient.
  • Relook, if you really want to use fscanf. The same logic can be achieved using other read methods related to FILE.

Treat the below code strictly as a pseudo code, just giving some indications. This doesn't handle all conditions at all. You need to work on lot of aspects of your code and handle additional error conditions etc. I have just given a pointer on how you can solve it.

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

int main(void)
{

  FILE *f = fopen("./check.txt", "r");
  int a[3][3] = {0};
  int n, res, i;

  res = fscanf(f, "%d", &n);

  i = 0;
  while(i<n)
  {
    for(int j = 0; j < n; j++)
    {
       int val = 0;
       res = fscanf(f, "%d", &val);
       if ((res == 0) || (res == EOF))
       {
           printf("Error: res = %d for i = %d, j = %d, val = %d\n", res, i, j, val);
           fseek(f, 1, SEEK_CUR);
       }
       else
           a[i][j] = val;
    }
    i++;

  }

  printf("The Matrix\n");
  for(int i = 0; i<n; i++)
  {
    for (int j=0; j<n; j++)
        printf("%d ", a[i][j]);
    printf("\n");
  }

}

Output:

Error: res = 0 for i = 1, j = 1, val = 0
The Matrix
12 3 87
78 0 9
45 0 23
Jay
  • 24,173
  • 25
  • 93
  • 141
  • When you are talking about allocation you are you referring to using malloc() function for lines and columns and checking if memory was allocated correctly ? ( if pointer is not NULL ) – The Virtuoso Feb 06 '19 at 06:31
  • Yes. I am talking about dynamically allocating the space for array correctly. You haven't posted your complete code, so I included this. – Jay Feb 06 '19 at 06:32
  • Yeah I usually like working with dynamic allocation but here I got lazy and decided to try to make the program work as quickly as possible ( after I make it work I usually try to look for steps I could skip in my final program and try to optimize it as much as possible ) – The Virtuoso Feb 06 '19 at 06:35
  • Ohh forgot to mention it , thank you the fseek() part resolved me, I need to study that function some more since this is the first time I found the need to use it – The Virtuoso Feb 06 '19 at 06:36
  • Most Welcome Virtuoso! :) – Jay Feb 06 '19 at 06:54
  • *"There is no need for you to do both fgets and fscanf. Only one is sufficient."*, I'd go as far as to say, using `fscanf` and `fgets` together is almost always a bad idea. Sure, `fgets` could be used to read until end of line for example to recover from error, but it will still depend on line being short enough to fit in the buffer, so not reliable. – hyde Feb 06 '19 at 08:14
1

Another example using a flat memory region instead of a somwhat inefficient "array" of pointers to pointers:

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

int is_magic_square(int *square, size_t dimension)
{
    if(dimension < 3) {
        return 0;
    }

    int prev_row_sum;
    int prev_col_sum;
    int d1_sum = 0;
    int d2_sum = 0;

    for (size_t y = 0; y < dimension; ++y) {
        int curr_row_sum = 0;
        int curr_col_sum = 0;
        for (size_t x = 0; x < dimension; ++x) {
            curr_row_sum += square[y * dimension + x];
            curr_col_sum += square[x * dimension + y];

            if (x == y) {
                d1_sum += square[y * dimension + x];
                d2_sum += square[y * dimension + dimension - 1 - x];
            }
        }

        if (y && (curr_row_sum != prev_row_sum || curr_col_sum != prev_col_sum)) {
            return 0;
        }

        prev_row_sum = curr_row_sum;
        prev_col_sum = curr_col_sum;
    }

    return prev_row_sum == d1_sum && prev_row_sum == d2_sum ? prev_row_sum : 0;
}

int main(void)
{
    char const *filename = "test.txt";
    FILE *input = fopen(filename, "r");
    if (!input) {
        fprintf(stderr, "Couldn't open \"%s\" for reading :(\n\n", filename);
        return EXIT_FAILURE;
    }

    size_t dimension;
    if (fscanf(input, " %zu", &dimension) != 1) {
        fprintf(stderr, "Faild to read squares dimensions from \"%s\" :(\n\n", filename);
        fclose(input);
        return EXIT_FAILURE;
    }

    int result = EXIT_FAILURE;
    printf("Reading a %zu x %zu square from \"%s\" ...\n\n", dimension, dimension, filename);

    int *square = calloc(dimension * dimension, sizeof(*square));
    if (!square) {
        fputs("Not enough memory :(\n\n", stderr);
        goto cleanup;
    }

    for (size_t y = 0; y < dimension; ++y, putchar('\n')) {
        for (size_t x = 0; x < dimension; ++x) {
            int value;
            if (fscanf(input, " %d", &value) != 1 || value < 1) {
                fprintf(stderr, "\n\nFailed to read value at (%zu, %zu) from \"%s\" :(\n\n",
                        x + 1, y + 1, filename);
                goto cleanup;
            }

            for (size_t pos = 0; pos < y * dimension + x; ++pos) {
                if (square[pos] == value) {
                    fprintf(stderr, "\n\nDuplicate value %d found at (%zu, %zu) in \"%s\" :(\n\n",
                            value, x + 1, y + 1, filename);
                    goto cleanup;
                }
            }

            if(value > dimension * dimension) {
                fprintf(stderr, "\n\nValue %d at (%zu, %zu) in \"%s\" is out of range 1, 2, ..., %zu^2 :(\n",
                        value, x + 1, y + 1, filename, dimension);
                goto cleanup;
            }

            printf("%4d ", value);
            square[y * dimension + x] = value;
        }
    }

    int sum = is_magic_square(square, dimension);
    printf("\nThis %s a perfect square!\n", sum ? "is" : "is not");

    if (sum) {
        printf("It's sum is %d.\n", sum);
    }

    putchar('\n');
    result = EXIT_SUCCESS;

cleanup:
    free(square);
    fclose(input);
    return result;
}

Sample Input:

4
10    3   13    8
 5   16    2   11
 4    9    7   14
15    6   12    1

Sample Output:

Reading a 4 x 4 square from "test.txt" ...

  10    3   13    8
   5   16    2   11
   4    9    7   14
  15    6   12    1

This is a perfect square!
It's sum is 34.
Swordfish
  • 12,971
  • 3
  • 21
  • 43