自己动手写分布式搜索引擎
上QQ阅读APP看书,第一时间看更新

3.3.4 模糊匹配

关于字符串的前缀匹配问题:只查询产品名字为字母A打头的搜索,可以使用前缀匹配(PrefixQuery)实现。查询语法是A*。注意,查询的不一定是整个字段以A开头的记录,而只是其中的单词以A开头。

        Query query = new WildcardQuery(new Term(FIELD, "cut*")); // 通配符

等价于:

        Query query = new PrefixQuery(new Term(FIELD, "cut")); // 自动在结尾添加 *

测试WildcardQuery():

        // 索引一些文档
        writer.addDocument(createDocument("1", "foo bar baz"));
        writer.addDocument(createDocument("2", "red green blue"));
        writer.addDocument(createDocument("3",
                "The Lucene was made by Doug Cutting"));
        writer.close();


        IndexReader reader = DirectoryReader.open(directory);


        IndexSearcher searcher = new IndexSearcher(reader);


        Query query = new WildcardQuery(new Term(FIELD, "cut*"));


        TopDocs topDocs = searcher.search(query, 10);

这里的cut只能是小写,否则就匹配不上。如果用户输入的是大写,如Cut,可以用QueryParser()转换成小写。

使用反转Token的方法允许通配符出现在开头。ReversedWildcardsTokenFilter可以实现Token反转。ReversedWildcardsTokenFilter返回原来的Token和反转的Token,其中反转的Token的positionIncrement值是0。

可以用一个标识字符来避免正常Token和反转Token之间的冲突。例如:“DNA”反转后就变成“and”了,和正常的词“and”有冲突。但是使用标识字符后,“DNA”就变成了“\u0001and”。

可以把ReversedWildcardsTokenFilter加入分析器链,这样在做索引的时候就可以使用了。

有些英文单词需要查询扩展,如搜索“dog”的同时查找“dogs”。FuzzyQuery()有限状态查询(Finite-State Query)用编辑距离衡量相似度,例如“dog”和“dogs”的编辑距离是1。FuzzyQuery()内部使用编辑距离有限状态机实现,所以性能很好。

        int maxEdits = 2;  //编辑距离最多不能超过2
        new FuzzyQuery(new Term("title", "dog"), maxEdits);

可以设置相同前缀的长度。例如,相同前缀的长度是1:

        FuzzyQuery query = new FuzzyQuery(new Term("field", "WEBER"), 2, 1);

query要求匹配的词必须以W开头,而且匹配的词与查询词之间的编辑距离不超过2。这个FuzzyQuery等价于如下的有限状态自动机:

        LevenshteinAutomata builder = new LevenshteinAutomata("EBER", true);
        Automaton a = builder.toAutomaton(2); //最大编辑距离是2
        Automaton b = BasicAutomata.makeChar('W'); //创建一个字符W组成的自动机
        Automaton c = BasicOperations.concatenate(b, a); //连接两个自动机b和a

这里的BasicOperations.concatenate()方法把b的结束状态和a的开始状态用空转换连接起来,也就是按顺序走过a中的状态,然后走b中的状态。例如,自动机b可以接收字符W,然后自动机a继续接收EB,就可以结束了。这说明自动机c可以接收WEB。

测试自动机c可以接收哪些字符:

        System.out.println(BasicOperations.run(c, "WBR")); //输出true
        System.out.println(BasicOperations.run(c, "WEB")); //输出true
        System.out.println(BasicOperations.run(c, "WEBE")); //输出true
        System.out.println(BasicOperations.run(c, "WEBER")); //输出true

在中文人名中使用自动机:

        LevenshteinAutomata builder = new LevenshteinAutomata("杰伦", true);
        Automaton a = builder.toAutomaton(1); //最大编辑距离是1
        Automaton b = BasicAutomata.makeChar(’周’);
        Automaton c = BasicOperations.concatenate(b, a); //连接两个自动机b和c
        System.out.println(BasicOperations.run(c, "周杰伦")); //匹配写错的人名

在中文人名中使用模糊查询:

        FuzzyQuery query = new FuzzyQuery(new Term("field", "周杰伦"), 1, 1);

“dogs~”这样的模糊查询语法使用FuzzyQuery()。FuzzyQuery()还可以用于拼写检查。

可以根据Automaton对象构建AutomatonQuery。

        Term term= new Term("yourfield", "周~*");
        LevenshteinAutomata builder = new LevenshteinAutomata("杰伦", true);
        Automaton a = builder.toAutomaton(1); //最大编辑距离是1
        Automaton b = BasicAutomata.makeChar(’周’);
        Automaton c= BasicOperations.concatenate(b, a); //连接两个自动机b和c
        AutomatonQuery query = new AutomatonQuery(term, c);

再给出一个星闭包的例子:

        //查询的term表示,包含列
        Term term= new Term("yourfield", "bla~*");
        //对所有的和“bla”编辑距离在2以内的字符串构建一个确定性有限状态自动机
        Automaton fuzzy = new LevenshteinAutomata("bla", false).toAutomaton(2);
        //串联fuzzy和另外一个DFA等于"*"操作符,也就是星闭包
        Automaton fuzzyPrefix=
                    BasicOperations.concatenate(fuzzy, BasicAutomata.makeAnyString());
        //构建一个查询,用它搜索以得到结果
        AutomatonQuery query = new AutomatonQuery(term, fuzzyPrefix);

Lucene的NumericRangeQuery采用了Trie树结构的索引,可以模仿NumericRangeQuery编写字符串的前缀匹配实现。