首先得得到我们的常用词频,保存在ciku.ini中里面保存32280个词汇,保存形式如下
至于怎么得到词库,办法各异了,我这里是从xp自带的拼音和五笔输入法的码表文件中提取的词汇,去除两个码表相同的词汇,得到的总词汇有32280个,
1)在C盘建立ciku文件夹,拷贝“词库提取”文件夹中的词库文件ciku.ini到ciku目录,最终得到的结果也会在C盘的ciku下面的output.ini里面,保存形式如下。
[MyWord]
祖父=275
一个=144
。。。。
2)在C盘新建ceshi(可以是任意目录,只分析里面的txt文件)文件夹,该文件夹放入需要进行词频统计的文档,文档可以为多个;
3)在命令行下运行统计程序tongji.exe,后面的参数就是要检索的目录(也可以在命令行下输入帮助参数 \? 获取帮助信息)
运行效果如下:
开始运行的效果如下: 也可以直接在程序后面将目录路径作为参数传入
代码如下:
#include <windows.h>
#include <boost/regex.hpp>
#include <string>
#include <iostream>
#include <fstream>
#define MAXCI 32280
#define MAXLEN 204800 //每次读取不大于20k的行
using namespace std;
using namespace boost;
void RegFile(const char * filename);
void SortInto();
typedef struct WordRate
{
string word;
int rate;
} Word;
//装载所有的词的数组
Word MyWord[33000];
//标志词库里面的词是不是已经全部读到了内存
bool ReadFlag=true;
//程序开始的时间
int starttime;
void main(int agrc,char *argv[])
{
string Dir; //要搜索的目录的路径
starttime=GetTickCount(); //记下程序运行的开始时间
string Fullname;
string Filename; //单个文件的名称
WIN32_FIND_DATA FileInfo; //这个结构体 存储了检索到的文件的信息 (文件大小,名称,时间等信息)
HANDLE FileHandle; //文件句柄
if(agrc != 1) //agrc 是我们在命令行里面传入的参数的个数,如果没有参数 argc就是1
{ //如果有参数就将它存入Dir这个string变量
Dir.assign(argv[1]);
}
else //参数个数为零就提示输入
{
cout<<"请输入要搜索的目录\n如:C:\\test\\* \nc:\\test 就是你要检索的目录或者带帮助参数 \\? 看看吧"<<endl;
cin>>Dir;
//exit(0);
}
if( Dir =="\\?" )
{
cout<<"-------------中文词频统计程序-------------"<<endl<<endl;
cout<<"--------传入的参数应该如:C:\\test\\*----------"<<endl;
cout<<"--------词库文件ciku.ini和最后的结果都保存在C盘的ciku文件夹下----------"<<endl;
cout<<"--------所以先在C盘建立一个ciku目录将词库文件ciku.ini拷贝进去----------"<<endl;
exit(0);
}
//argv[1]就是我们在命令行里面传入的参数,不允许是C盘根目录,*号表示改目录下面的所有内容
FileHandle=::FindFirstFile(Dir.c_str(),&FileInfo);
//删除参入参数里面最后一个*符号,作为文件的目录名称,以便和后面找到的文件的名称拼起来,
Dir.erase(Dir.length()-1,1);
printf ("Target Dir is %s \n",Dir.c_str());
//如果打开第一个文件失败,就直接退出
if(FileHandle==INVALID_HANDLE_VALUE)
{
cout<<"打开目录失败 \n输入目录如:c:\\test\\* \n c:\\test\\* 就是你要检索的目录"<<endl;
return ;
}
//如果能找到第一个文件,就根据文件检索句柄搜索目录下所有文件,
while( FindNextFile(FileHandle,&FileInfo) != 0 )
{
//这里将见多到的文件名和路径拼接起来,组成一个fullname,
Fullname=Dir;
Filename.assign(FileInfo.cFileName);
Fullname.append(Filename);
if( -1 == Filename.find(".txt") || 4 > ((FileInfo.nFileSizeHigh * (MAXDWORD +1)) + FileInfo.nFileSizeLow) )
{
//只检索目录下的文本文件,如果文件大小为小于4个字节,也不进行匹配
continue;
}
cout<<"------------开始检索文件"<<Fullname<<" --------------"<<endl;
//就带全路径的文件名称转化为C风格的字符串,传给RegFile去统计
RegFile(Fullname.c_str());
}
//所有的文件都检索完毕之后就开始对词频信息进行排序
SortInto();
FindClose(FileHandle );
}
BOOL comp(const Word *lhs ,const Word *rhs )
{
return lhs->rate < rhs->rate;
}
void sortBySTL()
{
Word * pWord;
pWord=&MyWord[MAXCI];
// std::sort(&MyWord,pWord,comp);
}
//根据出现的频率的排序
void SortInto()
{
DeleteFile("c://test//output.ini");
int i=0,j;
Word TmpWord;
char rate_str[10];
cout<<"开始排序,并输出到文件"<<endl;
//根据结构体数组的频率进行冒泡排序
for(i = 0;i <= MAXCI ; i++ )
{
//如果词频为零就不进行排序也不写入
if( MyWord[i].rate > 0)
{
for(j = i+1 ; j < MAXCI ; j++ )
{
if(MyWord[i].rate < MyWord[j].rate)
{
TmpWord=MyWord[i];
MyWord[i]=MyWord[j];
MyWord[j]=TmpWord;
}
}
//清空词频字符串缓冲区
::memset(rate_str,0,10);
//词频是整型,转化为字符串型
itoa(MyWord[i].rate,rate_str,10);
//讲词频信息写入到文件,格式:你好=25
::WritePrivateProfileString("MyWord",MyWord[i].word.c_str(),rate_str,"c://ciku//output.ini");
cout<<"“"<<MyWord[i].word<<"” 频率 = "<<MyWord[i].rate<<" —>用时(秒) ="<<(GetTickCount()-::starttime)/1000<<endl;
}
}
}
//根据传入的文件名称进行匹配
void RegFile(const char * filename)
{
char container[100];
int index=0;
char index_str[10];
//正则表达式的匹配字串,就是从词库里面读取的词汇,然后去检索,类似一个模版
regex expression;
//用来接收匹配的结果
smatch what;
//当前要检索的文件
ifstream myfile;
//指向要匹配的文本的迭代器,it指向开始,end指向尾部
std::string::const_iterator it;
std::string::const_iterator end;
//in要检索的文本内容
std::string in;
std::string Tempstr;
myfile.open(filename,ios::in);
//如果打开失败就退出
if(myfile.fail())
{
cout<<"open file "<<filename<<" failed"<<endl;
return;
}
//从文件中读取一行
while(getline(myfile,in))
{
//每次开始都将要匹配的字符串空间清空;
in="";
//从文件当前处按行读取,直到读取刚刚超过MAXLEN个字节,这里是20k
while(in.length() < MAXLEN && getline(myfile,Tempstr) )
{
in.append(Tempstr);
Tempstr="";
}
//每次的比较都是从第一个词汇开始,第二轮的20K文件又要从第一个开始匹配
index=0;
while(index<=MAXCI)
{
//初始化迭代器
it = in.begin();
end = in.end();
//如果词库里面所有的信息都已经读到了内存 就不用读取
if(ReadFlag && index<=MAXCI)
{
::memset(container,0,100);
itoa(index,index_str,10);
//从 ciku.ini这个文件里依次读取词汇到container
::GetPrivateProfileString("MyWord",index_str,"Nothing",container,99,"c://ciku//ciku.ini");
//将
MyWord[index].word.assign(container);
MyWord[index].rate=0;
//第一次从词库文件里面读取到MAXCI=32280个词汇
if(index == MAXCI )
//以后不用从文件里面读取了,所有词汇都已经在存到结构体里面了
ReadFlag=false;
}
//将当前的词汇作为一个正则表达式匹配串去检索相同的字符串
expression.assign( MyWord[index].word.c_str() );
//开始用上面的表达式来检索迭代器it和end所包含的内容,
while(regex_search(it,end,what,expression))
{
//每一次匹配到一个结果,就会返回指向匹配内容之后的iterator,
//又将它付给it,这样就形成了一个滚动匹配
it=what[0].second;
//匹配到一个词汇就将它对应的词频加一
MyWord[index].rate++;
//同时每一次匹配到一个词汇就将它删除,减少以后匹配的次数
in.erase(in.find(MyWord[index].word.data()),MyWord[index].word.length());
}
//如果词频不为零 打印一次匹配次信息
if(MyWord[index].rate)
{
cout<<"匹配文件:"<<filename<<"\n当前匹配“"<<MyWord[index].word<<"” 第"<<index<<"个词 —>用时(秒)="<<(GetTickCount()-::starttime)/1000<<endl;
}
//如果当前匹配文段长度为零 就直接结束本轮匹配
if(in.length ==0)
{
break;
}
//指向下一个词
index++;
}
}
//关闭文件
myfile.close();
}