首页 > 解决方案 > 在 C 中读取具有不均匀大小的立方体和额外字符的迷宫文件的数组

问题描述

我试图弄清楚如何正确地从 C 中的 .txt 文档中读取和存储迷宫。这个迷宫的最大尺寸为 40x40“立方体”。读完后,我需要解决它,方法是把从左下角到右下角的路径用 *. 我习惯于使用 2D 数组,但是这个问题一直困扰着我,因为我不明白如果行和列不完全均匀,或者我什至如何准确地在中间打印 *在我解决了每个“立方体”之后。我读过其他由 1 和 0 组成的迷宫示例,或者甚至全部由墙壁组成的 #,这使得阅读和跟踪变得容易,但不能像这样输入。在解决将由双空格分隔的第一个迷宫后,我需要在同一个文本文件中读取其他迷宫。以下是其中一个迷宫的示例:

+---+---+---+---+---+  
|       |           |  
+---+   +   +   +   +  
|   |   |   |   |   |  
+   +   +   +   +---+  
|   |   |   |       |  
+   +   +   +---+   +  
|       |   |       |  
+   +---+---+   +   +  
|               |   |  
+---+---+---+---+---+ 

这是我迄今为止所做的一些代码,用于错误检查和读取字符。在其中,我试图初始化一个 120x120 的数组,读取当前字符,并将这些字符转换为 -1 或 0 以对应于墙壁或空白空间。:

/* Read in a grid as parameter of max 120x120 to account for '+' and'-' */
int readLine(int grid2D[120][120])
{
    int row = 0;
    int col = 0;

    int isNewLine = TRUE;

    /* Loop through while getchar does not equal EOF */
    while ((c = getchar()) != EOF)
    {

        /* Check for foreign characters, return FALSE if found */
        if ((c != '+') || (c != '-') || (c != '|') || (c != ' '))
        {
            /* If c = \n , avoid tripping error, and do nothing */
            if(c == '\n'){}
            else
              errorFree = FALSE;
        }

        /* If character is a '+' '-' or '|', it is a wall, set to -1 to 
        use int's for wall tracking */
        else if (row%2 == 0)
        {
           if(c == '|')
           {
              grid2D[row][col] = -1;
              col++;
           }
        }
        else if((c == '+') || (c == '-'))
        {
           grid2D[row][col] = -1;
           col++;
        }
        else
        {
            if(c == '\n')
            {
               col = 0;
               row++;
            }
        }

    isNewLine = TRUE;

    return isNewLine;
}

任何指导将不胜感激,我不确定我正在做的方法是否正确。我相信我目前正在正确检查错误,但是我很难理解我应该如何跟踪每个“立方体”,因为每个“立方体”的字符不均匀,它们的尺寸更像 5x1 立方体(a +---+for一侧和另一侧|

标签: carraysstringmultidimensional-array

解决方案


针对您在评论中提出的问题,确定行和列的大小非常简单。当您从文件中读取一行数组时fgets,您可以使用strlen()来确定字符数(但请注意,它还包含该'\n'字符-因此您需要减去一个-您可以结合修剪'\n'from结束)

一旦您阅读了第一行并考虑了'\n',设置一个变量来保存数组中的字符(列)数。由于您知道您的数组是一个多维数据集,因此您可以将第一行的长度与读取的每隔一行的长度进行比较,以验证所有行的列数相同。

当您循环和处理每一行的输入时,您只需保留一个行计数器,当您完成读取时,它将保存数组中的行数。

有两种方法可以处理阵列的存储。您可以声明一个足够大的数组来容纳您预期的最大迷宫(同时保持它足够小以适合堆栈256x512在 Linux 和 Windoze 上都是安全的),或者您可以为列和行动态分配存储空间,realloc()用于分配额外的存储空间按要求。(在那里,您可以处理高达计算机内存限制的迷宫大小 - 但它确实增加了复杂性)

例如,您对我的阵列的“困惑”11x21是可以理解的。这一切都源于终端上的字符大约是宽度的两倍。因此,要打印一个“立方体”字符,您需要的列数大约是行数的两倍。这根本不是问题。如果您正确地对列和行的读取进行编码,并使用变量来跟踪列和行的数量——那么差异只不过是您的代码在几个变量中跟踪的数字。

以下是一个简短的示例,用于解决您在读取未知数或行和列达到固定最大值时遇到的障碍。(而不是动态分配和重新分配——我们可以留到以后)。为此,我们#define为最大列数设置一个常数,然后知道我们需要该行数的 1/2,该行数为#define一个常数,例如

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

#define MAXC 512    /* declare consts for max NxN you can handle */
#define MAXR (MAXC + 1) / 2

然后,声明变量来跟踪当前的行数和列数 ( ) 以及声明一个数组来保存迷宫是一件row简单colnrow, ncol事情a[MAXR][MAXC] = {""};。然后,如果将文件名作为第一个参数给出,则可以打开文件(stdin如果没有给出参数,则默认读取)。在任何一种情况下,您都可以验证您是否打开了一个流以供阅读,例如

    size_t row = 0, col = 0, nrow = 0, ncol = 0;
    char a[MAXR][MAXC+1] = {""}; /* delcare and initialize array */
    /* 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;
    }

随着您的文件流打开以供读取,现在只需读取文件中的所有数据行。由于您希望所有行都有偶数个字符,因此您的迷宫实际上是一个立方体,您可以保存第一行中的字符数(在修剪 之后'\n')并使用它来比较每个其他行中的字符数行来验证你有一个立方体。当您阅读行时,您还需要保护您的数组边界,这样您就不会尝试在数组中存储比您要保存的行更多的行,因此简单的检查row < MAXR结合您fgets (a[row], MAXC, fp)将施加该限制,例如

    while (row < MAXR && fgets (a[row], MAXC, fp)) {
        size_t len = strlen (a[row]);       /* get length of row */
        if (len && a[row][len-1] == '\n')   /* validate it fits in array */
            a[row][--len] = 0;  /* remove trailing '\n' char from end */
        else if (len == MAXC) {
            fprintf (stderr, "error: row exceeds %d chars.\n", MAXC);
            return 1;
        }
        if (!row)       /* if 1st row - set expected ncol for each row */
            ncol = len;
        if (ncol != len) {  /* validate all other rows against 1st */
            fprintf (stderr, "error: unequal columns (%lu) on row (%lu)\n",
                    len, row);
            return 1;
        }
        /* your code goes here - example just outputs array */
        for (col = 0; col < ncol; col++)
            putchar (a[row][col]);
        putchar ('\n');

        row++;  /* advance row counter when done processing row */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */
    nrow = row;                     /* save the total number of rows */

您现在拥有存储的所有行和列,并且您已经设置了nrowncol值,并为您提供了nrow x ncol数组。我将留给您的路径逻辑,但我确实想提供一个示例,说明如何在您的路径中' '替换为。'*'以下对每个可能的路径字符执行此操作,每个可能的路径字符都施加了每个'*'具有相邻空间的约束(您可以根据需要进行调整)。在这里,我们只是循环0 -> nrow-1并嵌套一个循环 from 0 -> ncol-1to 循环遍历数组中的每个字符。

在检查一行中的相邻单元格时唯一需要注意的问题是,在检查左侧的列而不是在迷宫的右边缘时,必须确保自己不在迷宫的左边缘检查右侧的列时(访问数组边界之外的元素将调用Undefined Behavior

您将边缘检查作为简单地添加到您的if (...)语句内部的条件,例如

    /* you can make multiple passes over the array to determine your path.
     * below is just an example of replacing the spaces in the path with
     * asterisks.
     */
    puts ("\nreplacing path spaces with asterisks\n");
    for (row = 0; row < nrow; row++) {
        for (col = 0; col < ncol; col++) {
            /* if adjacents and current ' ', replace with '*' */
            if (col && col < ncol - 1 &&    /* col > 0 && col < ncol-1 */
                    /* next checks adjacent and current all ' ' */
                    a[row][col-1] == ' ' && a[row][col] == ' ' && 
                    a[row][col+1] == ' ')
                a[row][col] = '*';  /* if conditions met, set element '*' */
            putchar (a[row][col]);
        }
        putchar ('\n');
    }

将所有部分放在一个简短的示例中以读取任何512字符宽的迷宫,您可以执行以下操作:

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

#define MAXC 512    /* declare consts for max NxN you can handle */
#define MAXR (MAXC + 1) / 2

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

    size_t row = 0, col = 0, nrow = 0, ncol = 0;
    char a[MAXR][MAXC+1] = {""}; /* delcare and initialize array */
    /* 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;
    }

    while (row < MAXR && fgets (a[row], MAXC, fp)) {
        size_t len = strlen (a[row]);       /* get length of row */
        if (len && a[row][len-1] == '\n')   /* validate it fits in array */
            a[row][--len] = 0;  /* remove trailing '\n' char from end */
        else if (len == MAXC) {
            fprintf (stderr, "error: row exceeds %d chars.\n", MAXC);
            return 1;
        }
        if (!row)       /* if 1st row - set expected ncol for each row */
            ncol = len;
        if (ncol != len) {  /* validate all other rows against 1st */
            fprintf (stderr, "error: unequal columns (%lu) on row (%lu)\n",
                    len, row);
            return 1;
        }
        /* your code goes here - example just outputs array */
        for (col = 0; col < ncol; col++)
            putchar (a[row][col]);
        putchar ('\n');

        row++;  /* advance row counter when done processing row */
    }
    if (fp != stdin) fclose (fp);   /* close file if not stdin */
    nrow = row;                     /* save the total number of rows */

    /* you can make multiple passes over the array to determine your path.
     * below is just an example of replacing the spaces in the path with
     * asterisks.
     */
    puts ("\nreplacing path spaces with asterisks\n");
    for (row = 0; row < nrow; row++) {
        for (col = 0; col < ncol; col++) {
            /* if adjacents and current ' ', replace with '*' */
            if (col && col < ncol - 1 &&    /* col > 0 && col < ncol-1 */
                    /* next checks adjacent and current all ' ' */
                    a[row][col-1] == ' ' && a[row][col] == ' ' && 
                    a[row][col+1] == ' ')
                a[row][col] = '*';  /* if conditions met, set element '*' */
            putchar (a[row][col]);
        }
        putchar ('\n');
    }

    return 0;
}

示例使用/输出

正如所指出的,代码只是读取并输出原始迷宫,然后在迷宫上进行第二次传递,输出它的路径填充有'*'

$ ./bin/array2dread <dat/arrmaze.txt
+---+---+---+---+---+
|       |           |
+---+   +   +   +   +
|   |   |   |   |   |
+   +   +   +   +---+
|   |   |   |       |
+   +   +   +---+   +
|       |   |       |
+   +---+---+   +   +
|               |   |
+---+---+---+---+---+

replacing path spaces with asterisks

+---+---+---+---+---+
| * * * | * * * * * |
+---+ * + * + * + * +
| * | * | * | * | * |
+ * + * + * + * +---+
| * | * | * | * * * |
+ * + * + * +---+ * +
| * * * | * | * * * |
+ * +---+---+ * + * +
| * * * * * * * | * |
+---+---+---+---+---+

如果您还有其他问题,请仔细查看并告诉我。


推荐阅读