Использование Lucene для подсчета результатов в категориях - PullRequest
18 голосов
/ 30 сентября 2008

Я пытаюсь использовать Lucene Java 2.3.2 для осуществления поиска по каталогу продуктов. Помимо обычных полей для продукта, есть поле под названием «Категория». Продукт может попасть в несколько категорий. В настоящее время я использую FilteredQuery для поиска одного и того же поискового запроса в каждой категории, чтобы получить количество результатов в каждой категории.

Это приводит к 20-30 внутренним поисковым вызовам на запрос для отображения результатов. Это значительно замедляет поиск. Есть ли более быстрый способ достижения того же результата с помощью Lucene?

Ответы [ 5 ]

9 голосов
/ 27 января 2009

Вот что я сделал, хотя он немного перегружен памятью:

Вам нужно заранее создать группу BitSet с, по одному для каждой категории, содержащую идентификатор документа для всех документов в категории. Теперь во время поиска вы используете HitCollector и сверяете идентификаторы документов с бит-наборами.

Вот код для создания наборов битов:

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 в ваш сборщик посещений (дааааааа ...). Или вы можете просто запустить поиск снова. 2 поиска лучше, чем 30.

8 голосов
/ 01 октября 2008

У меня недостаточно репутации, чтобы комментировать (!), Но в ответе Мэтта Квейла я уверен, что вы могли бы заменить это:

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

с этим:

int numDocs = terms.docFreq()

, а затем вообще избавиться от переменной td. Это должно сделать это еще быстрее.

2 голосов
/ 12 апреля 2009

Сачин, я думаю, ты хочешь граненый поиск . Это не выходит из коробки с Lucene. Я предлагаю вам попробовать SOLR , у которого огранка в качестве основной и удобной функции.

2 голосов
/ 30 сентября 2008

Возможно, вы захотите просмотреть все документы, которые соответствуют категориям, с помощью Итератор TermDocs .

Этот пример кода проходит через каждый термин «Категория», а затем подсчитывает количество документов, соответствующих этому термину.

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);
}
0 голосов
/ 24 декабря 2008

Итак, давайте посмотрим, правильно ли я понимаю вопрос: учитывая запрос пользователя, вы хотите показать, сколько совпадений есть для запроса в каждой категории. Правильно?

Подумайте об этом так: ваш запрос на самом деле originalQuery AND (category1 OR category2 or ...), кроме общего балла, который вы хотите получить для каждой из категорий. К сожалению, интерфейс для сбора хитов в Lucene очень узок, и дает вам только общий балл за запрос. Но вы могли бы реализовать собственный Scorer / Collector.

Посмотрите на источник для org.apache.lucene.search.DisjunctionSumScorer. Вы можете скопировать кое-что из этого, чтобы написать собственный счетчик, который будет перебирать совпадения категорий, пока идет ваш основной поиск. И вы можете сохранить Map<String,Long>, чтобы отслеживать матчи в каждой категории.

...