原文: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开发介绍 —— 二、分词(五)的更多相关文章

  1. Lucene.Net 2.3.1开发介绍 —— 二、分词(六)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(六) Lucene.Net的上一个版本是2.1,而在2.3.1版本中才引入了Next(Token)方法重载,而ReusableStrin ...

  2. Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关 ...

  3. Lucene.Net 2.3.1开发介绍 —— 二、分词(四)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(四) 2.1.2 可以使用的内置分词 简单的分词方式并不能满足需求.前文说过Lucene.Net内置分词中StandardAnalyze ...

  4. Lucene.Net 2.3.1开发介绍 —— 二、分词(二)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(二) 1.2.分词的过程 1.2.1.分词器工作的过程 内置的分词器效果都不好,那怎么办?只能自己写了!在写之前当然是要先看看内置的分词 ...

  5. Lucene.Net 2.3.1开发介绍 —— 二、分词(一)

    原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(一) Lucene.Net中,分词是核心库之一,当然,也可以将它独立出来.目前Lucene.Net的分词库很不完善,实际应用价值不高.唯 ...

  6. Lucene.Net 2.3.1开发介绍 —— 四、搜索(二)

    原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(二) 4.3 表达式用户搜索,只会输入一个或几个词,也可能是一句话.输入的语句是如何变成搜索条件的上一篇已经略有提及. 4.3.1 观察 ...

  7. Lucene.Net 2.3.1开发介绍 —— 三、索引(二)

    原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(二) 2.索引中用到的核心类 在Lucene.Net索引开发中,用到的类不多,这些类是索引过程的核心类.其中Analyzer是索引建立的 ...

  8. Lucene.Net 2.3.1开发介绍 —— 三、索引(四)

    原文:Lucene.Net 2.3.1开发介绍 -- 三.索引(四) 4.索引对搜索排序的影响 搜索的时候,同一个搜索关键字和同一份索引,决定了一个结果,不但决定了结果的集合,也确定了结果的顺序.那个 ...

  9. Lucene.Net 2.3.1开发介绍 —— 四、搜索(三)

    原文:Lucene.Net 2.3.1开发介绍 -- 四.搜索(三) Lucene有表达式就有运算符,而运算符使用起来确实很方便,但另外一个问题来了. 代码 4.3.4.1 Analyzer anal ...

随机推荐

  1. Android动态加载jar、apk的实现

    前段时间到阿里巴巴参加支付宝技术分享沙龙,看到支付宝在Android使用插件化的技术,挺好奇的.正好这几天看到了农民伯伯的相关文章,因此简单整理了下,有什么错误希望大神指正. 核心类 1.1     ...

  2. 【iOS】用Layer创建一个三维模型以及拖动

    关于CALayer的介绍以及基本属性,在这篇博客中有交代:CoreAnimation —— CALayer 这篇博客讲述简单的通过对layer的transform属性的设置一个CATransform3 ...

  3. 常见LINQ语句学习

    1.读取20条最新留言 public ActionResult Index() { var mostRecentEntries = (from entry in _db.Entries orderby ...

  4. Java基础02 方法与数据成员

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 在Java基础01 从HelloWorld到面向对象,我们初步了解了对象(obje ...

  5. 【HTTP】Fiddler(二) - 使用Fiddler做抓包分析

    上文( http://blog.csdn.net/ohmygirl/article/details/17846199 )中已经介绍了Fiddler的原理和软件界面.本文主要针对Fiddler的抓包处理 ...

  6. 73_leetcode_Construct Binary Tree from Inorder and Postorder Traversal

    Given inorder and postorder traversal of a tree, construct the binary tree 1:中序和后序遍历构成一棵树.2:採用递归的方法. ...

  7. 设计模式模式适配器(Adapter)摘录

    23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,他们帮助建立一个系统,是独立于如何.这是一个这些对象和陈述的组合.创建使用继承一个类架构更改实例,一个对象类型模 ...

  8. FxMaker用法

    第一步:选sceneFxMaker幕后,执行 第二步:执行界面,选中EffectParticle制作粒子特效 第三步:随便点中一个粒子特效.例如以下所看到的 第四步:点中右側的"Explos ...

  9. Python的字符串格式化符号

    1.字符串格式化符号含义 %c:格式化字符及其ASCII码 %s:格式化字符串 %d:格式化整数 %o:格式化无符号八进制数 %x:格式化无符号十六进制数 %X:格式化无符号十六进制数(大写) %f: ...

  10. UVA11552------FEWEST FLOPS------区间型的DP

    题目地址:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem& ...