如何从Lucene的特定字段中获取唯一术语列表?

10

我有一个包含多个字段的大型语料库索引。只有其中一个字段包含文本。 我需要基于该字段从整个索引中提取唯一的单词。 请问有人知道如何在Java的Lucene中实现这个功能吗?

6个回答

28
如果您正在使用Lucene 4.0 API,您需要从索引读取器中获取字段。然后,Fields提供了获取索引中每个字段的词语的方法。以下是如何执行此操作的示例:

如果您正在使用Lucene 4.0 API,您需要从索引读取器中获取字段。然后,Fields提供了获取索引中每个字段的词语的方法。以下是如何执行此操作的示例:

        Fields fields = MultiFields.getFields(indexReader);
        Terms terms = fields.terms("field");
        TermsEnum iterator = terms.iterator(null);
        BytesRef byteRef = null;
        while((byteRef = iterator.next()) != null) {
            String term = new String(byteRef.bytes, byteRef.offset, byteRef.length);

        }

最终,对于Lucene的新版本,您可以通过调用BytesRef获取字符串:

       byteRef.utf8ToString();

代替

       new String(byteRef.bytes, byteRef.offset, byteRef.length);

如果您想获得文档频率,可以执行:

       int docFreq = iterator.docFreq();

这是Lucene现代版本的正确答案。 - Robert

10
你正在寻找术语向量(一个包含字段中所有单词及其使用次数的集合,不包括停用词)。你将使用IndexReader的getTermFreqVector(docid, field)获取索引中每个文档的向量,并将它们填充到一个HashSet中。
另一种选择是使用terms()并只选择你感兴趣的字段的术语。
IndexReader reader = IndexReader.open(index);
TermEnum terms = reader.terms();
Set<String> uniqueTerms = new HashSet<String>();
while (terms.next()) {
        final Term term = terms.term();
        if (term.field().equals("field_name")) {
                uniqueTerms.add(term.text());
        }
}

这不是最优解决方案,您正在阅读并且丢弃所有其他字段。在Lucene 4中有一个名为Fields的类,仅返回单个字段的terms(field)


我知道这个函数。但是我需要整个语料库中的唯一单词,而不是每个文档的单词。 - Hossein
是的,谢谢。我正在使用terms(),但它返回了所有被索引过的内容。我找不到设置它仅选择特定字段中的术语的地方。你有相关的参考资料吗? - Hossein
2
即使在Lucene 3上,您也不必扫描所有字段的术语来实现此目的。这似乎没有记录,但是reader.terms(new Term(fieldName, termText))将按字段名称排序返回Term,并且在相同字段的术语中按术语文本排序。因此,如果您在上面使用了terms.term(fieldName, ""),并且在第一次出现!term.field().equals(fieldName)时进行break,则会得到所需的结果。但是,由于这没有记录,它是否会有一天崩溃呢?就我所看到的,Lucene自己的WildcardQuery也是基于此构建的,而且Lucene 3不太可能再发生太大变化了。 - ddekany
@basZero 我不记得为什么,但看代码来看,这应该是出于性能和代码可读性的考虑。在我看来,变量默认应该是 final 的。 - milan
术语“向量”可能存储,也可能不存储,这并不是问题的意思。 - pokeRex110
显示剩余2条评论

3

同样的结果,只是更加简洁,可以使用lucene-suggest包中的LuceneDictionary。它会处理不包含任何术语的字段,并返回一个BytesRefIterator.EMPTY。这将避免出现NPE错误 :)

    LuceneDictionary ld = new LuceneDictionary( indexReader, "field" );
    BytesRefIterator iterator = ld.getWordsIterator();
    BytesRef byteRef = null;
    while ( ( byteRef = iterator.next() ) != null )
    {
        String term = byteRef.utf8ToString();
    }

同时,getWordsIterator()被替换为getEntryIterator()。 - rmuller
1
这种方法的缺点是需要使用lucene-suggest包。因此,如果不使用它,我更喜欢@pokeRex110提出的解决方案。 - rmuller

2

从Lucene 7+开始,上述链接以及一些相关链接已经过时。

以下是当前的内容:

// IndexReader has leaves, you'll iterate through those
int leavesCount = reader.leaves().size();
final String fieldName = "content";

for(int l = 0; l < leavesCount; l++) {
  System.out.println("l: " + l);
  // specify the field here ----------------------------->
  TermsEnum terms = reader.leaves().get(l).reader().terms(fieldName).iterator();
  // this stops at 20 just to sample the head
  for(int i = 0; i < 20; i++) {
    // and to get it out, here -->
    final Term content = new Term(fieldName, BytesRef.deepCopyOf(terms.next()));
    System.out.println("i: " + i + ", term: " + content);
  }
}

1
答案使用 TermsEnumterms.next() 存在微妙的 off by one bug。这是因为 TermsEnum 已经指向第一个 term,所以 while(terms.next()) 会导致第一个 term 被跳过。
相反,请使用 for 循环:
TermEnum terms = reader.terms();
for(Term term = terms.term(); term != null; terms.next(), term = terms.term()) {
    // do something with the term
}

修改已接受答案中的代码:

IndexReader reader = IndexReader.open(index);
TermEnum terms = reader.terms();
Set<String> uniqueTerms = new HashSet<String>();
for(Term term = terms.term(); term != null; terms.next(), term = terms.term()) {
        if (term.field().equals("field_name")) {
                uniqueTerms.add(term.text());
        }
}

0
与 @pokeRex110 的解决方案略有不同(已在 Lucene 9.3.0 上测试)。
Terms terms = MultiTerms.getTerms(indexReader, "title");
if (terms != null) {
    TermsEnum iter = terms.iterator();
    BytesRef byteRef = null;
    while ((byteRef = iter.next()) != null) {
        System.out.printf("%s (freq=%s)%n", 
            byteRef.utf8ToString(), 
            iter.docFreq()
        );
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接