📄 namerecognizer.cpp
字号:
#include "stdafx.h"
#include "math.h" // 包含log函数的定义
#include "NameRecognizer.h"
#include "MyDictionary.h"
# define HZ_NUM 6768
# define HZ_ID(c1,c2) ((c1)-176) *94 +((c2)-161)
/*
# define MaxWordLength 8 // 最大词长为8个字节(即4个汉字)
# define Separator "/ " // 词界标记
# define Max2Fee -2.14
# define Max3Fee -0.80
# define CorpusSize 200000 // 语料库规模,用于计算词的出现概率
const int SurNameSize=174000; // 姓名语料库中姓氏用字总数
const int GivenNameSize=320000; // 姓名语料库中人名用字总数
*/
extern int MaxWordLength;
extern CString Separator;
extern long CorpusSize;
extern float Max2Fee;
extern float Max3Fee;
extern long SurNameSize;
extern long GivenNameSize;
# if !pDict
extern CMyDictionary pDict;
# endif
struct NameZi{// 定义一个数组存放每个国标汉字用作姓,和用作人名的次数
int sName,gName;
} namezis[HZ_NUM];
double sFee(CString z)
{ // 根据一个汉字作为姓氏使用的次数计算该汉字作为姓氏的费用
int wFreq=pDict.GetFreq(z);
if(wFreq==-1)
wFreq=0;
double wFee=-log((double)(wFreq+1)/CorpusSize);
int id=HZ_ID((unsigned char) z[0], (unsigned char) z[1]);
namezis[id].sName=pDict.GetSnameFreq(z); // 从数据库中读出一个汉字作为姓氏使用的次数
if(id>=0 && id<HZ_NUM && namezis[id].sName>0)
return -log((double)(namezis[id].sName+1)/SurNameSize)-wFee;
else
return 20.0;
}
double gFee(CString z)
{ // 根据一个汉字作为人名使用的次数计算该汉字作为人名的费用
int wFreq=pDict.GetFreq(z);
if(wFreq==-1)
wFreq=0;
double wFee=-log((double)(wFreq+1)/CorpusSize); // 汉字作为单字词使用的费用
int id=HZ_ID((unsigned char)z[0], (unsigned char)z[1]);
namezis[id].gName=pDict.GetGnameFreq(z); //从数据库中读出一个汉字作为人名使用的次数
if(id>=0 && id<HZ_NUM) {
if(namezis[id].gName>0)
return -log((double)(namezis[id].gName+1)/GivenNameSize)-wFee;// 汉字作为人名使用的费用减去它作为单字词使用的费用
else {// 如果当前汉字不在姓名用字表hanziname中
return -log(1/(double)GivenNameSize)-wFee; // 汉字作为人名使用的费用减去它作为单字词使用的费用
} // C++中如果两个整数相除,默认返回值也是整数,因此如果不在GivenNameSize前加上强制数据类型转换,返回值为0,0的对数无意义
}
else
return 20.0;
}
double sgFee(CString sg)
{// 计算候选姓名的费用
double fee=pDict.GetFee(sg,TRUE); // 把候选词串作为一个姓名看待:姓+名,查询它在姓名表中的费用值
if(fee<20.0)
return fee; // 如果这个名字已经在姓名表中存在,就直接返回它的费用值
if(sg.GetLength()==4) { // 如果候选词串是两个汉字,在姓名表中不是一个完整的名字,就将它作为“名”来查找
fee=pDict.GetFee(sg,FALSE);
if(fee<20.0)
return fee; // 如果姓名表中有这个“双名”,返回其费用值
}
// 如果不是上述情况,就临时调用费用函数计算这个字串作为“姓+名”模式
// 或者“双名”使用的费用
CString z=sg.Left(2);
// int id=HZ_ID((unsigned char)z[0],(unsigned char)z[1]); // 取最左边汉字的GB码
// namezis[id].sName=pDict.GetSnameFreq(z);
if(pDict.GetSnameFreq(z)>0) {// 看看这个串最左边的汉字是否是“姓氏”用字
fee=sFee(sg.Left(2))+gFee(sg.Mid(2,2));
if(sg.GetLength()==4)
fee+=-log(0.37);
else
fee+=gFee(sg.Right(2))+(-log(0.63));
}// 实际上,即便最左边的汉字是姓氏用字,它也可能是作为人名使用,比如“辛楣”中的辛,在“赵辛楣”中就是作为人名使用
else { // 如果不是姓氏用字
if (sg.GetLength()>=6)
fee=20;
else
if (sg.GetLength()==4) // 如果是两个汉字,就假定是双名,没有姓的词串,来计算它作为人名的费用值
fee=gFee(sg.Left(2))+gFee(sg.Right(2));
}
//////////////////////////////////////////////
// 以下是陈书中计算汉字串姓名费用值的程序段
// 假定输入汉字串一定是“姓”+“名”模式
// fee=sFee(sg.Left(2))+gFee(sg.Mid(2,2));
// if(sg.GetLength()==4)
// fee+=-log(0.37);
// else
// fee+=gFee(sg.Right(2))+(-log(0.63));
//////////////////////////////////////////////
return fee; // 返回计算所得费用值
}
BOOL isHomoPair (CMaybeName *p1, CMaybeName *p2)
{// 判断两个候选姓名是否有相同的起点位置
if(p1->offset==p2->offset)
return TRUE;
else
return FALSE;
}
BOOL isCrossPair (CMaybeName *p1, CMaybeName *p2)
{// 判断两个候选姓名是否有部分重叠现象
if(p1->offset==p2->offset || p1->offset+p1->length<=p2->offset || p2->offset+p2->length<=p1->offset)
return FALSE;
else
return TRUE;
}
CString CheckStr(CString s1)
{// 对连续单字形成的串进行检查,看其中是否含有姓名词
CObArray maybeNames; // 存放候选姓名的动态数组
CMaybeName *p,*p1,*p2; // 用来访问候选姓名数组元素的指针
int i,j,len;
for(i=0;i<s1.GetLength();i+=2) {
for(len=4;len<=6 && i+len<=s1.GetLength();len+=2) {
double fee=sgFee(s1.Mid(i,len)); // 计算候选姓名的费用
if(len==4 && fee>=Max2Fee || len>=6 && fee>=Max3Fee) // 如果不是姓名
continue; // 继续查下一个候选姓名
p=new CMaybeName(i,len,fee);
maybeNames.Add(p); // 如果符合标准,加入到候选姓名组
}
}
BOOL iDeleted = FALSE;
for(i=0;i<maybeNames.GetSize();) {
for(j=i+1;j<=i+2 && j<maybeNames.GetSize();) {
p1=(CMaybeName *)maybeNames[i];
p2=(CMaybeName *)maybeNames[j];
if(isHomoPair(p1,p2)) { // 检查两个候选姓名是否有相同起点
if(p1->fee>p2->fee) {
maybeNames.RemoveAt(i);
iDeleted=TRUE;
break;
}
else
maybeNames.RemoveAt(j);
}
else
if(isCrossPair(p1,p2)) { // 检查两个候选姓名的尾跟头是否有重叠部分
if(p1->fee<=p2->fee)
maybeNames.RemoveAt(j);
else {
maybeNames.RemoveAt(i);
iDeleted=TRUE;
break;
}
}
else
j++;
}
if(!iDeleted)
i++;
else
iDeleted=FALSE;
}
// create the target string
CString s2="";
if(maybeNames.GetSize()==0) { // 如果不存在候选姓名,将每个单字作为词输出
for(i=0;i<s1.GetLength();i+=2)
s2+=s1.Mid(i,2)+Separator;
return s2;
}
for(i=0;i<maybeNames.GetSize(); i++) {
if(i==0)
j=0;
else
j=p->offset+p->length;
p=(CMaybeName *)maybeNames[i];
for(;j<p->offset;j+=2)
s2+=s1.Mid(j,2)+Separator;
// plus the single Character word before a name word
CString w=s1.Mid(p->offset,p->length);
// s2+=w+Separator;
s2 = s2 + w + "/nr "; // 直接在识别得到的姓名词后标上词性nr
// 以下两行是陈书程序段,假定候选姓名是“姓+名”模式,
// 实际上,也可能仅仅是“名”,不包含“姓”
// CString sName=w.Left(2), gName=w.Mid(2);
// pDict.Insert(sName,gName,p->fee);
// 修改为:
CString ML_HZ=w.Left(2),gName=w.Mid(2);
// int id=HZ_ID((unsigned char)ML_HZ[0],(unsigned char)ML_HZ[1]); // 取最左边汉字的GB码
if(pDict.GetSnameFreq(ML_HZ)>0) // 如果左边这个汉字是姓氏用字
pDict.Insert(ML_HZ,gName,p->fee); // 就把整个字串作为“姓+名”模式加入到词典
else // 否则,将单字或者双字作为“名”加入到词典
if (w.GetLength()<=4)
pDict.Insert(" ",w,p->fee);
} // 显然,目前程序无法判断“辛楣”是一个单姓单名,
// 还是一个缺姓的双名(比如“赵辛楣”)
for(j=p->offset+p->length;j<s1.GetLength();j+=2)
s2+=s1.Mid(j,2)+Separator;
return s2;
}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -