在进行搜索的时候会涉及到一些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));
}
}
}
|
效果查看

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

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