Использование Lucene для подсчета результатов в категориях
Я пытаюсь использовать Lucene Java 2.3.2 для реализации поиска в каталоге продуктов. Помимо обычных полей для продукта есть поле под названием "Категория". Продукт может попадать в несколько категорий. В настоящее время я использую FilteredQuery для поиска одного и того же слова поиска с каждой категорией, чтобы получить количество результатов для каждой категории.
Это приводит к 20-30 внутренним поисковым вызовам для каждого запроса для отображения результатов. Это значительно замедляет поиск. Есть ли более быстрый способ добиться того же результата с помощью Lucene?
Ответы
Ответ 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>
для отслеживания совпадений в каждой категории.