使用Lucene.Net达成全文检索!基础解说(二)

上一集当中我们完成了Lucene基本操作中的Create与Read,这一集会将CRUD中的Update与Delete的操作方法告诉你,并且本集会着重於讲解关於"Norms"与权重(Boost)在Lucene中的使用操作。

对於Lucene还没有一些基本认识的朋友,建议先回到上一篇文章中阅读呦!

更新其实就是将存在的索引删除并重新建立Document,不存在的则直接新增。
首先准备一组资料准备更新

List<Product> GetUpdateProductsInformation()
{
    return new List<Product>
    {
        new Product{ Id = 6, Name = "香蕉", Description = "运动完後吃根香蕉补充养分。"},
        new Product{ Id = 2, Name = "橘子", Description = "橘子跟柳丁你分得出来吗?"}
    };
}

欲更新的Document必须与创建所引时使用的Document栏位相同

void Update(string key, List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using( var directory = FSDirectory.Open(dir))
    {
        using(var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
        {
            foreach (var index in information)
            {
                var document = new Document();
                document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
                document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
                document.Add(new Field("Description", index.Description, Field.Store.YES, Field.Index.ANALYZED));
                indexWriter.UpdateDocument(new Term("Name", key) ,document);
            }
        }
    }
}

来测试看看
https://ithelp.ithome.com.tw/upload/images/20220420/20145396gKXNevsTvC.png
可以看见 Name = 橘子 的索引已经改为我们新准备的资料罗。
再来是删除!
与更新非常相似,只需要使用deleteDocument()就可以了。

void Delete(string key, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(dir))
    {
        using (var indexWriter = new IndexWriter(directory, analyzer, false, IndexWriter.MaxFieldLength.LIMITED))
        {
            indexWriter.DeleteDocuments(new Term("Name", key));
            indexWriter.Optimize();
            indexWriter.Commit();
        }
    }
}

再来看看输出结果
https://ithelp.ithome.com.tw/upload/images/20220420/201453964goy4nSQSk.png
 可以发现 Score :0.7554128, Id :2, Name :橘子, Description :医生给娜美最珍贵的宝藏。这笔索引已经被移除罗!
  
可以发现笔者於更新或删除时都是输入单一字来做异动,除了表达可以对索引做复合更动外,
是因为更新与删除索引同样会使用到分词器(analyzer),
所输入的索引值非ID等数值时必须要配合分词器的分词能力才能取得所想异动的索引喔!

Boost是什麽呢?
Boost 分为 :

  1. Index Time Boost : 在建立索引时就计算好的值。例如上一篇中提到的(NORMS)
  2. Query Time Boost : 查询时赋与搜寻条件不同的值以影响结果。
    我们先来测试Index Time Boost的部分
void CreateIndexWithBoost(List<Product> information, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(dir))
    {
        using (var indexWriter = new IndexWriter(directory, analyzer, true, IndexWriter.MaxFieldLength.LIMITED))
        {
            foreach (var index in information)
            {
                var document = new Document();
                document.Add(new Field("Id", index.Id.ToString(), Field.Store.YES, Field.Index.NO));
                document.Add(new Field("Name", index.Name, Field.Store.YES, Field.Index.ANALYZED));
                document.Add(new Field("Description", index.Description, Field.Store.YES, Field.Index.ANALYZED));
                document.GetField("Name").Boost = 1.5F;
                document.GetField("Description").Boost = 0.5F;

                indexWriter.AddDocument(document);
            }
            indexWriter.Optimize();
            indexWriter.Commit();
        }
    }
}

并记得重新CreateIndex才能刷新栏位的权重值喔。
https://ithelp.ithome.com.tw/upload/images/20220420/20145396iALDp3uQfh.png
很明显的搜寻出来的Score分数变动了! 但是有没有发现明明Name栏位的Boost改成了1.5,苹果的数值却仍然只有一半呢?
这是因为我们的Search中所参照的栏位为Description,所以在计算Score的时候其实是完全没有参与的喔!
另外要记得,使用Index Time Boost的时候,欲给予铨重分配的栏位Field.Index不能使用NO_NORMS,不然这个栏位并不会纪录权重的资料。

再来我们试试看Query Time Boost

void SearchWithBoost(string searchValue, DirectoryInfo dir, StandardAnalyzer analyzer)
{
    using (var directory = FSDirectory.Open(_dir))
    {
        using (var indexSearcher = new IndexSearcher(directory))
        {
            var query = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Name", analyzer).Parse(searchValue);
            var query2 = new QueryParser(Lucene.Net.Util.Version.LUCENE_CURRENT, "Description", analyzer).Parse(searchValue);

            query.Boost = 2.0F;
            query2.Boost = 0.5F;

            BooleanQuery booleanQuery = new BooleanQuery();
            booleanQuery.Add(query, Occur.SHOULD);
            booleanQuery.Add(query2, Occur.SHOULD);

            var hits = indexSearcher.Search(booleanQuery, 20);
            if (!hits.ScoreDocs.Any())
            {
                Console.WriteLine("查无相关结果。");
                return;
            }
            Document doc;
            foreach (var hit in hits.ScoreDocs)
            {
                doc = indexSearcher.Doc(hit.Doc);
                Console.WriteLine("Score :" + hit.Score + ", Id :" + doc.Get("Id") + ", Name :" + doc.Get("Name") + ", Description :" + doc.Get("Description"));
            }
        }
    }
}

这次我们搜寻两个栏位"Name"与"Description",并使用 BooleanQuery来将其组合。
BooleanQuery中的 Occur有三种参数 : "MUST","MUST_NOT","SHOULD",功能与字面上的意思一样为"必须要有","必须没有"与"有无都包含"。

 https://ithelp.ithome.com.tw/upload/images/20220420/20145396kPqa6RCVsI.png
查询出来的分数就不一样罗!

以上就是这一次的分享,Lucene是一款容易入门但是要实际上战场却又十分复杂的功能,想要达成真正高效能的全文检索,在前期的文件规画配置与资料的权重配比都是一个巨大的挑战。未来会继续分享关於Lucene的其他有趣功能,还请继续期待呦!
另外也可以到我的GitHub下载范例来参考呦!

参考文件:

  1. 黑暗大大的全文检索笔记
  2. Makble
  3. CSDN Jack2013tong 文章

<<:  iPhone 永久删除的照片如何复原?

>>:  【C#】Behavioral Patterns Chain of Responsibility Mode

JavaScript Day24 - Promise(1)

ES6:Promise Promise:代表一个即将成功或失败的非同步操作 会有这几状态: 搁置 (...

Day 6 - 二进位会不会被禁位?

简介 今天要介绍的东西,是在编写程序的时候,可能需要具备的一个知识,但是好像不知道也不会影响那麽大(...

Day 23【Tokens' Owner】FUN SIDE PROJECT

【前言】 终於到了验证的最後一步啦,感觉时间过得很快,一眨眼就到了这里,网站也变得非常完整。感谢大...

[Day25] Scrum失败经验谈 – 与需求单位之间的断层

使用者意识 真为基础、善为核心、美为极致 承[Day24] Scrum失败经验谈 – 壁垒分明的职务...

Day 03 : ML in Production 的挑战

在 Day2 提到什麽是用於生产的机械学习 ML in Production ,今天来谈用於生产的机...