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????????????else
104????????????{
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?CharType
141????{
142????????None,???//默認值,不可識別類型
143????????English,????//拉丁字符,用英文標識
144????????Chinese,????//CJK字符,以中文代表
145????????Number,?????//阿拉伯數字
146????????Control?????//控制符號,指控制符號已經各種標點符號等
147????}
148
149}
?
代碼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?DoubleTokenizerTest
12????{
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????????????????????}
111
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????????????{
122????????????????//在分詞結果范圍內取詞
123
124????????????????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?CharType
161????{
162????????None,???//默認值,不可識別類型
163????????English,????//拉丁字符,用英文標識
164????????Chinese,????//CJK字符,以中文代表
165????????Number,?????//阿拉伯數字
166????????Control?????//控制符號,指控制符號已經各種標點符號等
167????}
168
169}
170
?
代碼改造成了2.1.3.6。主要的改變在于用父類的input字段保持了讀入流,然后用Token作為緩沖區,因為它實現了可變緩沖區,簡化了我們的開發。測試結果。
?
搜索詞:英語
結果:
content:英語
英語單詞,語法,口語都很重要。
口語,語法,單詞都是英語的重要組成部分。
-----------------------------------
搜索詞:語法
結果:
content:語法
英語單詞,語法,口語都很重要。
口語,語法,單詞都是英語的重要組成部分。
-----------------------------------
搜索詞:單詞
結果:
content:單詞
英語單詞,語法,口語都很重要。
口語,語法,單詞都是英語的重要組成部分。
我們要學好英語不但要學語法,單詞還有口語。
-----------------------------------
搜索詞:口語
結果:
content:口語
英語單詞,語法,口語都很重要。
口語,語法,單詞都是英語的重要組成部分。
我們要學好英語不但要學語法,單詞還有口語。
-----------------------------------
搜索詞:+content:"英" +content:"語" +content:"單" +content:"詞"
結果:
+content:英 +content:語 +content:單 +content:詞
-----------------------------------
終于OK了!!!呵呵。
?
(PS:長時間編寫,可能內容太長了,造成我機器編寫這個章節有點卡,所以,這里提前結束。)
?
?
轉載于:https://www.cnblogs.com/birdshover/archive/2008/08/31/1279894.html
總結
以上是生活随笔為你收集整理的Lucene.Net 2.3.1开发介绍 —— 二、分词(五)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 博客导读(09.3.21)
- 下一篇: asp.net电子商务开发实战 视频 第