【问题标题】:Compare 2 string比较 2 个字符串
【发布时间】:2012-05-08 09:20:59
【问题描述】:

我有以下 2 个字符串:

String A: Manchester United
String B: Manchester Utd

两个字符串含义相同,但包含不同的值。

如何比较这些字符串以获得“匹配分数”,例如,在这种情况下,第一个单词相似,“曼彻斯特”,第二个单词包含相似的字母,但位置不正确。

在我提供 2 个字符串后,是否有任何简单的算法可以返回“匹配分数”?

【问题讨论】:

标签: c# asp.net .net


【解决方案1】:

您可以计算两个字符串之间的Levenshtein distance,如果它小于某个值(您必须定义),您可能认为它们非常接近。

【讨论】:

    【解决方案2】:

    我需要做这样的事情并使用 Levenshtein 距离。

    我将它用于 SQL Server UDF,该 UDF 用于超过一百万行的查询(以及最多 6 或 7 个单词的文本)。

    我发现如果单独比较每个单词,算法运行得更快,“相似度指数”更精确。 IE。您将每个输入字符串拆分为单词,并将一个输入字符串的每个单词与另一个输入字符串的每个单词进行比较。

    请记住,Levenshtein 给出了差异,您必须将其转换为“相似性指数”。我使用了距离除以最长单词的长度(但有一些变化)

    第一条规则:单词的顺序和数量

    您还必须考虑:

    • 如果两个输入中的字数必须相同,或者可以更改
    • 如果两个输入的顺序必须相同,或者可以更改。

    根据这一点,算法会发生变化。例如,如果字数不同,应用第一条规则非常快。而且,第二条规则减少了比较的次数,特别是在比较文本中有很多单词的情况下。稍后将通过示例进行解释。

    第二条规则:加权每个比较对的相似度

    我还将长词的权重高于短词,以获得全局相似度指数。我的算法在比较对中取两个词中最长的一个,并且赋予具有较长词的对比具有较短词的对更高的权重,尽管与对长度不完全成正比。

    样品对比:相同顺序

    在这个例子中,它使用了不同数量的单词:

    • 比较“Ma​​nchester United”和“Manchester Utd FC”

    如果保证两个输入中的单词顺序相同,则应比较这些对:

    Manchester United
    Manchester Utd    FC
    

    (Manchester,Manchester) (Utd,United) (FC: 未比较)

    Manchester     United
    Manchester Utd FC
    

    (Manchester,Manchester) (Utd: 未比较) (United,FC)

               Machester United
    Manchester Utd       FC
    

    (曼彻斯特:未比较)(Manchester,Utd)(United,FC)

    显然,第一组对的得分最高。

    实施

    以相同的顺序比较单词。

    字数较多的字符串是一个固定向量,在本例中显示为 A,B,C,D,E。其中 v[0] 是单词 A,v[1] 是单词 B,依此类推。

    对于字数较少的字符串,我们需要创建可以与第一组进行比较的所有可能的索引组合。在这种情况下,单词数较少的字符串由 a,b,c 表示。

    您可以使用一个简单的循环来创建代表要比较的对的所有向量,就像这样

    A,B,C,D,E   A,B,C,D,E   A,B,C,D,E   A,B,C,D,E   A,B,C,D,E   A,B,C,D,E
    a,b,c       a,b,  c     a,b,    c   a,  b,c     a,  b,  c   a,    b,c
    0 1 2       0 1   3     0 1     4   0   2 3     0   2   4   0     3 4
    
    A,B,C,D,E   A,B,C,D,E   A,B,C,D,E   A,B,C,D,E
      a,b,c       a,b,  c     a,  b,c       a,b,c
      1 2 3       1 2   4     1   3 4       2 3 4
    

    样本中的数字是具有第一组单词索引的向量,必须与第一组中的索引进行比较。即 v[0]=0,表示将短集 (a) 的索引 0 与长集 (A) 的索引 0 进行比较,v[1]=2 表示将短集 (b) 的索引 1 与索引 2 进行比较长集 (C),以此类推。

    要计算这个向量,只需从 0,1,2 开始。将可以移动的最新索引向右移动,直到不能再移动:

    移动最后一个:

    0,1,2 -> 0,1,3 -> 0,1,4 
    No more moves possible, move the previous index, and restore the others
    to the lowest possible values (move 1 to 2, restore 4 to 3)
    

    当最后一个不能再移动时,移动最后一个之前的那个,并将最后一个重置到最近的可能位置(1移动到2,4移动到3):

    0,2,3 -> 0,2,4
    No more moves possible of the last, move the one before the last
    

    再次移动最后一个。

    0,3,4
    No more moves possible of the last, move the one before the last
    Not possible, move the one before the one before the last, and reset the others:
    

    移动上一个:

    1,2,3 -> 1,2,4
    

    等等。看图

    当您拥有所有可能的组合时,您可以比较定义的对。

    第三条规则:停止比较的最小相似度

    达到最小相似度时停止比较:根据您想要执行的操作,您可以设置一个阈值,当达到该阈值时,停止比较。

    如果您无法设置阈值,那么至少您可以在每对单词的相似度达到 100% 的情况下随时停止。这样可以节省大量时间。

    在某些情况下,如果相似度至少达到 75%,您可以简单地决定停止比较。如果您想向用户显示与用户提供的字符串相似的所有字符串,则可以使用此选项。

    示例:与词序变化的比较

    如果顺序可以改变,则需要将第一组的每个单词与第二组的每个单词进行比较,并对结果组合取最高分,其中包括最短对的所有单词与第二对的不同单词相比,以所有可能的方式排序。为此,您可以填充 (n X m) 个元素的矩阵的上三角形或下三角形,然后从矩阵中获取所需的元素。

    第四条规则:归一化

    您还必须在比较之前对单词进行规范化,如下所示:

    • 如果不区分大小写,则将所有单词转换为大写或小写
    • 如果不区分重音,请删除所有单词中的重音
    • 如果你知道有常用的缩写,你也可以将它们归一化为缩写以加快速度(即,将united转换为utd,而不是utd转换为united)

    缓存优化

    为了优化过程,我缓存了我可以缓存的任何一个,即不同大小的比较向量,比如向量 0,1,2-0,1,3,-0,1,4-0,2,3,在 A、B、C、D、E 到 a、b、c 比较示例中:长度为 3,5 的所有比较将在首次使用时计算,并针对所有 3 个单词到 5 个单词的传入比较进行回收。

    其他算法

    我试过汉明距离,结果不太准确。

    你可以做更复杂的事情,比如语义比较、语音比较,考虑到一些字母是一样的(比如bv,对于几种语言,比如西班牙语,没有区别)。其中一些非常容易实现,而另一些则非常困难。

    注意:我没有包括 Levenhstein distance 的实现,因为你可以很容易地找到它在不同的语言上实现

    【讨论】:

      【解决方案3】:

      看看这篇文章,它解释了如何做到这一点并提供了示例代码:)

      Fuzzy Matching (Levenshtein Distance)

      更新:

      这里是方法代码,以两个字符串为参数,计算两个字符串的“Levenshtein Distance”

      public static int Compute(string s, string t)
          {
          int n = s.Length;
          int m = t.Length;
          int[,] d = new int[n + 1, m + 1];
      
          // Step 1
          if (n == 0)
          {
              return m;
          }
      
          if (m == 0)
          {
              return n;
          }
      
          // Step 2
          for (int i = 0; i <= n; d[i, 0] = i++)
          {
          }
      
          for (int j = 0; j <= m; d[0, j] = j++)
          {
          }
      
          // Step 3
          for (int i = 1; i <= n; i++)
          {
              //Step 4
              for (int j = 1; j <= m; j++)
              {
              // Step 5
              int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;
      
              // Step 6
              d[i, j] = Math.Min(
                  Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                  d[i - 1, j - 1] + cost);
              }
          }
          // Step 7
          return d[n, m];
          }
      

      【讨论】:

      • 仅链接的答案不是好的答案。请解释为什么如何回答这个问题。
      【解决方案4】:

      检测重复项有时可能比计算 Levenshtein 实例复杂“一点”。 考虑以下示例:

      1. Jeff, Lynch, Maverick, Road, 181, Woodstock  
      2. Jeff, Alf., Lynch, Maverick, Rd, Woodstock, NY
      

      这种重复可以通过复杂的聚类算法进行匹配。

      有关更多信息,您可能需要查看一些研究论文,例如 “大型数据库中重复检测的有效增量聚类”。

      (示例来自论文)

      【讨论】:

        【解决方案5】:

        您正在寻找的是字符串相似性度量。有多种方法可以做到这一点:

        1. 编辑两个字符串之间的距离(如答案 #1)
        2. 将字符串转换为字符集(通常在二元组或单词上),然后计算两组的布鲁斯系数或骰子系数。
        3. 将字符串投影到词向量(单词或二元组)并计算两个向量之间的余弦距离。

        我通常发现选项 #2 是最容易实现的,如果您的字符串是短语,那么您可以简单地在词边界上对它们进行标记。 在上述所有情况下,您可能需要先删除停用词(常用词,如 and、a、the 等),然后再进行标记。 更新:链接

        Dice Coefficient

        Cosine Similarity

        Implementing Naive Similarity engine in C# *警告:不要脸的自我宣传

        【讨论】:

        • 如果您指出在哪里可以找到有关 Bruce 和 Dice 系数以及余弦距离的信息,那就太好了。我们,“普通人”不知道他们是什么。
        【解决方案6】:

        这是使用 Levenshtein 距离算法的替代方法。这是根据 Dice 系数比较字符串,比较每个字符串中常见字母对的数量,生成一个介于 0 和 1 之间的值,0 表示不相似,1 表示完全相似

            public static double CompareStrings(string strA, string strB)
            {
                List<string> setA = new List<string>();
                List<string> setB = new List<string>();
        
                for (int i = 0; i < strA.Length - 1; ++i)
                    setA.Add(strA.Substring(i, 2));
        
                for (int i = 0; i < strB.Length - 1; ++i)
                    setB.Add(strB.Substring(i, 2));
        
                var intersection = setA.Intersect(setB, StringComparer.InvariantCultureIgnoreCase);
        
                return (2.0 * intersection.Count()) / (setA.Count + setB.Count);
            }
        

        这样调用方法:

        CompareStrings("Manchester United", "Manchester Utd");
        

        输出为:0.75862068965517238

        【讨论】:

          猜你喜欢
          • 2019-11-10
          • 1970-01-01
          • 1970-01-01
          • 2018-09-16
          • 2015-06-14
          • 2012-08-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多