• Lucene 时间排序


    在Lucene4.4中,想要实现搜索结果按照时间倒序的效果:如果两个文档得分相同,那么就按照发布时间倒序排列;否则就按照分数排列。这种效果在Lucene4.6中实现起来极其简单,直接利用search接口的Sort参数即可达成,完全不需要像某些人说的重写Similarity那么麻烦。三两行代码的事情,体现了Make it simple, stupid的精髓。

    首先来看看测试例子,这个例子中我建立了四个文档,按照内容-发布日期来表示分别是:

    2004年光棍节攻略 , 20041111

    2005年光棍节攻略 , 20051111

    2006年光棍节攻略 , 20061111

    游戏攻略 ,20141111

    统一使用“光棍节攻略”来搜索它们,用户希望最新的光棍节攻略排在第一。

    如果不做排序处理的话,用户体验非常糟糕:

    1. package com.hankcs.test;
    2.  
    3. import org.apache.lucene.analysis.Analyzer;
    4. import org.apache.lucene.document.*;
    5. import org.apache.lucene.index.*;
    6. import org.apache.lucene.queries.CustomScoreQuery;
    7. import org.apache.lucene.queries.function.FunctionQuery;
    8. import org.apache.lucene.queryparser.classic.ParseException;
    9. import org.apache.lucene.queryparser.classic.QueryParser;
    10. import org.apache.lucene.search.*;
    11. import org.apache.lucene.store.Directory;
    12. import org.apache.lucene.store.LockObtainFailedException;
    13. import org.apache.lucene.store.RAMDirectory;
    14. import org.apache.lucene.util.Version;
    15. import org.wltea.analyzer.lucene.IKAnalyzer;
    16.  
    17. import java.io.IOException;
    18.  
    19. /**
    20.  * @author hankcs
    21.  */
    22. public class TestSortByTime
    23. {
    24.     public static void main(String[] args)
    25.     {
    26.         // Lucene Document的主要域名
    27.         String fieldName = "text";
    28.  
    29.         // 实例化IKAnalyzer分词器
    30.         Analyzer analyzer = new IKAnalyzer();
    31.  
    32.         Directory directory = null;
    33.         IndexWriter iwriter;
    34.         IndexReader ireader = null;
    35.         IndexSearcher isearcher;
    36.         try
    37.         {
    38.             //索引过程**********************************
    39.             //建立内存索引对象
    40.             directory = new RAMDirectory();
    41.  
    42.             //配置IndexWriterConfig
    43.             IndexWriterConfig iwConfig = new IndexWriterConfig(Version.LUCENE_46, analyzer);
    44.             iwConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
    45.             iwriter = new IndexWriter(directory, iwConfig);
    46.             //写入索引
    47.             for (int i = 0; i < 3; ++i)
    48.             {
    49.                 int year = 2004 + i;
    50.                 Document doc = new Document();
    51.                 doc.add(new TextField(fieldName, year + "年光棍节攻略", Field.Store.YES));
    52.                 doc.add(new IntField("date",  year * 10000 + 1111, Field.Store.YES));
    53.                 iwriter.addDocument(doc);
    54.             }
    55.             // 加入一个干扰文档
    56.             Document doc = new Document();
    57.             doc.add(new TextField(fieldName, "游戏攻略", Field.Store.YES));
    58.             doc.add(new IntField("date",  20141111, Field.Store.YES));
    59.             iwriter.addDocument(doc);
    60.             iwriter.close();
    61.  
    62.             //搜索过程**********************************
    63.             //实例化搜索器
    64.             ireader = DirectoryReader.open(directory);
    65.             isearcher = new IndexSearcher(ireader);
    66.  
    67.             String keyword = "光棍节攻略";
    68.             //使用QueryParser查询分析器构造Query对象
    69.             QueryParser qp = new QueryParser(Version.LUCENE_46, fieldName, analyzer);
    70.             Query query = qp.parse(keyword);
    71.             System.out.println("Query = " + query);
    72.  
    73.             //搜索相似度最高的5条记录
    74.             TopDocs topDocs = isearcher.search(query, 5);
    75.             System.out.println("命中:" + topDocs.totalHits);
    76.             //输出结果
    77.             ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    78.             for (int i = 0; i < Math.min(5, scoreDocs.length); i++)
    79.             {
    80.                 Document targetDoc = isearcher.doc(scoreDocs[i].doc);
    81.                 System.out.print(targetDoc.getField(fieldName).stringValue());
    82.                 System.out.print(" , " + targetDoc.getField("date").numericValue());
    83.                 System.out.println(" , " + scoreDocs[i].score);
    84.             }
    85.  
    86.         } catch (CorruptIndexException e)
    87.         {
    88.             e.printStackTrace();
    89.         } catch (LockObtainFailedException e)
    90.         {
    91.             e.printStackTrace();
    92.         } catch (IOException e)
    93.         {
    94.             e.printStackTrace();
    95.         } catch (ParseException e)
    96.         {
    97.             e.printStackTrace();
    98.         } finally
    99.         {
    100.             if (ireader != null)
    101.             {
    102.                 try
    103.                 {
    104.                     ireader.close();
    105.                 } catch (IOException e)
    106.                 {
    107.                     e.printStackTrace();
    108.                 }
    109.             }
    110.             if (directory != null)
    111.             {
    112.                 try
    113.                 {
    114.                     directory.close();
    115.                 } catch (IOException e)
    116.                 {
    117.                     e.printStackTrace();
    118.                 }
    119.             }
    120.         }
    121.     }
    122. }

    输出:

    2004年光棍节攻略 , 20041111 , 0.71185887

    2005年光棍节攻略 , 20051111 , 0.71185887

    2006年光棍节攻略 , 20061111 , 0.71185887

    游戏攻略 , 20141111 , 0.049675122

    可以看到文档是严格按照分数排序的,如果分数相同,则按照索引顺序排序,导致最新的文章反而排在最下面。

    使用search接口的Sort参数优化搜索结果:

    1. package com.hankcs.test;
    2.  
    3. import org.apache.lucene.analysis.Analyzer;
    4. import org.apache.lucene.document.*;
    5. import org.apache.lucene.index.*;
    6. import org.apache.lucene.queries.CustomScoreQuery;
    7. import org.apache.lucene.queries.function.FunctionQuery;
    8. import org.apache.lucene.queryparser.classic.ParseException;
    9. import org.apache.lucene.queryparser.classic.QueryParser;
    10. import org.apache.lucene.search.*;
    11. import org.apache.lucene.store.Directory;
    12. import org.apache.lucene.store.LockObtainFailedException;
    13. import org.apache.lucene.store.RAMDirectory;
    14. import org.apache.lucene.util.Version;
    15. import org.wltea.analyzer.lucene.IKAnalyzer;
    16.  
    17. import java.io.IOException;
    18.  
    19. /**
    20.  * @author hankcs
    21.  */
    22. public class TestSortByTime
    23. {
    24.     public static void main(String[] args)
    25.     {
    26.         // Lucene Document的主要域名
    27.         String fieldName = "text";
    28.  
    29.         // 实例化IKAnalyzer分词器
    30.         Analyzer analyzer = new IKAnalyzer();
    31.  
    32.         Directory directory = null;
    33.         IndexWriter iwriter;
    34.         IndexReader ireader = null;
    35.         IndexSearcher isearcher;
    36.         try
    37.         {
    38.             //索引过程**********************************
    39.             //建立内存索引对象
    40.             directory = new RAMDirectory();
    41.  
    42.             //配置IndexWriterConfig
    43.             IndexWriterConfig iwConfig = new IndexWriterConfig(Version.LUCENE_46, analyzer);
    44.             iwConfig.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
    45.             iwriter = new IndexWriter(directory, iwConfig);
    46.             //写入索引
    47.             for (int i = 0; i < 3; ++i)
    48.             {
    49.                 int year = 2004 + i;
    50.                 Document doc = new Document();
    51.                 doc.add(new TextField(fieldName, year + "年光棍节攻略", Field.Store.YES));
    52.                 doc.add(new IntField("date",  year * 10000 + 1111, Field.Store.YES));
    53.                 iwriter.addDocument(doc);
    54.             }
    55.             // 加入一个干扰文档
    56.             Document doc = new Document();
    57.             doc.add(new TextField(fieldName, "游戏攻略", Field.Store.YES));
    58.             doc.add(new IntField("date",  20141111, Field.Store.YES));
    59.             iwriter.addDocument(doc);
    60.             iwriter.close();
    61.  
    62.             //搜索过程**********************************
    63.             //实例化搜索器
    64.             ireader = DirectoryReader.open(directory);
    65.             isearcher = new IndexSearcher(ireader);
    66.  
    67.             String keyword = "光棍节攻略";
    68.             //使用QueryParser查询分析器构造Query对象
    69.             QueryParser qp = new QueryParser(Version.LUCENE_46, fieldName, analyzer);
    70.             Query query = qp.parse(keyword);
    71.             System.out.println("Query = " + query);
    72.  
    73.             //搜索相似度最高的5条记录
    74.             Sort sort = new Sort(new SortField("text", SortField.Type.SCORE), new SortField("date", SortField.Type.INT, true));
    75.             TopDocs topDocs = isearcher.search(query, 5, sort);
    76.             System.out.println("命中:" + topDocs.totalHits);
    77.             //输出结果
    78.             ScoreDoc[] scoreDocs = topDocs.scoreDocs;
    79.             for (int i = 0; i < Math.min(5, scoreDocs.length); i++)
    80.             {
    81.                 Document targetDoc = isearcher.doc(scoreDocs[i].doc);
    82.                 System.out.print(targetDoc.getField(fieldName).stringValue());
    83.                 System.out.print(" , " + targetDoc.getField("date").numericValue());
    84.                 System.out.println(" , " + scoreDocs[i].score);
    85.             }
    86.  
    87.         } catch (CorruptIndexException e)
    88.         {
    89.             e.printStackTrace();
    90.         } catch (LockObtainFailedException e)
    91.         {
    92.             e.printStackTrace();
    93.         } catch (IOException e)
    94.         {
    95.             e.printStackTrace();
    96.         } catch (ParseException e)
    97.         {
    98.             e.printStackTrace();
    99.         } finally
    100.         {
    101.             if (ireader != null)
    102.             {
    103.                 try
    104.                 {
    105.                     ireader.close();
    106.                 } catch (IOException e)
    107.                 {
    108.                     e.printStackTrace();
    109.                 }
    110.             }
    111.             if (directory != null)
    112.             {
    113.                 try
    114.                 {
    115.                     directory.close();
    116.                 } catch (IOException e)
    117.                 {
    118.                     e.printStackTrace();
    119.                 }
    120.             }
    121.         }
    122.     }
    123. }

    输出结果:

    命中:4

    2006年光棍节攻略 , 20061111 , NaN

    2005年光棍节攻略 , 20051111 , NaN

    2004年光棍节攻略 , 20041111 , NaN

    游戏攻略 , 20141111 , NaN

    我们看到“2006年光棍节攻略”因为时间比较新,并且相关性高,就排在了第一。“2005年光棍节攻略”相关度相同,因为时间旧就排在后面一点,而干扰文档“游戏攻略”即使时间最新,因为不相关的原因排在最后面。这种效果正好是我想要的,极大提升了用户体验。

  • 相关阅读:
    基于结构化平均感知机的分词器Java实现
    HanLP分词命名实体提取详解
    [英语学习]王秒同学《21天TED英语精练团》
    [不好分类]转帖:好好说话是个“技术活”(李笑来说话太直了?)
    [英语学习]3招速成英语发音 背景音乐和学习随感
    [读书笔记]《番茄工作法图解:简单易行的时间管理方法》
    [英语学习]给宝宝的英语原版资源
    [办公自动化]如何判断服务器是否开放某端口
    [他山之石]Google's Project Oxygen Pumps Fresh Air Into Management
    [读书笔记]云计算时代的网络,读《腾云,云计算和大数据时代网络技术揭秘》
  • 原文地址:https://www.cnblogs.com/dingjiaoyang/p/6115295.html
Copyright © 2020-2023  润新知