【问题标题】:Using Lucene to count results in categories使用 Lucene 统计分类结果
【发布时间】:2010-09-14 04:57:36
【问题描述】:

我正在尝试使用 Lucene Java 2.3.2 来实现对产品目录的搜索。除了产品的常规字段外,还有一个名为“类别”的字段。一个产品可以属于多个类别。目前,我使用 FilteredQuery 搜索每个类别的相同搜索词,以获取每个类别的结果数。

这会导致每个查询进行 20-30 次内部搜索调用以显示结果。这大大减慢了搜索速度。有没有更快的方法使用 Lucene 实现相同的结果?

【问题讨论】:

    标签: java search lucene filtering catalog


    【解决方案1】:

    这就是我所做的,虽然它有点占用内存:

    你需要提前创建一堆BitSets,每个类别一个,包含一个类别中所有文档的doc id。现在,在搜索时,您使用 HitCollector 并根据 BitSet 检查文档 ID。

    以下是创建位集的代码:

    public BitSet[] getBitSets(IndexSearcher indexSearcher, 
                               Category[] categories) {
        BitSet[] bitSets = new BitSet[categories.length];
        for(int i=0; i<categories.length; i++)
        {
            Query query = categories[i].getQuery();
            final BitSet bitset = new BitSet()
            indexSearcher.search(query, new HitCollector() {
                public void collect(int doc, float score) {
                    bitSet.set(doc);
                }
            });
            bitSets[i] = bitSet;
        }
        return bitSets;
    }
    

    这只是执行此操作的一种方法。如果您的类别足够简单,您可能可以使用 TermDocs 而不是运行完整搜索,但无论如何,这应该只在您加载索引时运行一次。

    现在,当需要计算搜索结果的类别时,您可以这样做:

    public int[] getCategroryCount(IndexSearcher indexSearcher, 
                                   Query query, 
                                   final BitSet[] bitSets) {
        final int[] count = new int[bitSets.length];
        indexSearcher.search(query, new HitCollector() {
            public void collect(int doc, float score) {
                for(int i=0; i<bitSets.length; i++) {
                    if(bitSets[i].get(doc)) count[i]++;
                }
            }
        });
        return count;
    }
    

    最终得到的是一个数组,其中包含搜索结果中每个类别的计数。如果您还需要搜索结果,则应将 TopDocCollector 添加到您的命中收集器(yo dawg ...)。或者,您可以再次运行搜索。 2 次搜索优于 30 次。

    【讨论】:

    • getCategoryCount 部分的其他实现:您实际上可以从搜索中获取一个 BitSet(使用收集器),然后将该 resultsBitSet 与您感兴趣的任何 categoryBitSet 相交。相交应该比检查每个更快doc,还可以在与结果BitSet相交之前,先对多个类别进行相交。
    【解决方案2】:

    我没有足够的声誉来评论(!)但在马特奎尔的回答中,我很确定你可以替换这个:

    int numDocs = 0;
    td.seek(terms);
    while (td.next()) {
        numDocs++;
    }
    

    用这个:

    int numDocs = terms.docFreq()
    

    然后完全摆脱 td 变量。这应该会使其更快。

    【讨论】:

    • 你很快就会到(评论)
    • 我这样做了,但它从所有文档中计数,在我的情况下,我想从结果集中计算类别。例如,如果用户搜索“apple”,那么我想显示在电子产品和水果类别中找到的匹配数。但是您和马特的建议对所有文件都进行了计数。我想我需要搜索我的搜索者而不是阅读者,但搜索者没有 TermDocs。
    【解决方案3】:

    您可能需要考虑使用TermDocs iterator 查看所有与类别匹配的文档。

    此示例代码遍历每个“类别”术语,然后计算与该术语匹配的文档数。

    public static void countDocumentsInCategories(IndexReader reader) throws IOException {
        TermEnum terms = null;
        TermDocs td = null;
    
    
        try {
            terms = reader.terms(new Term("Category", ""));
            td = reader.termDocs();
            do {
                Term currentTerm = terms.term();
    
                if (!currentTerm.field().equals("Category")) {
                    break;
                }
    
                int numDocs = 0;
                td.seek(terms);
                while (td.next()) {
                    numDocs++;
                }
    
                System.out.println(currentTerm.field() + " : " + currentTerm.text() + " --> " + numDocs);
            } while (terms.next());
        } finally {
            if (td != null) td.close();
            if (terms != null) terms.close();
        }
    }
    

    即使对于大型索引,此代码也应该运行得相当快。

    下面是一些测试该方法的代码:

    public static void main(String[] args) throws Exception {
        RAMDirectory store = new RAMDirectory();
    
        IndexWriter w = new IndexWriter(store, new StandardAnalyzer());
        addDocument(w, 1, "Apple", "fruit", "computer");
        addDocument(w, 2, "Orange", "fruit", "colour");
        addDocument(w, 3, "Dell", "computer");
        addDocument(w, 4, "Cumquat", "fruit");
        w.close();
    
        IndexReader r = IndexReader.open(store);
        countDocumentsInCategories(r);
        r.close();
    }
    
    private static void addDocument(IndexWriter w, int id, String name, String... categories) throws IOException {
        Document d = new Document();
        d.add(new Field("ID", String.valueOf(id), Field.Store.YES, Field.Index.UN_TOKENIZED));
        d.add(new Field("Name", name, Field.Store.NO, Field.Index.UN_TOKENIZED));
    
        for (String category : categories) {
            d.add(new Field("Category", category, Field.Store.NO, Field.Index.UN_TOKENIZED));
        }
    
        w.addDocument(d);
    }
    

    【讨论】:

    • 这只是计算类别字段中每个术语标记的文档,您可以使用 terms.docFreq() 更快地完成。缺少的是与用户搜索条件中的匹配项的交集。
    【解决方案4】:

    Sachin,我相信你想要faceted search。它不是开箱即用的 Lucene。我建议您尝试使用SOLR,它具有faceting 作为主要且方便的功能。

    【讨论】:

      【解决方案5】:

      所以让我看看我是否正确理解了这个问题:给定用户的查询,您想显示每个类别中的查询有多少匹配项。对吗?

      可以这样想:您的查询实际上是originalQuery AND (category1 OR category2 or ...),除了您希望获得每个类别的数字的总分。不幸的是,在 Lucene 中收集命中的界面非常狭窄,只能为您提供查询的总分。但是您可以实现自定义记分器/收集器。

      查看 org.apache.lucene.search.DisjunctionSumScorer 的源代码。您可以复制其中的一些内容来编写一个自定义记分器,该记分器在您的主要搜索进行时迭代类别匹配。您可以保留Map&lt;String,Long&gt; 来跟踪每个类别中的匹配项。

      【讨论】:

        猜你喜欢
        • 2010-09-05
        • 2012-07-09
        • 1970-01-01
        • 2013-07-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多