2008-09-30 9 views
18

Estoy intentando utilizar Lucene Java 2.3.2 para implementar la búsqueda en un catálogo de productos. Además de los campos regulares para un producto, hay un campo llamado 'Categoría'. Un producto puede caer en múltiples categorías. Actualmente, utilizo FilteredQuery para buscar el mismo término de búsqueda con cada categoría para obtener el número de resultados por categoría.Uso de Lucene para contar los resultados en las categorías

Esto da como resultado 20-30 llamadas de búsqueda interna por consulta para mostrar los resultados. Esto está ralentizando considerablemente la búsqueda. ¿Hay una manera más rápida de lograr el mismo resultado usando Lucene?

Respuesta

2

Es posible que desee considerar buscar entre todos los documentos que coinciden con categorías usando un TermDocs iterator.

Este código de ejemplo pasa por cada término de "Categoría", y luego cuenta el número de documentos que coinciden con ese término.

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(); 
    } 
} 

Este código debe ejecutarse razonablemente rápido, incluso para índices grandes.

Aquí hay un código que prueba que el método: (!)

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

Esto solo cuenta los documentos etiquetados por cada término en el campo Categoría, lo cual se puede hacer mucho más rápido con terms.docFreq(). Lo que falta es la intersección con los aciertos de los criterios de búsqueda del usuario. – erickson

8

no tengo la reputación suficiente para comentar, pero en la respuesta de Matt codorniz estoy bastante seguro de que podría sustituir a esto:

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

con esto:

int numDocs = terms.docFreq() 

y luego deshacerse de la variable td completo. Esto debería hacerlo aún más rápido.

+0

estarás allí en ningún momento (comentando) – mattlant

+0

Lo hice pero da cuenta de todos los documentos, en mi caso quiero contar la categoría de un conjunto de resultados. por ejemplo, si el usuario busca "manzana", quiero mostrar el número de coincidencias encontradas en la categoría de productos electrónicos y frutas. pero su sugerencia mate y da cuenta para todos los documentos. Creo que necesito buscar en mi buscador en lugar de leer, pero el buscador no tiene TermDocs. –

0

Así que déjame ver si entiendo la pregunta correctamente: dada una consulta del usuario, quieres mostrar cuántas coincidencias hay para la consulta en cada categoría. ¿Correcto?

Piénsalo de esta manera: tu pregunta es en realidad originalQuery AND (category1 OR category2 or ...) excepto que además de un puntaje en general, quieres obtener un número para cada una de las categorías. Lamentablemente, la interfaz para recopilar visitas en Lucene es muy limitada, y solo te da un puntaje general para una consulta. Pero podría implementar un marcador/recopilador personalizado.

Eche un vistazo a la fuente de org.apache.lucene.search.DisjunctionSumScorer. Puede copiar algo de eso para escribir un marcador personalizado que repite las coincidencias de categorías mientras se lleva a cabo su búsqueda principal. Y podría mantener un Map<String,Long> para realizar un seguimiento de las coincidencias en cada categoría.

9

Aquí es lo que hice, aunque es un poco pesado en la memoria:

Lo que necesita es crear de antemano un montón de BitSet s, uno para cada categoría, que contiene el identificador de documento de todos los documentos en una categoría. Ahora, en el tiempo de búsqueda, usa un HitCollector y verifica las identificaciones del documento contra los BitSets.

Aquí está el código para crear los conjuntos de bits:

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; 
} 

Esta es sólo una manera de hacer esto. Probablemente pueda usar TermDocs en lugar de ejecutar una búsqueda completa si sus categorías son lo suficientemente simples, pero esto solo debería ejecutarse una vez cuando cargue el índice de todos modos.

Ahora, cuando es el momento para contar categorías de resultados de búsqueda que hace esto:

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; 
} 

Lo que es terminar con una matriz que contiene el recuento de cada categoría dentro de los resultados de búsqueda. Si también necesita los resultados de búsqueda, debe agregar un TopDocCollector a su recopilador de hits (yo dawg ...). O bien, podría ejecutar la búsqueda nuevamente. 2 búsquedas son mejores que 30.

+1

Otra implementación para la parte getCategoryCount: en realidad podría obtener un BitSet de su búsqueda (utilizando un recopilador) y luego intersecar ese resultsBetSet con cualquier categoría en la que le interese. La intersección debe ser más rápida que consultar cada documento, y también puede cruzarse múltiples categorías antes de intersectar con los resultados BitSet. –

2

Sachin, creo que quiere faceted search. No sale de la caja con Lucene. Le sugiero que intente usar SOLR, que tiene faceting como una característica importante y conveniente.

Cuestiones relacionadas