最近在做一个邮件系统,需要解析邮件内容,然后搜索相应的数据库内容,进行邮件回复。

不难想象,在此过程中,除了邮件收发之外,还有分词、全文检索等功能需要实现。

稍微花了点时间,搜了一下,免费的.Net分词比较少...

全文检索更加,除了数据库方式的全文检索外,我看到的是满屏的Luceue。

我之前就有对Luceue头痛过,除了操作有点多之外,我仍然不能很好地理解它处理索引文件的方式(惭愧)

....

之后决定自己做一个小型的分词和全文索引的类库吧

首先是分词

看了蛮多的,都推荐 最大匹配法 分词,意思就是大词优先匹配切分

代码片段如下:

 

//分词函数,使用预载好的“字典”信息逐词匹配
public string Parse(string source) {
  //max 为最大词长,
  int max = MaxWordLength > source.Length ? source.Length : MaxWordLength;
  //_wordSet是一个集合,用于存储分出来的词,同时避免了重复词
  //清除上次缓存
  wordSet.Clear();
  //多设置一个string,为了备份,因为优先匹配出大词后删除,避免小词再次匹配
  //如 "程序员" 能同时匹配 "程序员" 和 "程序"
  string word = source;
  //大词优先,所以为--step
  for (int step = max; step >= MinWordLength; --step) {
    for (int i = 0; i <= word.Length - step; ++i) {
      string subword = word.Substring(i, step);
                if (WordsDictionary.ContainsKey(subword)) {
                  _wordSet.Add(WordsDictionary[subword]);
                     word = word.Replace(subword, "");
	   //这个操作时为了演示,就是在匹配出的词的前后加上'/'分隔符
               source = source.MarkWith(subword, '/');
                }
         }
    }
  //返回划分好的句子
   return source;
}
 

算法比较简单,这个代码片段也只是演示而已,可能有错漏之处,仅供借鉴

 

 

分词中另一个棘手的事情就是词库,也就是“字典”

我使用的字典是 Java分词工具 庖丁解牛(Paoding)的基础词库

内含24万常用词汇(t-base.dic 2.4M)

我不太喜欢用文件存储,所以自己写了个小程序导入了数据库

用数据库的话,就比较方便管理,比文件的合并和拆分好用

 

下来是全文索引

全文索引就是预先为资源中出现的关键字设定好资源的标识

只要分词 把搜索词 匹配出关键字,那么就能直接取出相对应的资源来

比如我自定义的格式如下:

 

 

15030|10,9,4,5,8
12372|1
155|10,4,6,7
122|1,2,4
11111|2,5,8,9
18888|1,23,9
...

 

 

其中"|"之前的是关键字的数据库主键Id,之后的是有该关键字的资源的数据库主键Id

这样就很明了了

比如 搜索词为:“程序员写程序”
分词为:“程序员/写/程序”

假设关键字“程序员”的数据库Id为 15030

那么即刻可以取出对应的资源为

Id为 4,5,8,9,10 的资源来


在全文索引上面,我选择了文件方式。

因为涉及的数据都是比较零散索引Id,在数据库中使用varchar或者text就发挥不出数据库的快速
但是如果用 int,就要涉及多表

 

代码片段:

 

public class IndexData {

     private HashSet<int> _index;
  ...
     public int Id { get; set; }
     ...
     public HashSet<int> Indexs {
       get { return _index; }
     }
	
     //从文件行初始化
   public IndexData(string indexData) : this() {
      string[] datas = indexData.Split('|');
      Id = Convert.ToInt32(datas[0]);
         string[] indexIds = datas[1].Split(',');
         foreach(string indexId in indexIds) {
           _index.Add(Convert.ToInt32(indexId));
         }
     }

     public bool IsContainIndex(int index) {
    ...
     }

     public void AddIndex(int index) {
    ...
     }

     public void RemoveIndex(int index) {
    ...
     }

     public override bool Equals(object obj) {
    ...
  }

     public override int GetHashCode() {
    ...
  }
	
  //重写ToString方法,方便写入文件
    public override string ToString() {
      StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.Append(Id).Append("|");
           foreach(int index in _index) {
             stringBuilder.Append(index).Append(",");
           }
           stringBuilder.Remove(stringBuilder.Length - 1, 1);
           return stringBuilder.ToString();
        }
    }
}
 
 

上面代码所示的是一条索引记录的实体

其中,构造函数从文件行得到实体,ToString()方法从实体转换为文件行

 

 

这里只提供一个小测试,纯做参考

 

词库:庖丁base词库 24万词

词库容器:数据库Sql Server 2005

词库预读:有,Dictionary

测试搜索词:以前总觉得,自己花了那么多的精力去学一门语言,到头来,被N多人说不是,觉得很委屈,血气方刚者,早已经破口大骂了

 

分词结果:以前/总/觉得,自己/花了/那么多/的/精力/去学一门/语言,到头来,被N多人说/不是,/觉得/很/委屈,/血气方刚/者,早/已经/破口大骂/了

耗时:152ms

符合资源条数:1

 

虽然分词并不是很准确,但是…

算是一种努力尝试吧,毕竟也有很多的地方可以优化…

 

如果大家有什么好的想法,欢迎讨论!

posted on 2010-11-15 17:36  猥琐代码男  阅读(815)  评论(2编辑  收藏  举报