Lucene.Net 2.3.1开发介绍 —— 二、分词(五)
原文:Lucene.Net 2.3.1开发介绍 —— 二、分词(五)
2.1.3 二元分词
上一节通过变换查询表达式满足了需求,但是在实际应用中,如果那样查询,会出现另外一个问题,因为,那样搜索,是只要出现这个字,不管它出现在什么位置。这就产生了上一小节开头讲的,对准确性产生了极大干扰。比如,如果有一段这样的话:“这是一个英雄!他有无法用词汇形容的孤单,但是他并没有用言语来表达。”这句话包含了“英 语 单 词”这四个字,但是却和“英语单词”一点关系都没有。首先想到的解决方法,就是把句子按词来划分,那么就能有效的降低干扰。最简单的解决方法,莫过于每两个字组成一个部分。
下面来构造核心算法。首先我们期望,只有中文(广义上指双字节文字,比如日文,韩文也在这个范围。)是按照二元拆分,而符号则是单符号拆分,对于英文则保持原样。因此,需要一个判断当前字符类型的函数。首先,构造一个枚举,如代码2.1.3.1。
代码 2.1.3.1
Code/// <summary>/// Char类型枚举,用于分词中类型状态比较/// </summary>public enum CharType{ None, //默认值,不可识别类型 English, //拉丁字符,用英文标识 Chinese, //CJK字符,以中文代表 Number, //阿拉伯数字 Control //控制符号,指控制符号已经各种标点符号等}
接下来需要有一个函数能够识别字符,把字符类型转换成我们需要的CharType。
代码 2.1.3.2
Code 1/**//// <summary> 2/// 获取Char类型 3/// </summary> 4/// <param name="c">字符</param> 5/// <returns>返回类型</returns> 6public static CharType GetCharType(char c) 7{ 8 switch (char.GetUnicodeCategory(c)) 9 {10 //大小写字符判断为英文字符11 case System.Globalization.UnicodeCategory.UppercaseLetter:12 case System.Globalization.UnicodeCategory.LowercaseLetter:13 return CharType.English;14 //其它字符判断问中文(CJK)15 case System.Globalization.UnicodeCategory.OtherLetter:16 return CharType.Chinese;17 //十进制数字18 case System.Globalization.UnicodeCategory.DecimalDigitNumber:19 return CharType.Number;20 //其他都认为是符号21 default:22 return CharType.Control;23 }24}
代码2.1.3.2粗略完成了我们想要的功能。现在就可以构造我们想要的算法了。
代码 2.1.3.3
Code 1using System; 2using System.Collections.Generic; 3using System.Text; 4using Lucene.Net.Analysis; 5using System.IO; 6 7namespace Test.Analysis 8{ 9 public class DoubleTokenizer : Tokenizer 10 { 11 /**//// <summary> 12 /// 保持传入的流 13 /// </summary> 14 private TextReader reader; 15 /**//// <summary> 16 /// 控制分词器只打开一次 17 /// </summary> 18 private bool done = true; 19 /**//// <summary> 20 /// 保存分词结果 21 /// </summary> 22 private List<Token> tokenlist; 23 24 public DoubleTokenizer(TextReader reader) 25 { 26 this.reader = reader; 27 } 28 /**//// <summary> 29 /// 上一个字的类型 30 /// </summary> 31 private CharType lastype = CharType.None; 32 /**//// <summary> 33 /// 当前读取到分词的记录数 34 /// </summary> 35 private int ptr = 0; 36 /**//// <summary> 37 /// 重写Next方法 38 /// </summary> 39 /// <param name="result"></param> 40 /// <returns></returns> 41 public override Token Next(Token result) 42 { 43 if (done) //第一次可以运行,运行后将被设置为false,在一个实例中只会运行一次 44 { 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 { 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 { 65 lastype = nowtype; 66 if (buffer.Length > 0) 67 { 68 token = new Token(buffer.ToString(), i - buffer.Length, i); 69 tokenlist.Add(token); 70 buffer.Remove(0, buffer.Length); 71 } 72 } 73 74 switch (nowtype) 75 { 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 { 89 token = new Token(nowchar.ToString() + nextchar.ToString(), i, i + 2); 90 tokenlist.Add(token); 91 i++; 92 continue; 93 } 94 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 else104 {105 //在分词结果范围内取词106 if (ptr < tokenlist.Count)107 return tokenlist[ptr++];108 //超出则返回结束符号109 return null;110 }111 }112 /**//// <summary>113 /// 获取Char类型114 /// </summary>115 /// <param name="c">字符</param>116 /// <returns>返回类型</returns>117 public static CharType GetCharType(char c)118 {119 switch (char.GetUnicodeCategory(c))120 {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 /**//// <summary>138 /// Char类型枚举,用于分词中类型状态比较139 /// </summary>140 public enum CharType141 {142 None, //默认值,不可识别类型143 English, //拉丁字符,用英文标识144 Chinese, //CJK字符,以中文代表145 Number, //阿拉伯数字146 Control //控制符号,指控制符号已经各种标点符号等147 }148149}
代码2.1.3.3就是构造完后的算法。意思就是把英文字母,数字按空格或者符号划分,而中文则二元拆分。现在来测试下效果。
代码 2.1.3.4
Code 1using System; 2using System.Collections.Generic; 3using System.Text; 4using NUnit.Framework; 5using System.IO; 6using Lucene.Net.Analysis; 7 8namespace Test.Analysis 9{10 [TestFixture]11 public class DoubleTokenizerTest12 {13 [Test]14 public void NextTest()15 {16 string testwords = "我是一个中国人,代码yurow001,真是个好名字啊!!!哈哈哈。。。";17 DoubleTokenizer tk = new DoubleTokenizer(new StringReader(testwords));18 Token token;19 while ((token = tk.Next()) != null)20 {21 Console.WriteLine(token.TermText() + "\t" + token.StartOffset() + "\t" + token.EndOffset());22 }23 tk.Close();24 }25 }26}27
代码 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
Code 1using System; 2using System.Collections.Generic; 3using System.Text; 4using Lucene.Net.Analysis; 5 6namespace Test.Analysis 7{ 8 public class DoubleAnalyzer : Analyzer 9 {10 public override TokenStream TokenStream(string fieldName, System.IO.TextReader reader)11 {12 return new DoubleTokenizer(reader);13 }14 }15}16
代码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
Code 1using System; 2using System.Collections.Generic; 3using System.Text; 4using Lucene.Net.Analysis; 5using System.IO; 6 7namespace Test.Analysis 8{ 9 public class DoubleTokenizer : Tokenizer 10 { 11 /**//// <summary> 12 /// 保持传入的流 13 /// </summary> 14 //private TextReader reader; 15 /**//// <summary> 16 /// 控制分词器只打开一次 17 /// </summary> 18 private bool done = true; 19 /**//// <summary> 20 /// 保存分词结果 21 /// </summary> 22 private List<Token> tokenlist; 23 24 public DoubleTokenizer(TextReader reader) 25 { 26 this.input = reader; 27 } 28 /**//// <summary> 29 /// 上一个字的类型 30 /// </summary> 31 private CharType lastype = CharType.None; 32 /**//// <summary> 33 /// 当前读取到分词的记录数 34 /// </summary> 35 private int ptr = 0; 36 /**//// <summary> 37 /// 重写Next方法 38 /// </summary> 39 /// <param name="result"></param> 40 /// <returns></returns> 41 public override Token Next(Token result) 42 { 43 if (done) //第一次可以运行,运行后将被设置为false,在一个实例中只会运行一次 44 { 45 done = false; 46 47 //------------------------------------------------------- 48 //使用传入参数作为缓冲区 49 char[] charbuffer = result.TermBuffer(); 50 int upto = 0; 51 result.Clear(); 52 while (true) 53 { 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 { 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 { 82 lastype = nowtype; 83 if (buffer.Length > 0) 84 { 85 token = new Token(buffer.ToString(), i - buffer.Length, i); 86 tokenlist.Add(token); 87 buffer.Remove(0, buffer.Length); 88 } 89 } 90 91 switch (nowtype) 92 { 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 {106 token = new Token(nowchar.ToString() + nextchar.ToString(), i, i + 2);107 tokenlist.Add(token);108 i++;109 continue;110 }111112 SingleChar: //处理单个字符113 token = new Token(nowchar.ToString(), i, i + 1);114 tokenlist.Add(token);115 continue;116 }117 //返回第一个分词结果,并且把指针移向下一位118 return tokenlist[ptr++];119 }120 else121 {122 //在分词结果范围内取词123124 if (ptr < tokenlist.Count)125 {126 return tokenlist[ptr++];127 }128 //超出则返回结束符号129 return null;130 }131 }132 /**//// <summary>133 /// 获取Char类型134 /// </summary>135 /// <param name="c">字符</param>136 /// <returns>返回类型</returns>137 public static CharType GetCharType(char c)138 {139 switch (char.GetUnicodeCategory(c))140 {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 /**//// <summary>158 /// Char类型枚举,用于分词中类型状态比较159 /// </summary>160 public enum CharType161 {162 None, //默认值,不可识别类型163 English, //拉丁字符,用英文标识164 Chinese, //CJK字符,以中文代表165 Number, //阿拉伯数字166 Control //控制符号,指控制符号已经各种标点符号等167 }168169}170
代码改造成了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开发介绍 —— 二、分词(五)的更多相关文章
- Lucene.Net 2.3.1开发介绍 —— 二、分词(六)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(六) Lucene.Net的上一个版本是2.1,而在2.3.1版本中才引入了Next(Token)方法重载,而ReusableStrin ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(三)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(四)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(四) 2.1.2 可以使用的内置分词 简单的分词方式并不能满足需求.前文说过Lucene.Net内置分词中StandardAnalyze ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(二)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(二) 1.2.分词的过程 1.2.1.分词器工作的过程 内置的分词器效果都不好,那怎么办?只能自己写了!在写之前当然是要先看看内置的分词 ...
- Lucene.Net 2.3.1开发介绍 —— 二、分词(一)
原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(一) Lucene.Net中,分词是核心库之一,当然,也可以将它独立出来.目前Lucene.Net的分词库很不完善,实际应用价值不高.唯 ...
- Lucene.Net 2.3.1开发介绍 —— 四、搜索(二)
原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(二) 4.3 表达式用户搜索,只会输入一个或几个词,也可能是一句话.输入的语句是如何变成搜索条件的上一篇已经略有提及. 4.3.1 观察 ...
- Lucene.Net 2.3.1开发介绍 —— 三、索引(二)
原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(二) 2.索引中用到的核心类 在Lucene.Net索引开发中,用到的类不多,这些类是索引过程的核心类.其中Analyzer是索引建立的 ...
- Lucene.Net 2.3.1开发介绍 —— 三、索引(四)
原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(四) 4.索引对搜索排序的影响 搜索的时候,同一个搜索关键字和同一份索引,决定了一个结果,不但决定了结果的集合,也确定了结果的顺序.那个 ...
- Lucene.Net 2.3.1开发介绍 —— 四、搜索(三)
原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(三) Lucene有表达式就有运算符,而运算符使用起来确实很方便,但另外一个问题来了. 代码 4.3.4.1 Analyzer anal ...
随机推荐
- HDU1316(求区间斐波那契数的个数)
题目:http://acm.hdu.edu.cn/showproblem.php?pid=1316 题意:给两个数a和b,其中它们可能很大,最大到10^100,然后求去区间[a,b]内有多少个fib数 ...
- Zabbix Step 1 : Install CentOS6.5 and Configration
[root@myzabbix Desktop]#rpm -ivh http://repo.zabbix.com/zabbix/2.2/rhel/6/x86_64/zabbix-release-2.2- ...
- Axis2(7):将Spring的装配JavaBean发布成WebService
在现今的Web应用中经常使用Spring框架来装载JavaBean.如果要想将某些在Spring中装配的JavaBean发布成WebService,使用Axis2的Spring感知功能是非常容易做到的 ...
- 关于std::string
主要注意的一个问题是:std::string 实际是类似一个 vector<char>的结构. 它里面是可以存放 ascii为0 的字符不算结尾 (否则 unicode方式的编码存放就有问 ...
- 查看htmlView
1.视图 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:too ...
- Arduino 入门程序示例之一排 LED(2015-06-11)
概述 最简单的一个 LED 的实验之后,自然是增加几个 LED,咱排成一排来玩吧.最后,再把一排的 LED 排成一个 8 字来玩——七段数码管. 示例程序 流水灯 第一个出场的肯定是经典的流水灯,也叫 ...
- JFinal教程1——小白的第一个JFinal程序
为了使小白能够完全的按步骤创建第一个JFinal应用并运行,笔者将以Java界最流行的Eclipse平台为例,搭建出所有基础教程中喜欢的Hello world应用. 1. JFinal简介 2. 小白 ...
- SGU 415. Necessary Coins ( 背包dp )
题意大概是:给出N个硬币, 面值为a_i, 问要凑成X元哪些硬币是不可或缺的.1 ≤ N ≤ 200, 1 ≤ x ≤ 10^4 直接枚举, 然后就是01背包了. 为了不让复杂度多乘个N, 我们就从左 ...
- Android下调用收发短信邮件等
Android下调用收发短信邮件等 1,调web浏览器Uri myBlogUri = Uri.parse("http://xxxxx.com");returnIt = new In ...
- servlet的filter的使用
一.概述 过滤器是servlet的一个重要特性,它提供一种机制,允许在过滤器中,即可以修改浏览器的请求信息,也可以对服务器处理后的响应信息进行修改. 一个过滤器是一个实现了Filter接口的java类 ...