133

我想比较几个字符串,并找到最相似的字符串。我想知道是否有任何库、方法或最佳实践可以返回哪些字符串与其他字符串更相似。例如:

  • “狐狸跳了”->“狐狸跳了”
  • “快狐跳”->“狐狸”

这种比较将返回第一个比第二个更相似。

我想我需要一些方法,例如:

double similarityIndex(String s1, String s2)

有没有这样的地方?

编辑:我为什么要这样做?我正在编写一个脚本,将 MS Project 文件的输出与一些处理任务的遗留系统的输出进行比较。因为遗留系统的字段宽度非常有限,所以在添加值时,描述会被缩写。我想要一些半自动的方法来查找 MS Project 中的哪些条目与系统上的条目相似,这样我就可以获得生成的密钥。它有缺点,因为它仍然必须手动检查,但它会节省很多工作

4

11 回答 11

179

在许多库中以 0%-100% 的方式计算两个字符串之间的相似度的常用方法是测量您必须将较长的字符串更改为多少(以 % 为单位)才能将其变为较短的字符串:

/**
 * Calculates the similarity (a number within 0 and 1) between two strings.
 */
public static double similarity(String s1, String s2) {
  String longer = s1, shorter = s2;
  if (s1.length() < s2.length()) { // longer should always have greater length
    longer = s2; shorter = s1;
  }
  int longerLength = longer.length();
  if (longerLength == 0) { return 1.0; /* both strings are zero length */ }
  return (longerLength - editDistance(longer, shorter)) / (double) longerLength;
}
// you can use StringUtils.getLevenshteinDistance() as the editDistance() function
// full copy-paste working code is below


计算editDistance()

上面的editDistance()函数预计会计算两个字符串之间的编辑距离。此步骤有多种实现方式,每种实现方式都可能更适合特定场景。最常见的是Levenshtein 距离算法,我们将在下面的示例中使用它(对于非常大的字符串,其他算法可能会执行得更好)。

这是计算编辑距离的两个选项:


工作示例:

在此处查看在线演示。

public class StringSimilarity {

  /**
   * Calculates the similarity (a number within 0 and 1) between two strings.
   */
  public static double similarity(String s1, String s2) {
    String longer = s1, shorter = s2;
    if (s1.length() < s2.length()) { // longer should always have greater length
      longer = s2; shorter = s1;
    }
    int longerLength = longer.length();
    if (longerLength == 0) { return 1.0; /* both strings are zero length */ }
    /* // If you have Apache Commons Text, you can use it to calculate the edit distance:
    LevenshteinDistance levenshteinDistance = new LevenshteinDistance();
    return (longerLength - levenshteinDistance.apply(longer, shorter)) / (double) longerLength; */
    return (longerLength - editDistance(longer, shorter)) / (double) longerLength;

  }

  // Example implementation of the Levenshtein Edit Distance
  // See http://rosettacode.org/wiki/Levenshtein_distance#Java
  public static int editDistance(String s1, String s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    int[] costs = new int[s2.length() + 1];
    for (int i = 0; i <= s1.length(); i++) {
      int lastValue = i;
      for (int j = 0; j <= s2.length(); j++) {
        if (i == 0)
          costs[j] = j;
        else {
          if (j > 0) {
            int newValue = costs[j - 1];
            if (s1.charAt(i - 1) != s2.charAt(j - 1))
              newValue = Math.min(Math.min(newValue, lastValue),
                  costs[j]) + 1;
            costs[j - 1] = lastValue;
            lastValue = newValue;
          }
        }
      }
      if (i > 0)
        costs[s2.length()] = lastValue;
    }
    return costs[s2.length()];
  }

  public static void printSimilarity(String s, String t) {
    System.out.println(String.format(
      "%.3f is the similarity between \"%s\" and \"%s\"", similarity(s, t), s, t));
  }

  public static void main(String[] args) {
    printSimilarity("", "");
    printSimilarity("1234567890", "1");
    printSimilarity("1234567890", "123");
    printSimilarity("1234567890", "1234567");
    printSimilarity("1234567890", "1234567890");
    printSimilarity("1234567890", "1234567980");
    printSimilarity("47/2010", "472010");
    printSimilarity("47/2010", "472011");
    printSimilarity("47/2010", "AB.CDEF");
    printSimilarity("47/2010", "4B.CDEFG");
    printSimilarity("47/2010", "AB.CDEFG");
    printSimilarity("The quick fox jumped", "The fox jumped");
    printSimilarity("The quick fox jumped", "The fox");
    printSimilarity("kitten", "sitting");
  }

}

输出:

1.000 is the similarity between "" and ""
0.100 is the similarity between "1234567890" and "1"
0.300 is the similarity between "1234567890" and "123"
0.700 is the similarity between "1234567890" and "1234567"
1.000 is the similarity between "1234567890" and "1234567890"
0.800 is the similarity between "1234567890" and "1234567980"
0.857 is the similarity between "47/2010" and "472010"
0.714 is the similarity between "47/2010" and "472011"
0.000 is the similarity between "47/2010" and "AB.CDEF"
0.125 is the similarity between "47/2010" and "4B.CDEFG"
0.000 is the similarity between "47/2010" and "AB.CDEFG"
0.700 is the similarity between "The quick fox jumped" and "The fox jumped"
0.350 is the similarity between "The quick fox jumped" and "The fox"
0.571 is the similarity between "kitten" and "sitting"
于 2013-04-15T14:58:30.910 回答
88

是的,有许多有据可查的算法,例如:

  • 余弦相似度
  • 杰卡德相似度
  • 骰子系数
  • 匹配相似度
  • 重叠相似度
  • 等等等等

可以在此处找到一个很好的摘要(“Sam 的字符串度量”)(原始链接已失效,因此它链接到 Internet 档案)

还要检查这些项目:

于 2009-06-05T09:59:57.183 回答
16

我将Levenshtein 距离算法翻译成 JavaScript:

String.prototype.LevenshteinDistance = function (s2) {
    var array = new Array(this.length + 1);
    for (var i = 0; i < this.length + 1; i++)
        array[i] = new Array(s2.length + 1);

    for (var i = 0; i < this.length + 1; i++)
        array[i][0] = i;
    for (var j = 0; j < s2.length + 1; j++)
        array[0][j] = j;

    for (var i = 1; i < this.length + 1; i++) {
        for (var j = 1; j < s2.length + 1; j++) {
            if (this[i - 1] == s2[j - 1]) array[i][j] = array[i - 1][j - 1];
            else {
                array[i][j] = Math.min(array[i][j - 1] + 1, array[i - 1][j] + 1);
                array[i][j] = Math.min(array[i][j], array[i - 1][j - 1] + 1);
            }
        }
    }
    return array[this.length][s2.length];
};
于 2010-11-01T15:33:29.607 回答
13

确实有很多字符串相似性度量:

  • Levenshtein 编辑距离;
  • Damerau-Levenshtein 距离;
  • Jaro-Winkler 相似性;
  • 最长公共子序列编辑距离;
  • Q-Gram(乌科宁);
  • n-Gram 距离(康德拉克);
  • 杰卡德指数;
  • Sorensen-Dice 系数;
  • 余弦相似度;
  • ...

你可以在这里找到这些解释和java实现: https ://github.com/tdebatty/java-string-similarity

于 2015-08-07T11:26:43.253 回答
11

您可以使用 Levenshtein 距离来计算两个字符串之间的差异。 http://en.wikipedia.org/wiki/Levenshtein_distance

于 2009-06-05T09:58:20.603 回答
11

您可以使用apache commons java library来实现这一点。看看其中的这两个函数:
- getLevenshteinDistance
- getFuzzyDistance

于 2017-04-10T21:17:48.617 回答
5

感谢第一个回答者,我认为computeEditDistance(s1,s2)有2次计算。由于它的高时间花费,决定提高代码的性能。所以:

public class LevenshteinDistance {

public static int computeEditDistance(String s1, String s2) {
    s1 = s1.toLowerCase();
    s2 = s2.toLowerCase();

    int[] costs = new int[s2.length() + 1];
    for (int i = 0; i <= s1.length(); i++) {
        int lastValue = i;
        for (int j = 0; j <= s2.length(); j++) {
            if (i == 0) {
                costs[j] = j;
            } else {
                if (j > 0) {
                    int newValue = costs[j - 1];
                    if (s1.charAt(i - 1) != s2.charAt(j - 1)) {
                        newValue = Math.min(Math.min(newValue, lastValue),
                                costs[j]) + 1;
                    }
                    costs[j - 1] = lastValue;
                    lastValue = newValue;
                }
            }
        }
        if (i > 0) {
            costs[s2.length()] = lastValue;
        }
    }
    return costs[s2.length()];
}

public static void printDistance(String s1, String s2) {
    double similarityOfStrings = 0.0;
    int editDistance = 0;
    if (s1.length() < s2.length()) { // s1 should always be bigger
        String swap = s1;
        s1 = s2;
        s2 = swap;
    }
    int bigLen = s1.length();
    editDistance = computeEditDistance(s1, s2);
    if (bigLen == 0) {
        similarityOfStrings = 1.0; /* both strings are zero length */
    } else {
        similarityOfStrings = (bigLen - editDistance) / (double) bigLen;
    }
    //////////////////////////
    //System.out.println(s1 + "-->" + s2 + ": " +
      //      editDistance + " (" + similarityOfStrings + ")");
    System.out.println(editDistance + " (" + similarityOfStrings + ")");
}

public static void main(String[] args) {
    printDistance("", "");
    printDistance("1234567890", "1");
    printDistance("1234567890", "12");
    printDistance("1234567890", "123");
    printDistance("1234567890", "1234");
    printDistance("1234567890", "12345");
    printDistance("1234567890", "123456");
    printDistance("1234567890", "1234567");
    printDistance("1234567890", "12345678");
    printDistance("1234567890", "123456789");
    printDistance("1234567890", "1234567890");
    printDistance("1234567890", "1234567980");

    printDistance("47/2010", "472010");
    printDistance("47/2010", "472011");

    printDistance("47/2010", "AB.CDEF");
    printDistance("47/2010", "4B.CDEFG");
    printDistance("47/2010", "AB.CDEFG");

    printDistance("The quick fox jumped", "The fox jumped");
    printDistance("The quick fox jumped", "The fox");
    printDistance("The quick fox jumped",
            "The quick fox jumped off the balcany");
    printDistance("kitten", "sitting");
    printDistance("rosettacode", "raisethysword");
    printDistance(new StringBuilder("rosettacode").reverse().toString(),
            new StringBuilder("raisethysword").reverse().toString());
    for (int i = 1; i < args.length; i += 2) {
        printDistance(args[i - 1], args[i]);
    }


 }
}
于 2014-10-18T13:09:06.087 回答
3

理论上,您可以比较编辑距离

于 2009-06-05T09:59:28.230 回答
3

这通常使用编辑距离度量来完成。搜索“edit distance java”会出现许多库,比如这个

于 2009-06-05T10:00:17.027 回答
3

如果您的字符串变成文档,对我来说听起来像是抄袭发现者。也许用这个词搜索会发现一些好东西。

《Programming Collective Intelligence》有一章是关于判断两个文档是否相似的。代码是用 Python 编写的,但它干净且易于移植。

于 2009-06-05T10:01:27.460 回答
-1

您还可以使用 z 算法来查找字符串中的相似性。点击这里https://teakrunch.com/2020/05/09/string-similarity-hackerrank-challenge/

于 2020-05-10T09:11:15.443 回答