2012-01-06 4 views
1

J'ai des documents contenant des index Lucene comme ceux-ci:Lucene requête (avec le zona?)

_id  |   Name   |  Alternate Names  | Population 

123  Bosc de Planavilla    (some names here in   5000 
345  Planavilla      other languages)    20000 
456  Bosc de la Planassa           1000 
567  Bosc de Plana en Blanca          100000 

Quel est le meilleur type de requête Lucene je devrais utiliser et comment dois-je structurer considérant que je besoin des éléments suivants:

  1. Si une requêtes utilisateur pour: « restaurant italien près de Bosc de Planavilla » Je veux document identifiant 123 à retourner car son contient une correspondance exacte avec le nom du doc.

  2. Si une requêtes utilisateur pour: « restaurant italien près Planavilla » Je veux document identifiant 345 parce que la requête contient une correspondance exacte et il a la plus forte population. Si un utilisateur demande "Restaurant italien près de Bosc" Je veux 567 parce que la requête contient "Bosc" ET de la 3 "Bosc" il a le plus pop.

il y a probablement beaucoup d'autres cas d'utilisation ... mais vous avez le sentiment de ce que je dois ...

Quel genre de requête fera ce formulaire moi? Dois-je générer un mot N grammes (shingles) et créer une requête booléenne ORed à l'aide des bardeaux, puis appliquer un scoring personnalisé? ou une requête de phrase régulière fera-t-elle l'affaire? J'ai aussi vu DisjunctionMaxQuery mais je ne sais pas si c'est ce que je cherche ...

L'idée, comme vous l'avez peut-être compris maintenant, est de trouver la position exacte qu'un utilisateur a impliqué dans sa requête. A partir de là, je peux commencer ma recherche Geo et ajouter d'autres questions autour de cela.

Quelle est la meilleure approche?

Merci d'avance.

Répondre

1

Voici le code pour le tri ainsi.Bien que je pense qu'il serait plus logique d'ajouter une notation personnalisée en tenant compte de la taille de la ville plutôt que de forcer la sorte sur la population. Veuillez également noter que ceci utilise le FieldCache, qui n'est peut-être pas la meilleure solution en ce qui concerne l'utilisation de la mémoire.

public class ShingleFilterTests { 
    private Analyzer analyzer; 
    private IndexSearcher searcher; 
    private IndexReader reader; 
    private QueryParser qp; 
    private Sort sort; 

    public static Analyzer createAnalyzer(final int shingles) { 
     return new Analyzer() { 
      @Override 
      public TokenStream tokenStream(String fieldName, Reader reader) { 
       TokenStream tokenizer = new WhitespaceTokenizer(reader); 
       tokenizer = new StopFilter(false, tokenizer, ImmutableSet.of("de", "la", "en")); 
       if (shingles > 0) { 
        tokenizer = new ShingleFilter(tokenizer, shingles); 
       } 
       return tokenizer; 
      } 
     }; 
    } 

    public class PopulationComparatorSource extends FieldComparatorSource { 
     @Override 
     public FieldComparator newComparator(String fieldname, int numHits, int sortPos, boolean reversed) throws IOException { 
      return new PopulationComparator(fieldname, numHits); 
     } 

     private class PopulationComparator extends FieldComparator { 
      private final String fieldName; 
      private Integer[] values; 
      private int[] populations; 
      private int bottom; 

      public PopulationComparator(String fieldname, int numHits) { 
       values = new Integer[numHits]; 
       this.fieldName = fieldname; 
      } 

      @Override 
      public int compare(int slot1, int slot2) { 
       if (values[slot1] > values[slot2]) return -1; 
       if (values[slot1] < values[slot2]) return 1; 
       return 0; 
      } 

      @Override 
      public void setBottom(int slot) { 
       bottom = values[slot]; 
      } 

      @Override 
      public int compareBottom(int doc) throws IOException { 
       int value = populations[doc]; 
       if (bottom > value) return -1; 
       if (bottom < value) return 1; 
       return 0; 
      } 

      @Override 
      public void copy(int slot, int doc) throws IOException { 
       values[slot] = populations[doc]; 
      } 

      @Override 
      public void setNextReader(IndexReader reader, int docBase) throws IOException { 
       /* XXX uses field cache */ 
       populations = FieldCache.DEFAULT.getInts(reader, "population"); 
      } 

      @Override 
      public Comparable value(int slot) { 
       return values[slot]; 
      } 
     } 
    } 

    @Before 
    public void setUp() throws Exception { 
     Directory dir = new RAMDirectory(); 
     analyzer = createAnalyzer(3); 

     IndexWriter writer = new IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED); 
     ImmutableList<String> cities = ImmutableList.of("Bosc de Planavilla", "Planavilla", "Bosc de la Planassa", 
                   "Bosc de Plana en Blanca"); 
     ImmutableList<Integer> populations = ImmutableList.of(5000, 20000, 1000, 100000); 

     for (int id = 0; id < cities.size(); id++) { 
      Document doc = new Document(); 
      doc.add(new Field("id", String.valueOf(id), Field.Store.YES, Field.Index.NOT_ANALYZED)); 
      doc.add(new Field("city", cities.get(id), Field.Store.YES, Field.Index.ANALYZED)); 
      doc.add(new Field("population", String.valueOf(populations.get(id)), 
            Field.Store.YES, Field.Index.NOT_ANALYZED)); 
      writer.addDocument(doc); 
     } 
     writer.close(); 

     qp = new QueryParser(Version.LUCENE_30, "city", createAnalyzer(0)); 
     sort = new Sort(new SortField("population", new PopulationComparatorSource())); 
     searcher = new IndexSearcher(dir); 
     searcher.setDefaultFieldSortScoring(true, true); 
     reader = searcher.getIndexReader(); 
    } 

    @After 
    public void tearDown() throws Exception { 
     searcher.close(); 
    } 

    @Test 
    public void testShingleFilter() throws Exception { 
     System.out.println("shingle filter"); 

     printSearch("city:\"Bosc de Planavilla\""); 
     printSearch("city:Planavilla"); 
     printSearch("city:Bosc"); 
    } 

    private void printSearch(String query) throws ParseException, IOException { 
     Query q = qp.parse(query); 
     System.out.println("query " + q); 
     TopDocs hits = searcher.search(q, null, 4, sort); 
     System.out.println("results " + hits.totalHits); 
     int i = 1; 
     for (ScoreDoc dc : hits.scoreDocs) { 
      Document doc = reader.document(dc.doc); 
      System.out.println(i++ + ". " + dc + " \"" + doc.get("city") + "\" population: " + doc.get("population")); 
     } 
     System.out.println(); 
    } 
} 

Cela donne les résultats suivants:

query city:"Bosc Planavilla" 
results 1 
1. doc=0 score=1.143841[5000] "Bosc de Planavilla" population: 5000 

query city:Planavilla 
results 2 
1. doc=1 score=1.287682[20000] "Planavilla" population: 20000 
2. doc=0 score=0.643841[5000] "Bosc de Planavilla" population: 5000 

query city:Bosc 
results 3 
1. doc=3 score=0.375[100000] "Bosc de Plana en Blanca" population: 100000 
2. doc=0 score=0.5[5000] "Bosc de Planavilla" population: 5000 
3. doc=2 score=0.5[1000] "Bosc de la Planassa" population: 1000 
+0

Merci beaucoup! Votre approche est similaire à celle avec laquelle je me suis retrouvé et elle donne de bons résultats. Mais ce n'est pas parfait ... sur un index de 3 millions de doc, j'obtiens des réponses aussi hautes qu'une seconde (sur une seule machine). De plus, j'obtiens souvent des bizarreries comme lors de la recherche de "Indian Bar Paris" qui renvoie la "Réserve indienne Rich Bar" qui n'est pas vraiment ce qui était prévu :). Je vais essayer de l'affiner un peu plus en utilisant le scoring et peut-être l'indexation du temps en fonction du type de fonction si possible. Merci pour ton aide ! – azpublic

+0

1 seconde des sons pour 3million de documents sons beaucoup trop. Comment allez-vous trier? Vous pouvez utiliser un profileur pour vérifier où va le processeur. Je suis à la recherche d'un index de 40 millions de documents avec des requêtes compliquées et la facette et le tri personnalisé en environ 70 ms. – wesen

1

Comment numériser les champs? Les stockez-vous en tant que chaîne complète? Aussi, comment analysez-vous la requête? Ok, donc je joue un peu avec ça. J'ai utilisé un StopFilter pour supprimer la, en, de. J'ai ensuite utilisé un filtre de bardeaux pour obtenir une combinaison multiple afin de faire les "correspondances exactes". Ainsi par exemple, Bosc de Planavilla est symbolisé par [Bosc] [Bosc Planavilla] et Bosc de Plana en Blanca se transforme en [Bosc] [Bosc Plana] [Plana Blanca] [Bosc Plana Blanca]. C'est ainsi que vous pouvez avoir des "correspondances exactes" sur des parties de la requête. Je demande ensuite la chaîne exacte que l'utilisateur a transmise, bien qu'il puisse y avoir une certaine adaptation là aussi. Je suis allé avec le cas simple pour que les résultats correspondent mieux à ce que vous cherchiez.

Code est J'utilise ici (Lucene 3.0.3):

public class ShingleFilterTests { 
    private Analyzer analyzer; 
    private IndexSearcher searcher; 
    private IndexReader reader; 

    public static Analyzer createAnalyzer(final int shingles) { 
     return new Analyzer() { 
      @Override 
      public TokenStream tokenStream(String fieldName, Reader reader) { 
       TokenStream tokenizer = new WhitespaceTokenizer(reader); 
       tokenizer = new StopFilter(false, tokenizer, ImmutableSet.of("de", "la", "en")); 
       if (shingles > 0) { 
        tokenizer = new ShingleFilter(tokenizer, shingles); 
       } 
       return tokenizer; 
      } 
     }; 
    } 

    @Before 
    public void setUp() throws Exception { 
     Directory dir = new RAMDirectory(); 
     analyzer = createAnalyzer(3); 

     IndexWriter writer = new IndexWriter(dir, analyzer, IndexWriter.MaxFieldLength.UNLIMITED); 
     ImmutableList<String> cities = ImmutableList.of("Bosc de Planavilla", "Planavilla", "Bosc de la Planassa", 
                   "Bosc de Plana en Blanca"); 
     ImmutableList<Integer> populations = ImmutableList.of(5000, 20000, 1000, 100000); 

     for (int id = 0; id < cities.size(); id++) { 
      Document doc = new Document(); 
      doc.add(new Field("id", String.valueOf(id), Field.Store.YES, Field.Index.NOT_ANALYZED)); 
      doc.add(new Field("city", cities.get(id), Field.Store.YES, Field.Index.ANALYZED)); 
      doc.add(new Field("population", String.valueOf(populations.get(id)), 
            Field.Store.YES, Field.Index.NOT_ANALYZED)); 
      writer.addDocument(doc); 
     } 
     writer.close(); 

     searcher = new IndexSearcher(dir); 
     reader = searcher.getIndexReader(); 
    } 

    @After 
    public void tearDown() throws Exception { 
     searcher.close(); 
    } 

    @Test 
    public void testShingleFilter() throws Exception { 
     System.out.println("shingle filter"); 

     QueryParser qp = new QueryParser(Version.LUCENE_30, "city", createAnalyzer(0)); 

     printSearch(qp, "city:\"Bosc de Planavilla\""); 
     printSearch(qp, "city:Planavilla"); 
     printSearch(qp, "city:Bosc"); 
    } 

    private void printSearch(QueryParser qp, String query) throws ParseException, IOException { 
     Query q = qp.parse(query); 

     System.out.println("query " + q); 
     TopDocs hits = searcher.search(q, 4); 
     System.out.println("results " + hits.totalHits); 
     int i = 1; 
     for (ScoreDoc dc : hits.scoreDocs) { 
      Document doc = reader.document(dc.doc); 
      System.out.println(i++ + ". " + dc + " \"" + doc.get("city") + "\" population: " + doc.get("population")); 
     } 
     System.out.println(); 
    } 
} 

Je suis maintenant en tri par population.

Ceci affiche:

query city:"Bosc Planavilla" 
results 1 
1. doc=0 score=1.143841 "Bosc de Planavilla" population: 5000 

query city:Planavilla 
results 2 
1. doc=1 score=1.287682 "Planavilla" population: 20000 
2. doc=0 score=0.643841 "Bosc de Planavilla" population: 5000 

query city:Bosc 
results 3 
1. doc=0 score=0.5 "Bosc de Planavilla" population: 5000 
2. doc=2 score=0.5 "Bosc de la Planassa" population: 1000 
3. doc=3 score=0.375 "Bosc de Plana en Blanca" population: 100000 
+0

Nous vous remercions de votre Wesen vient de réponse. En fait, le champ de nom est indexé à l'aide d'un standard token, avec un filtre de jeton standard, un filtre de jeton minuscule et un filtre de jetons d'arrêt. Mais cela peut facilement être changé. Ma question est en fait aussi comment dois-je indexer et interroger parse? – azpublic