?? mpwordseg.cpp
字號:
#include "stdafx.h"
#include "math.h" // 包含log函數的定義
#include "MPWordSeg.h"
#include "MyDictionary.h"
#include "MyFileApp.h"
/*
# define MaxWordLength 8 // 最大詞長為8個字節(即4個漢字)
# define Separator "/ " // 詞界標記
# define CorpusSize 200000 // 定義語料庫規模
*/
extern CString Separator;
extern int MaxWordLength;
extern long CorpusSize;
CMyDictionary pDict; // 定義一個詞典類對象,全局變量
// 以下是最大概率法分詞程序
struct Candidate {
short offset, length; // 候選詞在輸入串中的起點,長度
short goodPrev; // 最佳前趨詞的序號
float fee, sumFee; // 候選詞的費用,路徑上的累計費用
} Candidates[100];// 假定最多100個候選詞
short getTmpWords(CString &s)
{ // 從輸入串中挑選可能是詞的單位作為最大概率法分詞的候選詞
short i=0,j,len,restlen,n=s.GetLength();
long freq;
CString w;
for(j=0;j<n;j+=2) {
for(len=2;len<=MaxWordLength;len+=2) {
restlen=n-j;
if (len<=restlen) // 如果剩余詞長度不夠長,跳出循環
w=s.Mid(j,len);
else
break;
freq=pDict.GetFreq(w); // 如果在數據庫中將wfreq字段設為雙精度型數字,則返回0值
if(len>2 && freq==-1)
continue;
if(freq==-1)
freq=0;
Candidates[i].offset=j;
Candidates[i].length=len;
Candidates[i].fee = (float)(-log((double)(freq+1)/CorpusSize));
Candidates[i].sumFee=0.0F;// 置初值
i++;
}
}
return i;
}
void getPrev(short i)
{ // 計算每一個候選詞的最佳前趨詞,以及當前詞的最小累計費用
if(Candidates[i].offset==0) {
Candidates[i].goodPrev=-1;
Candidates[i].sumFee=Candidates[i].fee;
return;
}
short j,minID=-1;
// for(j=i-1;j>=0;j--) {
// if(Candidates[j].offset+Candidates[j].length==Candidates[i].offset)
// break;
// }
// for(;Candidates[j].offset+Candidates[j].length==Candidates[i].offset;j--)
for(j=i-1;j>=0;j--)
{ //向左查找所有候選詞,得到前驅詞集合,從中挑選最佳前趨詞
if(Candidates[j].offset+Candidates[j].length==Candidates[i].offset) {
if(minID==-1 || Candidates[j].sumFee<=Candidates[minID].sumFee)
minID=j;
}
if(Candidates[i].offset-Candidates[j].offset>=8) // 向左查找候選詞最遠不超過4個漢字
break;
}
Candidates[i].goodPrev=minID;
Candidates[i].sumFee=Candidates[i].fee+Candidates[minID].sumFee;
return;
}
CString SegmentHzStrMP(CString s1)
{//最大概率法分詞程序,處理一個字符串
int len=s1.GetLength();
short n=getTmpWords(s1);
short minID=-1;
short i;
for(i=0;i<n;i++) {
getPrev(i);
if(Candidates[i].offset+Candidates[i].length==len) { // 如果當前詞是s1中最后一個可能的候選詞
if(minID==-1||Candidates[i].sumFee<Candidates[minID].sumFee) // 如果這個末尾候選詞的累計費用最小
minID=i; // 把當前詞的序號賦給minID,這就是最小費用路徑的終點詞的序號
// 這就是最后分詞結果最右邊的那個詞的序號
}
}
CString s2=""; // s2是輸出結果
for(i=minID;i>=0;i=Candidates[i].goodPrev) // 從右向左取詞候選詞
s2=s1.Mid(Candidates[i].offset,Candidates[i].length)+Separator+s2;
return s2;
}
CString SegmentSentenceMP (CString s1)
{// 最大概率法分詞程序:對句子進行分詞處理的函數
CString s2="";
int i,dd;
while(!s1.IsEmpty()) {
unsigned char ch=(unsigned char) s1[0];
if(ch<128) { // 處理西文字符
i=1;
dd=s1.GetLength();
while(i<dd && ((unsigned char)s1[i]<128) && (s1[i]!=10) && (s1[i]!=13)) // s1[i]不能是換行符或回車符
i++;
if ((ch!=32) && (ch!=10) && (ch!=13)) // 如果不是西文空格或換行或回車符
s2 += s1.Left(i) + Separator;
else {
if (ch==10 || ch==13) // 如果是換行或回車符,將它拷貝給s2輸出
s2+=s1.Left(i);
}
s1=s1.Mid(i);
continue;
}
else {
if (ch<176) { // 中文標點等非漢字字符
i=0;
dd=s1.GetLength();
while(i<dd && ((unsigned char)s1[i]<176) && ((unsigned char)s1[i]>=161)
&& (!((unsigned char)s1[i]==161 && ((unsigned char)s1[i+1]>=162 && (unsigned char)s1[i+1]<=168)))
&& (!((unsigned char)s1[i]==161 && ((unsigned char)s1[i+1]>=171 && (unsigned char)s1[i+1]<=191)))
&& (!((unsigned char)s1[i]==163 && ((unsigned char)s1[i+1]==172 || (unsigned char)s1[i+1]==161)
|| (unsigned char)s1[i+1]==168 || (unsigned char)s1[i+1]==169 || (unsigned char)s1[i+1]==186
|| (unsigned char)s1[i+1]==187 || (unsigned char)s1[i+1]==191))) //
i=i+2; // 假定沒有半個漢字
if (i==0)
i=i+2;
if (!(ch==161 && (unsigned char)s1[1]==161)) // 不處理中文空格
s2+=s1.Left(i) + Separator; // 其他的非漢字雙字節字符可能連續輸出
s1=s1.Mid(i);
continue;
}
}
// 以下處理漢字串
i=2;
dd=s1.GetLength();
while(i<dd && (unsigned char)s1[i]>=176)
// while(i<dd && (unsigned char)s1[i]>=128 && (unsigned char)s1[i]!=161)
i+=2;
s2+=SegmentHzStrMP(s1.Left(i));
s1=s1.Mid(i);
}
//////////////////////////////////////////////////////////
// 以下程序用于將表示時間的單位合并成一個分詞單位
int TmpPos;
const char * p;
CString s2_part_1;
if (s2.Find(" 年/")>=0) {
TmpPos=s2.Find(" 年/");
s2_part_1=s2.Mid(0,TmpPos);
p=(LPCTSTR) s2_part_1;
p=p+TmpPos-2;
if (p[0]=='1'||p[0]=='2'||p[0]=='3'||p[0]=='4'||p[0]=='5'||p[0]=='6'||p[0]=='7'||p[0]=='8'||p[0]=='9'||p[0]=='0') {
s2_part_1=s2_part_1.Mid(0,TmpPos-1);
s2=s2_part_1+s2.Mid(TmpPos+2);
}
}
if (s2.Find(" 月/")>=0) {
TmpPos=s2.Find(" 月/");
s2_part_1=s2.Mid(0,TmpPos);
p=(LPCTSTR) s2_part_1;
p=p+TmpPos-2;
if (p[0]=='1'||p[0]=='2'||p[0]=='3'||p[0]=='4'||p[0]=='5'||p[0]=='6'||p[0]=='7'||p[0]=='8'||p[0]=='9'||p[0]=='0') {
s2_part_1=s2_part_1.Mid(0,TmpPos-1);
s2=s2_part_1+s2.Mid(TmpPos+2);
}
}
if (s2.Find(" 日/")>=0) {
TmpPos=s2.Find(" 日/");
s2_part_1=s2.Mid(0,TmpPos);
p=(LPCTSTR) s2_part_1;
p=p+TmpPos-2;
if (p[0]=='1'||p[0]=='2'||p[0]=='3'||p[0]=='4'||p[0]=='5'||p[0]=='6'||p[0]=='7'||p[0]=='8'||p[0]=='9'||p[0]=='0') {
s2_part_1=s2_part_1.Mid(0,TmpPos-1);
s2=s2_part_1+s2.Mid(TmpPos+2);
}
}
//合并時間單位程序段結束
//////////////////////////////////////////////////////////
return s2;
}
void SegmentAFileMP (CString FileName)
{ // 最大概率法分詞程序:對文件進行分詞處理
if (pDict.myDatabaseName.IsEmpty()) {
AfxMessageBox("您沒有打開詞庫,無法進行分詞處理");
if(pDict.OpenMDB()==FALSE)
return;
}
FILE * in, * out;
in = fopen((const char*) FileName,"rt");
if(in==NULL) {
AfxMessageBox("無法打開文件");
return;
}
FileName=ChangeFileName(FileName,"-seg");
out = fopen((const char*) FileName,"wt");
if(out==NULL) {
AfxMessageBox("無法創建文件");
fclose(in);
return;
}
CStdioFile inFile(in),outFile(out);
char s[2048];
CString line;
while(inFile.ReadString(s,2048)) {// 循環讀入文件中的每一行
line = s;
line = SegmentSentenceMP(line); // 調用句子分詞函數進行分詞處理
outFile.WriteString(line); // 將分詞結果寫入目標文件
}
inFile.Close();
outFile.Close();
}
// 最大概率法分詞程序結束
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -