NameRecognizer.cpp
上传用户:qzyuheng
上传日期:2013-04-28
资源大小:71k
文件大小:7k
源码类别:

词法分析

开发平台:

Visual C++

  1. #include "stdafx.h"
  2. #include "math.h" // 包含log函数的定义
  3. #include "NameRecognizer.h"
  4. #include "MyDictionary.h"
  5. # define HZ_NUM 6768
  6. # define HZ_ID(c1,c2) ((c1)-176) *94 +((c2)-161)
  7. /*
  8. # define MaxWordLength 8  // 最大词长为8个字节(即4个汉字)
  9. # define Separator "/  "    // 词界标记
  10. # define Max2Fee  -2.14
  11. # define Max3Fee  -0.80
  12. # define CorpusSize 200000 // 语料库规模,用于计算词的出现概率
  13. const int SurNameSize=174000;  // 姓名语料库中姓氏用字总数
  14. const int GivenNameSize=320000; // 姓名语料库中人名用字总数
  15. */
  16. extern int MaxWordLength;
  17. extern CString Separator;
  18. extern long CorpusSize;
  19. extern float Max2Fee;
  20. extern float Max3Fee;
  21. extern long SurNameSize;
  22. extern long GivenNameSize;
  23. # if !pDict
  24. extern CMyDictionary pDict;
  25. # endif
  26. struct NameZi{// 定义一个数组存放每个国标汉字用作姓,和用作人名的次数
  27. int sName,gName;
  28. } namezis[HZ_NUM];
  29. double sFee(CString z)
  30. { // 根据一个汉字作为姓氏使用的次数计算该汉字作为姓氏的费用
  31. int wFreq=pDict.GetFreq(z);
  32. if(wFreq==-1)
  33. wFreq=0;
  34. double wFee=-log((double)(wFreq+1)/CorpusSize);
  35. int id=HZ_ID((unsigned char) z[0], (unsigned char) z[1]);
  36. namezis[id].sName=pDict.GetSnameFreq(z); // 从数据库中读出一个汉字作为姓氏使用的次数
  37. if(id>=0 && id<HZ_NUM && namezis[id].sName>0)
  38. return -log((double)(namezis[id].sName+1)/SurNameSize)-wFee;
  39. else
  40. return 20.0;
  41. }
  42. double gFee(CString z)
  43. { // 根据一个汉字作为人名使用的次数计算该汉字作为人名的费用
  44. int wFreq=pDict.GetFreq(z);
  45. if(wFreq==-1)
  46. wFreq=0;
  47. double wFee=-log((double)(wFreq+1)/CorpusSize); // 汉字作为单字词使用的费用
  48. int id=HZ_ID((unsigned char)z[0], (unsigned char)z[1]);
  49. namezis[id].gName=pDict.GetGnameFreq(z); //从数据库中读出一个汉字作为人名使用的次数
  50. if(id>=0 && id<HZ_NUM) {
  51. if(namezis[id].gName>0)
  52. return -log((double)(namezis[id].gName+1)/GivenNameSize)-wFee;// 汉字作为人名使用的费用减去它作为单字词使用的费用
  53. else {// 如果当前汉字不在姓名用字表hanziname中
  54. return -log(1/(double)GivenNameSize)-wFee; // 汉字作为人名使用的费用减去它作为单字词使用的费用
  55. } // C++中如果两个整数相除,默认返回值也是整数,因此如果不在GivenNameSize前加上强制数据类型转换,返回值为0,0的对数无意义
  56. }
  57. else
  58. return 20.0;
  59. }
  60. double sgFee(CString sg)
  61. {// 计算候选姓名的费用
  62. double fee=pDict.GetFee(sg,TRUE); // 把候选词串作为一个姓名看待:姓+名,查询它在姓名表中的费用值
  63. if(fee<20.0) 
  64. return fee; // 如果这个名字已经在姓名表中存在,就直接返回它的费用值
  65. if(sg.GetLength()==4) { // 如果候选词串是两个汉字,在姓名表中不是一个完整的名字,就将它作为“名”来查找
  66. fee=pDict.GetFee(sg,FALSE);
  67. if(fee<20.0)
  68. return fee; // 如果姓名表中有这个“双名”,返回其费用值
  69. }
  70. // 如果不是上述情况,就临时调用费用函数计算这个字串作为“姓+名”模式
  71. // 或者“双名”使用的费用
  72. CString z=sg.Left(2);
  73. // int id=HZ_ID((unsigned char)z[0],(unsigned char)z[1]); // 取最左边汉字的GB码
  74. // namezis[id].sName=pDict.GetSnameFreq(z);
  75. if(pDict.GetSnameFreq(z)>0) {// 看看这个串最左边的汉字是否是“姓氏”用字
  76. fee=sFee(sg.Left(2))+gFee(sg.Mid(2,2)); 
  77. if(sg.GetLength()==4)
  78. fee+=-log(0.37);
  79. else
  80. fee+=gFee(sg.Right(2))+(-log(0.63));
  81. }// 实际上,即便最左边的汉字是姓氏用字,它也可能是作为人名使用,比如“辛楣”中的辛,在“赵辛楣”中就是作为人名使用
  82. else { // 如果不是姓氏用字
  83. if (sg.GetLength()>=6)
  84. fee=20;
  85. else
  86. if (sg.GetLength()==4) // 如果是两个汉字,就假定是双名,没有姓的词串,来计算它作为人名的费用值
  87. fee=gFee(sg.Left(2))+gFee(sg.Right(2));
  88. }
  89. //////////////////////////////////////////////
  90. // 以下是陈书中计算汉字串姓名费用值的程序段
  91. // 假定输入汉字串一定是“姓”+“名”模式
  92. // fee=sFee(sg.Left(2))+gFee(sg.Mid(2,2)); 
  93. // if(sg.GetLength()==4)
  94. // fee+=-log(0.37);
  95. // else
  96. // fee+=gFee(sg.Right(2))+(-log(0.63));
  97. //////////////////////////////////////////////
  98. return fee; // 返回计算所得费用值
  99. }
  100. BOOL isHomoPair (CMaybeName *p1, CMaybeName *p2)
  101. {// 判断两个候选姓名是否有相同的起点位置
  102. if(p1->offset==p2->offset)
  103. return TRUE;
  104. else
  105. return FALSE;
  106. }
  107. BOOL isCrossPair (CMaybeName *p1, CMaybeName *p2)
  108. {// 判断两个候选姓名是否有部分重叠现象
  109. if(p1->offset==p2->offset || p1->offset+p1->length<=p2->offset || p2->offset+p2->length<=p1->offset)
  110. return FALSE;
  111. else
  112. return TRUE;
  113. }
  114. CString CheckStr(CString s1)
  115. {// 对连续单字形成的串进行检查,看其中是否含有姓名词
  116. CObArray maybeNames; // 存放候选姓名的动态数组
  117. CMaybeName *p,*p1,*p2; // 用来访问候选姓名数组元素的指针
  118. int i,j,len;
  119. for(i=0;i<s1.GetLength();i+=2) {
  120. for(len=4;len<=6 && i+len<=s1.GetLength();len+=2) {
  121. double fee=sgFee(s1.Mid(i,len)); // 计算候选姓名的费用
  122. if(len==4 && fee>=Max2Fee || len>=6 && fee>=Max3Fee) // 如果不是姓名
  123. continue;  // 继续查下一个候选姓名
  124. p=new CMaybeName(i,len,fee);
  125. maybeNames.Add(p); // 如果符合标准,加入到候选姓名组
  126. }
  127. }
  128. BOOL iDeleted = FALSE;
  129. for(i=0;i<maybeNames.GetSize();) {
  130. for(j=i+1;j<=i+2 && j<maybeNames.GetSize();) {
  131. p1=(CMaybeName *)maybeNames[i];
  132. p2=(CMaybeName *)maybeNames[j];
  133. if(isHomoPair(p1,p2)) { // 检查两个候选姓名是否有相同起点
  134. if(p1->fee>p2->fee) {
  135. maybeNames.RemoveAt(i);
  136. iDeleted=TRUE;
  137. break;
  138. }
  139. else
  140. maybeNames.RemoveAt(j);
  141. }
  142. else
  143. if(isCrossPair(p1,p2)) { // 检查两个候选姓名的尾跟头是否有重叠部分
  144. if(p1->fee<=p2->fee)
  145. maybeNames.RemoveAt(j);
  146. else {
  147. maybeNames.RemoveAt(i);
  148. iDeleted=TRUE;
  149. break;
  150. }
  151. }
  152. else 
  153. j++;
  154. }
  155. if(!iDeleted)
  156. i++;
  157. else
  158. iDeleted=FALSE;
  159. }
  160. // create the target string
  161. CString s2="";
  162. if(maybeNames.GetSize()==0) { // 如果不存在候选姓名,将每个单字作为词输出
  163. for(i=0;i<s1.GetLength();i+=2)
  164. s2+=s1.Mid(i,2)+Separator;
  165. return s2;
  166. }
  167. for(i=0;i<maybeNames.GetSize(); i++) { 
  168. if(i==0)
  169. j=0;
  170. else
  171. j=p->offset+p->length;
  172. p=(CMaybeName *)maybeNames[i];
  173. for(;j<p->offset;j+=2)
  174. s2+=s1.Mid(j,2)+Separator;
  175. // plus the single Character word before a name word
  176. CString w=s1.Mid(p->offset,p->length);
  177. // s2+=w+Separator;
  178. s2 = s2 + w + "/nr "; // 直接在识别得到的姓名词后标上词性nr
  179. // 以下两行是陈书程序段,假定候选姓名是“姓+名”模式,
  180. // 实际上,也可能仅仅是“名”,不包含“姓”
  181. // CString sName=w.Left(2), gName=w.Mid(2);
  182. // pDict.Insert(sName,gName,p->fee);
  183. // 修改为:
  184. CString ML_HZ=w.Left(2),gName=w.Mid(2);
  185. // int id=HZ_ID((unsigned char)ML_HZ[0],(unsigned char)ML_HZ[1]); // 取最左边汉字的GB码
  186. if(pDict.GetSnameFreq(ML_HZ)>0) // 如果左边这个汉字是姓氏用字
  187. pDict.Insert(ML_HZ,gName,p->fee); // 就把整个字串作为“姓+名”模式加入到词典
  188. else // 否则,将单字或者双字作为“名”加入到词典
  189. if (w.GetLength()<=4) 
  190. pDict.Insert(" ",w,p->fee);
  191. } // 显然,目前程序无法判断“辛楣”是一个单姓单名,
  192. // 还是一个缺姓的双名(比如“赵辛楣”)
  193. for(j=p->offset+p->length;j<s1.GetLength();j+=2)
  194. s2+=s1.Mid(j,2)+Separator;
  195. return s2;
  196. }