Lucene Query (с черепицей?) - PullRequest
       3

Lucene Query (с черепицей?)

1 голос
/ 06 января 2012

У меня есть индекс Lucene, содержащий такие документы:

_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

Какой тип запроса Lucene лучше всего использовать и как его структурировать, учитывая, что мне нужно следующее:

  1. Если пользователь запрашивает: «Итальянский ресторан возле Бош-де-Планавилла», я хочу, чтобы был возвращен документ с идентификатором 123, поскольку он содержит точное совпадение с именем документа.

  2. Если пользователь запрашивает: «Итальянский ресторан рядом с Планавиллой», я хочу документ с идентификатором 345, поскольку запрос содержит точное совпадение и имеет наибольшую численность населения.

  3. Если пользователь запрашиваетдля «Итальянского ресторана возле Боск» я хочу 567, потому что запрос содержит «Боск» И из 3 «Боск» он имеет наибольшую популярность.

, вероятно, есть много других вариантов использования ..... но вы чувствуете, что мне нужно ...

Какой запрос мне поможет?Должен ли я генерировать слова N граммов (черепица) и создавать логический запрос ORed с использованием черепицы, а затем применять пользовательский скоринг?или подойдет обычный запрос фразы?Я также видел DisjunctionMaxQuery, но не знаю, что именно он ищет ...

Идея, как вы, возможно, уже поняли, состоит в том, чтобы найти точное местоположение, подразумеваемое пользователем в его запросе.С этого момента я могу начать поиск по Geo и добавить дополнительные запросы по этому поводу.

Какой лучший подход?

Заранее спасибо.

Ответы [ 2 ]

1 голос
/ 13 января 2012

Вот и код для сортировки.Хотя я думаю, что было бы более разумно добавить пользовательский рейтинг, учитывающий размер города, а не грубую сортировку населения.Также обратите внимание, что здесь используется FieldCache, который может быть не лучшим решением в отношении использования памяти.

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

Это дает следующие результаты:

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
1 голос
/ 13 января 2012

Как вы токенизируете поля?Вы храните их как полную строку?Кроме того, как вы анализируете запрос?

Хорошо, так что я немного поиграюсь с этим.Я использовал StopFilter для удаления la, en, de.Затем я использовал фильтр гальки, чтобы получить несколько комбинаций, чтобы сделать «точные совпадения».Так, например, Боск де Планавилла получает жетоны как [Боск] [Боск Планавилла], а Боск де Плана ан Бланка получает жетоны к [Боск] [Боск Плана] [Плана Бланка] [Боск Плана Бланка].Это сделано для того, чтобы у вас могли быть "точные совпадения" по частям запроса.

Затем я запрашиваю точную строку, которую передал пользователь, хотя там также может быть некоторая адаптация.Я использовал простой случай, чтобы результаты лучше соответствовали тому, что вы искали.

Вот код, который я использую (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();
    }
}

Я сейчас ищув сортировку по населению.

Это распечатывает:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...