Подтвердить что ты не робот

Использование Lucene для подсчета результатов в категориях

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

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

4b9b3361

Ответ 1

Вот что я сделал, хотя он немного тяжел в памяти:

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

Здесь код для создания бит-наборов:

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.

Ответ 2

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

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

с этим:

int numDocs = terms.docFreq()

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

Ответ 3

Возможно, вам захочется рассмотреть все документы, соответствующие категориям, используя итератор 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);
}

Ответ 4

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

Ответ 5

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

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

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