基于字典的【正向最大减字】分词算法实现

基于字典的的正向最大减字匹配算法,在《搜索引擎原理、技术与系统》提到过,可以说是最简单的中文分词算法之一。本文简要介绍实现过程,并附上实现源代码,一起学习。该算法实现主要是基于一个词典,进行分词,所以词典质量直接影响分词的结果。但是算法上也存在一些硬伤,举个例子,”参加过世界杯的选手”。无论用多丰富的词典,此算法均会分成“参加 过世 界 杯 的 选手”。

 

词典

词典采用GBK编码,格式为“ID SP 词语 SP 频率”(点击这里下载),只需要读取每一行,拿到两个空格(SP)之间的词语,然后放到一个C++ STL的set对象即可。在加载词典的同时,可以算出最大词长nMaxWordLen,这个值会在后面的分词使用到。

 

分词

从句子的最左边开始,逐字节遍历。每次分词时,首先截取nMaxWordLen个字节,并在词典中搜寻,如果找到了,就分词。如果找不到,就递减字节,如此往复,直到只剩下一个字节为止,这时就判断是ASCII还是汉字,如果是前者,直接分割,后者就将当前字节和下一个字节一起分割。这样直到遍历完所有字节。

 

代码

/*
 * dict_seg.cpp
 * 基于字典的分词
 *  Created on: 2012-6-16
 *      Author: bourneli
 */

#include <string>
#include <iostream>
#include <fstream>
#include <set>
#include <vector>

using namespace std;

#define NL 0x0A         // new line
#define CR 0x0D         // carrige return
#define SP 0x20         // space
#define GBK_HB_MIN      0x80    // minimum of gbk high byte

#define DICT_PATH       "words.gbk.dict"        // 字典路径
#define TEXT_PATH       "sentence.txt"           // 句子文本路径

/**
 * 加载词典
 * @param string sDictPath 字典路径
 * @param set<string> oDict 字典
 * @param int nMaxLen 字典中的最长词的字节数
 */
void LoadDictionary(const string& sDictPath, set<string>& oDict, int& nMaxLen);

/**
 * 采用基于词典的最大正向匹配分词
 * @param string sSen 需要分词的句子
 * @param set<string> 字典
 * @param int 字典中的最长词的字节数
 * @param vector<string> 分词后的结果
 */
void SegmentSentence(const string& sSen, const set<string>& oDict, int nMaxLen, vector<string>& vecWords);

int main(int argc ,char** argv) {

    set<string> oDict;
    int nMax = 0;
    LoadDictionary(DICT_PATH, oDict, nMax);

    // 获取句子
    ifstream oReader(TEXT_PATH);
    if (!oReader.is_open()) {
        cout << "Fail to open file : " << TEXT_PATH;
    }
    string sSen = "";
    getline(oReader, sSen);
    oReader.close();
    cout << "Original Sentence: " << sSen << endl;

    /**
     * 采用正向最大匹配规则分词
     */
    vector<string> vecSeg;
    SegmentSentence(sSen, oDict, nMax, vecSeg);

    // 输出分词结果
    cout << "Segment: ";
    for (vector<string>::const_iterator cItr = vecSeg.begin(); cItr != vecSeg.end(); ++cItr) {
        cout << *cItr << " ";
    }
    cout << endl;
    return 0;
}

void SegmentSentence(const string& sSen, const set<string>& oDict, int nMaxWordLen, vector<string>& vecWords){
      // 遍历每一个字节(不是汉字)
      for (string::const_iterator cItr = sSen.begin(); cItr < sSen.end();) {
          int nWordLen = (cItr + nMaxWordLen) <= sSen.end() ? nMaxWordLen : (sSen.end() - cItr);
          while (nWordLen > 1) {
              string sWord(cItr, cItr + nWordLen);
              if (oDict.find(sWord) != oDict.end()) {
                  // 字典中找到此对象
                  vecWords.push_back(sWord);
                  break;
              } else {
                  nWordLen -= 1; // 这个地方可以根据GBK编码规则优化
              }
          }

          // 跟新当前索引
          if (nWordLen == 1 && GBK_HB_MIN <= static_cast<unsigned char>(*cItr)) {
              // 添加当前的单个汉字,gbk由两个byte组成
              vecWords.push_back(string(cItr, cItr + 2));
              cItr += 2;
          } else if (nWordLen == 1 && GBK_HB_MIN > static_cast<unsigned char>(*cItr)) {
              // 添加当前的ascii码
              vecWords.push_back(string(1, *cItr));
              cItr += 1;
          } else {
              cItr += nWordLen;
          }
      }
}

void LoadDictionary(const string& sDictPath, set<string>& oDict, int& nMaxWordLen) {
     // 加载字典
     ifstream oReader(sDictPath.c_str());
     if (!oReader.is_open()) {
         cout << "Fail to open file : " << DICT_PATH << endl;
         exit(0);
     }
     string sLine;
     nMaxWordLen = 0;
     while (getline(oReader, sLine)) {
         //cout << "Current Line :" << sLine << endl; // debug
         /**
          * 字典的每一行格式为:
          * ID SP 词语 SP 频率
          *
          * 只有两个空格(SP)之间的词语需要添加到本字典中
          */

         // 达到第一个空格
         int nSpOffset = 0;
         for (string::const_iterator cItr = sLine.begin(); cItr != sLine.end(); ++cItr, ++ nSpOffset) {
             if (SP == *cItr) {
                 break;
             }
         }

         // 读取词语
         string sWord("");
         for (string::const_iterator cItr = sLine.begin() + nSpOffset + 1; cItr != sLine.end(); ++cItr) {
             if (SP == *cItr) {
                 // 遇到第二个空格,断开
                 break;
             }

             sWord.append(1, *cItr);
             if (GBK_HB_MIN <= static_cast<unsigned char>(*cItr)) {
                 // 针对GBK编码特殊处理,避免截断词语
                 ++ cItr;
                 if (cItr != sLine.end()) {
                     sWord.append(1, *cItr);
                 } else {
                     cout << "line format error: '" << sLine << "'" << endl;
                     exit(0);
                 }
             }
         }

         // 将此与添加到字典中
         if ("" != sWord) {
             if (!oDict.insert(sWord).second) {
                 cout << "Word conflict: " << sWord << endl;
                 exit(0);
             }
         }
         // 比较最长字长
         if (nMaxWordLen < sWord.size()) {
             nMaxWordLen = sWord.size();
         }
     }
     oReader.close();
}
posted @ 2012-06-29 20:57  bourneli  阅读(1653)  评论(0编辑  收藏  举报