如何使用指针将矩阵文本文件读取到C中的链接列表中?

时间:2019-01-03 13:51:36

标签: c

我必须使用矩阵链接列表读取字母的文本文件,其中每个字母周围必须有8个指针。

这是我要做的: enter image description here

文本文件是这样的:

JDCPCPXOAA
ZXVOVXFRVV
NDLEIRBIEA
YTRQOMOIIO
FZZAPXERTQ
XAUEOEOOTO
PORTUOAZLZ
CZNOQUPUOP

在我的代码中,我只能阅读第一行中的字母。

有人可以帮我吗?

typedef struct letter           ///estrutura para cada letra da sopa
{
    char *lname;
    struct letter *pnext;
}LETTER;

typedef struct soup         ///estrutura para a sopa de letras
{
    int lin;
    int col;
    LETTER *pfirst;
}SOUP;

void read_soup_txt(SOUP *pcs,char *fn,int lin,int col)
{
  FILE *fp;
  fp=fopen(fn,"r");
  char c;
  if(fp!=NULL)
  {
    pcs->lin=lin;
    pcs->col=col;
    LETTER *current=malloc(sizeof(LETTER)),*previous;
    pcs->pfirst=current;

    for(int i=0;i<pcs->lin;i++)     ///linhas
    {
      for(int j=0;j<pcs->col;j++)     ///colunas
      {
        fscanf(fp,"%c",&c);                     ///le o char
        current->lname=malloc(sizeof(char));       ///aloca espaço para o char
        strcpy(current->lname,&c);              ///copia o char para a estrutura
        previous=current;

        if(i==pcs->lin-1)
        {
            current=NULL;
        }
        else
            current=malloc(sizeof(LETTER));
        previous->pnext=current;
      }
    }
  }
  else
    printf("Erro ao abrir arquivo!");
  fclose(fp);
}

1 个答案:

答案 0 :(得分:1)

  

每个字母周围必须有8个指针。

这意味着您的字母结构应类似于

struct letter {
    struct letter  *up_left;
    struct letter  *up;
    struct letter  *up_right;
    struct letter  *left;
    struct letter  *right;
    struct letter  *down_left;
    struct letter  *down;
    struct letter  *down_right;
    int             letter;
};

您也不需要字母汤。因为您按顺序读取字符,所以可以将它们直接读取到图形中。诀窍在于,您需要保持一个struct letter指针指向图中左上角的字母;一个struct letter指针,指向每一行的第一个字母;每个添加的新字母都有一个struct letter指针。

这是pseudocode中的逻辑:

Function ReadGraph(input):

    Let  topleft  = NULL     # Top left letter in the graph
    Let  leftmost = NULL     # Leftmost letter in current line
    Let  previous = NULL     # Previous letter in current line
    Let  current  = NULL     # Current letter
    Let  letter = ''

    Do:
        Read next letter from input
    While (letter is not a letter nor EOF)
    If letter is EOF:
        # No letters at all in the input, so no graph either.
        Return NULL
    End If

    topleft = new struct letter (letter)
    leftmost = topleft
    current = topleft

    # Row loop. First letter is already in current.
    Loop:

        # Loop over letters in the current line
        Loop:
            Read new letter from input
            If letter is EOF, or newline:
                Break
            End If

            previous = current
            current = new struct letter

            current->left = previous
            previous->right = current

            If current->left->up is not NULL:
                current->up_left = current->left->up
                current->up_left->down_right = current

                If current->up_left->right is not NULL:
                    current->up = current->up_left->right
                    current->up->down = current

                    If current->up->right is not NULL:
                        current->up_right = current->up->right
                        current->up_right->down_left = current
                    End If
                End If
            End If

        End Loop

        If letter is not EOF:
            While (letter is not EOF) and (letter is not a letter):
                Read new letter from input
            End While
        End If
        If letter is EOF:
            Break
        End If

        # We have a first letter on a new line.
        current = new letter structure

        current->up = leftmost
        leftmost->down = current

        If current->up->right is not NULL:
            current->up_right = current->up->right
            current->up_right->down_left = current
        End If

        leftmost = current

    End Loop

    Return topleft
End Function

请注意如何对输入流中的第一个字符进行不同的处理(从一开始),以及如何对每个后续行中的第一个字符进行不同的处理(在函数结尾附近)。从逻辑上或结构上来说,这可能感觉很奇怪,但是这样做可以使代码保持简单。

还请注意如何构建双向链接。因为我们是从上到下,从左到右阅读的,所以我们先建立链接,然后是左起,然后是左起,然后是上,然后是右起;反向链接紧接在向前链接之后。

这需要一些思考,以了解其工作原理。考虑:

  up_left │  up  │   up_right
──────────┼──────┼───────────
     left │ curr │      right
──────────┼──────┼───────────
down_left │ down │ down_right

构造curr时,我们知道left是否存在,因为我们分别处理每行的第一个字母。

如果curr->left为非NULL,并且curr->left->up为非NULL,则我们知道有前一行,我们可以指向curr->up_left指向它。当然,它的->down_right应该指向curr,以使链接保持一致。

如果curr->up_left为非NULL,而curr->up_left->right为非NULL,则我们知道上一行在同一列中有一个字母。我们可以将curr->up设置为指向它,而将其->down设置为指向curr

如果curr->up为非NULL,并且curr->up->right为非NULL,则我们知道上一行在下一列中有一个字母。我们可以将curr->up_right设置为指向它,而将其->down_left设置为指向curr

现在,因为我们从左到右读取每一行,所以每一行的所有列都被填充到最右边的列。如果继续使用上述逻辑,您将发现第二行填充了从第一行字母到第二行字母的其余链接,依此类推。

这还意味着,如果输入文件包含特殊字符,例如非字母节点的'*',则应在构造图形时创建它们,就像它们是普通字母一样,以确保上述逻辑链接作品。

在读取整个图之后,您可以从图中一一删除那些非字母节点。要删除节点,请首先将其的反向链接(从其相邻字母开始)设置为NULL,然后free()

free()对其进行结构化之前,我个人对结构进行了“毒化”,将letter设置为已知的不可能值(WEOF,用于宽输入范围),以及所有指向NULL,这样一来,如果其他代码在释放后使用了该结构(例如,在释放错误后使用 ),例如,因为它以某种方式缓存了指针,则更容易检测。

(当您free()指针时,C库通常不会立即将其返回给操作系统或将其清除;通常,动态分配的区域只会添加到内部可用堆中,因此将来的分配只能重用该内存。不幸的是,这意味着如果您不“释放”释放的结构,有时它们以后仍然可以访问。这种“使用后释放”的错误非常令人讨厌,绝对值得“不必要的”使用工作”,只是为了调试结构而使结构中毒。)

为促进中毒,并在某些情况下(如果不必要地降低了速度)使中毒变得容易,最好使用辅助函数来创建和破坏结构:

static inline struct letter *new_letter(const int letter)
{
    struct letter *one;

    one = malloc(sizeof *one);
    if (!one) {
        fprintf(stderr, "new_letter(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    one->up_left    = NULL;
    one->up         = NULL;
    one->up_right   = NULL;
    one->left       = NULL;
    one->right      = NULL;
    one->down_left  = NULL;
    one->down       = NULL;
    one->down_right = NULL;

    one->letter = letter;

    return one;
}

static inline void free_letter(struct letter *one)
{
    if (one) {
        one->up_left    = NULL;
        one->up         = NULL;
        one->up_right   = NULL;
        one->left       = NULL;
        one->right      = NULL;
        one->down_left  = NULL;
        one->down       = NULL;
        one->down_right = NULL;
        one->letter     = EOF;
        free(one);
    }
}

我通常在定义struct letter的头文件中包含这些函数;因为它们是微小的类似于宏的函数,所以将它们标记为static inline,告诉C编译器它们只需要在同一编译单元中就可以访问,并且不需要生成函数并调用这些函数。函数,但可以将代码内联到调用它们的位置。


我个人使用以下代码编写并验证了上述伪代码

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

struct letter {
    struct letter  *chain;  /* Internal chain of all known letters */

    struct letter  *up_left;
    struct letter  *up;
    struct letter  *up_right;
    struct letter  *left;
    struct letter  *right;
    struct letter  *down_left;
    struct letter  *down;
    struct letter  *down_right;

    wint_t          letter;
};

static struct letter *all_letters = NULL;

struct letter *new_letter(wint_t letter)
{
    struct letter *one;

    one = malloc(sizeof *one);
    if (!one) {
        fprintf(stderr, "new_letter(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    one->letter = letter;

    one->chain = all_letters;
    all_letters = one;

    one->up_left    = NULL;
    one->up         = NULL;
    one->up_right   = NULL;
    one->left       = NULL;
    one->right      = NULL;
    one->down_left  = NULL;
    one->down       = NULL;
    one->down_right = NULL;

    return one;
}

我更喜欢使用宽输入,因为在兼容的操作系统中,您可以使用任何将您的语言环境视为字母的字形,而不仅仅是ASCII A-Z。您所需要做的就是拥有

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Current locale is not supported by your C library.\n");
    if (fwide(stdin, 1) < 1)
        fprintf(stderr, "Warning: Wide standard input is not supported by your C library for current locale.\n");
    if (fwide(stdout, 1) < 1)
        fprintf(stderr, "Warning: Wide standard output is not supported by your C library for current locale.\n");
在您的main()开头

,并使用广泛的I / O功能(fwprintf()fgetwc()等),前提是您拥有标准的C环境。 (显然,某些Windows用户在Windows中对UTF-8支持存在问题。请向Microsoft投诉;以上行为是基于C标准的。)

chain成员用于将所有创建的字母链接到一个链接列表中,因此我们可以使用下面的函数以Graphviz Dot语言绘制整个图形。 (Graphviz适用于所有操作系统,在我看来,这是开发或调试使用链表或图形的代码时的绝佳工具。)circo实用程序似乎非常擅长绘制此类代码图。

int letter_graph(FILE *out)
{
    struct letter  *one;

    /* Sanity check. */
    if (!out || ferror(out))
        return -1;

    /* Wide output. */
    if (fwide(out) < 1)
        return -1;

    fwprintf(out, L"digraph {\n");
    for (one = all_letters; one != NULL; one = one->chain) {
        fwprintf(out, L"    \"%p\" [ label=\"%lc\" ];\n",
                      (void *)one, one->letter);
        if (one->up_left)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↖\" ];\n",
                          (void *)one, (void *)(one->up_left));
        if (one->up)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↑\" ];\n",
                          (void *)one, (void *)(one->up));
        if (one->up_right)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↗\" ];\n",
                          (void *)one, (void *)(one->up_right));
        if (one->left)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"←\" ];\n",
                          (void *)one, (void *)(one->left));
        if (one->right)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"→\" ];\n",
                          (void *)one, (void *)(one->right));
        if (one->down_left)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↙\" ];\n",
                          (void *)one, (void *)(one->down_left));
        if (one->down)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↓\" ];\n",
                          (void *)one, (void *)(one->down));
        if (one->down_right)
            fwprintf(out, L"    \"%p\" -> \"%p\" [ label=\"↘\" ];\n",
                         (void *)one, (void *)(one->down_right));
    }
    fwprintf(out, L"}\n");

    return 0;
}

如果输入文件是

ABC
DEF
GHI

图形的点描述是

digraph {
    "0x1c542f0" [ label="I" ];
    "0x1c542f0" -> "0x1c54170" [ label="↖" ];
    "0x1c542f0" -> "0x1c541d0" [ label="↑" ];
    "0x1c542f0" -> "0x1c54290" [ label="←" ];
    "0x1c54290" [ label="H" ];
    "0x1c54290" -> "0x1c54110" [ label="↖" ];
    "0x1c54290" -> "0x1c54170" [ label="↑" ];
    "0x1c54290" -> "0x1c541d0" [ label="↗" ];
    "0x1c54290" -> "0x1c54230" [ label="←" ];
    "0x1c54290" -> "0x1c542f0" [ label="→" ];
    "0x1c54230" [ label="G" ];
    "0x1c54230" -> "0x1c54110" [ label="↑" ];
    "0x1c54230" -> "0x1c54170" [ label="↗" ];
    "0x1c54230" -> "0x1c54290" [ label="→" ];
    "0x1c541d0" [ label="F" ];
    "0x1c541d0" -> "0x1c54050" [ label="↖" ];
    "0x1c541d0" -> "0x1c540b0" [ label="↑" ];
    "0x1c541d0" -> "0x1c54170" [ label="←" ];
    "0x1c541d0" -> "0x1c54290" [ label="↙" ];
    "0x1c541d0" -> "0x1c542f0" [ label="↓" ];
    "0x1c54170" [ label="E" ];
    "0x1c54170" -> "0x1c53ff0" [ label="↖" ];
    "0x1c54170" -> "0x1c54050" [ label="↑" ];
    "0x1c54170" -> "0x1c540b0" [ label="↗" ];
    "0x1c54170" -> "0x1c54110" [ label="←" ];
    "0x1c54170" -> "0x1c541d0" [ label="→" ];
    "0x1c54170" -> "0x1c54230" [ label="↙" ];
    "0x1c54170" -> "0x1c54290" [ label="↓" ];
    "0x1c54170" -> "0x1c542f0" [ label="↘" ];
    "0x1c54110" [ label="D" ];
    "0x1c54110" -> "0x1c53ff0" [ label="↑" ];
    "0x1c54110" -> "0x1c54050" [ label="↗" ];
    "0x1c54110" -> "0x1c54170" [ label="→" ];
    "0x1c54110" -> "0x1c54230" [ label="↓" ];
    "0x1c54110" -> "0x1c54290" [ label="↘" ];
    "0x1c540b0" [ label="C" ];
    "0x1c540b0" -> "0x1c54050" [ label="←" ];
    "0x1c540b0" -> "0x1c54170" [ label="↙" ];
    "0x1c540b0" -> "0x1c541d0" [ label="↓" ];
    "0x1c54050" [ label="B" ];
    "0x1c54050" -> "0x1c53ff0" [ label="←" ];
    "0x1c54050" -> "0x1c540b0" [ label="→" ];
    "0x1c54050" -> "0x1c54110" [ label="↙" ];
    "0x1c54050" -> "0x1c54170" [ label="↓" ];
    "0x1c54050" -> "0x1c541d0" [ label="↘" ];
    "0x1c53ff0" [ label="A" ];
    "0x1c53ff0" -> "0x1c54050" [ label="→" ];
    "0x1c53ff0" -> "0x1c54110" [ label="↓" ];
    "0x1c53ff0" -> "0x1c54170" [ label="↘" ];
}

(这是相反的顺序,因为我在链接列表的开头插入了每个新字母)。 circo从中得出以下图形:

3×3 letter grid, 8-way links

在开发过程中,我还要检查链接是否一致:

    for (one = all_letters; one != NULL; one = one->chain) {

        if (one->up_left && one->up_left->down_right != one)
            fprintf(stderr, "'%c'->up_left is broken!\n", one->letter);
        if (one->up && one->up->down != one)
            fprintf(stderr, "'%c'->up is broken!\n", one->letter);
        if (one->up_right && one->up_right->down_left != one)
            fprintf(stderr, "'%c'->up_right is broken!\n", one->letter);
        if (one->left && one->left->right != one)
            fprintf(stderr, "'%c'->left is broken!\n", one->letter);
        if (one->right && one->right->left != one)
            fprintf(stderr, "'%c'->right is broken!\n", one->letter);
        if (one->down_left && one->down_left->up_right != one)
            fprintf(stderr, "'%c'->down_left is broken!\n", one->letter);
        if (one->down && one->down->up != one)
            fprintf(stderr, "'%c'->down is broken!\n", one->letter);
        if (one->down_right && one->down_right->up_left != one)
            fprintf(stderr, "'%c'->down_right is broken!\n", one->letter);
    }

通过一致的链接,我的意思是如果为a->left == b,则为b->right == a。当然,支票无法判断a->leftb->right是否错误;它只能检测它们是否一致。