在进行搜索的时候会涉及到一些NLP相关的技术实现,其中搜索词改错与搜索词改写就是一个非常重要的流程,在这里介绍一种基于前缀树的搜索词改写的实现方案,由于公司的使用场景是垂直领域的搜索,涉及到的搜索词大多数都是楼盘相关,所以本文主要使用该算法实现搜索词的改错与扩词。

相关理论支撑

拼写纠错工业级别的实现非常复杂,但是简单一些的拼写纠错也能得到80%-90%的正确性,一位大牛黄振就实现了这一算法。具体实现参考黄振大神的实现(How to Write a Spelling Corrector) 参考链接

本文基于这一原理的基础上使用java实现

话不多说直接上代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//构建前缀树

package com.pcauto.utils.bkutils;

import java.util.*;

public class BKTreeAlgo<T> {
    private final int radius;

    private Node root;
    private Metric<T> metric;

    public BKTreeAlgo(int radius, Metric<T> metric) {
        this.radius = radius;
        this.metric = metric;
    }

    public void add(T value) {
        if (root == null)
            root = new Node(value);
        else {
            root.add(value);
        }
    }

    public void addAll(Collection<? extends T> collection) {
        for (T val : collection) {
            add(val);
        }
    }

    public Set<T> search(T value) {
        Set<T> result = new HashSet<>();
        if (root != null)
            root.search(value, result);
        return result;
    }

    class Node {
        private T value;
        private Map<Integer, Node> childs;

        Node(T v) {
            this.value = v;
            this.childs = new HashMap<Integer, Node>();
        }

        void add(T value) {
            int distance = metric.getMetric(this.value, value);
            if (this.childs.containsKey(distance)) {
                this.childs.get(distance).add(value);
            } else {
                this.childs.put(distance, new Node(value));
            }
        }

        void search(T value, Set<T> resultSet) {
            int distance = BKTreeAlgo.this.metric.getMetric(this.value, value);

            if (distance <= radius) {
                resultSet.add(this.value);
            }

            for (int i = Math.max(distance - radius, 1); i <= distance + radius; i++) {
                Node ch = this.childs.get(i);
                if (ch != null)
                    ch.search(value, resultSet);
            }
        }
    }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

package com.pcauto.utils.bkutils;

public interface Metric<T> {
    int getMetric(T t1, T t2);
}



package com.pcauto.utils.bkutils;


public class StringEditMetric implements Metric<String> {
    @Override
    public int getMetric(String s1, String s2) {
        int[][] dp = new int[s1.length() + 1][s2.length() + 1];
        for (int i = 1; i <= s1.length(); i++) {
            dp[i][0] = i;
        }
        for (int i = 1; i <= s2.length(); i++) {
            dp[0][i] = i;
        }
        for (int i = 1; i <= s1.length(); i++) {
            for (int j = 1; j <= s2.length(); j++) {
                dp[i][j] = Math.min(
                        dp[i - 1][j - 1] + (s1.charAt(i - 1) == s2.charAt(j - 1) ? 0 : 1),
                        Math.min(dp[i][j - 1], dp[i - 1][j]) + 1
                );
            }
        }

        return dp[s1.length()][s2.length()];
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//构建树的词典读取
package com.pcauto.utils.bkutils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class DataHandler {

    public static Set<String> getDictionary() throws IOException{
        Set<String> dictionary = new HashSet<>();
        BufferedReader bufferedReader = new BufferedReader(new FileReader(new File("/Users/glin/IdeaProjects/Basic_Learn/nlp/zh_correct_pinyin-master/src/main/java/com/pcauto/utils/bkutils/data/PROJECTS.csv")));
        String temp;
        while ((temp = bufferedReader.readLine()) != null){
            dictionary.add(temp);
        }
        bufferedReader.close();
        return dictionary;
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//代码测试
package com.pcauto.utils.bkutils;


import java.io.IOException;
import java.util.Scanner;

public class SearchTest {

    public static void main(String[] args) {

        //数据初始化
        BKTreeAlgo<String> bkTree = new BKTreeAlgo<String>(1, new StringEditMetric());
        try {
            bkTree.addAll(DataHandler.getDictionary());
        } catch (IOException e) {
            e.printStackTrace();
        }

        //数据搜索
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()) {
            String word = scanner.next().trim();
            System.out.printf("匹配列表: %s\n", bkTree.search(word));
        }

    }
}

效果查看

效果测试

优化点

这种实现只是实现了汉字的纠错与搜索词的扩展,如果需要兼容拼音搜索的功能,就需要初始化一个拼音的前缀树来实现搜索词的纠错与扩词,原理与以上的实现是一样的,这里不做介绍,有兴趣或者有相关需求的小伙伴可以尝试实现。

拼音实现效果

以上的纠错与扩词只是一个初步实现,以后有时间将系统的实现一个搜索流程,敬请期待!!