拼音搜索

拼音搜索目前在中文搜索领域应用非常广泛。看了一些同行的文章,基本上是做n-gram算法切分子串,然后根据同义词的原理,搜索相关的词。这样在保证查全率的同时,在精度上有不小的损失。个人的理解是这样,在建立索引的阶段,用拼音组件将汉字转换成拼音,然后用正则去完成拼音字符的提取。比如zhangsanfeng,提取出三个词,zhang,san,feng;在查询的阶段,首先判断用户的输入符合不符合拼音的规则,如果符合,则进行正则的提取,如果不符合,则直接用IK分词器进行常规的分词。

一个让人措手不及的问题是,在Lucene当中,分词器进行分词,是通过Reader流进行处理的,如果要判断用户的输入符合不符合拼音规则,则需要完整的读完整个Reader的内容。比如zhangsanfeng是符合拼音规则的,而zhangsf则不符合,一个需要使用PatternTokenizer来处理,一个需要使用IKTokenizer来处理,同时进行Reader的复位(reset)。不幸的是,Lucene中内部使用的ReusableStringReader类是final类型的,reset()并没有重载,也就是不支持复位的功能。当然,可以使用BufferedReader来进行包装。而且,更关键的问题是,在Lucene当中,不止Reader是复用的,Analyzer也是复用的,createComponents()方法则在初始化时调用一次,把正则的处理放在createComponents()里并不合适,而Analyzer的很多方法都是final的,也就是没有办法进行覆盖。总之单纯写一个Analyzer并不合适。

因此仿照IKTokenizer,自己写了一个PinyinTokenizer类,在reset()方法中完成Reader的读取,拼音正则的判断。

 

在incrementToken方法中,完成词元属性的设置、拼音分词,IK常规分词等操作。

 

完整的PinYinAnalyzer类

public class PinYinAnalyzer extends Analyzer {
	/**
	 * 重载Analyzer接口,构造分词组件
	 */
	@Override
	protected TokenStreamComponents createComponents(String fieldName, final Reader input) {

		return new TokenStreamComponents(new PinYinTokenizer(input));

	}

}

完整的PinYinTokenizer类

public class PinYinTokenizer extends Tokenizer {

	// IK分词器实现
	private IKSegmenter _IKImplement;

	// 词元文本属性
	private final CharTermAttribute termAtt;
	// 词元位移属性
	private final OffsetAttribute offsetAtt;
	// 词元分类属性(该属性分类参考org.wltea.analyzer.core.Lexeme中的分类常量)
	private final TypeAttribute typeAtt;
	// 记录最后一个词元的结束位置
	private int endPosition;
	private final StringBuilder sb = new StringBuilder();
	private final char[] buffer = new char[8192];
	private boolean isLegal;
	private int index;
	private Matcher matcher;
	private Pattern pattern;
	public String pinyinSegRegEx = "[^aoeiuv]?h?[iuv]?(ai|ei|ao|ou|er|ang?|eng?|ong|a|o|e|i|u|ng|n)?";
	public String pinyinRegEx = "(a[io]?|ou?|e[inr]?|ang?|ng|[bmp](a[io]?|[aei]ng?|ei|ie?|ia[no]|o|u)|pou|me|m[io]u|[fw](a|[ae]ng?|ei|o|u)|fou|wai|[dt](a[io]?|an|e|[aeio]ng|ie?|ia[no]|ou|u[ino]?|uan)|dei|diu|[nl](a[io]?|ei?|[eio]ng|i[eu]?|i?ang?|iao|in|ou|u[eo]?|ve?|uan)|nen|lia|lun|[ghk](a[io]?|[ae]ng?|e|ong|ou|u[aino]?|uai|uang?)|[gh]ei|[jqx](i(ao?|ang?|e|ng?|ong|u)?|u[en]?|uan)|([csz]h?|r)([ae]ng?|ao|e|i|ou|u[ino]?|uan)|[csz](ai?|ong)|[csz]h(ai?|uai|uang)|zei|[sz]hua|([cz]h|r)ong|y(ao?|[ai]ng?|e|i|ong|ou|u[en]?|uan)|\\s)+";

	public PinYinTokenizer(Reader in) {
		super(in);
		pattern = Pattern.compile(pinyinSegRegEx);
		matcher = pattern.matcher("");
		offsetAtt = addAttribute(OffsetAttribute.class);
		termAtt = addAttribute(CharTermAttribute.class);
		typeAtt = addAttribute(TypeAttribute.class);
		_IKImplement = new IKSegmenter(input, true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.lucene.analysis.TokenStream#incrementToken()
	 */
	@Override
	public boolean incrementToken() throws IOException {

		// 清除所有的词元属性
		clearAttributes();
		if (isLegal) {
			if (index >= sb.length())
				return false;
			while (matcher.find()) {
				index = matcher.start(0);
				final int endIndex = matcher.end(0);
				if (index == endIndex)
					continue;
				termAtt.setEmpty().append(sb, index, endIndex);
				offsetAtt.setOffset(correctOffset(index), correctOffset(endIndex));
				return true;
			}
			index = Integer.MAX_VALUE; // mark exhausted
			return false;
		} else {
			Lexeme nextLexeme = _IKImplement.next();
			if (nextLexeme != null) {
				// 将Lexeme转成Attributes
				// 设置词元文本
				termAtt.append(nextLexeme.getLexemeText());
				// 设置词元长度
				termAtt.setLength(nextLexeme.getLength());
				// 设置词元位移
				offsetAtt.setOffset(nextLexeme.getBeginPosition(), nextLexeme.getEndPosition());
				// 记录分词的最后位置
				endPosition = nextLexeme.getEndPosition();
				// 记录词元分类
				typeAtt.setType(nextLexeme.getLexemeTypeString());
				// 返会true告知还有下个词元
				return true;
			}
			// 返会false告知词元输出完毕
			return false;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.apache.lucene.analysis.Tokenizer#reset(java.io.Reader)
	 */
	@Override
	public void reset() throws IOException {
		super.reset();
		fillBuffer(sb, this.input);
		String str = sb.toString();
		isLegal = str.matches(pinyinRegEx);
		if (isLegal) {
			matcher.reset(str);
			index = 0;
		} else {
			_IKImplement.reset(new StringReader(str));
		}
	}

	private void fillBuffer(StringBuilder sb, Reader input) throws IOException {
		int len;
		sb.setLength(0);
		while ((len = input.read(buffer)) > 0) {
			sb.append(buffer, 0, len);
		}
		input.close();
	}

	@Override
	public final void end() {
		int finalOffset = correctOffset(this.endPosition);
		offsetAtt.setOffset(finalOffset, finalOffset);
	}

 

转载于:https://my.oschina.net/nickname/blog/792923

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值