2008-09-30 9 views
18

Ich versuche, Lucene Java 2.3.2 zu verwenden, um Suche nach einem Katalog von Produkten zu implementieren. Neben den regulären Feldern für ein Produkt gibt es ein Feld namens "Kategorie". Ein Produkt kann in mehrere Kategorien fallen. Derzeit verwende ich FilteredQuery, um mit jeder Kategorie nach dem gleichen Suchbegriff zu suchen, um die Anzahl der Ergebnisse pro Kategorie zu ermitteln.Verwenden von Lucene zum Zählen von Ergebnissen in Kategorien

Dies führt zu 20-30 internen Suchaufrufen pro Abfrage, um die Ergebnisse anzuzeigen. Dies verlangsamt die Suche erheblich. Gibt es einen schnelleren Weg, um das gleiche Ergebnis mit Lucene zu erreichen?

Antwort

2

Sie können sich überlegen, alle Dokumente durchzugehen, die Kategorien mit einer TermDocs iterator entsprechen.

Dieser Beispielcode durchläuft jeden "Kategorie" -Begriff und zählt dann die Anzahl der Dokumente, die mit diesem Begriff übereinstimmen.

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

Dieser Code sollte auch für große Indizes einigermaßen schnell ausgeführt werden.

Hier ist ein Code, der diese Methode testet: (!)

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

Dies zählt nur die Dokumente, die von jedem Begriff im Feld Category markiert sind, was Sie mit terms.docFreq() viel schneller machen könnten. Was fehlt, ist die Schnittmenge mit den Treffern aus den Suchkriterien des Nutzers. – erickson

8

Ich habe nicht genug Ruf zu kommentieren, aber in Matt Wachtel Antwort, die ich bin mir ziemlich sicher, dass Sie diese ersetzen könnten:

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

mit diesem:

int numDocs = terms.docFreq() 

und dann ganz der td Variable loszuwerden. Dies sollte es noch schneller machen.

+0

Sie werden in kürzester Zeit da sein (kommentieren) – mattlant

+0

Ich habe das getan, aber es zählt aus allen Dokumenten, in meinem Fall möchte ich die Kategorie aus einer Ergebnismenge zählen. zum Beispiel, wenn der Benutzer nach "Apfel" sucht, dann möchte ich die Anzahl der gefundenen Übereinstimmungen in der Kategorie Elektronik und Früchte anzeigen. aber dein und matter Vorschlag zählt für alle Dokumente. Ich denke, ich muss eher nach meinem Sucher als nach dem Leser suchen, aber der Sucher hat keine TermDocs. –

0

So lassen Sie mich sehen, wenn ich die Frage richtig verstehe: Gegeben eine Abfrage vom Benutzer, möchten Sie zeigen, wie viele Übereinstimmungen es für die Abfrage in jeder Kategorie gibt. Richtig?

Denken Sie so: Ihre Abfrage ist eigentlich originalQuery AND (category1 OR category2 or ...) außer eine Gesamtpunktzahl, die Sie eine Nummer für jede der Kategorien erhalten möchten. Leider ist die Oberfläche für das Sammeln von Treffern in Lucene sehr eng, sodass Sie nur eine Gesamtpunktzahl für eine Suchanfrage erhalten. Sie könnten jedoch einen benutzerdefinierten Scorer/Collector implementieren.

Werfen Sie einen Blick auf die Quelle für org.apache.lucene.search.DisjunctionSumScorer. Sie könnten etwas davon kopieren, um einen benutzerdefinierten Scorer zu schreiben, der während der Hauptsuche durch Kategorieübereinstimmungen iteriert. Und Sie können eine Map<String,Long> behalten, um die Spiele in jeder Kategorie zu verfolgen.

9

Hier ist, was ich getan habe, obwohl es ein bisschen schwer auf dem Gedächtnis ist:

Was Sie brauchen, ist im Voraus ein paar BitSet s, ein für jede Kategorie zu erstellen, die doc-ID aller Dokumente in einer Aufnahme Kategorie. Jetzt verwenden Sie in der Suchzeit eine HitCollector und überprüfen die Doc-IDs gegen die BitSets.

Hier ist der Code, um die Bit-Sätze zu erstellen:

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

Dies ist nur eine Möglichkeit, dies zu tun. Sie könnten wahrscheinlich TermDocs anstelle einer vollständigen Suche verwenden, wenn Ihre Kategorien einfach genug sind, aber dies sollte nur einmal ausgeführt werden, wenn Sie den Index trotzdem laden.

Nun, wenn es Zeit ist, Kategorien Suche zu zählen Ergebnisse, die Sie dies tun:

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

Was Sie am Ende mit einem Array ist die Anzahl der verschiedenen Kategorien in den Suchergebnissen enthält. Wenn Sie auch die Suchergebnisse benötigen, sollten Sie Ihrem Hit-Collector einen TopDocCollector hinzufügen (yo dawg ...). Oder Sie können die Suche einfach erneut ausführen. 2 Suchen sind besser als 30.

+1

Andere Implementierung für den getCategoryCount-Teil: Sie könnten tatsächlich ein BitSet aus Ihrer Suche (mit einem Kollektor) erhalten und dann das ErgebnisBitSet mit dem gewünschten CategoryBitSet schneiden. Schnittpunkt sollte schneller sein als jedes Dokument überprüfen, und Sie können auch schneiden mehrere Kategorien vor dem Schnitt mit den Ergebnissen BitSet. –

2

Sachin, ich glaube, Sie wollen faceted search. Es kommt nicht mit Lucene aus der Box. Ich schlage vor, Sie versuchen mit SOLR, die faceting als eine wichtige und praktische Funktion hat.