本文设计了一种带逆向回退策略的正向最大匹配。
分词概述
英文文本的处理相对简单,每一个单词之间有空格或标点符号隔开。如果不考虑短语,仅以单词作为唯一的语义单元的话,处理英文单词切分相对简单,只需要分类多有单词,去除标点符号。中文自然语言处理首先要解决的难题就是中文分词技术。
中文分词(Chinese Word Segmentation) 指的是将一个汉字序列切分成一个个单独的词。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,
算法分类
现有的分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。
字符匹配
这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配。
常用的几种机械分词方法如下:
1)正向最大匹配法(由左到右的方向);
2)逆向最大匹配法(由右到左的方向);
3)最少切分(使每一句中切出的词数最小);
4)双向最大匹配法(进行由左到右、由右到左两次扫描)
理解法
这种分词方法是通过让计算机模拟人对句子的理解,达到识别词的效果。其基本思想就是在分词的同时进行句法、语义分析,利用句法信息和语义信息来处理歧义现象。它通常包括三个部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息来对分词歧义进行判断,即它模拟了人对句子的理解过程。这种分词方法需要使用大量的语言知识和信息。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。
统计法
从形式上看,词是稳定的字的组合,因此在上下文中,相邻的字同时出现的次数越多,就越有可能构成一个词。因此字与字相邻共现的频率或概率能够较好的反映成词的可信度。可以对语料中相邻共现的各个字的组合的频度进行统计,计算它们的互现信息。定义两个字的互现信息,计算两个汉字X、Y的相邻共现概率。互现信息体现了汉字之间结合关系的紧密程度。当紧密程度高于某一个阈值时,便可认为此字组可能构成了一个词。这种方法只需对语料中的字组频度进行统计,不需要切分词典,因而又叫做无词典分词法或统计取词方法。但这种方法也有一定的局限性,会经常抽出一些共现频度高、但并不是词的常用字组,例如“这一”、“之一”、“有的”、“我的”、“许多的”等,并且对常用词的识别精度差,时空开销大。实际应用的统计分词系统都要使用一部基本的分词词典(常用词词典)进行串匹配分词,同时使用统计方法识别一些新的词,即将串频统计和串匹配结合起来,既发挥匹配分词切分速度快、效率高的特点,又利用了无词典分词结合上下文识别生词、自动消除歧义的优点。
常见项目
极易分词
MMAnalyzer极易中文分词组件,由中科院提供的中文极易分词器。比较完善的中文分词器。
支持英文、数字、中文(简体)混合分词。
常用的数量和人名的匹配。
超过22万词的词库整理。
实现正向最大匹配算法。
IK
IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出了3个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
Paoding
Paoding(庖丁解牛分词)基于Java的开源中文分词组件,提供lucene和solr 接口,具有极 高效率和 高扩展性。引入隐喻,采用完全的面向对象设计,构思先进。
高效率:在PIII 1G内存个人机器上,1秒可准确分词 100万汉字。
采用基于不限制个数的词典文件对文章进行有效切分,使能够将对词汇分类定义。
能够对未知的词汇进行合理解析。
仅支持Java语言。
MMSEG4J
MMSEG4J基于Java的开源中文分词组件,提供lucene和solr 接口:
1.mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。
2.MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。
smallseg
开源的,基于DFA的轻量级的中文分词工具包。
特点:可自定义词典、切割后返回登录词列表和未登录词列表、有一定的新词识别能力。
知更鸟分词实现
中文分词既然作为自然语言处理的基本工具,那么我们就来实现一个基于字符匹配的中文分词,用笔者的英文名(Robin)给她起个名字,就叫做——知更鸟分词器(RS/Robin-Segmenter)。
算法描述
基本实现算法:
(1)正向最大匹配;
(2)逆向最大匹配;
(3)带逆向回退策略的正向最大匹配(重点)。
支持以下功能:
- 新词发现、新词标注;
- 字符转换、格式统一;
- 自定义分割符;
- 特殊符号保留;
- 检索分词模式;
数据结构
基于字典字符匹配匹配的分词算法,重点是字典数据结构的设计。好的字典数据结构能够极大提升匹配检索的效率。
采用基本容器HashMap实现的一种变种字典树(Trie)结构。其基本的思想如图。
假设词典:中国 | 中南海 | 人民 | 人民币 | 人情 | 银行 | 中国人民 | 中国人民银行 | 中国人寿保险 | ……
例如:“中国人民银行行长易纲。”这样一条文本的匹配过程如图示红线路径。分词结果应该示“中国人民银行 行长 易纲”。
由于中文词首字分布比较均匀,并且查询首字的概率远大于其他非首字,根节点采用map数组方式,数组下标为字符的Unicode码减去最小中文汉字 Unicode码。数组维度为汉字的Unicode码范围。
代码实现
Unicode工具类:
package com.robin.segment.robinseg;
import com.robin.config.ConfigUtil;
import com.robin.file.FileUtil;
import java.util.HashSet;
import java.util.Set;
/**
* <DT><B>描述:</B></DT>
* <DD>Unicode 编码工具类</DD>
*
* @version Version1.0
* @author Robin
* @version <I> V1.0 Date:2018-01-28</I>
* @author <I> E-mail:[email protected]</I>
*/
public class Unicode {
/** 量词配置路径 */
private static final String QUANTIFIER_PATH;
/** 量词集合 */
private static Set<Character> quantifierSet;
/** 中文数词路径 */
private static final String NUMERAL_PATH;
/** 中文数词集合 */
private static Set<Character> numeralSet;
/** 词典文件编码 */
private static final String DIC_ENCODING;
/** 空字符 Unicode */
public static final int NULL = 0x0;
/** 半角空格 Unicode */
public static final int DBC_BLANK = 0x20;
/** 半角句点 Unicode */
public static final int DBC_POINT = 0x2E;
/** 半角0 Unicode */
public static final int DBC_0 = 0x30;
/** 半角9 Unicode */
public static final int DBC_9 = 0x39;
/** 半角A Unicode */
public static final int DBC_A = 0x41;
/** 半角Z Unicode */
public static final int DBC_Z = 0x5A;
/** 半角a Unicode */
public static final int DBC_LA = 0x61;
/** 半角z Unicode */
public static final int DBC_LZ = 0x7A;
/** 普通最小中文汉字 Unicode */
public static final int MIN_CHINESE = 0x4E00;
/** 普通最大中文汉字 Unicode */
public static final int MAX_CHINESE = 0x9FA5;
/** 全角空格 Unicode */
public static final int SBC_BLANK = 0x3000;//0xFF00也是全角空格
/** 关注全角下限 Unicode */
public static final int SBC_LOW_LIMIT = 0xFF00;
/** 关注全角上限 Unicode */
public static final int SBC_UP_LIMIT = 0xFF5E;
/** 全角句点 Unicode */
public static final int SBC_POINT = 0xFF0E;
/** 全角0 Unicode */
public static final int SBC_0 = 0xFF10;
/** 全角9 Unicode */
public static final int SBC_9 = 0xFF19;
/** 全角A Unicode */
public static final int SBC_A = 0xFF21;
/** 全角Z Unicode */
public static final int SBC_Z = 0xFF3A;
/** 全角a Unicode */
public static final int SBC_LA = 0xFF41;
/** 全角z Unicode */
public static final int SBC_LZ = 0xFF5A;
/** 半角全角数字或字母 Unicode 编码偏移 */
public static final int OFFSET_DBC_SBC = 0xFEE0;
/** 英文大小写字母 Unicode 编码偏移 */
public static final int OFFSET_UP_LOW = 0x20;
static {
QUANTIFIER_PATH = ConfigUtil.getConfig("dic.quantifier");
NUMERAL_PATH = ConfigUtil.getConfig("dic.numeral");
DIC_ENCODING = ConfigUtil.getConfig("dic.encoding");
initQantifierSet();
initNumeralSet();
}
/**
* Unicode 字符类型
*/
public enum CharType {
/** 控制符 */
CONTROL,
/** 半角数字 */
DBC_DIGIT,
/** 半角大写字母 */
DBC_UPPER_CASE,
/** 半角小写字母 */
DBC_LOWER_CASE,
/** 中文 */
COMMON_CHINESE,
/** 全角字符 */
SBC_CHAR,
/** 全角字母 */
SBC_CASE,
/** 全角数字 */
SBC_DIGIT,
/** 全角大写字母 */
SBC_UPPER_CASE,
/** 全角小写字母 */
SBC_LOWER_CASE,
/** 小数点 */
DECIMAL_POINT,
/** 数字后缀 */
DECIMAL_SUFFIX,
/** 百分比符号 */
PERCENT_CHAR,
/** 空白符 */
BLANK_CHAR,
/** 其他 */
OTHER_CHAR,
}
/**
* 通过字符判断其类型
*
* @param ch Unicode字符
* @return 字符类型
*/
public static CharType getCharType(char ch) {
int value = ch;
//Unicode 普通中文汉字区
if ((value >= MIN_CHINESE) && (value <= MAX_CHINESE)) {
return CharType.COMMON_CHINESE;
}
//空白符、控制字符、半角空格0x20、全角空格0x3000
if ((value <= DBC_BLANK) || (value == SBC_BLANK)) {
return CharType.BLANK_CHAR;
}
//半角数字0-9:0x30-0x39
if ((value >= DBC_0) && (value <= DBC_9)) {
return CharType.DBC_DIGIT;
}
//半角字母a-z:0x61-0x7A
if ((value >= DBC_LA) && (value <= DBC_LZ)) {
return CharType.DBC_LOWER_CASE;
}
//半角字母A-Z:0x41-0x5A
if ((value >= DBC_A) && (value <= DBC_Z)) {
return CharType.DBC_UPPER_CASE;
}
//小数点:半角0x2E
if (value == DBC_POINT) {
return CharType.DECIMAL_POINT;
}
//数字后缀
if ((value == (int) ('%'))
|| (value == (int) ('$'))
|| (value == (int) ('¥'))) {
return CharType.DECIMAL_SUFFIX;
}
//关注的全角区:0xFF00-0xFF5E 与半角相距FEE0
if ((value >= SBC_LOW_LIMIT) && (value <= SBC_UP_LIMIT)) {
return CharType.SBC_CHAR;
}
//说明:全角其他子区可以不判断了,为扩展保留
//全角数字0-9:0xFF10-0xFF19 与半角相距FEE0
if ((value >= SBC_0) && (value <= SBC_9)) {
return CharType.SBC_CHAR;
}
//全角字母A-Z:0xFF21-0xFF3A 与半角相距FEE0
if ((value >= SBC_A) && (value <= SBC_Z)) {
return CharType.SBC_CHAR;
}
//全角字母a-z:0xFF41-0xFF5A 与半角相距FEE0
if ((value >= SBC_LA) && (value <= SBC_LZ)) {
return CharType.SBC_CHAR;
}
//其他字符
return CharType.OTHER_CHAR;
}
/**
* 判断字符否是数字类型字符。
*
* @param ch 字符
* @return boolean true-数字类型,fasle-非数字类型
*/
static boolean isDecimalType(char ch) {
if (Unicode.isDecimal(ch)) {
return true;
}
if (Unicode.isPoint(ch)) {
return true;
}
return Unicode.isDecimalSuffix(ch);
}
/**
* 判断字符否是数字。
*
* @param ch 字符
* @return boolean true-数字,fasle-非数字
*/
static boolean isDecimal(char ch) {
int unicode = ch;
//半角或全角数字
return ((unicode >= DBC_0) && (unicode <= DBC_9))
|| (unicode >= SBC_0) && (unicode <= SBC_9);
}
/**
* 判断字符是否是数字后缀。
*
* @param ch 字符
* @return boolean true-数字后缀,false-非数字后缀
*/
static boolean isDecimalSuffix(char ch) {
int unicode = ch;
//数字后缀
return (unicode == (int) ('%'))
|| (unicode == (int) ('$'))
|| (unicode == (int) ('¥'));
}
/**
* 判断字符是否是小数点。
*
* @param ch 字符
* @return boolean true-小数点,fasle-非小数点
*/
static boolean isPoint(char ch) {
int unicode = ch;
//半角或全角数字
return (unicode == DBC_POINT) || (unicode == SBC_POINT);
}
/**
* 初始化中文数字集合
*/
private static void initNumeralSet() {
numeralSet = new HashSet<>();
String numerals = FileUtil.readText(NUMERAL_PATH, DIC_ENCODING);
char[] symbolArr = numerals.toCharArray();
for (int i = 0; i < symbolArr.length; i++) {
numeralSet.add(symbolArr[i]);
}
}
/**
* 判断一个字符是否是中文数字
*
* @param ch 输入字符
* @return 是否是中文数字
*/
static boolean isNumeral(char ch) {
int unicode = ch;
if ((unicode < Unicode.MIN_CHINESE) || (unicode > Unicode.MAX_CHINESE)) {
return false;
}
return numeralSet.contains(ch);
}
/**
* 初始化单量词集合
*/
private static void initQantifierSet() {
quantifierSet = new HashSet<>();
String quantifiers = FileUtil.readText(QUANTIFIER_PATH, DIC_ENCODING);
char[] symbolArr = quantifiers.toCharArray();
for (int i = 0; i < symbolArr.length; i++) {
quantifierSet.add(symbolArr[i]);
}
}
/**
* 判断一个字符是否是中文量词
*
* @param ch 输入字符
* @return 是否是量词
*/
static boolean isQuantifier(char ch) {
int unicode = ch;
if ((unicode < Unicode.MIN_CHINESE) || (unicode > Unicode.MAX_CHINESE)) {
return false;
}
return quantifierSet.contains(ch);
}
}
分词器参数配置:
package com.robin.segment.robinseg;
/**
* <DT><B>描述:</B></DT>
* <DD>Robin分词器参数配置类</DD>
*
* @version 1.0
* @author Robin
* @version <I> V1.0 Date:2018-01-30</I>
* @author <I> E-mail:[email protected]</I>
*/
public class SegmentArgs {
/** 词结束符 */
static final char END_MARK = Unicode.NULL;
/** 分词方法 */
SegAlgorithm segMethod = SegAlgorithm.FORWARD;
/** 符号标志 */
boolean cleanSymbolFlag = true;
/** 新词标注标志 */
boolean markNewWordFlag = false;
/** 大小写转换 */
boolean downCasingFlag = true;
/** 分词合并模式-字典中未出现的孤立子合并 */
boolean mergePatternFlag = true;
/** 分词检索模式 */
boolean retrievalPatternFlag = false;
/** 分隔符 */
String separator = " ";
/** 词标符号 */
private static final String WORD_MARK = "⊙";
/** 新词标记 */
static final String NEW_WORD_MARK = "[" + WORD_MARK + "新词]";
/** 拼接词标记 */
static final String SPLICE_WORD_MARK = "[" + WORD_MARK + "拼接词]";
/** 混合词标记 */
static final String CN_EN_MIX_MARK = "[" + WORD_MARK + "混合词]";
/**
* Robin分词方法枚举类型
*/
public enum SegAlgorithm {
/** 带回退策略的正向最大匹配 */
FORWARD,
/** 简单反向最大匹配 */
REVERSE
}
/**
* 词的类别
*/
enum WordClass {
STANDARD,
/** 英文词,自动识别 */
ENGLISH,
/** 数量词,包括自动识别的中文数量词 */
QUANTIFIER,
/** 新词,识别的含有中文字符的新词 */
NEW_WORD,
/** 中英文混合词 */
CN_EN_MIX,
/** 拼接新词,标准词与孤立字符拼接出来的新词 */
SPLICE_WORD,
/** 孤立中文字符 */
ISOLATED_CN,
/** 其他,上述之外 */
OTHER
}
/**
* 构造方法
*/
public SegmentArgs() {
}
/**
* 重置默认配置参数
*/
public void resetDefaultArgs() {
this.segMethod = SegAlgorithm.FORWARD;
this.cleanSymbolFlag = true;
this.markNewWordFlag = false;
this.downCasingFlag = true;
this.mergePatternFlag = true;
this.retrievalPatternFlag = false;
}
/**
* 设置分词分隔符
*
* @param separator 分隔符
*/
void setSeparator(String separator) {
this.separator = separator;
}
/**
* 设置删除符号标志
*
* @param flag 是否删除
*/
public void setCleanSymbolFlag(boolean flag) {
this.cleanSymbolFlag = flag;
}
/**
* 设置是否检索型分词
*
* @param flag 是否检索型分词
*/
public void setRetrievalPatternFlag(boolean flag) {
this.retrievalPatternFlag = flag;
}
/**
* 设置拼接模式标志
*
* @param flag 是否拼接模式
*/
public void setMergePatternFlag(boolean flag) {
this.mergePatternFlag = flag;
}
/**
* 设置新词标注开关
*
* @param flag 是否标注
*/
public void setMarkNewWordFlag(boolean flag) {
this.markNewWordFlag = flag;
}
/**
* 设置分词算法 目前支持正向最大匹配、反向最大匹配 分词算法修改需要重新加载词典
*
* @param segAlgorithm 分词算法
*/
public void setSegAlgorithm(SegAlgorithm segAlgorithm) {
this.segMethod = segAlgorithm;
}
/**
* 设置是否大-小写字母转换
*
* @param flag 是否转换
*/
public void setDowncasingFlag(boolean flag) {
this.downCasingFlag = flag;
}
}
词典:
package com.robin.segment.robinseg;
import com.robin.config.ConfigUtil;
import com.robin.log.RobinLogger;
import com.robin.file.FileUtil;
import com.robin.segment.robinseg.Unicode.CharType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <DT><B>描述:</B></DT>
* <DD>词典类</DD>
*
* @version Version1.0
* @author Robin
* @version <I> V1.0 Date:2018-01-28</I>
* @author <I> E-mail:[email protected]</I>
*/
class Dictionary {
private static final Logger LOGGER = RobinLogger.getLogger();
// 词典路径
private static final String DIC_PATH;
// 词典文件编码
private static final String DIC_ENCODING;
// 词典MapTree
private static Map<Character, Object> reverseDicMap = null;
// 词典MapTree数组
private static Map[] forwardDicArr = null;
static {
DIC_PATH = ConfigUtil.getConfig("dic.base");
if (DIC_PATH == null) {
LOGGER.log(Level.SEVERE, "dictionary file path config err.");
}
String encoding = ConfigUtil.getConfig("dic.encoding");
if (null == encoding) {
DIC_ENCODING = "UTF-8";
} else {
DIC_ENCODING = encoding;
}
loadForwardDictionary();
loadReverseDictionary();
}
static Map<Character, Object> getReverseDicMap() {
return reverseDicMap;
}
static Map[] getForwardDicArr() {
return forwardDicArr;
}
/**
* 加载正向词典mapTree数组
*/
private static void loadForwardDictionary() {
if (null != forwardDicArr){
return;
}
LOGGER.log(Level.CONFIG, "Start load reverse dictionary.");
forwardDicArr = new Map[Unicode.MAX_CHINESE - Unicode.MIN_CHINESE];
//记录上一层map
Map<Character, Map> prevMap = null;
//记录下一层map
Map<Character, Map> nextMap = null;
// 读取词典文件,词之间使用空格符分割
String dicStr = FileUtil.readText(DIC_PATH, DIC_ENCODING);
String[] wordArr = dicStr.split("\n");
//循环处理每一个词,注意去除第一行,避免文件BOM引起字符解析错误,
//java读取文件默认按照无BOM方式处理
for (int i = 1; i < wordArr.length; i++) {
String word = wordArr[i];
char[] chars = word.toCharArray();
if (chars.length <= 0) {
continue;
}
char currChar = ' ';
//循环处理每一个字符
for (int j = 0; j < chars.length; j++) {
currChar = chars[j];
if (j == 0) {
if (Unicode.getCharType(currChar).equals(CharType.COMMON_CHINESE)) {
int index = currChar - Unicode.MIN_CHINESE;
nextMap = forwardDicArr[index];
if (null == nextMap) {
nextMap = new HashMap<>();
forwardDicArr[index] = nextMap;
}
}
continue;
}
if (null == nextMap) {
nextMap = new HashMap<>();
nextMap.put(currChar, null);
if (null != prevMap) {
prevMap.put(chars[j - 1], nextMap);
}
} else if (!nextMap.containsKey(currChar)) {
nextMap.put(currChar, null);
}
prevMap = nextMap;
nextMap = nextMap.get(currChar);
}
//在一个词末尾加上标记,表示在此层可以成词
if (null == nextMap) {
nextMap = new HashMap<>();
if (null != prevMap) {
prevMap.put(currChar, nextMap);
}
}
nextMap.put(SegmentArgs.END_MARK, null);
}
LOGGER.log(Level.INFO, "Load forward dictionary successfully.");
}
/**
* 加载反向词典mapTree
*/
private static void loadReverseDictionary() {
if (null != reverseDicMap) {
return;
}
LOGGER.log(Level.CONFIG, "Start load reverse dictionary.");
reverseDicMap = new HashMap<>();
//记录上一层map
Map<Character, Object> prevMap = null;
//记录下一层map
Map<Character, Object> nextMap = null;
// 读取词典文件,词之间使用空格符分割
String dicStr = FileUtil.readText(DIC_PATH);
// 如果是反向匹配,生成反向词典
StringBuilder sb = new StringBuilder();
dicStr = sb.append(dicStr).reverse().toString();
String[] wordArr = dicStr.split("\n");
//循环处理每一个词
for (String word : wordArr) {
char[] chars = word.toCharArray();
if (chars.length <= 0) {
continue;
}
char currChar;
//循环处理每一个字符
for (int j = 0; j < chars.length; j++) {
currChar = chars[j];
if (j == 0) {
nextMap = reverseDicMap;
}
if (null == nextMap) {
nextMap = new HashMap<>();
nextMap.put(currChar, null);
if (null != prevMap) {
prevMap.put(chars[j - 1], nextMap);
}
} else if (!nextMap.containsKey(currChar)) {
nextMap.put(currChar, null);
}
prevMap = nextMap;
nextMap = (Map<Character, Object>) nextMap.get(currChar);
}
//在一个词末尾加上标记,表示在此层可以成词
currChar = chars[chars.length - 1];
nextMap = null;
if (null != prevMap) {
nextMap = (Map<Character, Object>) prevMap.get(currChar);
}
if (null == nextMap) {
nextMap = new HashMap<>();
if (null != prevMap) {
prevMap.put(currChar, nextMap);
}
}
nextMap.put(SegmentArgs.END_MARK, null);
}
LOGGER.log(Level.INFO, "Load reverse dictionary successfully.");
}
/**
* 获取分词全字符字典
*
* @return 分词字典
*/
public String getDicAllTrems() {
buildTrems(reverseDicMap);
return dicBuilder.toString();
}
private List<Character> wordList = new ArrayList<>();
private StringBuilder dicBuilder = new StringBuilder();
/**
* 递归构建全字符词典
*
* @param map Map + Tree 格式词典
*/
private void buildTrems(Map<Character, Object> map) {
if (null == map) {
for (int i = 0; i < wordList.size(); i++) {
Character character = wordList.get(i);
dicBuilder.append(character);
}
dicBuilder.append("\n");
return;
}
Iterator<Character> it = map.keySet().iterator();
while (it.hasNext()) {
Character character = it.next();
wordList.add(character);
Map<Character, Object> nextMap = (Map<Character, Object>) map.get(character);
buildTrems(nextMap);
wordList.remove(wordList.size() - 1);
}
}
}
分词器:
package com.robin.segment.robinseg;
import com.robin.segment.robinseg.SegmentArgs.SegAlgorithm;
import com.robin.segment.AbstractSegmenter;
import com.robin.segment.robinseg.SegmentArgs.WordClass;
import com.robin.segment.robinseg.Unicode.CharType;
import java.util.Map;
/**
* <DT><B>描述:</B></DT>
* <DD>Robin分词类</DD>
* <DD>实现:正向最大匹配算法,反向最大匹配(simple)</DD>
* <DD>默认:带逆向回退策略的正向最大匹配</DD>
*
* @version Version1.0
* @author Robin
* @version <I> V1.0 Date:2018-01-28</I>
* @author <I> E-mail:[email protected]</I>
*/
public class RobinSeg extends AbstractSegmenter {
//分词实例
private static AbstractSegmenter instance = null;
//分词配置项
private static final SegmentArgs SEG_CONG = new SegmentArgs();
//遍历字符序列的标记前一记录
private Record last = new Record();
//遍历字符序列的标记当前记录
private Record curr = new Record();
//存放已经分好词的序列
private StringBuilder splited;
// 当前最大索引(已经扫描到的最前索引,主要用于检索分词)
private int currMaxIndex = -1;
private final Map[] forwardDicArr;
private final Map<Character, Object> reverseDicMap;
/**
* 构造方法
*/
private RobinSeg() {
forwardDicArr = Dictionary.getForwardDicArr();
reverseDicMap = Dictionary.getReverseDicMap();
}
/**
*
* @return
*/
public SegmentArgs getSegmentConfInstance() {
return SEG_CONG;
}
/**
* 调用具体分词方法
*
* @param text 输入待分词文本
* @param separator 分隔符
* @return 已分词文本
*/
@Override
public String segment(String text, String separator) {
synchronized (this) {
if (SEG_CONG.segMethod.equals(SegAlgorithm.FORWARD)) {
return forwardMaxMatch(text, separator);
} else if (SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE)) {
return reverseMaxMatch(text, separator);
}
}
assert false : "算法类型参数错误,检查参数设置。";
return "";
}
/**
* 判断当前字符序列是否在字典中构成一个完整的词
*
* @return boolean 是否在字典中构成一个完整的词
*/
private boolean isStandardWord(char[] wordArr) {
if (wordArr.length <= 0) {
return false;
}
Map<Character, Map> nextMap = null;
if (Unicode.getCharType(wordArr[0]).equals(CharType.COMMON_CHINESE)) {
nextMap = forwardDicArr[wordArr[0] - Unicode.MIN_CHINESE];
}
int index = 1;
while ((null != nextMap) && (index < wordArr.length)) {
if (!nextMap.containsKey(wordArr[index])) {
return false;
}
nextMap = nextMap.get(wordArr[index]);
index++;
}
return (null != nextMap) && nextMap.containsKey(SegmentArgs.END_MARK);
}
/**
* 判断当前字符在字符序列里是否是孤立字符(不能够成为中文词首)
*
* @return boolean 是否是孤立字符
*/
private boolean isIsoLatedChar(char[] charArray, int cursor) {
int textLength = charArray.length;
int next = cursor + 1;
if (next >= textLength) {
return true;//字符序列结尾是孤立字符
}
Character curChar = charArray[next];
Map<Character, Map> nextMap;
if (Unicode.getCharType(curChar).equals(CharType.COMMON_CHINESE)) {
nextMap = (forwardDicArr[curChar - Unicode.MIN_CHINESE]);
if (null == nextMap) {
return false;//后一字符是字典中不存在的中文,认为本字符不孤立
}
} else {
return true;//后一字符是非中文其他任何符号都认为是孤立字符
}
while (next < textLength) {
next++;
if (nextMap.containsKey(SegmentArgs.END_MARK)) {
return true;
}
curChar = null;
if (next < textLength) {
curChar = charArray[next];
}
nextMap = nextMap.get(curChar);
if (null == nextMap) {
return false;
}
}
return false;
}
/**
* 词的记录私有类
*/
private class Record {
//词的类别
WordClass wordClass = WordClass.OTHER;
//以一个字符开头一条字符序列最多可能形成的词的数目,先估计最大不超过10个
private static final int MAX_NUM = 10;
int start = 0;
int maxIdx = -1;
int[] end = new int[MAX_NUM];
boolean continued = false;
Record next = null;
/**
* 重置记录项
*/
public void init(int start) {
this.start = start;
}
/**
* 记录构成词的游标位置 仅标准词(词典存在的词)才会调用此方法
*/
public void addIndex(int cursor) {
assert (maxIdx < MAX_NUM - 1) : "字符序列成词数目越界,扩大词数目.";
end[++maxIdx] = cursor;
}
/**
* 重置记录项
*/
public void reset() {
continued = false;
wordClass = WordClass.OTHER;
for (int i = 0; i <= maxIdx; i++) {
end[i] = 0;
}
maxIdx = -1;
}
}
/**
* 记录前移,交换角色
*/
private void moveRocord() {
Record swap = last;
last = curr;
curr = swap;
curr.reset();
}
/**
* 文本分词 正向最大匹配改进算法
*
* @param text 输出待分词文本
* @param separator 分隔符
* @return 使用分隔符隔开的词文本
*/
private String forwardMaxMatch(String text, String separator) {
assert SEG_CONG.segMethod.equals(SegAlgorithm.FORWARD) : "算法类型设置错误。";
currMaxIndex = -1;
SEG_CONG.setSeparator(separator);
splited = new StringBuilder();
//添加一个空格,为了处理字符结尾是标准词序列
char[] charArray = text.concat(" ").toCharArray();
int textLength = charArray.length;
int cursor = 0;
//循环遍历字符序列
while (cursor < charArray.length) {
int start = cursor;
if (!curr.continued) {
curr.init(start);
}
char currChar = charArray[cursor];
switch (Unicode.getCharType(currChar)) {
//普通中文汉字
case COMMON_CHINESE:
//搜索词典
boolean success = searchInDictionary(charArray, cursor);
if (success) {
if (SEG_CONG.retrievalPatternFlag) {
cursor++;
} else {
cursor = last.end[last.maxIdx];
}
continue;
}
if (SEG_CONG.retrievalPatternFlag) {
if (curr.start < currMaxIndex) {
cursor++;
continue;
}
}
//词典未搜索到,回退
cursor = start;
currChar = charArray[cursor];
//中文数字
if (Unicode.isNumeral(currChar)) {
cursor = handleCNNumeral(charArray, cursor);
continue;
}
//如果字符前面是未处理完的非标准词
if (curr.continued) {
if (curr.wordClass == WordClass.ENGLISH) {
curr.wordClass = WordClass.CN_EN_MIX;
} else if (curr.wordClass != WordClass.CN_EN_MIX) {
curr.wordClass = WordClass.NEW_WORD;
}
break;
}
//非单独孤立中文首字符
if (!isIsoLatedChar(charArray, cursor)) {
curr.wordClass = WordClass.NEW_WORD;
break;
}
//当前处理的字符是单个孤立的中文字符
if (SEG_CONG.mergePatternFlag
&& WordClass.STANDARD == last.wordClass) {
cursor = handleIsoLatedCNChar(charArray, cursor);
continue;
}
curr.wordClass = WordClass.ISOLATED_CN;
break;
//半角数字
case DBC_DIGIT:
curr.wordClass = WordClass.QUANTIFIER;
if (handleQuantifier(charArray, cursor)) {
cursor = last.end[last.maxIdx];
continue;
}
break;
//半角大写字母
case DBC_UPPER_CASE:
if (SEG_CONG.downCasingFlag) {
charArray[cursor]
= (char) (charArray[cursor] + Unicode.OFFSET_UP_LOW);
}
/**
* 穿越CASE到小写处理
*/
//半角小写字母
case DBC_LOWER_CASE:
if ((curr.wordClass == WordClass.NEW_WORD)
|| (curr.wordClass == WordClass.ISOLATED_CN)) {
curr.wordClass = WordClass.CN_EN_MIX;
} else if (curr.wordClass != WordClass.CN_EN_MIX) {
curr.wordClass = WordClass.ENGLISH;
}
break;
//关注的全角区
case SBC_CHAR:
//全角统一转换并回退处理
charArray[cursor]
= (char) (charArray[cursor] - Unicode.OFFSET_DBC_SBC);
continue;
//数字后缀
case DECIMAL_SUFFIX:
if (cursor > 0) {
//正向的考虑前一个字符
char prev = charArray[cursor - 1];
if (Unicode.isDecimal(prev)) {
splited.append(charArray[cursor]);
splited.append(SEG_CONG.separator);
curr.continued = false;
cursor++;
curr.end[0] = cursor;
curr.wordClass = WordClass.QUANTIFIER;
moveRocord();
continue;
}
}
/**
* 此处需要穿越到case OTHER_CHAR,判断符号是否保留,不满足数字后缀的
* 话更不满足小数要求,case穿越没问题,但是不能调换上下两条case语句位置.
*/
//小数点或英文句点符号
case DECIMAL_POINT:
if ((cursor > 0) && (cursor < textLength - 1)) {
char prev = charArray[cursor - 1];
char next = charArray[cursor + 1];
if (Unicode.isDecimal(prev) && Unicode.isDecimal(next)) {
curr.wordClass = WordClass.QUANTIFIER;
break;
}
}
/**
* 此处就是要穿越到下一条case,判断符号是否保留.
*/
// 其他所有字符
case OTHER_CHAR:
addSeparator(start);
if (!SEG_CONG.cleanSymbolFlag) {
splited.append(charArray[cursor]);
splited.append(SEG_CONG.separator);
}
cursor++;
curr.end[0] = cursor;
curr.wordClass = WordClass.OTHER;
moveRocord();
continue;
// 空白符
case BLANK_CHAR:
addSeparator(start);
cursor++;
curr.addIndex(cursor);
curr.wordClass = WordClass.OTHER;
moveRocord();
continue;
default:
break;
}
//将字典没有的字符添加到分词StringBuilder,并且不分割,与下一个孤立字符合并
splited.append(charArray[cursor]);
curr.continued = true;
cursor++;
}
return splited.toString();
}
/**
* 根据条件增加分隔符
*
* @param start 下一记录起始位置
*/
private void addSeparator(int start) {
//如果还未分割
if (curr.continued) {
if (SEG_CONG.markNewWordFlag) {
switch (curr.wordClass) {
case NEW_WORD:
splited.append(SegmentArgs.NEW_WORD_MARK);
break;
case CN_EN_MIX:
splited.append(SegmentArgs.CN_EN_MIX_MARK);
break;
case SPLICE_WORD:
splited.append(SegmentArgs.SPLICE_WORD_MARK);
break;
default:
break;
}
}
splited.append(SEG_CONG.separator);
curr.addIndex(start);
moveRocord();
curr.init(start);
}
}
/**
* 处理(阿拉伯数字)数量词
*
* @param charArray 字符序列
* @param cursor 输入游标位置
* @return boolean 是否完成
*/
private boolean handleQuantifier(char[] charArray, int cursor) {
int start = cursor;
char currChar = charArray[cursor];
//正向最大匹配的数字前一个是未分割
if (cursor > 0) {
//考虑前一个字符
Character prev = charArray[cursor - 1];
if (!Unicode.isDecimal(prev) && !Unicode.isPoint(prev)) {
addSeparator(start);
}
}
//正向最大匹配的数字+量词
if (cursor < charArray.length - 1) {
//正向的考虑后一个字符
Character next = charArray[++cursor];
if (Unicode.isQuantifier(next)) {
splited.append(currChar);
splited.append(next);
splited.append(SEG_CONG.separator);
curr.continued = false;
curr.wordClass = WordClass.QUANTIFIER;
cursor++;
curr.addIndex(cursor);
moveRocord();
return true;
}
}
return false;
}
/**
* 词典中查找匹配的词
*
* @param charArray 字符序列
* @param cursor 当前游标
* @return boolean 是否搜索到
*/
private boolean searchInDictionary(char[] charArray, int cursor) {
//本次词典搜索的起始字符位置
int start = cursor;
int textMaxIdx = charArray.length - 1;
char currChar = charArray[cursor];
Map<Character, Map> nextMap = forwardDicArr[currChar - Unicode.MIN_CHINESE];
while ((null != nextMap) && (cursor < textMaxIdx)) {
cursor++;
if (nextMap.containsKey(SegmentArgs.END_MARK)) {
addSeparator(start);
curr.addIndex(cursor);
curr.wordClass = WordClass.STANDARD;
}
currChar = charArray[cursor];
nextMap = nextMap.get(currChar);
if (null == nextMap) {
if (curr.maxIdx > -1) {
//词典存在最大匹配词
if (SEG_CONG.retrievalPatternFlag) {
for (int i = 0; i <= curr.maxIdx; i++) {
int end = curr.end[i];
for (int j = curr.start; j < end; j++) {
splited.append(charArray[j]);
}
splited.append(SEG_CONG.separator);
}
if (curr.end[curr.maxIdx] > currMaxIndex) {
currMaxIndex = curr.end[curr.maxIdx];
}
} else {
int end = curr.end[curr.maxIdx];
for (int i = curr.start; i < end; i++) {
splited.append(charArray[i]);
}
splited.append(SEG_CONG.separator);
}
moveRocord();
return true;
} else {
return false;
}
}
}
return false;
}
/**
* 处理中文数字
*
* @param charArray 字符序列
* @param cursor 输入游标位置
* @return cursor 输出游标位置
*/
private int handleCNNumeral(char[] charArray, int cursor) {
int start = cursor;
char currChar = charArray[cursor];
addSeparator(start);
splited.append(currChar);
cursor++;
curr.wordClass = WordClass.QUANTIFIER;
while (cursor < charArray.length) {
currChar = charArray[cursor];
if (Unicode.isNumeral(currChar)) {
splited.append(currChar);
cursor++;
} else if (Unicode.isQuantifier(currChar)) {
splited.append(currChar);
cursor++;
break;
} else {
break;
}
}
splited.append(SEG_CONG.separator);
curr.end[0] = cursor;
curr.maxIdx = 0;
moveRocord();
return cursor;
}
/**
* 处理中文孤立字符
*
* @param charArray 字符序列
* @param cursor 输入游标位置
* @return int 输出游标位置
*/
private int handleIsoLatedCNChar(char[] charArray, int cursor) {
char currChar = charArray[cursor];
if (last.maxIdx > 0) {
int bestEnd = last.end[last.maxIdx];
int betterEnd = last.end[last.maxIdx - 1];
int len = bestEnd - betterEnd + 1;
char[] wordArr = new char[len];
for (int i = 0; i < wordArr.length; i++) {
wordArr[i] = charArray[betterEnd + i];
}
if (isStandardWord(wordArr)) {
splited.delete(splited.length() - len, splited.length());
splited.append(SEG_CONG.separator);
last.maxIdx--;
curr.init(betterEnd);
curr.addIndex(bestEnd);
for (int i = betterEnd; i <= cursor; i++) {
splited.append(charArray[i]);
}
splited.append(SEG_CONG.separator);
curr.wordClass = WordClass.STANDARD;
moveRocord();
cursor++;
return cursor;
}
}
splited.delete(splited.length() - 1, splited.length());
splited.append(currChar);
Record temp = curr;
curr = last;//此处last仅作为作为持有对象的引用,并不是上一条记录了
last = temp;
curr.wordClass = WordClass.SPLICE_WORD;
curr.continued = true;
cursor++;
addSeparator(cursor);
return cursor;
}
/**
* 文本分词 反向
*
* @param text 输出待分词文本
* @param separator 分隔符
* @return 使用分隔符隔开的词文本
*/
private String reverseMaxMatch(String text, String separator) {
assert SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE) : "算法类型设置错误。";
boolean separated = true;
boolean hasAppend;
StringBuilder sb = new StringBuilder();
Map<Character, Object> nextLevelMap;
Map<Character, Object> rootMap = reverseDicMap;
int textMaxIndex = text.length() - 1;
text = sb.append(text).reverse().toString();
sb.delete(0, textMaxIndex + 1);
int cursor = 0;
int start;
int end = 0;
char[] charArray = text.toCharArray();
while (cursor < charArray.length) {
Character curChar = charArray[cursor];
start = cursor;
nextLevelMap = (Map<Character, Object>) rootMap.get(curChar);
if (null == nextLevelMap) {
hasAppend = false;
switch (Unicode.getCharType(curChar)) {
//普通中文汉字
case COMMON_CHINESE:
if (separated) {
if (!Unicode.isQuantifier(curChar)) {
sb.append('#');
}
sb.append(charArray[cursor]);
separated = false;
hasAppend = true;
} else {
if (Unicode.isQuantifier(curChar)) {
sb.append(separator);
separated = true;
}
}
break;
//半角数字
case DBC_DIGIT:
if (cursor < textMaxIndex) {
Character next = charArray[cursor + 1];
if (!Unicode.isDecimalType(next)) {
sb.append(charArray[cursor]);
sb.append(separator);
separated = true;
cursor++;
continue;
}
}
break;
//半角小写字母
case DBC_LOWER_CASE:
break;
//半角大写字母
case DBC_UPPER_CASE:
if (SEG_CONG.downCasingFlag) {
charArray[cursor] = (char) (charArray[cursor] + Unicode.OFFSET_UP_LOW);
}
break;
//关注的全角区,目前不会返回全角子区
case SBC_CHAR:
//全角字母
case SBC_CASE:
//全角数字
case SBC_DIGIT:
//全角大写字母
case SBC_UPPER_CASE:
//全角小写字母
case SBC_LOWER_CASE:
//全角统一转换并回退处理
charArray[cursor] = (char) (charArray[cursor] - Unicode.OFFSET_DBC_SBC);
continue;
//数字后缀
case DECIMAL_SUFFIX:
if (cursor < textMaxIndex) {
//反向的考虑后一个字符
Character next = charArray[cursor + 1];
if (Unicode.isDecimal(next)) {
if (!separated) {
sb.append(separator);
}
sb.append(charArray[cursor]);
separated = false;
cursor++;
continue;
}
}
/**
* 此处需要穿越到case OTHER_CHAR,判断符号是否保留,不满足数字后缀的
* 话更不满足小数要求,case穿越没问题,但是不能调换上下两条case语句位置.
*/
//小数点或英文句点符号
case DECIMAL_POINT:
if ((cursor > 0) && (cursor < textMaxIndex)) {
Character prev = charArray[cursor - 1];
Character next = charArray[cursor + 1];
if (Unicode.isDecimal(prev) && Unicode.isDecimal(next)) {
break;
}
}
/**
* 此处就是要穿越到下一条case,判断符号是否保留.
*/
// 其他所有字符
case OTHER_CHAR:
if (!separated) {
sb.append(separator);
}
if (!SEG_CONG.cleanSymbolFlag) {
sb.append(charArray[cursor]);
sb.append(separator);
}
separated = true;
hasAppend = true;
break;
// 空白符
case BLANK_CHAR:
if (!separated) {
sb.append(separator);
}
separated = true;
hasAppend = true;
break;
default:
break;
}
//将字典没有的字符添加到分词StringBuilder,并且不分割,与下一个孤立字符合并
if (!hasAppend) {
sb.append(charArray[cursor]);
separated = false;
}
cursor++;
continue;
}
cursor++;
while ((null != nextLevelMap) && (cursor <= textMaxIndex)) {
if (nextLevelMap.containsKey(SegmentArgs.END_MARK)) {
end = cursor;
}
curChar = charArray[cursor];
if (!nextLevelMap.containsKey(curChar)) {
if (end <= start) {
//孤立字符
if (Unicode.isQuantifier(charArray[start])) {
if (!separated) {
sb.append(separator);
}
} else if (separated) {
sb.append('#');
}
sb.append(charArray[start]);
separated = false;
cursor = start + 1;
} else {
//词典存的最大匹配词
if (!separated) {
sb.append(separator);
}
for (int i = start; i < end; i++) {
sb.append(charArray[i]);
}
sb.append(separator);
separated = true;
cursor = end;
}
break;
}
nextLevelMap = (Map<Character, Object>) nextLevelMap.get(curChar);
cursor++;
}
//解决最后一个可构成词,前一字符不能构成词,无分隔符问题
if (cursor == textMaxIndex + 1) {
if ((!separated) && (null != nextLevelMap)
&& (nextLevelMap.containsKey(SegmentArgs.END_MARK))) {
sb.append(separator);
}
for (int i = start; i
< charArray.length; i++) {
sb.append(charArray[i]);
}
}
}
String splitedText;
if (SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE)) {
splitedText = sb.reverse().toString();
} else {
splitedText = sb.toString();
}
return splitedText;
}
/**
* 获取 RobinSeg 分词类的实例
*
* @return 分词类的单实例
*/
public static AbstractSegmenter getInstance() {
if (null == instance) {
instance = new RobinSeg();
}
return instance;
}
}
运行结果
分词效果
-----------------------------分词效果-------------------------------
【-用例原始文本-】:马云,中国著名企业家,浙江绍兴人,阿里巴巴集团主要创始人之一。现任阿里巴巴集团主席和首席执行官,他是《福布斯》杂志创办50多年来成为封面人物的首位大陆企业家,曾获选为未来全球领袖。董事等职务。2013年3月11日,阿里巴巴集团董事局主席兼CEO马云昨日发出内部邮件称,集团首席数据官陆兆禧将接任CEO一职。
【拼接-分类-模式】:马云[⊙新词]|中国|著名|企业家|浙江|绍兴|人|阿里巴巴|集团|主要|创始人|之一|现任|阿里巴巴|集团|主席|和|首席执行官|他是|福布斯|杂志|创办|50|多年来|成为|封面人物|的|首位|大陆|企业家|曾|获选|为|未来|全球|领袖|董事|等|职务|2013年|3月|11日|阿里巴巴|集团|董事局|主席|兼|ceo马云[⊙混合词]|昨日|发出|内部|邮件|称|集团|首席|数据|官[⊙新词]|陆兆|禧|将|接任|ceo|一|职|
【拼接-检索-模式】:马云[⊙新词]|中|中国|著名|企业|企业家|浙江|绍兴|人|阿|阿里|阿里巴巴|巴巴|集团|主要|要|创始|创始人|人|之|之一|现任|任|阿|阿里|阿里巴巴|巴巴|集团|主席|和|首席|首席执行官|执行|执行官|他|他是|是|福布斯|杂志|创办|办|50|多|多年|多年来|年来|来|成|成为|为|封面|封面人物|面人|人|人物|的|首位|大|大陆|企业|企业家|曾|获选|选|选为|为|未来|来|全球|领|领袖|董事|等|职务|2013年|3月|11日|阿|阿里|阿里巴巴|巴巴|集团|董事|董事局|主席|兼|ceo马云[⊙混合词]|昨日|日|发|发出|出|内部|邮件|称|集团|首席|数|数据|据|官[⊙新词]|陆兆|禧|将|接|接任|任|ceo|一|职|
-------------------------------------------------------------------
【-用例原始文本-】:阿拉伯金额数字万位和元位是"0",或者数字中间连续有几个"0",万位、元位也是"0",但千位、角位不是"0"时,中文大写金额中可以只写一个零字,也可以不写"零"字。如¥1680.32,应写成人民币壹仟陆佰捌拾元零叁角贰分,或者写成人民币壹仟陆佰捌拾元叁角贰分,又如¥107000.53,应写成人民币壹拾万柒仟元零伍角叁分,或者写成人民币壹拾万零柒仟元伍角叁分。
【拼接-分类-模式】:阿拉伯|金额|数字|万位[⊙新词]|和|元位[⊙拼接词]|是|0|或者|数字|中间|连续|有几个|0|万位[⊙新词]|元位[⊙拼接词]|也是|0|但|千位[⊙新词]|角位[⊙拼接词]|不是|0|时|中文|大写|金额|中|可以|只写|一个|零字|也可以|不|写|零|字|如|1680.32|应|写成|人民币|壹仟|陆佰|捌拾元|零叁角|贰分|或者|写成|人民币|壹仟|陆佰|捌拾元|叁角|贰分|又如|107000.53|应|写成|人民币|壹拾万|柒仟|元|零伍角|叁分|或者|写成|人民币|壹拾万|零柒仟|元|伍角|叁分|
-------------------------------------------------------------------
【-用例原始文本-】:p非v IBM研究院 IBM非 VC维 VC银翘片,魅族MX系列。我很喜欢陈述高的演讲,我很不喜欢陈述高调的样子。人们向林俊德表示深切的问候。中华人民共和国
【拼接-分类-模式】:p非v[⊙混合词]|ibm|研究院|ibm非[⊙混合词]|vc维[⊙混合词]|vc银翘片[⊙混合词]|魅族mx[⊙混合词]|系列|我很|喜欢|陈述高[⊙拼接词]|的|演讲|我很|不喜欢|陈述|高调|的|样子|人们|向|林俊德[⊙新词]|表示|深切|的|问候|中华人民共和国|
-------------------------------------------------------------------
【-用例原始文本-】:姚明退役了。草泥马的,据路透社报道,印度尼西亚社会事务部一官员星期二(29日)表示,日惹市附近当地时间27日晨5时53分发生的里氏6.2级地震已经造成至少5427人死亡,20000余人受伤,近20万人无家可归。
【拆分-分类-模式】:姚明|退役|了|草泥马[⊙新词]|的|据|路透社|报道|印度尼西亚|社会|事务部|一|官员|星期二|29日|表示|日惹|市|附近|当地时间|27日|晨|5时|53分|发生|的|里氏|6.2级|地震|已经|造成|至少|5427|人|死亡|20000|余人|受伤|近|20万|人|无家可归|
【拼接-分类-模式】:姚明|退役|了|草泥马[⊙新词]|的|据|路透社|报道|印度尼西亚|社会|事务部|一|官员|星期二|29日|表示|日惹市[⊙拼接词]|附近|当地时间|27日|晨|5时|53分|发生|的|里氏|6.2级|地震|已经|造成|至少|5427|人|死亡|20000|余人|受伤|近|20万|人|无家可归|
-------------------------------------------------------------------
【-用例原始文本-】:歧义和同义词:研究生命起源,混合词: 做B超检查身体,本质是X射线,单位和全角: 2009年8月6日开始大学之旅,中文数字: 四分之三的人都交了六十五块钱班费,那是一九九八年前的事了,四川麻辣烫很好吃,五四运动留下的五四精神。笔记本五折包邮亏本大甩卖。人名识别: 我是陈鑫,也是jcesg的作者,三国时期的诸葛亮是个天才,我们一起给刘翔加油,罗志高兴奋极了因为老吴送了他一台笔记本。配对标点: 本次『畅想杯』黑客技术大赛的得主为电信09-2BF的张三,奖励C++程序设计语言一书和【畅想网络】的『PHP教程』一套。特殊字母: 【Ⅰ】(Ⅱ),英文数字: bug report [email protected] or visit http://code.google.com/p/jcseg, 15% of the day's time i will be there.特殊数字: ① ⑩ ⑽ ㈩.
【拆分-分类-模式】:歧义|和|同义词|研究生|命|起源|混合|词|做|b超[⊙混合词]|检查|身体|本质|是|x|射线|单位|和|全角|2009年|8月|6日|开始|大学|之旅|中文数字|四分之三|的|人|都|交了|六十五块|钱|班费|那是|一九九八年|前|的|事|了|四川|麻辣烫|很好|吃|五四运动|留下|的|五四|精神|笔记本|五折|包|邮|亏本|大|甩卖|人名|识别|我是|陈鑫[⊙新词]|也是|jcesg|的|作者|三国|时期|的|诸葛亮[⊙新词]|是个|天才|我们|一起|给|刘翔[⊙新词]|加油|罗志[⊙新词]|高兴|奋|极了|因为|老吴|送了|他|一台|笔记本|配对|标点|本次|畅想|杯|黑客|技术|大赛|的|得主|为|电信|09|2bf|的|张三|奖励|c|程序设计语言|一书|和|畅想|网络|的|php|教程|一套|特殊|字母|英文|数字|bug|report|chenxin|619315|gmail|com|or|visit|http|code|google|com|p|jcseg|15%|of|the|day|s|time|i|will|be|there|特殊|数字|
【拼接-分类-模式】:歧义|和|同义词|研究|生命|起源|混合词[⊙拼接词]|做|b超[⊙混合词]|检查|身体|本质|是|x|射线|单位|和|全角|2009年|8月|6日|开始|大学|之旅|中文数字|四分之三|的|人|都|交了|六十五块|钱|班费|那是|一九九八年|前|的事[⊙拼接词]|了|四川|麻辣烫|很好|吃|五四运动|留下|的|五四|精神|笔记本|五折|包邮[⊙拼接词]|亏本|大|甩卖|人名|识别|我是|陈鑫[⊙新词]|也是|jcesg|的|作者|三国|时期|的|诸葛亮[⊙新词]|是个|天才|我们|一起|给|刘翔[⊙新词]|加油|罗志[⊙新词]|高兴奋[⊙拼接词]|极了|因为|老吴|送了|他|一台|笔记本|配对|标点|本次|畅想杯[⊙拼接词]|黑客|技术|大赛|的|得主|为|电信|09|2bf|的|张三|奖励|c|程序设计语言|一书|和|畅想|网络|的|php|教程|一套|特殊|字母|英文|数字|bug|report|chenxin|619315|gmail|com|or|visit|http|code|google|com|p|jcseg|15%|of|the|day|s|time|i|will|be|there|特殊|数字|
-------------------------------------------------------------------
【-用例原始文本-】: ENGLISH CHInese WhAt are you doING!. The state investigation that followed the AJC’s analyses of test scores depicted a culture that rewarded cheaters, punished whistle-blowers and covered up improprieties.
【拼接-分类-模式】:english|chinese|what|are|you|doing|the|state|investigation|that|followed|the|ajc|s|analyses|of|test|scores|depicted|a|culture|that|rewarded|cheaters|punished|whistle|blowers|and|covered|up|improprieties|
-------------------------------------------------------------------
【-用例原始文本-】:全角半角、大小写转换 小数:5条狗,4.5个 6.3 6.7 0.6 7.8 1.5 ENglIsH ChineSE. TExt.SEGMenT 0.1%2.3$4.5¥6.7%8.9¥ 11.2 %¥$
【拆分-分类-模式】:全角|半角|大小写|转换|小数|5条|狗|4.5个|6.3|6.7|0.6|7.8|1.5|english|chinese|text|segment|0.1%|2.3$|4.5¥|6.7%|8.9¥|11.2|
-------------------------------------------------------------------
分词性能
⊙字符总数:[ 17.25 ]W. 用时:[ 18 ]ms。 速度:[ 958W ]字符/秒。文件名:data/凯文凯利-失控.txt
失控|全人类|的|最终|命运|和|结局|作者|美|凯文[⊙新词]|凯利[⊙新词]|著|东西|文库|译|出版社|新星|出版社|出版|时间|2010|12|1|版次|1|页数|700|字数|500000|印刷|时间|2010|12|1|开本|16开|纸张|胶版纸|印次|1|isbn|9787513300711|包装|平装|内容简介|这是|黑客帝国|主要|演员|的|必读物[⊙拼接词]|之一|这本|关于|
-------------------------------------------------------------------
⊙字符总数:[ 37.10 ]W. 用时:[ 34 ]ms。 速度:[ 1091W ]字符/秒。文件名:data/绝影-疯狂的程序员.txt
疯狂|的|程序员|绝影[⊙新词]|1|hello|world|天|已经|七分|黑了|屋里|却|还没|开灯|这个|全身|黑衣服|的|男子|突然|像|想起|什么|从|包里[⊙拼接词]|掏出烟[⊙拼接词]|抽出|一只|递给|旁边|的|人|兄弟|抽烟|么|那烟[⊙拼接词]|是|红塔山|旁边|那人|连忙|一边|摆手|一边|说|不|不|语气|有点|紧张|好像|那|黑衣服|递过来|的|不是烟[⊙拼接词]|是|海
-------------------------------------------------------------------
⊙字符总数:[ 4.27 ]W. 用时:[ 5 ]ms。 速度:[ 853W ]字符/秒。文件名:data/高晓松-如丧.txt
如丧[⊙拼接词]|我们|终于|老得|可以|谈谈|未来|序|时隔|十二年|为|出版|第二|本|文集|坐在|洛杉矶|垂垂[⊙新词]|夕阳|下|校对|文稿|看到|歌词|部分|时|忽然|瞥见|好多年|前|给|叶蓓|的|几句词[⊙拼接词]|夕阳|你|温暖|的|肩膀|我|柔软的|心房|大地|以及|忧伤|每一|天|在|我心|上|我为你舞[⊙拼接词]|在|远方|我是|你的|花|我不|管|春天|有多|长|正好在|夕
-------------------------------------------------------------------
⊙字符总数:[ 235.73 ]W. 用时:[ 238 ]ms。 速度:[ 990W ]字符/秒。文件名:data/谭松波-体育-2805P.txt
1|跳水队[⊙拼接词]|老大哥|熊倪[⊙新词]|在|一个|集体|跳水|的|动作|中|因|队友|失手|不|配合|头撼[⊙新词]|对方|的|臀部|而|伤|及|後颈[⊙新词]|香港电[⊙拼接词]|高台|集体|跳水|失手|奥运|巨星|熊倪[⊙新词]|伤颈[⊙拼接词]|万众瞩目|的|中国|跳水队[⊙拼接词]|昨天|首次|在|九龙|公园|表演|时|险|生|悲剧|跳水队[⊙拼接词]|老大哥|熊倪[⊙新词]|在|
-------------------------------------------------------------------
⊙字符总数:[ 14.01 ]W. 用时:[ 14 ]ms。 速度:[ 1001W ]字符/秒。文件名:data/塞林格-麦田里的守望者.txt
麦田|里的|守望|者|作者|塞林格[⊙新词]|状态|全本|内容简介|本书|的|主人公|霍尔顿[⊙拼接词]|是个|中学生|出身|于|富裕|的|中产阶级|家庭|他|虽|只有|16岁[⊙新词]|但|比|常人|高出|一头|整日|穿着|风雨衣|戴着|鸭舌帽|游游荡荡|不愿|读书|他对|学校|里的|一切|老师|同学|功课|球赛|等等|全都|腻烦|透了|3次[⊙新词]|被|学校|开除|又一个|学期结束|了|他|
-------------------------------------------------------------------
分析总结
由于笔者没有重点研究中文分词,代码也仅是简单实现,分词效果没有进行专业评估。后续将看到RS分词在文本分类中的应用,验证分类结果与其他分词器差别不大。
- 可用性:支持多种模式,有效支持中文文本分类的分类结果;
- 效率高:Core 2.50GHz下单线程 分词速度:千万字符/秒 量级,字节处理速度超过15MB/s.
参考:
[1]https://blog.csdn.net/u013063153/article/details/72904322
[2]http://blog.jobbole.com/111680/
[3]http://blog.51cto.com/tianxingzhe/1720067
[4]https://blog.csdn.net/flysky1991/article/details/73948971