测试下:

现在我们有了字典树,然后就不能以字典树来foreach,字典树用于检索。我们就以用户输入的字符串为数据源,去字典树种查找是否存在错词。因此需要对输入字符串进行取词检索。也就是分词,分词我们采用前向最大匹配。
前向最大匹配
我们分词的目的是将输入字符串分成若干个词语,前向最大匹配就是从前向后寻找在词典中存在的词。
例子:我们假设maxLength= 3,即假设单词的最大长度为3。实际上我们应该以字典树中的最大单词长度,作为最大长度来分词(上面我们的字典最大长度应该是2)。这样效率更高,为了演示匹配过程就假设maxLength为3,这样演示的更清楚。
用前向最大匹配来划分“我们应该早睡早起” 这句话。因为我是错词匹配,所以这句话我改成“我门应该旱睡旱起”。
第一次:取子串 “我门应”,正向取词,如果匹配失败,每次去掉匹配字段最后面的一个字。
“我门应”,扫描词典中单词,没有匹配,子串长度减 1 变为“我门”。
“我门”,扫描词典中的单词,匹配成功,得到“我门”错词,输入变为“应该旱”。
第二次:取子串“应该旱”
“应该旱”,扫描词典中单词,没有匹配,子串长度减 1 变为“应该”。
“应该”,扫描词典中的单词,没有匹配,输入变为“应”。
“应”,扫描词典中的单词,没有匹配,输入变为“该旱睡”。
第三次:取子串“该旱睡”
“该旱睡”,扫描词典中单词,没有匹配,子串长度减 1 变为“该旱”。
“该旱”,扫描词典中的单词,没有匹配,输入变为“该”。
“该”,扫描词典中的单词,没有匹配,输入变为“旱睡旱”。
第四次:取子串“旱睡旱”
“旱睡旱”,扫描词典中单词,没有匹配,子串长度减 1 变为“旱睡”。
“旱睡”,扫描词典中的单词,匹配成功,得到“旱睡”错词,输入变为“早起”。
以此类推,我们得到错词 我们/旱睡/旱起。
因为我是结合字典树匹配错词所以一个字也可能是错字,则匹配到单个字,如果只是分词则上面的到一个字的时候就应该停止分词了,直接字符串长度减1。
这种匹配方式还有后向最大匹配以及双向匹配,这个大家可以去了解下。
实现前向最大匹配,这里后向最大匹配也可以一起实现。
public class ErrorWordMatch
{
private static ErrorWordMatch singleton = new ErrorWordMatch();
private static Trie trie = new Trie();
private ErrorWordMatch()
{
}
public static ErrorWordMatch Singleton()
{
return singleton;
}
public void LoadTrieData(List<string> errorWords)
{
foreach (var errorWord in errorWords)
{
trie.Add(errorWord);
}
}
/// <summary>
/// 最大 正向/逆向 匹配错词
/// </summary>
/// <param name="inputStr">需要匹配错词的字符串</param>
/// <param name="leftToRight">true为从左到右分词,false为从右到左分词</param>
/// <returns>匹配到的错词</returns>
public List<string> MatchErrorWord(string inputStr, bool leftToRight)
{
if (string.IsNullOrWhiteSpace(inputStr))
return null;
if (trie.Size() == 0)
{
throw new ArgumentException("字典树没有数据,请先调用 LoadTrieData 方法装载字典树");
}
//取词的最大长度
int maxLength = trie.MaxLength();
//取词的当前长度
int wordLength = maxLength;
//分词操作中,处于字符串中的当前位置
int position = 0;
//分词操作中,已经处理的字符串总长度
int segLength = 0;
//用于尝试分词的取词字符串
string word = "";
//用于储存正向分词的字符串数组
List<string> segWords = new List<string>();
//用于储存逆向分词的字符串数组
List<string> segWordsReverse = new List<string>();
//开始分词,循环以下操作,直到全部完成
while (segLength < inputStr.Length)
{
//如果剩余没分词的字符串长度<取词的最大长度,则取词长度等于剩余未分词长度
if ((inputStr.Length - segLength) < maxLength)
wordLength = inputStr.Length - segLength;
//否则,按最大长度处理
else
wordLength = maxLength;
//从左到右 和 从右到左截取时,起始位置不同
//刚开始,截取位置是字符串两头,随着不断循环分词,截取位置会不断推进
if (leftToRight)
position = segLength;
else
position = inputStr.Length - segLength - wordLength;
//按照指定长度,从字符串截取一个词
word = inputStr.Substring(position, wordLength);
//在字典中查找,是否存在这样一个词
//如果不包含,就减少一个字符,再次在字典中查找
//如此循环,直到只剩下一个字为止
while (!trie.Contains(word))
{
//如果最后一个字都没有匹配,则把word设置为空,用来表示没有匹配项(如果是分词直接break)
if (word.Length == 1)
{
word = null;
break;
}
//把截取的字符串,最边上的一个字去掉
//从左到右 和 从右到左时,截掉的字符的位置不同
if (leftToRight)
word = word.Substring(0, word.Length - 1);
else
word = word.Substring(1);
}
//将分出匹配上的词,加入到分词字符串数组中,正向和逆向不同
if (word != null)
{
if (leftToRight)
segWords.Add(word);
else
segWordsReverse.Add(word);
//已经完成分词的字符串长度,要相应增加
segLength += word.Length;
}
else
{
//没匹配上的则+1,丢掉一个字(如果是分词 则不用判断word是否为空,单个字也返回)
segLength += 1;
}
}
//如果是逆向分词,对分词结果反转排序
if (!leftToRight)
{
for (int i = segWordsReverse.Count - 1; i >= 0; i--)
{
//将反转的结果,保存在正向分词数组中 以便最后return 同一个变量segWords
segWords.Add(segWordsReverse[i]);
}
}
return segWords;
}
}










