/* * Copyright 2011-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.lucene.analysis.kr.freq; import jodd.util.collection.SortedArrayList; import lombok.extern.slf4j.Slf4j; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.Term; import org.apache.lucene.index.TermDocs; import org.apache.lucene.index.TermEnum; import java.io.IOException; import java.util.Arrays; import java.util.Comparator; /** * 빈도수가 높은 Terms 를 조회합니다. * * @author 배성혁 sunghyouk.bae@gmail.com * @since 13. 5. 30. 오후 9:10 */ @Slf4j public class HighFreqTerms { /** 빈도수에 따른 조회 Term의 기본 갯수 */ public static final int DEFAULT_TERMS_NUM = 100; /** * 인덱스 파일로부터 빈도수가 높은 단어들을 조회합니다. * * @param readers 인덱스 리더 * @param numTerms 조회할 단어의 최대 갯수 * @param field 엔티티의 필드 (null이면 모든 필드에서 찾는다) * @return 빈도수가 높은 단어들의 배열 * @throws java.io.IOException 인덱스 파일 처리 실패 시 */ public static TermFreq[] getHighFreqTerms(IndexReader[] readers, int numTerms, String field) throws IOException { HighFreqTermQueue queue = new HighFreqTermQueue(numTerms); if (field != null) { for (IndexReader reader : readers) { TermEnum terms = reader.terms(new Term(field)); if (terms != null && terms.term() != null) { do { if (!terms.term().field().equals(field)) break; queue.insertWithOverflow(new TermFreq(terms.term(), terms.docFreq())); } while (terms.next()); } } } else { for (IndexReader reader : readers) { TermEnum terms = reader.terms(); if (terms != null) { while (terms.next()) { queue.insertWithOverflow(new TermFreq(terms.term(), terms.docFreq())); } } } } TermFreq[] result = new TermFreq[queue.size()]; result = queue.toArray(result); return result; } /** * 인덱스 파일로부터 빈도수가 높은 단어들을 조회합니다. * * @param reader 인덱스 리더 * @param numTerms 조회할 단어의 최대 갯수 * @param field 엔티티의 필드 (null이면 모든 필드에서 찾는다) * @return 빈도수가 높은 단어들의 배열 * @throws java.io.IOException 인덱스 파일 처리 실패 시 */ public static TermFreq[] getHighFreqTerms(IndexReader reader, int numTerms, String field) throws IOException { HighFreqTermQueue queue = new HighFreqTermQueue(numTerms); if (field != null) { TermEnum terms = reader.terms(new Term(field)); if (terms != null && terms.term() != null) { do { if (!terms.term().field().equals(field)) { break; } queue.insertWithOverflow(new TermFreq(terms.term(), terms.docFreq())); } while (terms.next()); } else { log.info("No terms for field=[{}]", field); } } else { TermEnum terms = reader.terms(); if (terms != null) { while (terms.next()) { queue.insertWithOverflow(new TermFreq(terms.term(), terms.docFreq())); } } } TermFreq[] result = new TermFreq[queue.size()]; result = queue.toArray(result); return result; } /** * 검색한 단어의 빈도 정보를 전체 빈도 수에 따라 역순으로 정렬합니다. * * @param reader 인덱스 리더 * @param termFreqs 단어/빈도수 정보 */ public static TermFreq[] sortByTotalTermFreq(IndexReader reader, TermFreq[] termFreqs) throws Exception { TermFreq[] tfs = new TermFreq[termFreqs.length]; long totalTermFreq; for (int i = 0; i < termFreqs.length; i++) { totalTermFreq = getTotalTermFreq(reader, termFreqs[i].term); tfs[i] = new TermFreq(termFreqs[i].term, termFreqs[i].docFreq, totalTermFreq); } Comparator<TermFreq> c = new TotalTermFreqComparatorSortDescending(); Arrays.sort(tfs, c); return tfs; } /** * 해당 단어가 들어간 문서들에서 모든 빈도 수 (문서의 수가 아닌)를 계산합니다. * * @param reader 인덱스 리더 * @param term 단어 * @return 단어의 총 빈도 수 ( 문서 내의 모든 빈도 수의 합 ) * @throws Exception */ public static long getTotalTermFreq(IndexReader reader, Term term) throws Exception { long totalTermFreq = 0; TermDocs docs = reader.termDocs(term); while (docs.next()) { totalTermFreq += docs.freq(); } return totalTermFreq; } /** 빈도수가 높은 순으로 저장하는 큐입니다 */ public static class HighFreqTermQueue extends SortedArrayList<TermFreq> { private final int capacity; public HighFreqTermQueue(int capacity) { super(new DocFreqComparatorDescending()); this.capacity = capacity; } public void insertWithOverflow(TermFreq termFreq) { int index = indexOf(termFreq); if (index >= 0) { termFreq.docFreq += get(index).docFreq; remove(index); add(termFreq); } else { add(termFreq); if (size() > capacity) { remove(size() - 1); } } } private static final long serialVersionUID = 7262421000814128023L; } public static class DocFreqComparatorDescending implements Comparator<TermFreq> { @Override public int compare(TermFreq a, TermFreq b) { return b.docFreq - a.docFreq; } } /** 전체 빈도수의 역순 (최다 빈도수가 상위에 오도록) 으로 정렬할 수 있도록 하는 비교자입니다. */ public static class TotalTermFreqComparatorSortDescending implements Comparator<TermFreq> { @Override public int compare(TermFreq a, TermFreq b) { if (a.totalTermFreq < b.totalTermFreq) { return 1; } else if (a.totalTermFreq > b.totalTermFreq) { return -1; } else { return 0; } } } }