C-單詞統(tǒng)計 2.2

emmmmm.......

詳解:

問題:


對讀入的某個文本文件input.txt中,拆出英文單詞,輸出一個按字典順序排列的單詞表,結(jié)果輸出在文本文件output.txt中,每個單詞一行,并在單詞后輸出該單詞出現(xiàn)的個數(shù),兩個字段之間用逗號分隔。約定單詞僅由英文字母組成,單詞間由非英文字母分隔,相同單詞只輸出一個,大小寫不區(qū)分。
例如文本文件input.txt為:
Hello world.
Hello every one.
Let us go.
則輸出文本文件output.txt為:
every,1
go,1
hello,2
let,1
one,1
us,1
world,1


給的結(jié)構(gòu)體
struct myword
{
    int     num;
    char    *word;
    struct  myword  *next;    /*  指向下一個節(jié)點 */
};

這個問題拿到了之后,可以發(fā)現(xiàn)程序涉及兩個文件一個input.txt,這個在程序里用的test.txt進行測試,輸出文件out.txt。

我們從文件input文件中讀單詞,然后統(tǒng)計所有的單詞個數(shù),輸出到output。

所以整個程序的流程可以劃分為:

main()

1.打開文件input.txt
2.找單詞,并將其存入鏈表 struct myword *find_words(FILE *input)
3.將鏈表輸出到文件 void print_link_to_file(struct myword *head)
4.釋放鏈表所占的空間 void destory_word(struct myword *head)

我們可以看下這個main()函數(shù)

void  main()
{
    /*讀取infile_name文件,用find_words找到單詞,存儲到head鏈表,最后將鏈表輸出到文件*/ 
    void print_link_to_file(struct myword *head);     //輸出鏈表到文件 
    struct myword *find_words(FILE *input);           //找單詞,輸出單詞鏈表 
    void destory_word(struct myword *head);         //釋放鏈表內(nèi)存

    char  infile_name[]={"word.txt"};
    struct myword *head;
    FILE *input;
    input = fopen(infile_name, "r");                  //打開文件 
    if (input == NULL)
    {
        printf("打開文件%s失敗\n",infile_name);
        exit(0);
    }

    head = find_words(input);                         //找單詞 
    fclose(input);                                    //關(guān)閉文件 
    print_link_to_file(head);                         //將鏈表輸出到文件 
}

程序的主要功能有三個,對應上面的找單詞并將其存入鏈表、輸出到文件、釋放鏈表空間。

定義的是返回空類型的void main()函數(shù),前三行是聲明我們的三個函數(shù)。最后三行是使用。
具體實現(xiàn)我們到后面說:

  • find_words(FILE *input) 是一個輸入文件指針類型的函數(shù),輸出的結(jié)構(gòu)體指針
  • print_link_to_file(struct myword *head) 輸入的是結(jié)構(gòu)體指針,這里我們輸入的是鏈表的頭結(jié)點,空類型函數(shù)
  • destory_word(struct myword *head) 輸入結(jié)構(gòu)體指針,也是鏈表頭節(jié)點,釋放鏈表所占的空間
    其中最復雜的就是find_words()

我們倒著來,從簡單的開始:

destory_word(struct myword *head) //釋放內(nèi)存

我們在使用完鏈表之后,程序結(jié)束了,但是這些鏈表還是在內(nèi)存中,所以我們在用完了之后要將內(nèi)存釋放掉。方法很簡單,遍歷整個鏈表,將每一個節(jié)點的空間都是釋放掉。每一個節(jié)點都是一個myword結(jié)構(gòu)體。

void destory_word(struct myword *head)
{
    printf("start free!\n");
    struct myword *tmp;
    while(head!=NULL)
    {
        tmp = head;
        head  = head->next;
        free(tmp);
    }
    printf("Free Done!\n");
} 

遍歷整個鏈表,用tmp保存當前頭結(jié)點,然后,頭結(jié)點指向下一個節(jié)點,然后釋放tmp的內(nèi)存,繼續(xù)遍歷下一個。
然后是輸出到鏈表。

void print_link_to_file(struct myword *head)

這是個空類型函數(shù),輸入鏈表頭,和剛才一樣遍歷鏈表,不過把釋放空間的操作換成輸出到文件。這里我們打開一個output.txt文件來保存文件。

void print_link_to_file(struct myword *head)
{
    /*輸入鏈表,將鏈表內(nèi)容輸出到input.txt*/ 
    printf("NOW,print LINK  to File......\n");
    struct myword *p;
    FILE  *output;
    char outfile_name[]={"output.txt"};
    output = fopen(outfile_name, "w");
    if (output == NULL)
    {
        printf("打開文件%s失敗\n",outfile_name);
        exit(0);
    }
    p = head->next;
    do
    {
        fprintf(output,"%s,%d\n",p->word,p->num);
        p = p->next;
    }while(p!=NULL);
    fclose(output);
    printf("print to file Done!\n");
 }

這里也是非常簡單的,不過這里我用的do...while語句,這是書上看來的,也可以改寫成while語句。這里不多說,下面開始找單詞環(huán)節(jié)。

find_words(FILE *input)

在開始之前,我們先想一想如何找單詞?根據(jù)題目來看,兩個單詞之間是用的非字符分割的,所以這里我們把類似于“point-to-point”這種看做三個單詞而不是一個,降低難度。
我們讀文件有三種,每次讀一個字符,每次讀一串字符,還有就是隨機讀寫。
我在開始寫之前想過好幾種方案,還有的是室友想的,我一股腦先都放出來:

  • 每次讀一行,將它存到數(shù)組中,然后判斷數(shù)組中的單詞數(shù),當然也是遍歷數(shù)組,遇到非字母就分隔開,將前面的看做一個單詞。生成節(jié)點,插入鏈表,然后接著讀文件。
  • 每次讀一個字符,如果遇到字母,就將其存到數(shù)組中,直到遇到非字母,然后把數(shù)組中的字符串看做一個單詞。生成節(jié)點,插入鏈表,然后接著讀文件。
  • 每次讀一個文件,如果是字母則寫入文件tmp.txt中,如果遇到非字母,或者換行,則在tmp.txt中寫入換行。完成所有的讀寫后,在重新開始讀tmp.txt,每次讀一行,把每一行的單詞讀出,生成節(jié)點,插入鏈表,然后接著讀文件。

前兩個我寫的,第三個是室友想的。emmm。。。這個文件一定長。
先不管效率,三種理論上都能讀出單詞。
我是用的第二種方法:

struct myword *find_words(FILE *input)
{
    /*輸入文件指針,找到單詞,生成鏈表,輸出鏈表頭head,
    從文件中一個字符一個字符的讀取,如果遇到字母則將其存入
    字符數(shù)組中,直到遇到非字母, 
    【在數(shù)組的后面加入'\0',以此來分割數(shù)組中的
    單詞,因為前一個單詞可能比當前單詞長】 
    則數(shù)組中的就是一個單詞,
    生成節(jié)點,并插入相應位置。 
    用字符數(shù)組保存當前單詞*/ 
    printf("start find Words....\n");
    struct myword *head, *tmp;
    struct myword *instert_word(struct myword *tmp,struct myword *head);  //插入節(jié)點
    struct myword *new_word(char word[]);  //生成一個新的節(jié)點,返回指針 
    char ch;
    char word[25];
    int i=0;
    head = NULL;
    
    while (! feof(input))
    {
        ch = fgetc(input);
        if ((ch>=65 && ch <=90) || (ch>=97 && ch <=122))
        {
            word[i]=ch;
            i++;
        }
        else
        {
            word[i]='\0';
            if (strlen(word) !=0)
            {
                tmp = new_word(word);
                head = instert_word(tmp,head);
            }   
            i=0;
        }
    }
    printf("find Words Done!\n");
    return head;
}

我用了循環(huán)來讀取文件,這里有個點需要注意,每次循環(huán)結(jié)束,每當我讀到字母時,是將字母存入第i個位置,然后i++;如果是非字母則將當前數(shù)組的第i個位置賦值為"\0",當前面出現(xiàn)了單詞,也就是strlen(word) !=0的情況,生成新的單詞節(jié)點,并插入鏈表,然后不管有沒有單詞都將i=0??梢园l(fā)現(xiàn)我并沒有將數(shù)組的內(nèi)容清空。只是每次將第i個位置復制為"\0",因為strlen(word)在統(tǒng)計字符串長度時,遇到"\0"就會停止,如果第一個位置就是"\0",則認為沒有單詞。
這是很重要的一點。

這里我們找出了單詞,但是我們還沒有鏈表,也沒法統(tǒng)計單詞的個數(shù)。
接下就是一個小的輔助函數(shù),

new_word(word)

這個函數(shù)接受一個字符串數(shù)組,并返回一個指向?qū)慕Y(jié)構(gòu)體的指針。

struct myword * new_word(char word[]) 
 {
    /*輸入一個單詞數(shù)組,返回一個myword結(jié)構(gòu)體*/
    struct myword *tmp;
    char *t_word;
    tmp=(struct myword *) malloc(LEN);                //給一個新的節(jié)點申請空間 
    //動態(tài)申請一個空間來保存myword->word,因為這本身是個指針,需要指向一個數(shù)組來保存
    t_word = (char *) malloc(strlen(word));
    strcpy(t_word,strlwr(word));
    tmp->word = t_word;
    tmp->num = 1;
    tmp->next=NULL;
    return tmp;
 }

這里需要注意的是,給的結(jié)構(gòu)體并沒有給存放單詞的字符串數(shù)組,而是給了一個字符型指針。所以我們要為每一個節(jié)點分配空間的同時,還要給每一個節(jié)點的單詞分配空間。還有就是,字符串賦值 的時候要用strcpy()函數(shù)。
然后就是重頭戲插入鏈表

instert_word(tmp,head)

這個函數(shù)輸入鏈表的頭結(jié)點和要插入的節(jié)點,返回一個指向鏈表頭節(jié)點的指針。
題目的要求還要統(tǒng)計單詞的個數(shù),而且要排序。一開始我是這么想的:先生成一個沒有排序的鏈表,然后在用排序算法,對鏈表進行排序,后來一想,為啥不在插入的時候就按順序插入呢!
于是有了下面的:

struct myword *instert_word(struct myword *tmp,struct myword *head)
 {
    /*將新的tmp節(jié)點插入到head對應的位置
    ->遍歷所有節(jié)點 
     # head空             放到第一個節(jié)點
     # 與當前節(jié)點相同     當前節(jié)點的num+1
     # 比當前節(jié)點大       如果比下一個節(jié)點小,則插在當前節(jié)點后
                          如果比下一個節(jié)點大 ,則遍歷下一個節(jié)點 
     # 比第一個節(jié)點小     放在第一個節(jié)點前,頭結(jié)點后
                        【因為鏈表是有序的,且在上一情況中處理,
                         在后面不存在比當前節(jié)點小的情況】 
     */ 
    struct myword *cur;
    if (head==NULL)
    {
        head = (struct myword *) malloc(LEN);
        head->next = tmp;
    }
    else
    {
        cur = head->next;
        while (cur!=NULL)
        {
            if (strcmp(tmp->word,cur->word)<0)
            {
                //小于第一個值 ,因為鏈表有序,如果比第一個小,那就是最小的 
                tmp->next = cur;
                head->next = tmp;
                break;
            }
            if (strcmp(tmp->word,cur->word)==0)
            {
                //等于當前值 
                cur->num++;
                free(tmp);                        //這個節(jié)點已經(jīng)存在,釋放tmp空間 
                break;
            }
            else if (strcmp(tmp->word,cur->word)>0)
            {   
                //大于當前值 
                if (cur->next!=NULL)// && (tmp->word,cur->next->word)<0
                {
                    //后面不為空 
                    if (strcmp(tmp->word,cur->next->word)<0)
                    {
                        //小于下一個值 ,插入當前節(jié)點之后 
                        tmp->next = cur->next;
                        cur->next = tmp;
                        break; 
                    }
                    //大于等于下一個值 
                    cur = cur->next;
                }
                else
                {
                    //后面為空 
                    cur->next= tmp; 
                    break; 
                }
            } 
        }
    }
    return head;
 }

值得引起注意的就是,第一個節(jié)點的插入。剩下的就是插入節(jié)點的操作了。在比較字符串大小的時候不能像字符或者數(shù)字一樣直接比較,要用strcmp()。
這個函數(shù)只可意會不可言傳!自己理解吧!

最后

我們先打開了文件,然后初始化鏈表頭head指針,然后調(diào)用find_words()函數(shù),返回統(tǒng)計好的單詞鏈表。這是將返回的結(jié)果賦給head,這時head就是含有所有單詞的鏈表了。然后調(diào)用我們的 print_link_to_file(head)函數(shù),將文件輸出到文件中,最后銷毀鏈表。

最新的程序:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define LEN sizeof(struct myword)

struct myword
{
    int     num;
    char    *word;
    struct  myword  *next;    /*  指向下一個節(jié)點 */
};


void  main()
{
    /*讀取infile_name文件,用find_words找到單詞,存儲到head鏈表,最后將鏈表輸出到文件*/ 
    void print_link_to_file(struct myword *head);     //輸出鏈表到文件 
    struct myword *find_words(FILE *input);           //找單詞,輸出單詞鏈表 
    void destory_word(struct myword *head);
    char  infile_name[]={"test.txt"};
    struct myword *head;
    FILE *input;
    input = fopen(infile_name, "r");                  //打開文件 
    if (input == NULL)
    {
        printf("打開文件%s失敗\n",infile_name);
        exit(0);
    }
    head = find_words(input);                         //找單詞 
    fclose(input);                                    //關(guān)閉文件 
    print_link_to_file(head);                         //將鏈表輸出到文件 
    destory_word(head);
}

void print_link_to_file(struct myword *head)
{
    /*輸入鏈表,將鏈表內(nèi)容輸出到input.txt*/ 
    printf("NOW,print LINK  to File......\n");
    struct myword *p;
    FILE  *output;
    char outfile_name[]={"output.txt"};
    output = fopen(outfile_name, "w");
    if (output == NULL)
    {
        printf("打開文件%s失敗\n",outfile_name);
        exit(0);
    }
    p = head->next;
    do
    {
        fprintf(output,"%s,%d\n",p->word,p->num);
        p = p->next;
    }while(p!=NULL);
    fclose(output);
    printf("print to file Done!\n");
 }
 
struct myword * new_word(char word[]) 
 {
    /*輸入一個單詞數(shù)組,返回一個myword結(jié)構(gòu)體*/
    struct myword *tmp;
    char *t_word;
    tmp=(struct myword *) malloc(LEN);                //給一個新的節(jié)點申請空間 
    //動態(tài)申請一個空間來保存myword->word,因為這本身是個指針,需要指向一個數(shù)組來保存
    t_word = (char *) malloc(strlen(word));
    strcpy(t_word,strlwr(word));
    tmp->word = t_word;
    tmp->num = 1;
    tmp->next=NULL;
    return tmp;
 }
 
struct myword *instert_word(struct myword *tmp,struct myword *head)
 {
    /*將新的tmp節(jié)點插入到head對應的位置
    ->遍歷所有節(jié)點 
     # head空             放到第一個節(jié)點
     # 與當前節(jié)點相同     當前節(jié)點的num+1
     # 比當前節(jié)點大       如果比下一個節(jié)點小,則插在當前節(jié)點后
                          如果比下一個節(jié)點大 ,則遍歷下一個節(jié)點 
     # 比第一個節(jié)點小     放在第一個節(jié)點前,頭結(jié)點后
                        【因為鏈表是有序的,且在上一情況中處理,
                         在后面不存在比當前節(jié)點小的情況】 
     */ 
    struct myword *cur;
    if (head==NULL)
    {
        head = (struct myword *) malloc(LEN);
        head->next = tmp;
    }
    else
    {
        cur = head->next;
        while (cur!=NULL)
        {
            if (strcmp(tmp->word,cur->word)<0)
            {
                //小于第一個值 ,因為鏈表有序,如果比第一個小,那就是最小的 
                tmp->next = cur;
                head->next = tmp;
                break;
            }
            if (strcmp(tmp->word,cur->word)==0)
            {
                //等于當前值 
                cur->num++;
                free(tmp);                        //這個節(jié)點已經(jīng)存在,釋放tmp空間 
                break;
            }
            else if (strcmp(tmp->word,cur->word)>0)
            {   
                //大于當前值 
                if (cur->next!=NULL)// && (tmp->word,cur->next->word)<0
                {
                    //后面不為空 
                    if (strcmp(tmp->word,cur->next->word)<0)
                    {
                        //小于下一個值 ,插入當前節(jié)點之后 
                        tmp->next = cur->next;
                        cur->next = tmp;
                        break; 
                    }
                    //大于等于下一個值 
                    cur = cur->next;
                }
                else
                {
                    //后面為空 
                    cur->next= tmp; 
                    break; 
                }
            } 
        }
    }
    return head;
 }
 
struct myword *find_words(FILE *input)
{
    /*輸入文件指針,找到單詞,生成鏈表,輸出鏈表頭head,
    從文件中一個字符一個字符的讀取,如果遇到字母則將其存入
    字符數(shù)組中,直到遇到非字母, 
    【在數(shù)組的后面加入'\0',以此來分割數(shù)組中的
    單詞,因為前一個單詞可能比當前單詞長】 
    則數(shù)組中的就是一個單詞,
    生成節(jié)點,并插入相應位置。 
    用字符數(shù)組保存當前單詞*/ 
    printf("start find Words....\n");
    struct myword *head, *tmp;
    struct myword *instert_word(struct myword *tmp,struct myword *head);  //插入節(jié)點
    struct myword *new_word(char word[]);  //生成一個新的節(jié)點,返回指針 
    char ch;
    char word[25];
    int i=0;
    head = NULL;
    
    while (! feof(input))
    {
        ch = fgetc(input);
        if ((ch>=65 && ch <=90) || (ch>=97 && ch <=122))
        {
            word[i]=ch;
            i++;
        }
        else
        {
            word[i]='\0';
            if (strlen(word) !=0)
            {
                tmp = new_word(word);
                head = instert_word(tmp,head);
            }   
            i=0;
        }
    }
    printf("find Words Done!\n");
    return head;
}

void destory_word(struct myword *head)
{
    printf("start free!\n");
    struct myword *tmp;
    while(head!=NULL)
    {
        tmp = head;
        head  = head->next;
        free(tmp);
    }
    printf("Free Done!\n");
} 

這次的收獲不少:

  • 寫代碼前要思考清楚整體的框架
  • 命名要規(guī)范,見名知意,這樣看到變量就不用發(fā)愁是那個了
  • 寫長的程序,要分成一步一步來,可以單寫一個test.c專門用來編寫當前的功能,測試時,要注意到每一種可能,自己寫一個測試的text,而不是直接用目標文件,太大浪費時間
  • 在關(guān)鍵的步驟一定要寫清注釋,不然五分鐘后就不知道為什么這么寫了
  • if-else if-else 用的時候要分清到底是只能選擇一種執(zhí)行,還是出現(xiàn)了就執(zhí)行
  • 長的程序最好先用一個函數(shù)實現(xiàn)在之后,在考慮分模塊,考慮優(yōu)化,不然可能會做無用功
  • 有時候別人不能理解你在說什么,那可能是因為你真的很優(yōu)秀
  • 但是為了更優(yōu)秀,你需要讓你說的話誰都能聽懂,這說明你真的懂了

這是一種學習技巧————費曼技巧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容