【问题标题】:Algorithm in .NET to detect simple typos.NET 中用于检测简单拼写错误的算法
【发布时间】:2011-10-30 20:48:16
【问题描述】:

.NET 是否存在能够从预定义单词列表中检测错别字的现有算法?

例如,假设“Stuff”一词在我的列表中,有人键入“Stuf”、“sutff”、“stff”或“stiff”。我想能够建议这个人“东西”这个词可能是正确的词。

我不是在谈论任何语法问题,也不是在谈论任何一个字母缺失、替换或混合的内容。

我的目标是防止在不同类型的列表中输入相同的单词。并不是说大写和小写不会对我造成问题,因为一切都是小写的。

【问题讨论】:

标签: .net algorithm spell-checking


【解决方案1】:

这是一个经过充分研究的问题,有很多很好的算法可以做到这一点。它们中的大多数通过构建某种数据结构来保存所有合法词,从而可以有效地找到具有相似 edit distance 的词。非正式地,两个字符串之间的编辑距离是将一个字符串转换为另一个字符串所需的更改次数。例如,给定单词“mispell”和“mispell”,编辑距离为 1(只需在单词中插入另一个 's'),而“cat”和“dog”之间的编辑距离为 3(替换每个字母) .

拼写错误的单词可能与预期的单词只有很小的编辑距离,因此,如果您可以以一种方式存储单词,对于任何字符串,查询与目标单词编辑距离很小的单词字符串,您可以提供用户可能意思的可能单词的候选列表。

保存这些数据的一个常见数据结构是trie,这是一个 26 路树结构,其中每个节点存储一个单词的前缀,每条边对应于在当前前缀上附加一些字母。如果您有这样的结构,您可以使用简单的递归树搜索找到与特定单词“接近”的单词(可能有一定的编辑距离)。在每一点上,记录您希望与目标单词保持多远的编辑距离,以及到目前为止您已处理了多少拼写错误的单词。在每一点,您可以沿着树中与单词中的字母相对应的边缘,也可以使用一个编辑距离通过沿着树中的不同边缘插入新字母。

另一个经常用于此的数据结构是BK-tree,它以一种方式存储字符串,您可以在距离某个源字符串一定编辑距离的地方有效地查询所有单词。这将更直接地解决您的问题,尽管与尝试相比,关于如何构建 BK-trees 的在线资源较少。

一旦您找到了在某个编辑距离内的词,您可能希望在将它们呈现给用户之前以某种方式对它们进行排名。这需要你对人们在实践中倾向于犯什么样的错别字有所了解。常见的拼写错误包括

  • 换位,两个字母互换:“thme”而不是“them”
  • 替换,其中使用了错误的字母:“arithmatic”而不是“arithmetic”
  • 删除,其中一个字母被遗漏:“helo”而不是“hello”
  • 重复,其中一个字母重复:“tommorrow”而不是“tomorrow”

要构建一个好的拼写检查器,理想情况下,您应该拥有与每种类型的错误相关的某种概率。这样,当您获得可能的更正列表时,您可以将它们从最可能到最不可能的顺序排列。

希望这会有所帮助!

【讨论】:

    【解决方案2】:

    这里有一个很好的一步一步来做你正在寻找的用 python 实现的东西,但是也有 C# 和其他语言实现的链接。

    http://norvig.com/spell-correct.html

    【讨论】:

    • 它是 25 行 Python 和基于字典的,正是这种小规模任务所需要的!
    【解决方案3】:

    也许您想寻找三元组搜索?三元组搜索需要创建您输入的 3 个字母的每个可能性,并在匹配中查找相似的字符串。

    【讨论】:

      【解决方案4】:

      在 C# 中发布我的实现,允许长度 >= 2 的字符串。 它检查 2 个单词是否匹配,不考虑 TranspositionsSubstitutionsDeletions(对删除后长度 >=2 的单词有效)和 Repetitions (允许多个)。

      public bool CheckWordsSameWithTypo(string word1, string word2)
          {
              if (word1.Length < 2 || word2.Length < 2) return false;
              
              //transposition "thme" instead of "them"        
              bool matchWithTrans = false;
              #region transLogic
              var transOptions1 = new List<string>();
              var transOptions2 = new List<string>();
              for (int i = 1; i < word1.Length;  i++)
              {
                  var wordArr = word1.ToArray();
                  char letter1 = wordArr[i -1];
                  char letter2 = wordArr[i];
                  wordArr[i -1] = letter2;
                  wordArr[i] = letter1;
                  transOptions1.Add(new string(wordArr));
              }
              for (int i = 1; i < word2.Length;  i++)
              {
                  var wordArr = word2.ToArray();
                  char letter1 = wordArr[i -1];
                  char letter2 = wordArr[i];
                  wordArr[i -1] = letter2;
                  wordArr[i] = letter1;
                  transOptions2.Add(new string(wordArr));
              }
              if (transOptions1.Any(p => p.Equals(word2)) || transOptions2.Any(p => p.Equals(word1))) matchWithTrans = true;
              #endregion
              
              //substitution "arithmatic" instead of "arithmetic"
              bool matchWithSubst = false;
              #region substLogic
              var substOptionPatterns1 = new List<string>();
              var substOptionPatterns2 = new List<string>();
              for (int i = 0; i < word1.Length;  i++)
              {
                  var wordArr = word1.ToArray();
                  wordArr[i] = '.';
                  substOptionPatterns1.Add(new string(wordArr));
              }
              for (int i = 0; i < word2.Length;  i++)
              {
                  var wordArr = word2.ToArray();
                  wordArr[i] = '.';
                  substOptionPatterns2.Add(new string(wordArr));
              }
              foreach(var patt in substOptionPatterns1)
              {
                  Regex regex = new Regex(patt);
                  if (regex.IsMatch(word2)) matchWithSubst = true;
              }
              foreach(var patt in substOptionPatterns2)
              {
                  Regex regex = new Regex(patt);
                  if (regex.IsMatch(word1)) matchWithSubst = true;
              }
              #endregion
              
              //deletion "helo" instead of "hello"
              bool matchWithDeletion = false;
              #region deletionLogic
              var delOptions1 = new List<string>();
              var delOptions2 = new List<string>();
              for (int i = 0; i < word1.Length;  i++)
              {
                  delOptions1.Add(word1.Remove(i, 1));
              }
              for (int i = 0; i < word2.Length;  i++)
              {
                  delOptions2.Add(word2.Remove(i, 1));
              }
              if (delOptions1.Any(p => p.Length>1 && p.Equals(word2)) || delOptions2.Any(p => p.Length>1 && p.Equals(word1))) matchWithDeletion = true;
              #endregion
              
              //repetition "tommorrow" instead of "tomorow"
              bool matchWithRepetition = false;
              #region repsLogic
              StringBuilder word1_distinctBuilder = new StringBuilder(word1.Substring(0, 1));
              for (int i = 1; i < word1.Length;  i++)
              {
                  string currentLetter = word1.Substring(i, 1);
                  if(!word1_distinctBuilder.ToString().Substring(word1_distinctBuilder.ToString().Length-1, 1).Equals(currentLetter))
                  {
                      word1_distinctBuilder.Append(currentLetter);
                  }
              }
              StringBuilder word2_distinctBuilder = new StringBuilder(word2.Substring(0, 1));
              for (int i = 1; i < word2.Length;  i++)
              {
                  string currentLetter = word2.Substring(i, 1);
                  if(!word2_distinctBuilder.ToString().Substring(word2_distinctBuilder.ToString().Length-1, 1).Equals(currentLetter))
                  {
                      word2_distinctBuilder.Append(currentLetter);
                  }
              }
              matchWithRepetition = word1_distinctBuilder.ToString().Equals(word2_distinctBuilder.ToString());
              #endregion
              return matchWithTrans || matchWithSubst || matchWithDeletion || matchWithRepetition;
          }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-10-16
        • 2015-09-23
        • 1970-01-01
        • 1970-01-01
        • 2014-11-10
        • 1970-01-01
        • 2012-03-04
        相关资源
        最近更新 更多