題目
原問題鏈接
編程統計出input.txt文件保存的文章中,每個單詞出現的次數。文章內容如下:
In this chapter we will be looking at files and directories and how to manipulate them. We will learn how to create files, open them, read, write and close them. We'll also learn how programs can manipulate directories, to create, scan and delete them, for example. After the last chapter's diversion into shells, we now start programming in C.
Before proceeding to the way UNIX handles file I/O, we'll review the concepts associated with files, directories and devices. To manipulate files and directories, we need to make system calls (the UNIX parallel of the Windows API), but there also exists a whole range of library functions, the standard I/O library (stdio), to make file handling more efficient.
這段文字來自網絡。為了統計更有意義,加入兩個條件:
統計過程中不考慮空格和標點符號
不區分大小寫(可以把所有字母轉成小寫后參與統計)
解題思路
做一個詞頻統計工具
觀察文本發現,兩個單詞之間都有空格隔開。這就比較好處理了,因為我們可以很輕松的實現一次讀取一個單詞。
如果實際要處理的文本有標點之后沒空格的情況,如
I love you.My program.
那我們還要加一個deliver函數把you和My分開
如何存儲出現過的單詞
1.二維數組搭配一個一維數組實現
2.利用結構體做一個鏈表實現
我選擇了第二種,因為我覺得結構體是通往抽象編程的路,而且使用結構體可讀性更好。
具體思路
1.一次從文本讀取一個字符串word
2.把字符串word的大寫字母轉化為小寫
3.把字符串word里的 ,.() 四種標點消除(想做成一個好用的詞頻統計工具的話,根據實際需要增添要消除的標點符號)
4.檢查word是否出現過
沒有出現過: 把他存入結構體
出現過:找到存儲這個word的結構體,并把里面的計數變量加一
5.打印所有存儲了單詞的結構體
源碼
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILE_PATH "..\\要統計的文本.txt" //宏存儲文本相對路徑
#define WORD_LENGTH 60
#define LENGTH sizeof(WORD)
struct WORD
{
char word[WORD_LENGTH]; //存放單詞
int wordCount; //本單詞出現次數
struct WORD *pNext; //next指針
};
struct WORD pHeader; //鏈表的頭結點
WORD *countWord(char *word, WORD *pLastWord);
void changeToLower(char *word);
void checkPunct(char *word);
int checkCountWord(char *word);
void addcount(char * word);
WORD * addNewWord(char *word, WORD *pLastWord);
void printResult();
void freeAll();
void main()
{
WORD *pLastWord = &pHeader;//指向最后一個結點的指針
char tempWord[WORD_LENGTH];//儲存本次讀取的字符串
pHeader.pNext = NULL;//把頭節點的指向初始化為空
FILE *fp;
if ((fp = fopen(FILE_PATH, "r")) == NULL)//如果打開文本為空
{
printf("System can`t find this file!\n");
exit(0);
}
while ((fscanf(fp, "%s", tempWord)) != EOF)//如果文件還沒讀取完
{
pLastWord = countWord(tempWord, pLastWord);//統計詞頻
}
fclose(fp);//關閉文件
printResult();//打印統計結果
freeAll();//釋放所有動態申請的內存
system("PAUSE");
}
WORD *countWord(char *word,WORD *pLastWord)
{
changeToLower(word);//把Word全部轉換為小寫字母
checkPunct(word);//去除word中的 (),. 如果文本含有其他特殊標點,這里需要再增加一點代碼
if (checkCountWord(word))//如果本單詞在前文出現過
{
addcount(word);//把這個單詞的計數加一
}
else
{
pLastWord = addNewWord(word, pLastWord);//建立一個新的單詞結點
}
return pLastWord;
}
void changeToLower(char *word)//把字符串全部轉化為小寫字母
{
int i;
for (i = 0; word[i] != '\0'; i++)
{
if (word[i] >= 65 && word[i] <= 90)
{
word[i] += 32;
}
}
}
void checkPunct(char *word)//去除標點符號
{
int i, k;
for (i = 0; word[i] != '\0'; i++)//遍歷字符串
{
if (word[i] == '(' || word[i] == ')' || word[i] == ',' || word[i] == '.')//如果含有標點
{
for (k = i; word[k] != '\0'; k++)
{
word[k] = word[k + 1];
}
i--;//防止"sdio)."這種連續多個特殊字符出現的情況,再次檢查當前index下的字符
}
}
}
int checkCountWord(char *word)//不在已存儲單詞中返回0. 在就返回1
{
WORD *p;
p = &pHeader;
if (pHeader.pNext == NULL)//如果檢查的是第一個單詞
{
return 0;
}
do
{
p = p->pNext;
if (strcmp(word, p->word) == 0)
{
return 1;
}
} while (p->pNext != NULL);
return 0;
}
void addcount(char * word)//把出現過的單詞計數加一
{
WORD *p;
p = &pHeader;
do
{
p = p->pNext;
if (strcmp(word, p->word) == 0)//遍歷到重復的結點,并把計數 +1
{
p->wordCount++;
return;
}
} while (p->pNext != NULL);
}
WORD * addNewWord(char *word, WORD *pLastWord)//加一個新單詞結點
{
WORD *pTemp;
if (pHeader.pNext == NULL)//如果添加的是第一個結點
{
pHeader.pNext = (WORD *)malloc(LENGTH);
strcpy(pHeader.pNext->word, word);
pHeader.pNext->wordCount = 1;
pHeader.pNext->pNext = NULL;
pLastWord = pHeader.pNext;
}
else
{
pTemp = pLastWord;
pLastWord = (WORD *)malloc(LENGTH);
strcpy(pLastWord->word, word);
pLastWord->wordCount = 1;
pLastWord->pNext = NULL;
pTemp->pNext = pLastWord;
}
return pLastWord;
}
void printResult()//打印存儲的所有單詞結點
{
WORD *p;
int sign = 0;
p = &pHeader;
do
{
p = p->pNext;
printf("%-12s%d\t\t", p->word, p->wordCount);
sign++;
if (sign % 3 == 0)
{
printf("\n");
}
} while (p->pNext != NULL);
}
void freeAll()
{
WORD *p ,*piror;
p = pHeader.pNext;//頭節點不是動態申請的不能釋放
do
{
piror = p;
p = p->pNext;
free(piror);
} while (p->pNext != NULL);
free(p);
}
執行結果
后來改成了雙向鏈表并在打印前加了排序的函數,下載了一本英文版的 <百年孤獨> 進行了詞頻統計
很好用(亂碼是因為下載的文本文件本身就有亂碼...)
就是運行效率有些低
統計<百年孤獨>運行時間記錄(800K的文本文件)
- 打印結果占用2秒
- 排序代碼占用1秒
- 其他代碼占用3秒
- 總計運行時間6秒
總結
去年考英語六級時,我下載了一本英文電子版的<The Great Gatsby>,想統計出它的高頻單詞并背誦。我用了一個在線詞頻統計工具來統計,因為各種特殊字符的存在結果特別不理想。
現在學會了寫自己的程序,我可以根據自己的實際需求來改寫出合適的程序。It's really cool!
而且這個鏈表思維和我寫的貪吃蛇實現蛇身體的思維很相似,我可以優化我的貪吃蛇程序了!