15、学习Lucene3.5索引之同义词分词器具体实现

1.首先创建同义词过滤器

package synonymous;

import org.apache.lucene.analysis.TokenFilter;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
import java.util.TreeMap;

/**
 * 同义词过滤器设计思路:
 * 1.取出分好的语汇单词
 * 2.查同义词表,若有同义词,则在相同位置添加同义词
 *   (1)若有同义词,入栈,记录当前状态
 *   (2)跳转到下一个元素
 *   (3)恢复前一状态
 *   (4)同义词出栈,在同一位置存储同义词(要设置PositionIncrementAttribute属性为0)
 */
public final class MySynonymousFilter extends TokenFilter {
    /**
     * cta是索引中的语汇单元,不是真实的数据
     */
    private CharTermAttribute cta = null;
    /**
     * pia记录分词过后的语汇单词之间的距离
     */
    private PositionIncrementAttribute pia = null;
    /**
     * 用于记录当前状态(等跳转到下一元素之后,可以通过恢复状态,来执行添加前一元素的同义词,而不覆盖掉前一元素)
     */
    private State currentState = null;

    /**
     * 栈,存储前一元素的所以同义词(好处,每次取出当前同义词后(执行操作完毕后),就需要删除掉当前同义词,处理下一同义词)
     */
    private Stack<String> synonymousWordStack = null;

    protected MySynonymousFilter(TokenStream input) {
        super(input);
        //从TokenStream中取出分好的语汇单词
        cta = input.addAttribute(CharTermAttribute.class);
        //从TokenStream中取出分好的语汇单词的距离
        pia = input.addAttribute(PositionIncrementAttribute.class);

        synonymousWordStack = new Stack<String>();
    }

    @Override
    public boolean incrementToken() throws IOException {

//        if (cta.toString().equals("我")){
//            //清空cta所在位置数据(此时,cta已是索引中的数据,不再是真实数据!!)
//            cta.setEmpty();
//            //在cta所在位置追加
//            cta.append("咱");
//        }
        /**
         * 尝试一
         *     问题:若在相同位置添加同义词,则原来的词汇会被覆盖掉?
         *     解决办法:先记录当前状态,然后移动到以下一位置,再恢复原状态,进行添加同义词操作。
         *     原因:因为是通过记录的状态进行操作的,原位置数据并没有被改变,这样就保留了原数据
         *     具体操作:
         *         1.添加状态变量 private State currentState,并捕获当前状态
         *         2.移动到下一个元素
         *         问题:现在我是通过获取同义词数组,判断数组是否为空,然后进行处理,这样,却无法实现跳转到下一元素
         *               再进行处理?
         *         解决办法:定义一个成员变量:栈,每次进入之前,先判断前一次同义词栈是否为空,不为空,则进行处理。
         *         具体操作:
         *                  1.添加成员变量 private Stack<String> synonymousWordStack 同义词栈
         *                  2.将当前元素的所有的同义词入栈
         */
        //查询同义词组
//        String[] sws = getSynonymousWords(cta.toString());
//        //代表有同义词
//        if (sws!= null){
//            //只要有同义词,则捕获当前状态
//            currentState = captureState();
//            for (String str:sws){
//                cta.setEmpty();
//                cta.append(str);
//            }
//        }
        //
        /**
         * 每次进入之前,先判断栈是否为空,不为空,则上一个词有待处理的同义词
         * 注意:这里的栈,是前面的元素若有同义词的话,会将同义词入栈
         * 为什么这么做?
         * 原因:因为加同义词的正确操作步骤是,先判断是否有同义词,若有则先保存当前状态,然后跳转到下一元素,再恢复状态,
         *       最后处理同义词,而栈就是我们实现判断是否有同义词的工具。
         */
        if (!synonymousWordStack.isEmpty()){

            //1.先恢复前一状态(此时的cta代表当前元素的前一个元素)
            restoreState(currentState);

            //2.处理栈中存储的同义词
            //2.1取出栈顶元素
            String str = synonymousWordStack.pop();
            //2.2当前cta置空(注意:这里的cta已经写到TokenStream当中,这里只是一个状态数据,所以覆盖了也没事)
            cta.setEmpty();
            //2.3添加同义词
            cta.append(str);
            //2.4设置位置增量为0(和原cta在同一位置)
            pia.setPositionIncrement(0);
            //2.5处理一个,返回一个,否则,每次处理占中
            /**
             * 注意:处理一个,返回一个true!!
             * 原因:因为假如栈中的元素超过一个,那么每处理栈中元素一次,都会调用this.input.incrementToken()一次,这样会将
             *       元素向前推进,等处理栈中元素完毕之后,中间已倍推进的元素将无法在进行同义词处理(改成while,那么有多
             *       个同义词,只会剩下最后一个)
             */
            return true;
        }
        /**
         * 判断当前是否还有元素,没有元素,则返回false
         * input是父类TokenFilter的成员变量,从Tokenizer或者其他的TokenFilter传递过来的就是这个input(其实就是TokenStream)
         * 注意:
         *     incrementToken()相当于迭代器,取出本下标的值,并往后推移一位
         * 特别注意:
         *     必须等栈中的所有元素处理完毕之后,再进行this.input.incrementToken()判断!
         *     原因:因为假如栈中的元素超过一个,那么再循环的时候this.input.incrementToken()会将元素向前推进,等处理
         *           栈中元素完毕之后,中间已倍推进的元素将无法在进行同义词处理
         */
        if (!this.input.incrementToken()){
            return false;
        }

        //若当前cta所对应的词有同义词,则保存状态,等待后续使用
        if (getSynonymousWords(cta.toString())){
            currentState = captureState();
        }
        return true;
    }


    //获取同义词
    private boolean getSynonymousWords(String key){
        Map<String,String[]> map = new HashMap<String, String[]>();
        map.put("我",new String[]{"咱","俺"});
        map.put("中国",new String[]{"大陆","天朝"});
        String[] sws = map.get(key);

        //将key所对应的所有的同义词入栈
        if (sws!=null && sws.length>0){
            for (String str:sws){
                synonymousWordStack.push(str);
            }
            //代表有同义词
            return true;
        }
        //代表无同义词
        return false;
    }
}

2.其次创建同义词分词器

package synonymous;

import com.chenlb.mmseg4j.Dictionary;
import com.chenlb.mmseg4j.MaxWordSeg;
import com.chenlb.mmseg4j.analysis.MMSegTokenizer;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;

import java.io.Reader;

public final class MySynonymousAnalyzer extends Analyzer{
    @Override
    public TokenStream tokenStream(String fieldName, Reader reader) {
        /**
         * 基本思路:
         * 1.首先用new MMSegTokenizer(new MaxWordSeg(Dictionary.getInstance()), reader)对输入流“Reader”进行分词
         * 2.然后使用自定义的同义词过滤器“MySynonymousFilter”进行订制过滤
         */
        return new MySynonymousFilter(new MMSegTokenizer(new MaxWordSeg(Dictionary.getInstance()), reader));
    }
}

3.最后使用同义词分词器检索

@Test
    public void testMySynonymousAnalyzer(){
        String str = "我,潘畅,来自中国江苏省泗洪县峰山乡邮局西侧50米";
        Analyzer analyzer = new MySynonymousAnalyzer();
//        AnalyzerUtils.displayToken(str, analyzer);
        search(str, analyzer);
    }
    private void search(String str, Analyzer analyzer){
        try {
            Directory directory = new RAMDirectory();
            IndexWriter indexWriter = new IndexWriter(directory,new IndexWriterConfig(Version.LUCENE_35,analyzer));
            Document document = new Document();
            document.add(new Field("content", str, Field.Store.YES, Field.Index.ANALYZED));
            indexWriter.addDocument(document);
            indexWriter.close();
            IndexReader indexReader = IndexReader.open(directory);
            IndexSearcher indexSearcher = new IndexSearcher(indexReader);
            Query query = new TermQuery(new Term("content", "天朝"));
            TopDocs topDocs = indexSearcher.search(query, 10);
            ScoreDoc[] scoreDocs = topDocs.scoreDocs;
            if (scoreDocs != null && scoreDocs.length>0){
                Document doc = indexSearcher.doc(scoreDocs[0].doc);
                System.out.println(doc.get("content"));
            } else {
                System.out.println("搜不到!");
            }
            indexReader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值