简体   繁体   中英

Storing maze into 2d array

I'm trying to code the following maze, from a file given as command line argument, into a 2d matrix:

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

#define MAX 300

int countLines(FILE *fp);
int countColumns(FILE *fp);
void storeMaze(int N, int M, int matrix[N][M], FILE *fp);


int main(int argc, char **argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "Invalid format.\nCorrect format: %s maze_file.dat\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *fin = fopen(argv[1], "rb");

    if(!fin)
    {
        fprintf(stderr, "Couldn't open file: %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    int N = countLines(fin); // lines in the matrix
    rewind(fin);
    int  M = countColumns(fin); // columns in the matrix
    rewind(fin);
//  printf("Lines: %d | Columns: %d\n", N, M);
    int maze[N][M];
    storeMaze(N, M, maze, fin);

    fclose(fin);

    return 0;
}

int countLines(FILE *fp)
{
    char c;
    int count = 0;
    while((c = fgetc(fp)) != EOF)
    {
        if (c == '\n') // Increment count if this character is newline
        {
            count++;
        }
    }
    return count;
}

int countColumns(FILE *fp)
{
    char c;
    int count = 0;
    while((c = fgetc(fp)) != EOF)
    {
        if (c == '\n') // Increment count if this character is newline
        {
            break;
        }
        count++;
    }
    return count;
}

void storeMaze(int N, int M, int matrix[N][M], FILE *fp)
{
    int i, j, startPoint, endPoint;
    char buf[MAX];

    for(i = 0; i < N; i++)
    {
        for(j = 0; j < M; j++)
        {
            while(fgets(buf, sizeof buf, fp))
            {
                if(buf[j] == '#')
                {
                    matrix[i][j] = 0;
                }
                if(buf[j] == ' ')
                {
                    matrix[i][j] = 1;
                }
            }

            printf("%d ", matrix[i][j]);
        }
    }
    putchar('\n');
}

There should only be only one's and zero's in the output based on each character in the file, some of them are printed correctly, but most are garbage values. I haven't really used matrices until now so I'm guessing maybe I passed the parameters wrong in the storeMaze or something. Any ideas?

The memory for the array isn't clean. A simple hack is to write a null-byte as part of your allocation (or a space).

Edit : Sorry. I didn't check out how you were initializing your arrays. You should use constants for the upperbound size. Fixed my answer

int main(int argc, char **argv)
{
    if(argc != 2)
    {
        fprintf(stderr, "Invalid format.\nCorrect format: %s maze_file.dat\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    FILE *fin = fopen(argv[1], "rb");

    if(!fin)
    {
        fprintf(stderr, "Couldn't open file: %s\n", argv[1]);
        exit(EXIT_FAILURE);
    }

    int N = countLines(fin); // lines in the matrix
    rewind(fin);
    int M = countColumns(fin); // columns in the matrix
    rewind(fin);
//  printf("Lines: %d | Columns: %d\n", N, M);
    int maze[50][50];
    storeMaze(N, M, maze, fin);

    fclose(fin);

    return 0;
}

void storeMaze(int N, int M, int matrix[N][M], FILE *fp)
{
    int z=0, i, startPoint, endPoint;
    char buf[1000];

    while (fgets(buf, sizeof buf, fp)) {

      for(i = 0; buf[i] != '\n' && z < N; i++){
          if(buf[i] == '#')
          {
              matrix[z][i] = 0;
          }
          else if(buf[i] == ' ')
          {
              matrix[z][i] = 1;
          }

          printf("%d ", matrix[z][i]);
      }
      z++;

    }
    putchar('\n');
}

You have a number of issues, not the least of which is attempting to read a line of input at every character location using fgets() (normally, that is the correct tool for reading a line-at-a-time , however, where you use it, you should be reading a character-at-a-time .

Your countLines() and countColumns() should be combined as both the number of columns and number of rows can be determined in a single pass through the file. Simply pass the address of both N and M as parameters, and once the number of rows and columns have been determined in your function, update the values at the address held by both N and M , respectively.

When determining the number of rows and columns you must validate that each row has the same number of columns in the file. If not, the file does not have a valid format for your maze.

When determining the number of rows in the file, you must handle the case where the file contains a non-POSIX eof. (no '\n' after the final text in the file). You can do that when end-of-file is reached by checking whether the previous character was a '\n' character. If it isn't, you need to increment your row-count by 1 to account for the fact the final line is not followed by '\n' .

Using fgetc() for a character-by-character read of the file is fine. The I/O subsystem provides a read buffer for data read from the filesystem and reading with fgetc() simply reads from that buffer (you don't do repeated disk reads for each character). On Linux and Windows the buffer is BUFSZ bytes ( 8192 on Linux, and 512 bytes on windows).

A simple way to determine both the rows and columns is simply to loop continually reading with fgetc() and keeping track of the row and column count as you read through the file. Keeping track of the character from the last iteration will let you determine if the file as a '\n' after the last line of data in the file (a POSIX end-of-file), if not, add 1 to your row count.

You could write a combined countRowsCols() function as follows. The code is commented so you can follow the logic of keeping track of where you are in the file, and how to set the number of columns based on the first row column count, and then validate each subsequent row has the same number of columns, eg

/* determine number of rows and columns in fp, and
 * validate each row has the same number of columns.
 * returns 0 on failure, 1 otherwise, the values at
 * the addresses for rows and cols are updated to
 * hold the number of rows and columns in fp.
 */
int countRowsCols (FILE *fp, int *rows, int *cols)
{
    int row = 0,            /* row counter */
        col = 0,            /* column counter  */
        last = 0;           /* hold char read during last loop iteration */
    
    *rows = *cols = 0;      /* zero value at pointer addresses */
    
    for (;;) {                                  /* loop continually */
        int c = fgetc (fp);                     /* read character */
        if (c == EOF) {                         /* if EOF */
            if (last != '\n') {                 /* if last not \n, non-POSIX eof */
                row++;                          /* add +1 to row */
                if (*cols && col != *cols) {    /* if *cols set and col != *cols */
                    fputs ("error: unequal number of columns in file.\n", stderr);
                    return 0;
                }
            }
            break;                  /* break on EOF */
        }
        else if (c == '\n') {       /* if char is '\n' */
            if (!*cols)             /* if *cols not set */
                *cols = col;        /* set no. of cols based on 1st row */
            if (*cols != col) {     /* check col against *cols for remaining lines */
                fputs ("error: unequal number of columns in file.\n", stderr);
                return 0;
            }
            col = 0;                /* reset column count */
            row++;                  /* increment rows */
        }
        else                        /* normal character */
            col++;                  /* increment col count */
        last = c;                   /* set last = c */
    }
    
    return *rows = row;     /* return result of comparison validating all rows read */
}

After determining the number of rows and columns, the read loop in storeMaze() functions largely the same. Read a character at a time, when the column count is reached, check the next character is '\n' and reset the column count for the next line and validate your row count as you go. When you reach the end, validate you have read the expected row number of column character lines. You could write it similar to:

/* fill VLA (NxM) from fp setting element 1 if character is '#'
 * or 0 for all others. validate \n follows each row, optional
 * on last line. returns 1 on success, 0 otherwise.
 */
int storeMaze (int N, int M, int (*matrix)[M], FILE *fp)
{
    int row = 0;            /* row counter */
    
    while (row < N) {
        int c,              /* char to read */
            col = 0;        /* column counter */
        do {                /* loop M times */
            if ((c = fgetc (fp)) == '\n' || c == EOF) {   /* read c, check EOL and EOF */
                fputs ("error: premature EOL or EOF.\n", stderr);
                return 0;
            }
            matrix[row][col++] = (c == '#') ? 1 : 0;      /* set value, increment col */
        } while (col < M);
        
        if ((c = fgetc (fp)) != '\n' && c != EOF) {       /* validate next char is \n */
            fputs ("error: invalid column count.\n", stderr);
            return 0;
        }
        row++;              /* increment row */
    }
    
    return row == N;        /* return result of comparison validating all rows read */
}

( note: the change in return type from void to int in order to be able to communicate success or failure of the storeMaze() function back to the caller. For any function that is critical to the continued operation of your code, you must validate whether the function succeeds or fails . Choosing a void type for your function throws away the primary means you have of communicating success or failure back to the calling function ( main() here).

Putting it altogether, you could do something like the following. Note, after filling maze with values, the values in maze are then used to generate output that should match your original data file. That is one easy way you have to confirm your program logic, eg

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

int countRowsCols (FILE *fp, int *rows, int *cols);
int storeMaze(int N, int M, int (*matrix)[M], FILE *fp);

int main (int argc, char **argv)
{
    if (argc != 2) {    /* validate 1 argument given for filename */
        fprintf (stderr, "Invalid format.\n"
                         "Correct format: %s maze_file.dat\n", argv[0]);
        exit (EXIT_FAILURE);
    }
    
    int M = 0, N = 0;                   /* initialize VLA bounds zero */
    FILE *fin = fopen (argv[1], "r");   /* open file */

    if (!fin) { /* validate file open for reading ("b" not required) */
        fprintf (stderr, "error: file open failed '%s'.\n", argv[1]);
        exit (EXIT_FAILURE);
    }
    
    /* count rows/columns in single pass, update values for N & M */
    if (!countRowsCols (fin, &N, &M))   /* VALIDATE return */
        return 1;
    
    int maze[N][M];                     /* declare VLA (consider dynamic allocation */
    rewind (fin);                       /* rewind file for read into maze */
    
    /* read values into maze */
    if (!storeMaze (N, M, maze, fin))   /* VALIDATE return */
    
    fclose (fin);                       /* close file */

    for (int i = 0; i < N; i++) {       /* output maze, converting 0,1s to char */
        for (int j = 0; j < M; j++)     /* to validate values in maze */
            putchar (maze[i][j] ? '#' : ' ');
        putchar ('\n');
    }
}

/* determine number of rows and columns in fp, and
 * validate each row has the same number of columns.
 * returns 0 on failure, 1 otherwise, the values at
 * the addresses for rows and cols are updated to
 * hold the number of rows and columns in fp.
 */
int countRowsCols (FILE *fp, int *rows, int *cols)
{
    int row = 0,            /* row counter */
        col = 0,            /* column counter  */
        last = 0;           /* hold char read during last loop iteration */
    
    *rows = *cols = 0;      /* zero value at pointer addresses */
    
    for (;;) {                                  /* loop continually */
        int c = fgetc (fp);                     /* read character */
        if (c == EOF) {                         /* if EOF */
            if (last != '\n') {                 /* if last not \n, non-POSIX eof */
                row++;                          /* add +1 to row */
                if (*cols && col != *cols) {    /* if *cols set and col != *cols */
                    fputs ("error: unequal number of columns in file.\n", stderr);
                    return 0;
                }
            }
            break;                  /* break on EOF */
        }
        else if (c == '\n') {       /* if char is '\n' */
            if (!*cols)             /* if *cols not set */
                *cols = col;        /* set no. of cols based on 1st row */
            if (*cols != col) {     /* check col against *cols for remaining lines */
                fputs ("error: unequal number of columns in file.\n", stderr);
                return 0;
            }
            col = 0;                /* reset column count */
            row++;                  /* increment rows */
        }
        else                        /* normal character */
            col++;                  /* increment col count */
        last = c;                   /* set last = c */
    }
    
    return *rows = row;     /* return result of comparison validating all rows read */
}

/* fill VLA (NxM) from fp setting element 1 if character is '#'
 * or 0 for all others. validate \n follows each row, optional
 * on last line. returns 1 on success, 0 otherwise.
 */
int storeMaze (int N, int M, int (*matrix)[M], FILE *fp)
{
    int row = 0;            /* row counter */
    
    while (row < N) {
        int c,              /* char to read */
            col = 0;        /* column counter */
        do {                /* loop M times */
            if ((c = fgetc (fp)) == '\n' || c == EOF) {   /* read c, check EOL and EOF */
                fputs ("error: premature EOL or EOF.\n", stderr);
                return 0;
            }
            matrix[row][col++] = (c == '#') ? 1 : 0;      /* set value, increment col */
        } while (col < M);
        
        if ((c = fgetc (fp)) != '\n' && c != EOF) {       /* validate next char is \n */
            fputs ("error: invalid column count.\n", stderr);
            return 0;
        }
        row++;              /* increment row */
    }
    
    return row == N;        /* return result of comparison validating all rows read */
}

Example Use/Output

$ /bin/maze_cli dat/maze_cli.txt
################
#              #
###########    #
#              #
## # # #  ######
#              #
#              #
#              #
################

Validating maze Values

You can validate your program logic by outputting the characters generated by the 0 s and 1 s in maze and using diff to compare the output with the original data file, eg

$ diff dat/maze_cli.txt <(./bin/maze_cli dat/maze_cli.txt)

Or, confirming the operation on input files with and without a POSIX eof,

$ diff <(./bin/maze_cli dat/maze_cli.txt) <(./bin/maze_cli dat/maze_cli_neol.txt)

No output confirm no differnce between the input data file and the output generated from maze values converted back to characters.


Simplifying Using fgets() For Each Read

While a character oriented approach is fine, you can simplify the process by using fgets() to read a line-at-a-time in both countRowsCols() and storeMaze() . You included fgets() in your storeMaze() , but called it M times per-row, instead of calling it once.

In countRowsCols() , you can get the number of columns in each row and trim the '\n' from the end of the buffer filled by fgets() in a single call to strcspn() . See man 3 strspn . After the line has been read into the buffer buf , that can be done as:

    while (fgets (buf, MAXC, fp)) {             /* read each line into buf */
        size_t len;
        buf[(len = strcspn (buf, "\n"))] = 0;   /* trim newline, save length */

len holding the number of characters, not including the '\n' is the number of columns in that line. After declaring a constant for the maximum number of characters in each line (eg #define MAXC 1024 ) and including string.h , you could do:

/* determine number of rows and columns in fp, and
 * validate each row has the same number of columns.
 * returns 0 on failure, 1 otherwise, the values at
 * the addresses for rows and cols are updated to
 * hold the number of rows and columns in fp.
 */
int countRowsCols (FILE *fp, int *rows, int *cols)
{
    char buf[MAXC];                 /* buffer for each line */
    int row = 0;                    /* row counter */
    
    *rows = *cols = 0;              /* zero value at pointer addresses */
    
    while (fgets (buf, MAXC, fp)) {             /* read each line into buf */
        size_t len;
        buf[(len = strcspn (buf, "\n"))] = 0;   /* trim newline, save length */
        
        if (!*cols)                 /* set *cols based on first line no. of columns */
            *cols = len;
        
        if (len != (size_t)*cols) { /* check col against *cols for remaining lines */
            fputs ("error: unequal number of columns in file.\n", stderr);
            return 0;
        }
        
        row++;                      /* increment row counter */
    }
    
    return *rows = row;     /* return result of comparison validating all rows read */
}

Your storeMaze() is similarly simplified, after reading with fgets() simply looping over M characters in each line is all that is needed to fill the maze, eg

/* fill VLA (NxM) from fp setting element 1 if character is '#'
 * or 0 for all others. validate \n follows each row, optional
 * on last line. returns 1 on success, 0 otherwise.
 */
int storeMaze (int N, int M, int (*matrix)[M], FILE *fp)
{
    char buf[MAXC];             /* buffer for each line */
    int row = 0;                /* row counter */
    
    while (row < N && fgets (buf, MAXC, fp)) {          /* check bounds, read line */
        for (int i = 0; i < M && buf[i]; i++)           /* loop over each char */
            matrix[row][i] = (buf[i] == '#') ? 1 : 0;   /* fill maxtrix elements */
        
        row++;                  /* increment row */
    }
    
    return row == N;        /* return result of comparison validating all rows read */
}

You can simply swap out the functions with the ones that used fgetc() without any other changes aside from defining MAXC and including string.h . The output and handling of the row and column counts are the same.

Look things over and let me know if you have further questions.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM