标签:
原文:Lucene.Net 2.3.1开发介绍 —— 二、分词(五)2.1.3 二元分词
上一节通过变换查询表达式满足了需求,但是在实际应用中,如果那样查询,会出现另外一个问题,因为,那样搜索,是只要出现这个字,不管它出现在什么位置。这就产生了上一小节开头讲的,对准确性产生了极大干扰。比如,如果有一段这样的话:“这是一个英雄!他有无法用词汇形容的孤单,但是他并没有用言语来表达。”这句话包含了“英 语 单 词”这四个字,但是却和“英语单词”一点关系都没有。首先想到的解决方法,就是把句子按词来划分,那么就能有效的降低干扰。最简单的解决方法,莫过于每两个字组成一个部分。
下面来构造核心算法。首先我们期望,只有中文(广义上指双字节文字,比如日文,韩文也在这个范围。)是按照二元拆分,而符号则是单符号拆分,对于英文则保持原样。因此,需要一个判断当前字符类型的函数。首先,构造一个枚举,如代码2.1.3.1。
代码 2.1.3.1
![技术分享](/Images/OutliningIndicators/ContractedBlock.gif)
Code
/// <summary>
/// Char类型枚举,用于分词中类型状态比较
/// </summary>
public enum CharType
{
None, //默认值,不可识别类型
English, //拉丁字符,用英文标识
Chinese, //CJK字符,以中文代表
Number, //阿拉伯数字
Control //控制符号,指控制符号已经各种标点符号等
}
接下来需要有一个函数能够识别字符,把字符类型转换成我们需要的CharType。
代码2.1.3.2粗略完成了我们想要的功能。现在就可以构造我们想要的算法了。
代码 2.1.3.3
![技术分享](/Images/OutliningIndicators/ContractedBlock.gif)
Code
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using Lucene.Net.Analysis;
5
using System.IO;
6![技术分享](/Images/OutliningIndicators/None.gif)
7
namespace Test.Analysis
8![技术分享](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![技术分享](/Images/OutliningIndicators/ContractedBlock.gif)
{
9
public class DoubleTokenizer : Tokenizer
10![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
11![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
12
/// 保持传入的流
13
/// </summary>
14
private TextReader reader;
15![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
16
/// 控制分词器只打开一次
17
/// </summary>
18
private bool done = true;
19![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
20
/// 保存分词结果
21
/// </summary>
22
private List<Token> tokenlist;
23![技术分享](/Images/OutliningIndicators/InBlock.gif)
24
public DoubleTokenizer(TextReader reader)
25![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
this.reader = reader;
27
}
28![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
29
/// 上一个字的类型
30
/// </summary>
31
private CharType lastype = CharType.None;
32![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
33
/// 当前读取到分词的记录数
34
/// </summary>
35
private int ptr = 0;
36![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
37
/// 重写Next方法
38
/// </summary>
39
/// <param name="result"></param>
40
/// <returns></returns>
41
public override Token Next(Token result)
42![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
43
if (done) //第一次可以运行,运行后将被设置为false,在一个实例中只会运行一次
44![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
45
done = false;
46
string text = reader.ReadToEnd();
47
//输入为空,则返回结束符号
48
if (string.IsNullOrEmpty(text))
49
return null;
50
//初始化分词结果
51
tokenlist = new List<Token>();
52
//缓冲器,主要用于暂时保存英文数字字符。
53
StringBuilder buffer = new StringBuilder();
54
Token token;
55
for (int i = 0; i < text.Length; i++)
56![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
57
char nowchar = text[i];
58
char nextchar = new char();
59
CharType nowtype = GetCharType(nowchar);
60
if (i < text.Length - 1) //取下一个字符
61
nextchar = text[i + 1];
62
//状态转换
63
if (nowtype != lastype)
64![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
65
lastype = nowtype;
66
if (buffer.Length > 0)
67![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
68
token = new Token(buffer.ToString(), i - buffer.Length, i);
69
tokenlist.Add(token);
70
buffer.Remove(0, buffer.Length);
71
}
72
}
73![技术分享](/Images/OutliningIndicators/InBlock.gif)
74
switch (nowtype)
75![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
76
case CharType.None:
77
case CharType.Control:
78
goto SingleChar;
79
case CharType.Chinese:
80
break;
81
case CharType.English:
82
case CharType.Number:
83
buffer.Append(nowchar);
84
continue;
85
}
86
//处理连续两个中文字符
87
if (GetCharType(nextchar) == CharType.Chinese)
88![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
89
token = new Token(nowchar.ToString() + nextchar.ToString(), i, i + 2);
90
tokenlist.Add(token);
91
i++;
92
continue;
93
}
94![技术分享](/Images/OutliningIndicators/InBlock.gif)
95
SingleChar: //处理单个字符
96
token = new Token(nowchar.ToString(), i, i + 1);
97
tokenlist.Add(token);
98
continue;
99
}
100
//返回第一个分词结果,并且把指针移向下一位
101
return tokenlist[ptr++];
102
}
103
else
104![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
105
//在分词结果范围内取词
106
if (ptr < tokenlist.Count)
107
return tokenlist[ptr++];
108
//超出则返回结束符号
109
return null;
110
}
111
}
112![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
113
/// 获取Char类型
114
/// </summary>
115
/// <param name="c">字符</param>
116
/// <returns>返回类型</returns>
117
public static CharType GetCharType(char c)
118![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
119
switch (char.GetUnicodeCategory(c))
120![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
121
//大小写字符判断为英文字符
122
case System.Globalization.UnicodeCategory.UppercaseLetter:
123
case System.Globalization.UnicodeCategory.LowercaseLetter:
124
return CharType.English;
125
//其它字符判断问中文(CJK)
126
case System.Globalization.UnicodeCategory.OtherLetter:
127
return CharType.Chinese;
128
//十进制数字
129
case System.Globalization.UnicodeCategory.DecimalDigitNumber:
130
return CharType.Number;
131
//其他都认为是符号
132
default:
133
return CharType.Control;
134
}
135
}
136
}
137![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
138
/// Char类型枚举,用于分词中类型状态比较
139
/// </summary>
140
public enum CharType
141![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
142
None, //默认值,不可识别类型
143
English, //拉丁字符,用英文标识
144
Chinese, //CJK字符,以中文代表
145
Number, //阿拉伯数字
146
Control //控制符号,指控制符号已经各种标点符号等
147
}
148![技术分享](/Images/OutliningIndicators/InBlock.gif)
149
}
代码2.1.3.3就是构造完后的算法。意思就是把英文字母,数字按空格或者符号划分,而中文则二元拆分。现在来测试下效果。
代码 2.1.3.4 就是测试代码,测试的输入包含了各种字符。来看一下效果。
测试结果:
我是 0 2
一个 2 4
中国 4 6
人 6 7
, 7 8
代码 8 10
yurow 10 15
001 15 18
, 18 19
真是 19 21
个好 21 23
名字 23 25
啊 25 26
! 26 27
! 27 28
! 28 29
哈哈 29 31
哈 31 32
。 32 33
。 33 34
。 34 35
应该说结果符合我们的预期。下来写个Analyzer包装,并把这个包装应用到上一节2.1.2 的方案里去。
代码2.1.3.5就是包装的结果。测试结果:
搜索词:英语
结果:
content:英语
-----------------------------------
搜索词:语法
结果:
content:语法
-----------------------------------
搜索词:单词
结果:
content:单词
-----------------------------------
搜索词:口语
结果:
content:口语
-----------------------------------
搜索词:+content:"英" +content:"语" +content:"单" +content:"词"
结果:
+content:英 +content:语 +content:单 +content:词
-----------------------------------
What‘s happened? 为什么没有结果?分词器写错了?不要灰心!让我们来分析一下。在DoubleTokenizer类构造函数下一个断点,调试。因为,如果能正确运行,这个构造函数肯定要进入的。调试后看到了什么?传入的TextReader的类型是Lucene.Net.Index.DocumentsWriter.ReusableStringReader。查看Lucene.Net.Index.DocumentsWriter.ReusableStringReader类的定义,它继承自StringReader类,但是它重写掉了一些方法,而且,我们并没有发现我们使用的ReadToEnd方法。问题可能出在这里。看到ReusableStringReader类重写的Read(char[],int,int)方法,试试这个。
代码 2.1.3.6
![技术分享](/Images/OutliningIndicators/ContractedBlock.gif)
Code
1
using System;
2
using System.Collections.Generic;
3
using System.Text;
4
using Lucene.Net.Analysis;
5
using System.IO;
6![技术分享](/Images/OutliningIndicators/None.gif)
7
namespace Test.Analysis
8![技术分享](/Images/OutliningIndicators/ExpandedBlockStart.gif)
![技术分享](/Images/OutliningIndicators/ContractedBlock.gif)
{
9
public class DoubleTokenizer : Tokenizer
10![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
11![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
12
/// 保持传入的流
13
/// </summary>
14
//private TextReader reader;
15![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
16
/// 控制分词器只打开一次
17
/// </summary>
18
private bool done = true;
19![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
20
/// 保存分词结果
21
/// </summary>
22
private List<Token> tokenlist;
23![技术分享](/Images/OutliningIndicators/InBlock.gif)
24
public DoubleTokenizer(TextReader reader)
25![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
26
this.input = reader;
27
}
28![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
29
/// 上一个字的类型
30
/// </summary>
31
private CharType lastype = CharType.None;
32![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
33
/// 当前读取到分词的记录数
34
/// </summary>
35
private int ptr = 0;
36![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
37
/// 重写Next方法
38
/// </summary>
39
/// <param name="result"></param>
40
/// <returns></returns>
41
public override Token Next(Token result)
42![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
43
if (done) //第一次可以运行,运行后将被设置为false,在一个实例中只会运行一次
44![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
45
done = false;
46![技术分享](/Images/OutliningIndicators/InBlock.gif)
47
//-------------------------------------------------------
48
//使用传入参数作为缓冲区
49
char[] charbuffer = result.TermBuffer();
50
int upto = 0;
51
result.Clear();
52
while (true)
53![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
54
int length = input.Read(charbuffer, upto, charbuffer.Length - upto);
55
if (length <= 0)
56
break;
57
upto += length;
58
if (upto == charbuffer.Length)
59
charbuffer = result.ResizeTermBuffer(1 + charbuffer.Length);
60
}
61
result.SetTermLength(upto);
62
//------------------------------------------------------
63
string text = result.TermText();
64
//输入为空,则返回结束符号
65
if (string.IsNullOrEmpty(text))
66
return null;
67
//初始化分词结果
68
tokenlist = new List<Token>();
69
//缓冲器,主要用于暂时保存英文数字字符。
70
StringBuilder buffer = new StringBuilder();
71
Token token;
72
for (int i = 0; i < text.Length; i++)
73![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
74
char nowchar = text[i];
75
char nextchar = new char();
76
CharType nowtype = GetCharType(nowchar);
77
if (i < text.Length - 1) //取下一个字符
78
nextchar = text[i + 1];
79
//状态转换
80
if (nowtype != lastype)
81![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
82
lastype = nowtype;
83
if (buffer.Length > 0)
84![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
85
token = new Token(buffer.ToString(), i - buffer.Length, i);
86
tokenlist.Add(token);
87
buffer.Remove(0, buffer.Length);
88
}
89
}
90![技术分享](/Images/OutliningIndicators/InBlock.gif)
91
switch (nowtype)
92![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
93
case CharType.None:
94
case CharType.Control:
95
goto SingleChar;
96
case CharType.Chinese:
97
break;
98
case CharType.English:
99
case CharType.Number:
100
buffer.Append(nowchar);
101
continue;
102
}
103
//处理连续两个中文字符
104
if (GetCharType(nextchar) == CharType.Chinese)
105![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
106
token = new Token(nowchar.ToString() + nextchar.ToString(), i, i + 2);
107
tokenlist.Add(token);
108
i++;
109
continue;
110
}
111![技术分享](/Images/OutliningIndicators/InBlock.gif)
112
SingleChar: //处理单个字符
113
token = new Token(nowchar.ToString(), i, i + 1);
114
tokenlist.Add(token);
115
continue;
116
}
117
//返回第一个分词结果,并且把指针移向下一位
118
return tokenlist[ptr++];
119
}
120
else
121![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
122
//在分词结果范围内取词
123![技术分享](/Images/OutliningIndicators/InBlock.gif)
124
if (ptr < tokenlist.Count)
125![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
126
return tokenlist[ptr++];
127
}
128
//超出则返回结束符号
129
return null;
130
}
131
}
132![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
133
/// 获取Char类型
134
/// </summary>
135
/// <param name="c">字符</param>
136
/// <returns>返回类型</returns>
137
public static CharType GetCharType(char c)
138![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
139
switch (char.GetUnicodeCategory(c))
140![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
141
//大小写字符判断为英文字符
142
case System.Globalization.UnicodeCategory.UppercaseLetter:
143
case System.Globalization.UnicodeCategory.LowercaseLetter:
144
return CharType.English;
145
//其它字符判断问中文(CJK)
146
case System.Globalization.UnicodeCategory.OtherLetter:
147
return CharType.Chinese;
148
//十进制数字
149
case System.Globalization.UnicodeCategory.DecimalDigitNumber:
150
return CharType.Number;
151
//其他都认为是符号
152
default:
153
return CharType.Control;
154
}
155
}
156
}
157![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
158
/// Char类型枚举,用于分词中类型状态比较
159
/// </summary>
160
public enum CharType
161![技术分享](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
162
None, //默认值,不可识别类型
163
English, //拉丁字符,用英文标识
164
Chinese, //CJK字符,以中文代表
165
Number, //阿拉伯数字
166
Control //控制符号,指控制符号已经各种标点符号等
167
}
168![技术分享](/Images/OutliningIndicators/InBlock.gif)
169
}
170![技术分享](/Images/OutliningIndicators/None.gif)
代码改造成了2.1.3.6。主要的改变在于用父类的input字段保持了读入流,然后用Token作为缓冲区,因为它实现了可变缓冲区,简化了我们的开发。测试结果。
搜索词:英语
结果:
content:英语
英语单词,语法,口语都很重要。
口语,语法,单词都是英语的重要组成部分。
-----------------------------------
搜索词:语法
结果:
content:语法
英语单词,语法,口语都很重要。
口语,语法,单词都是英语的重要组成部分。
-----------------------------------
搜索词:单词
结果:
content:单词
英语单词,语法,口语都很重要。
口语,语法,单词都是英语的重要组成部分。
我们要学好英语不但要学语法,单词还有口语。
-----------------------------------
搜索词:口语
结果:
content:口语
英语单词,语法,口语都很重要。
口语,语法,单词都是英语的重要组成部分。
我们要学好英语不但要学语法,单词还有口语。
-----------------------------------
搜索词:+content:"英" +content:"语" +content:"单" +content:"词"
结果:
+content:英 +content:语 +content:单 +content:词
-----------------------------------
终于OK了!!!呵呵。
(PS:长时间编写,可能内容太长了,造成我机器编写这个章节有点卡,所以,这里提前结束。)
Lucene.Net 2.3.1开发介绍 —— 二、分词(五)
标签:
原文地址:http://www.cnblogs.com/lonelyxmas/p/4602520.html