Lucene Index - запрос одного термина и фразы - PullRequest
1 голос
/ 28 марта 2012

Я прочитал некоторые документы и построил индекс lucene, который выглядит как

Документы:

id        1
keyword   foo bar
keyword   john

id        2
keyword   foo

id        3
keyword   john doe
keyword   bar foo
keyword   what the hell

Я хочу запросить lucene таким образом, чтобы я мог объединить один термин ифразы

Допустим, мой запрос

foo bar

должен вернуть идентификаторы документов 1, 2 и 3

Запрос

"foo bar"

должен вернуть идентификаторы документов 1

запрос

john

должен вернуть идентификаторы документов 1 и 3

запрос

john "foo bar"

должен вернуть идентификаторы документов 1

Моя реализация в Java не работает.Также не помогло чтение тонны документов.

Когда я запрашиваю свой индекс с помощью

"foo bar"

, я получаю 0 обращений

Когда я запрашиваю свой индекс с помощью

foo "john doe"

Я возвращаю идентификаторы документов 1, 2 и 3 (я бы ожидал только идентификатор документа 3, так как запрос подразумевается как foo И «Джон Доу»). Проблема в том, что «Джон Доу» возвращает 0хиты, но foo возвращает 3 хита.

Моя цель - объединить отдельные термины и фразы.Что я делаю неправильно?Я также безуспешно играл с анализаторами.

Моя реализация выглядит следующим образом:

Indexer

  import ...

  public class Indexer
  {
    private static final Logger LOG = LoggerFactory.getLogger(Indexer.class);

    private final File indexDir;

    private IndexWriter writer;

    public Indexer(File indexDir)
    {
    this.indexDir = indexDir;
    this.writer = null;
  }

  private IndexWriter createIndexWriter()
  {
    try
    {
      Directory dir = FSDirectory.open(indexDir);
      Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_34);
      IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_34, analyzer);
      iwc.setOpenMode(OpenMode.CREATE_OR_APPEND);
      iwc.setRAMBufferSizeMB(256.0);
      IndexWriter idx = new IndexWriter(dir, iwc);
      idx.deleteAll();
      return idx;
    } catch (IOException e)
    {
      throw new RuntimeException(String.format("Could create indexer on directory [%s]", indexDir.getAbsolutePath()), e);
    }
  }

  public void index(TestCaseDescription desc)
  {
    if (writer == null)
      writer = createIndexWriter();

    Document doc = new Document();
    addPathToDoc(desc, doc);
    addLastModifiedToDoc(desc, doc);
    addIdToDoc(desc, doc);
    for (String keyword : desc.getKeywords())
      addKeywordToDoc(doc, keyword);

    updateIndex(doc, desc);
  }

  private void addIdToDoc(TestCaseDescription desc, Document doc)
  {
    Field idField = new Field(LuceneConstants.FIELD_ID, desc.getId(), Field.Store.YES, Field.Index.ANALYZED);
    idField.setIndexOptions(IndexOptions.DOCS_ONLY);
    doc.add(idField);
  }

  private void addKeywordToDoc(Document doc, String keyword)
  {
    Field keywordField = new Field(LuceneConstants.FIELD_KEYWORDS, keyword, Field.Store.YES, Field.Index.ANALYZED);
    keywordField.setIndexOptions(IndexOptions.DOCS_ONLY);
    doc.add(keywordField);
  }

  private void addLastModifiedToDoc(TestCaseDescription desc, Document doc)
  {
    NumericField modifiedField = new NumericField(LuceneConstants.FIELD_LAST_MODIFIED);
    modifiedField.setLongValue(desc.getLastModified());
    doc.add(modifiedField);
  }

  private void addPathToDoc(TestCaseDescription desc, Document doc)
  {
    Field pathField = new Field(LuceneConstants.FIELD_PATH, desc.getPath(), Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
    pathField.setIndexOptions(IndexOptions.DOCS_ONLY);
    doc.add(pathField);
  }

  private void updateIndex(Document doc, TestCaseDescription desc)
  {
    try
    {
      if (writer.getConfig().getOpenMode() == OpenMode.CREATE)
      {
        // New index, so we just add the document (no old document can be there):
        LOG.debug(String.format("Adding testcase [%s] (%s)", desc.getId(), desc.getPath()));
        writer.addDocument(doc);
      } else
      {
        // Existing index (an old copy of this document may have been indexed) so
        // we use updateDocument instead to replace the old one matching the exact
        // path, if present:
        LOG.debug(String.format("Updating testcase [%s] (%s)", desc.getId(), desc.getPath()));
        writer.updateDocument(new Term(LuceneConstants.FIELD_PATH, desc.getPath()), doc);
      }
    } catch (IOException e)
    {
      throw new RuntimeException(String.format("Could not create or update index for testcase [%s] (%s)", desc.getId(),
          desc.getPath()), e);
    }
  }

  public void store()
  {
    try
    {
      writer.close();
    } catch (IOException e)
    {
      throw new RuntimeException(String.format("Could not write index [%s]", writer.getDirectory().toString()));
    }
    writer = null;
  }
}

Searcher:

import ...

public class Searcher
{
  private static final Logger LOG = LoggerFactory.getLogger(Searcher.class);

  private final Analyzer analyzer;

  private final QueryParser parser;

  private final File indexDir;

  public Searcher(File indexDir)
  {
    this.indexDir = indexDir;
    analyzer = new StandardAnalyzer(Version.LUCENE_34);
    parser = new QueryParser(Version.LUCENE_34, LuceneConstants.FIELD_KEYWORDS, analyzer);
    parser.setAllowLeadingWildcard(true);
  }

  public List<String> search(String searchString)
  {
    List<String> testCaseIds = new ArrayList<String>();
    try
    {
      IndexSearcher searcher = getIndexSearcher(indexDir);

      Query query = parser.parse(searchString);
      LOG.info("Searching for: " + query.toString(parser.getField()));
      AllDocCollector results = new AllDocCollector();
      searcher.search(query, results);

      LOG.info("Found [{}] hit", results.getHits().size());

      for (ScoreDoc scoreDoc : results.getHits())
      {
        Document doc = searcher.doc(scoreDoc.doc);
        String id = doc.get(LuceneConstants.FIELD_ID);
        testCaseIds.add(id);
      }

      searcher.close();
      return testCaseIds;
    } catch (Exception e)
    {
      throw new RuntimeException(String.format("Could not search index [%s]", indexDir.getAbsolutePath()), e);
    }

  }

  private IndexSearcher getIndexSearcher(File indexDir)
  {
    try
    {
      FSDirectory dir = FSDirectory.open(indexDir);
      return new IndexSearcher(dir);
    } catch (IOException e)
    {
      LOG.error(String.format("Could not open index directory [%s]", indexDir.getAbsolutePath()), e);
      throw new RuntimeException(e);
    }
  }
}

Ответы [ 3 ]

3 голосов
/ 28 марта 2012

Почему вы используете DOCS_ONLY ?! Если вы индексируете только документы, то у вас есть только базовый инвертированный индекс с отображениями term-> document, но нет информации о близости. Вот почему ваши запросы фраз не работают.

0 голосов
/ 29 марта 2012

Проблема была решена заменой

StandardAnalyzer

с

KeywordAnalyzer

для индексатора и поисковика.

Поскольку я смог указать, что StandardAnalyzer разбивает вводимый текст на несколько слов, я заменил его на KeywordAnalyzer, поскольку ввод (который может состоять из одного или нескольких слов) останется нетронутым. Он распознает такой термин, как

bla foo

как одно ключевое слово.

0 голосов
/ 28 марта 2012

Я думаю, вы примерно хотите:

keyword:"foo bar"~1^2 OR keyword:"foo" OR keyword:"bar"

То есть фраза соответствует «foo bar» и увеличивает ее (предпочитают полную фразу), ИЛИ соответствует «foo», ИЛИ соответствует «bar».

Полный синтаксис запроса здесь: http://lucene.apache.org/core/old_versioned_docs/versions/3_0_0/queryparsersyntax.html

EDIT:

Похоже, вы упускаете одну вещь - оператор по умолчанию OR. Так что вы, вероятно, хотите сделать что-то вроде этого:

+keyword:john AND +keyword:"foo bar"

Знак плюс означает «должен содержать». Вы ставите AND в явном виде, так что документ должен содержать и то, и другое (скорее, по умолчанию, что означает «должен содержать john ИЛИ должен содержать« foo bar »).

...