全文検索エンジンLuceneで自作のAnalyzer/Tokenizerを使用する

端的に言うと、AnalyzerとTokenizerを継承したクラスをそれぞれ作れば良い。

今回作成したTokenizerは以前よりQMACloneで使用している、辞書に含まれている単語を抜き出すというTokenizerである。アルゴリズムはVitabiアルゴリズムを少しだけ変えただけなので省略。ソースコード例はこちら↓

package tv.dyndns.kishibe.server.relevance;

import java.io.IOException;
import java.io.Reader;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.analysis.Tokenizer;
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
import org.apache.lucene.analysis.tokenattributes.TermAttribute;
import org.apache.lucene.util.AttributeSource;

public class VitabiTokenizer extends Tokenizer {
	private final TermAttribute termAtt;
	private final OffsetAttribute offsetAtt;
	private final List<Integer> offsets = new ArrayList<Integer>();
	private final List<Integer> lengths = new ArrayList<Integer>();
	private char[] buffer;
	private int wordIndex = 0;

	public VitabiTokenizer(Reader input) {
		super(input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	public VitabiTokenizer(AttributeFactory factory, Reader input) {
		super(factory, input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	public VitabiTokenizer(AttributeSource source, Reader input) {
		super(source, input);
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(TermAttribute.class);
		initialize();
	}

	private void initialize() {
		String s = null;
		try {
			s = IOUtils.toString(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		offsets.clear();
		lengths.clear();
		s = Normalizer.normalize(s, Form.NFKC);
		s = s.toLowerCase();
		Trie.getInstance().parse(s, null, offsets, lengths);
		buffer = s.toCharArray();
		wordIndex = 0;
	}

	@Override
	public boolean incrementToken() throws IOException {
		if (wordIndex == offsets.size()) {
			return false;
		} else {
			final int offset = offsets.get(wordIndex);
			final int length = lengths.get(wordIndex);
			++wordIndex;
			termAtt.setTermBuffer(buffer, offset, length);
			offsetAtt.setOffset(offset, offset + length);
			return true;
		}
	}

	@Override
	public void end() throws IOException {
		super.end();
		offsetAtt.setOffset(buffer.length, buffer.length);
	}

	@Override
	public void reset(Reader input) throws IOException {
		super.reset(input);
		initialize();
	}
}

Tokenizerを使用するクラスは、getAttribute()でTermAttributeとOffsetAttributeを取得して使用する。この準備のため、それぞれのインスタンスをフィールドに持たせておく。続いて実際のTokenizeを行う。本来はincrementToken()の中で1トークンずつ行うのだが、今回は既に用意してあったライブラリとの兼ね合いで、initialize()の中でTokenizeを行い、その結果を保持してincrementToken()でTokenizeの結果をTermAttributeとOffsetAttributeの中に入れるという形で行っている。もしかしたら速度が遅いかもしれない・・・。