⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 namerecognizer.cpp

📁 一个集分词、词性标注和格式转换的强大的工具包
💻 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

# if !tracefile
extern FILE * tracefile;
# 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;

	fprintf(tracefile,"\n候选姓名\t费用\n");
	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); // 如果符合标准,加入到候选姓名组
			fprintf(tracefile,s1.Mid(i,len));
			fprintf(tracefile,"\t%f",fee);
		}
	}

	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) { // 如果不存在候选姓名,将每个单字作为词输出
		fprintf(tracefile,"###不存在候选姓名###\n");
		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 + -