寫英語作文的時候,常常要求滿足一定的字數,在以往,要么,我們一個一個地數,要么,我們估算一行的單詞數,然后用行數進行估算。第一種方法太費時,如果要是寫個長篇大論,那幾乎是mission imposible,而第二種方法有不太准確。這就給我們留下了一個問題:如何又快又准確地統計一篇英文文章中的單詞數?
程序,就是用來幫助人們完成這些看起來枯燥繁瑣但是帶有一定規律性的事情的。
要解決這個問題,最自然的想法是,讀取文章的所有內容(用fopen()和fgets()),然后一個單詞一個單詞地統計,然而,我們在這里遇到了一個難題:程序看不懂英文,他如何知道什么是一個單詞,什么不是一個單詞呢?我們似乎在這里遇到了障礙,可是,如果我們換個角度思考問題,也許會柳暗花明又一村:我們注意到,文章中的單詞都是用空格間隔開的,那么換句話說,也就是
單詞數=空格數+1
程序不認識單詞,但是程序認識空格啊。這樣,整個問題實際上轉換為了統計文章中的空格數。
有了這樣的問題轉換,整個問題就簡單多了。你可以先按照這個思路自己實現,先來看看這個小例子代碼實現:
/* * wordcounts1.c * * Created on: 2013年10月31日17:15:00 * Author: Bruce */ #include <stdio.h> #include <string.h> int main() { //假設需要統計的字符串都保存在str字符數組中 char str[128] = "This is a C program!"; //用於保存空格字符數的變量 int numWhiteSpace = 0; //循環遍歷整個字符數組,統計空格字符數 int i = 0; while('\0' != str[i]) { //判斷當前字符是否是空格 if(' ' == str[i]){ ++numWhiteSpace; //空格數加1 } ++i; //檢查下一個字符 } //輸出字符串中的單詞數,也就是空格數加1. printf("There are %d words in \"%s\"",numWhiteSpace+1,str); return 0; }
要在原程序的基礎上進行擴展,實現對文章中單詞數的統計,我們有兩個工作要做:
- 將原來全部放在main()函數中進行的單詞統計功能抽取出來,單獨成一個獨立的函數countword(),它可以接受一個char*類型的參數,返回的是這個指針所指向的字符串中的單詞數。這里需要注意的是,因為在文章中,換行也會分割單詞,所以我們也應將換行字符’\n’統計在內。
- 單獨地利用fopen()以及fgets()等操作文件的函數,將文件中的字符串讀取到一個字符數組中,供上一步完成的countword()函數進行統計。這些工作都提取到read()函數中完成。
經過這樣的分析我們程序在main()函數中要完成的工作也就清晰了,首先用read()函數讀取文件到一個字符數組,然后用countword()函數對讀取得到的字符串中的單詞數進行統計。
經過這樣的分析,再結合我們在《C程序設計伴侶》中學到的文件讀寫,字符串處理,main()函數的參數等知識,整個程序的實現就很簡單了。
/* * wordcounts2.c * * Created on: 2013年10月31日17:15:00 * Author: Bruce */ #include <stdio.h> #include <string.h> #include <stdbool.h> //讀取文件到字符數組中 bool read(const char * file,char * str) { if(NULL == file || NULL == str) { return false; } //以只讀方式打開文件 FILE * fp = fopen(file,"r"); if(NULL != fp) { //用fgets()函數讀取文件 char line[128] = ""; while(fgets(line,128,fp) != NULL) { //將讀取得到的字符串保存到目標數組中 strcat(str,line); } //關閉文件 fclose(fp); fp = NULL; return true; } else { printf("Cannot open %s.",file); return false; } } int countWord(char * text) { if(NULL != text) { //循環遍歷整個字符數組,統計空格字符以及換行字符數 int i = 0; int num = 0; //空格或換行字符數 while('\0' != text[i]) { //判斷當前字符是否是空格或者換行字符,如果是,則統計 if(' ' == text[i] || '\n' == text[i]) { ++num; } ++i; //檢查下一個字符 } return num+1; // 返回單詞數目 } else { return 0; } } int main( int argc, char * argv[]) { if(argc != 2) { puts("ARGUMENS ERROR. eg. count demo.txt"); return 1; } char text[1028*10] = ""; if(read(argv[1],text)) { int n = countWord(text); // 輸出字符串中的單詞數,也就是空格數加1 printf("there is(are) %d word(s) in the \"%s\"",n,argv[1]); } return 0; }
然后,用
gcc countword.c –o countword.exe
將其編譯成countword.exe應用程序,並用下面的命令統計一篇文章的單詞數:
countword.exe demo.txt
這樣,我們就可以用這個程序統計一篇英文文章中的單數了,再也不用一個個數的頭暈眼花了。這正是體現了程序的作用,幫助人類又快又好地完成一些繁瑣而有規律的工作。
可是,當用過一段時間后,我們卻發現程序統計的結果並不是那么准確,有時會多統計幾個單詞,這又是為什么呢?這個新問題又該如何解決呢?欲知后事如何,在往下看。
上面說到我們對字符數實現簡單擴展,使其可以統計一整片文章中的單詞數。然而在使用的時候,我們卻發現其統計的單詞數並不是很准確,有時候多統計了,有時候又少統計了。當程序的執行結果與我們的預期相左時,這就意味着我們的程序中出現了臭蟲,也就是傳說中的Bug。
那么這個臭蟲在哪兒呢?如何找到具體的位置並把他消滅呢?這時就需要用到我們的調試技術了。所謂調試,也就是在程序中設置斷點(程序執行過程中暫停的位置,在Visual Studio中,可以使用F9在光標當前行設置斷點,當在調試模式下,即用F5調試運行程序時,程序會在此暫停,這樣我們就可以觀察程序的中間運行情況。),然后用F10單步運行程序,觀察程序的執行情況同算法的預想情況有什么差別,當出現差別的地方,就是Bug所在的地方。通過調試,我們發現在原來的程序中有這樣兩個問題:
- 錯誤地對連續的多個空格字符進行了統計。連續的多個空格字符,也只是起一次分割作用,但是程序卻不管這些,只要遇到空格就統計在內,將多個空格分隔的兩個單詞統計成了多個單詞,所以產生了多統計的情況。
- 沒有統計標點符號。除了空格之外,在文章中起分割作用的還有標點符號,比如逗號,句號等等,這些分割符號都沒有統計在內,所以用逗號分割的兩個單詞被統計成了一個單詞,因而出現了少統計的情況。
我們找到了產生Bug的原因,那么如何解決呢?
很自然的想法就是見招拆招。對第一個bug,一種方式是對文章進行預處理,刪除其中的冗余的空格,另一種方法是將冗余的連續多個空格字符替換為有效字符,這兩種方法都是為了達到只保留一個空格字符的目的。
面對第二個bug,解決辦法就是將其他分割字符也統計在內,包括逗號,句號,問號,感嘆號,冒號,省略號,破折號,前引號,后引號,斜杠,反斜杠,等等等等。
這樣的解決方法雖然簡單直觀,可是卻比較難以實現:第一個,需要對文章進行預處理,找到並刪除其中的冗余空格不是一件容易的事情,需要遍歷整個字符串。第二個,文章中的分割符號種類舉不勝舉,如何保證所有分割符號都統計在內了?看來,上面的方法行不太通,我們只好另辟蹊徑了。
回到問題的原點,我們要統計的是一篇文章中的單詞數,我們注意到單詞有這樣一個簡單的特征:總是以非字母字符(比如,空格,逗號)后的字母字符開始,並用字母字符一直延續,直到再次遇到非字母字符作為結束,利用這個特征,我們就可以定義確定一個單詞了。
另外,我們還注意到這樣一個規律:整篇文章的單詞數等於已經找到的單詞數加上剩余部分的單詞數,像這種可以把一個大問題拆分成多個相似的小問題來解決的情況,最自然的解決方法就是遞歸(參考《C程序設計伴侶》的7.6 函數的遞歸調用——統計字符串出現的次數 函數的遞歸調用)這種思路來解決。
按照上面的簡單分析,我們可以將整個程序改寫如下:
/* * wordcounts3.c * * Created on: 2013年10月31日17:15:00 * Author: Bruce */ #include <stdio.h> #include <string.h> #include <stdbool.h> //讀取文件到字符數組中 bool read(const char * file,char * str) { if(NULL == file || NULL == str) { return false; } //以只讀方式打開文件 FILE * fp = fopen(file,"r"); if(NULL != fp) { //用fgets()函數讀取文件 char line[128] = ""; while(fgets(line,128,fp) != NULL) { //將讀取得到的字符串保存到目標數組中 strcat(str,line); } //關閉文件 fclose(fp); fp = NULL; return true; } else { printf("Cannot open %s.",file); return false; } } //統計字符串中單詞數 int countWord(char * text) { //開始尋找開始位置 //一直從字符串開始位置向后尋找 //直到遇到一個字母字符 while(!isalpha(*text)) { // 如果找到了字符串結束位置, // 意味着整個字符串尋找完畢, // 結束整個遞歸 if('\0' == *text) { return 0; } ++text; // 查找下一個字符 } while(isalpha(*text)) { // 如果當前字符是字母字符 // 則繼續查找下一個字符 ++text; } return 1 + countWord(text); } int main( int argc, char * argv[]) { if(argc != 2) { puts("ARGUMENS ERROR. eg. count demo.txt"); return 1; } char text[1028*10] = ""; if(read(argv[1],text)) { int n = countWord(text); // 輸出字符串中的單詞數,也就是空格數加1 printf("there is(are) %d word(s) in the \"%s\"",n,argv[1]); } return 0; }
經過這樣的改進,這個程序就可以處理比較復雜混亂的文章了,例如,下面這么神經分裂的文章,他都能夠正確處理:
tought is a seed,d ? and action i,… s a tree .
到這里,整個統計文章中單詞數的問題算是比較圓滿地解決了。這里只是簡單地分析了一下解決問題的思路,要想完全理解並掌握,還需要進一步深入的學習C語言的知識並熟練運用。這是《C程序設計伴侶》中的一個實際例子的改變,書中還有很多其他類似的有趣的例子,有興趣的同學可以參考。
最后發現,學了C語言,還是可以做很多事情的,農民用鐮刀改變世界,工人用斧頭改變世界,我們用代碼改變世界。自豪吧,程序員!
轉自:好知網