【问题标题】:Read Text Files and Save Datas in Linked List读取文本文件并在链表中保存数据
【发布时间】:2019-04-02 06:55:30
【问题描述】:

我想阅读一些不同类别的文本文件并建立一个链表,其中包含来自这些文本文件的不同单词以及这些单词在文本文件中出现的总次数。链表应按字母顺序排列。但是当我运行代码时,只打印了三个不同的单词数百次,它们的出现值始终为 1。阅读所有单词没有问题。我通过在 while 循环中添加 printf 语句对其进行了测试,它可以正确打印所有单词。我猜insert函数有问题。

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

typedef struct Term {
    char * termName;
    int occur;
    struct Term * next;
} term;

term * insert(term * root, char * word);

int main (void) {

    setlocale(LC_ALL, "Turkish");

    FILE *fPtr;

    int counter = 1;

    char path[50];

    snprintf(path, sizeof(path), "dataset\\econ\\%d.txt", counter);

    term * terms;
    terms = NULL;

    while (fPtr = fopen(path, "r")) {
        while(!feof(fPtr)) {
            char word[20];          
            fscanf(fPtr, "%s", &word);
            terms = insert(terms, word);
        }
        fclose(fPtr);   
        counter++;
        snprintf(path, sizeof(path), "dataset\\econ\\%d.txt", counter);
    }

    counter = 1;
    snprintf(path, sizeof(path), "dataset\\health\\%d.txt", counter);   

    while (fPtr = fopen(path, "r")) {
         while(!feof(fPtr)) {
            char word[20];          
            fscanf(fPtr, "%s", &word);
            terms = insert(terms, word);
        }
        fclose(fPtr);   
        counter++;
        snprintf(path, sizeof(path), "dataset\\health\\%d.txt", counter);
    }

    counter = 1;
    snprintf(path, sizeof(path), "dataset\\magazin\\%d.txt", counter);

    while (fPtr = fopen(path, "r")) {
        while(!feof(fPtr)) {
            char word[20];          
            fscanf(fPtr, "%s", &word);
            terms = insert(terms, word);
        }
        fclose(fPtr);
        counter++;
        snprintf(path, sizeof(path), "dataset\\magazin\\%d.txt", counter);
    }

    fclose(fPtr);

    while (terms -> next != NULL) {
        printf("%s: %d\n", terms -> termName, terms -> occur);
        terms = terms -> next;
    }
}

term * insert(term * root, char * word) {
    if (root == NULL) {
        root = (term *)malloc(sizeof(term));
        root -> next = NULL;
        root -> termName = word;
        root -> occur = 1;
        return root;
    } else if((strcmp(root-> termName, word)) < 0) {
        term * temp = (term *)malloc(sizeof(term));
        temp -> termName = word;
        temp -> occur = 1;
        temp -> next = root;
        return temp;
    } else {
        term * iter = root;
        while ((iter -> next != NULL) && (strcmp(iter -> termName, word) > 
0)) {           
            iter = iter -> next;
            if (strcmp(iter -> termName, word) == 0) {
                iter -> occur += 1;
                return root;
            }
        }       
        term * temp = (term *)malloc(sizeof(term));
        temp -> next = iter -> next;
        iter -> next = temp;
        temp -> termName = word;
        temp -> occur = 1;
        return root;        
    }
}

【问题讨论】:

    标签: c linked-list


    【解决方案1】:

    这条线(以及其他类似的线)是问题所在。

    temp -> termName = word;
    

    您正在分配 word 所指向的任何内容,即您在此处声明的数组:

    char word[20];
    

    其范围仅限于最内部的while 循环。循环完成后,数组使用的内存是公平的游戏,可以被另一个变量使用,这意味着您的代码受到未定义行为的严重打击。最后你能得到任何可识别的单词完全是运气。

    所以使用这两种方法中的任何一种来复制它

    temp -> termName = strdup(word);
    

    temp -> termName = malloc(strlen(word)+1); // Always remember to allocate enough space for the NUL terminating character
    strcpy(temp->termName, word);
    

    别忘了也释放它。

    您也没有看到计数上升的原因,理论上它应该这样做,因为传入的 word 将与列表节点中的字符串相同,因为您检查该词是否列表中已经存在是错误的。

    检查字符串是否相同是正确的,但您永远不会触发它,因为while 循环只检查字符串是否在当前节点之后。

        while ((iter -> next != NULL) && (strcmp(iter -> termName, word) > 0)) {           
    

    将这段代码移到while循环之外会更有意义

    if (strcmp(iter -> termName, word) == 0) {
        iter -> occur += 1;
        return root;
    }
    

    更详细地看,insert 例程的整个 else 部分无法按照当前编写的方式工作。

    想象一下,如果您要在其中插入以下字符串:“A”“C”和“E”。您的代码将以相反的顺序添加它们,因此您会在输出中得到“E”、“C”、“A”。

    如果您尝试添加“D”,它会将其放在“C”之后。首先将其与“D”进行比较,strcmp 将返回一个正数。然后它将它与“C”进行比较,循环将停止,因为它返回一个负数。然后在“C”之后添加“D”

    根据前一个块if((strcmp(root-&gt; termName, word)) &lt; 0),当strcmp 返回负值时,您希望在要比较的节点之前插入新节点。但是你不能这样做,因为你不知道之前的节点是什么。

    因此,通过结合这两段代码,添加对前一个节点的一些跟踪和一些其他调整,您的 insert 函数变为:

    term * insert(term * root, char * word) {
        if (root == NULL) {
            root = (term *)malloc(sizeof(term));
            root -> next = NULL;
            root -> termName = strdup(word);
            root -> occur = 1;
            return root;
        } else {
            term * iter = root, *last = NULL;
            while ((iter != NULL) && (strcmp(iter -> termName, word) > 0)) {        
                last = iter;
                iter = iter -> next;
            }
    
            if ((iter)&&(strcmp(iter -> termName, word) == 0)) {
                iter -> occur += 1;
                return root;
            } else if (last == NULL) {
                term * temp = (term *)malloc(sizeof(term));
                temp -> termName = strdup(word);
                temp -> occur = 1;
                temp -> next = root;
                return temp;
            } else {
                term * temp = (term *)malloc(sizeof(term));
                temp -> next = last -> next;
                last -> next = temp;
                temp -> termName = strdup(word);
                temp -> occur = 1;
                return root;
            }
        }
    }
    

    所以它现在正在检查以查找它在当前节点之前不再按字母顺序排列的节点。如果节点有相同的词,则更新occur。如果我们还没有设置last,这意味着我们在开头,所以在开头添加新节点并返回它。最后我们知道last节点在这个词之后,iter节点在它之前(或者不存在),所以我们在中间插入新词。

    【讨论】:

    • strdup(word) 有效。它不再只打印三个字。但是还是有一些字被打印了两次,一些出现的值非常高。另一个问题是链表不是按字母顺序排列的。 (我把你说的块移到了循环之外)
    • 你的insert 函数的else 部分的整个逻辑在仔细检查后基本上是错误的。我会用更多细节更新我的答案,因为评论可能太长了。
    • 非常感谢您的详细回复。我明白其中的逻辑。
    猜你喜欢
    • 2017-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-24
    相关资源
    最近更新 更多