有时,简单的算法也有其实用的意义,由于之前公司内部搜索引擎优化的需要,我根据《编程珠玑》中查找英文单词变位词的算法,来实现搜索纠错的功能。
在搜索时,有时记不住单词,会出现拼写错误的情况,例如,搜索“height”时手误,搜索了“heigth”,那么我们要在搜索不到的情况下,给他变位词作为提示(注:因为公司内部搜索,该算法已经够用,真正的搜索引擎应该是采用更高效的算法,请有经验的前辈赐教)。
算法分三步:①对单词签名; ②根据签名的字典序排序; ③根据排序结果挤压,使变位词聚合在一起;
a) 快速单词签名算法(当前仅使用于英文单词,如果包含其他字符,需做拓展):
鉴于英文字母可枚举且数量只有26个,故这里采用变形的基数排序算法,以实现快速签名:
Step 1: 定义一个26位的 int[] 数组,数组从0~25分别表示:A,B,C,D …… X,Y,Z (如图1),初始化全部为0:
A | B | C | D | E | F | G |
H | I | J | K | L | M | N |
O | P | G | R | S | T | U |
V | W | X | Y | Z |
|
(图1)
Step 2: 将英文字母转换统一(大小写统一),如 Jary → jary,再使用如下公式:存入下标为x-97的位置,例如 a的ascii码为97,则它对应存入byte[]数组中的97-97=0的位置,即该位置自增1;以此类推。
Stpe 3: 给单词签名,从0到25扫描数组,例如 what → ahtw, wath → ahtw, system → ems2ty ;
这样我们就可以只通过一次扫描来获取一个单词的签名,代码如下:
1 public static string SignWord(string word)
2 {
3 int[] dict = new int[26];
4
5 var array = word.ToLower().ToArray();
6 foreach(var a in array) {
7 dict[a - 97]++;
8 }
9 StringBuilder sb = new StringBuilder();
10 for (var i = 0; i < 26; i++) {
11 if (dict[i] != 0) {
12 var t = dict[i].ToString().Equals("1") ? "": dict[i].ToString();
13 sb.Append((char)(i + 97) + t);
14 }
15 }
16 return sb.ToString();
17 }
b) 相同签名的单词的挤压,例如,what跟wath有相同的签名,则放到一起,我们可以根据签名的字典排序,但是,使用C#提供的Dictnary可以快速排序加压,代码如下:
1 public static Dictionary < string,List < string >> SqueezeList(string filePath)
2 {
3 Dictionary < string,List < string >> wordDict = new Dictionary < string,List < string >> ();
4 StreamReader fs = new StreamReader(filePath, Encoding.Default);
5 string line;
6 while (!string.IsNullOrEmpty(line = fs.ReadLine())) {
7 var key = SignWord(GetCleanString(line));
8 if (wordDict.ContainsKey(key)) {
9 wordDict[key].Add(line);
10 } else {
11 var list = new List < string > () {
12 line
13 };
14 wordDict.Add(key, list);
15 }
16 }
17 fs.Close();
18 return wordDict;
19 }
这样,我们就将单词以签名为Key,放入到 Dictionary 中;当然,实际应用中,我们会以将Dictionary序列化到文本文件中,这样就不用每次使用时都算一次了;
PS:对于搜索方面的学习,都是看着论文什么的摸索前进,其中走了很多弯路和歪解,如果本文有什么纰漏或错误的地方,请指出和谅解,谢谢;