将文件中的单词读入动态二维数组

Reading the words of a file into a dynamic 2D array

我正在尝试读取文件并将每个单词存储到动态分配的二维数组中。输入文件的大小未知。

我完全迷路了,不知道如何才能“fix/finish”这个程序。

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

int main(void) {
    char filename[25];
    printf("Input the filename");
    scanf("%s", filename);
    fileConverter(filename);
}

int fileConverter(char filename[25]) {
    //int maxLines = 50000;
    //int maxWordSize = 128;
    //char words[maxLines][maxWordSize];
    //char **words;
    char **arr = (char**) calloc(num_elements, sizeof(char*));

    for ( i = 0; i < num_elements; i++ ) {
        arr[i] = (char*) calloc(num_elements_sub, sizeof(char));
    }
    FILE *file = NULL;
    int amountOfWords = 0;
    file = fopen(filename, "r");
    if(file == NULL) {
        exit(0);
    }
    while(fgets(words[amountOfWords], 10000, file)) {
        words[amountOfWords][strlen(words[amountOfWords]) - 1] = "[=10=]";
        amountOfWords++;
    }

    for(int i = 0; i < amountOfWords; i++) {
        printf("a[%d] = ", i);
        printf("%s\n", words[i]);
    }
    printf("The file contains %d words and the same amount of lines.\n", amountOfWords);

    return amountOfWords;

分配您需要的动态二维数组:

void allocChar2Darray(size_t rows, size_t columns, char (**array)[columns])
{
    *array = malloc(rows * sizeof(**array));
}

这类问题的主要挑战是

  • 在程序读取新单词时重新分配字符串数组,并且
  • 处理大于 fgets 使用的缓冲区的单词。

解决这类解析问题的一般方法是设计一个状态机。这里的状态机有两种状态:

  1. 当前字符是空白。行动:继续读取空白,直到我们到达缓冲区的末尾,或者直到我们到达一个非空白字符,在这种情况下,我们切换到状态 2。
  2. 当前字符是非空白(即一个词)。行动:继续读取非空白,直到我们到达缓冲区的末尾,或者直到我们到达一个空白字符,在这种情况下,我们将我们刚刚读取的单词复制到字符串数组并切换到状态 1.

特别困难的是我们处于状态 2 并到达缓冲区末尾的情况。这意味着这个词跨越多个缓冲区。为了适应这一点,我们稍微偏离了直接状态机实现。状态 2 略有不同,具体取决于我们是在阅读一个新单词还是继续在前一个缓冲区中开始的单词。
我们现在跟踪 wordSize。如果我们从缓冲区的开头开始读取,但 wordSize 不为 0,那么我们知道我们正在继续前一个单词 并且 我们知道它的大小 realloc 我们需要。

下面是一种可能的实现方式。所有的工作都在 wordArrayRead 函数中完成。从函数顶部遍历它:

首先我们声明我们在 lineBuffer 读取中需要的变量:单词本身的索引和我们当前正在读取的单词的长度,然后是缓冲区本身的声明。外部循环使用 fgets 重复读取,直到我们耗尽输入。 我们从索引 0 开始读取并在空终止符处停止。第一个 if 语句检查我们是否应该处于状态 2:当前字符是单词的开头或者我们已经在读一个单词。

状态 2
索引 wordStartIdx 停留在单词(段)的第一个字符处,我们将 wordEndIdx 移动到单词(段)的末尾或缓冲区的末尾。 然后我们检查是否需要增加字符串数组的大小。这里我们将其增加到之前大小的 2 倍 + 1 以避免频繁重新分配。 我们设置一个布尔值,指示我们是否已到达单词的末尾。如果有,我们需要在字符串末尾分配并写入空终止符。 如果 wordLength == 0 意味着我们正在读取一个新单词并且必须第一次为它分配内存。如果 wordLength != 0,我们必须重新分配以附加到现有单词。 我们将当前在 lineBuffer 中的单词(段)复制到字符串数组中。 现在,我们做一些簿记。如果我们到达了一个单词的末尾,我们将写入空终止符,增加索引以指向下一个单词位置并重置 wordLength。如果不是这种情况,我们只将 wordLength 增加到我们刚刚读取的段的长度。最后,我们更新 wordStartIdx,它仍然指向单词的开头,指向单词的结尾,因此我们可以继续迭代缓冲区。

状态 1
完成状态 2 处理后,我们进入只有两条线的状态 1。它只是简单地推进索引,直到我们到达非空白处。请注意 lineBuffer ('[=27=]') 的空终止符 而不是 算作空白,因此此循环不会继续超过缓冲区的末尾。

处理完所有输入后,我们将字符串数组缩小为其数据的实际大小。这“纠正”了每次大小不够大时将大小增加 2n+1 的分配策略。

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

// BUFFER_SIZE must be >1U
#define BUFFER_SIZE 1024U

struct WordArray
{
    char **words;
    size_t numberOfWords;
};

static struct WordArray wordArrayConstruct(void);
static void wordArrayResize(struct WordArray *wordArray, size_t const newSize);
static void wordArrayDestruct(struct WordArray *wordArray);
static void wordArrayRead(FILE *restrict stream, struct WordArray *wordArray);
static char *reallocStringWrapper(char *restrict str, size_t const newSize);
static void wordArrayPrint(struct WordArray const *wordArray);

int main(void)
{
    struct WordArray wordArray = wordArrayConstruct();
    wordArrayRead(stdin, &wordArray);
    wordArrayPrint(&wordArray);
    wordArrayDestruct(&wordArray);
}

static void wordArrayRead(FILE *restrict stream, struct WordArray *wordArray)
{
    size_t wordArrayIdx = 0U;
    size_t wordLength = 0U;

    char lineBuffer[BUFFER_SIZE];
    while (fgets(lineBuffer, sizeof lineBuffer, stream) != NULL)
    {
        size_t wordStartIdx = 0U;
        while (lineBuffer[wordStartIdx] != '[=10=]')
        {
            if (!isspace(lineBuffer[wordStartIdx]) || wordLength != 0U)
            {
                size_t wordEndIdx = wordStartIdx;
                while (!isspace(lineBuffer[wordEndIdx]) && wordEndIdx != BUFFER_SIZE - 1U)
                    ++wordEndIdx;

                if (wordArrayIdx >= wordArray->numberOfWords)
                    wordArrayResize(wordArray, wordArray->numberOfWords * 2U + 1U);

                size_t wordSegmentLength = wordEndIdx - wordStartIdx;
                size_t foundWordEnd = wordEndIdx != BUFFER_SIZE - 1U; // 0 or 1 bool

                // Allocate for a new word, or reallocate for an existing word
                // If a word end was found, add 1 to the size for the '[=10=]' character
                char *dest = wordLength == 0U ? NULL : wordArray->words[wordArrayIdx];
                size_t allocSize = wordLength + wordSegmentLength + foundWordEnd;
                wordArray->words[wordArrayIdx] = reallocStringWrapper(dest, allocSize);

                memcpy(&(wordArray->words[wordArrayIdx][wordLength]),
                       &lineBuffer[wordStartIdx], wordSegmentLength);

                if (foundWordEnd)
                {
                    wordArray->words[wordArrayIdx][wordLength + wordSegmentLength] = '[=10=]';
                    ++wordArrayIdx;
                    wordLength = 0U;
                }
                else
                {
                    wordLength += wordSegmentLength;
                }
                wordStartIdx = wordEndIdx;
            }

            while (isspace(lineBuffer[wordStartIdx]))
                ++wordStartIdx;
        }
    }

    // All done. Shrink the words array to the size of the actual data
    if (wordArray->numberOfWords != 0U)
        wordArrayResize(wordArray, wordArrayIdx);
}

static struct WordArray wordArrayConstruct(void)
{
    return (struct WordArray) {.words = NULL, .numberOfWords = 0U};
}

static void wordArrayResize(struct WordArray *wordArray, size_t const newSize)
{
    assert(newSize > 0U);
    char **tmp = (char**) realloc(wordArray->words, newSize * sizeof *wordArray->words);

    if (tmp == NULL)
    {
        wordArrayDestruct(wordArray);
        fprintf(stderr, "WordArray allocation error\n");
        exit(EXIT_FAILURE);
    }

    wordArray->words = tmp;
    wordArray->numberOfWords = newSize;
}

static void wordArrayDestruct(struct WordArray *wordArray)
{
    for (size_t wordStartIdx = 0U; wordStartIdx < wordArray->numberOfWords; ++wordStartIdx)
    {
        free(wordArray->words[wordStartIdx]);
        wordArray->words[wordStartIdx] = NULL;
    }

    free(wordArray->words);
}

static char *reallocStringWrapper(char *restrict str, size_t const newSize)
{
    char *tmp = (char*) realloc(str, newSize);

    if (tmp == NULL)
    {
        free(str);
        fprintf(stderr, "Realloc string allocation error\n");
        exit(EXIT_FAILURE);
    }

    return tmp;
}

static void wordArrayPrint(struct WordArray const *wordArray)
{
    for (size_t wordStartIdx = 0U; wordStartIdx < wordArray->numberOfWords; ++wordStartIdx)
        printf("%zu: %s\n", wordStartIdx, wordArray->words[wordStartIdx]);
}

注意:此程序从 stdin 读取输入,就像 Unix/Linux 实用程序通常所做的那样。使用输入重定向从文件读取,或向 readWordArray 函数提供文件描述符。