Сравнение строк сходства в Java
Я хочу сравнить несколько строк друг с другом и найти те, которые наиболее похожи. Мне было интересно, есть ли какая-либо библиотека, метод или лучшая практика, которая вернет мне, какие строки больше похожи на другие строки. Например:
- "Быстрая лиса прыгнула" → "Лиса прыгнула"
- "Быстрая лиса подпрыгнула" → "Лиса"
Это сравнение вернет, что первое более похоже на второе.
Я думаю, мне нужен какой-то метод, например:
double similarityIndex(String s1, String s2)
Есть ли такая вещь где-то?
EDIT: Почему я это делаю? Я пишу script, который сравнивает вывод файла MS Project с выходом какой-либо старой системы, которая обрабатывает задачи. Поскольку унаследованная система имеет очень ограниченную ширину поля, при добавлении значений описания сокращаются. Мне нужен полуавтоматический способ найти, какие записи из MS Project похожи на записи в системе, поэтому я могу получить сгенерированные ключи. У этого есть недостатки, поскольку он должен быть все еще проверен вручную, но это сэкономит много работы.
Ответы
Ответ 1
Да, есть много хорошо документированных алгоритмов вроде:
- Косинус сходства
- сходство с Jaccard
- Коэффициент кости
- Соответствие подобия
- Сходство над перекрытием
- и т.д.
В качестве альтернативы вы можете проверить это
Проверьте также эти проекты:
Ответ 2
Общий способ вычисления сходства между двумя строками в формате 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"
Ответ 3
Вы можете использовать расстояние Левенштейна для вычисления разницы между двумя строками.
http://en.wikipedia.org/wiki/Levenshtein_distance
Ответ 4
Я перевел алгоритм расстояния 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];
};
Ответ 5
На самом деле существует множество мер сходства строк:
- Расстояние редактирования Левенштейна;
- расстояние Дамерау-Левенштейна;
- сходство Яро-Винклера;
- Дальнейшее редактирование расстояний;
- Q-Gram (Ukkonen);
- n-грамм (Кондрак);
- индекс Jaccard;
- Коэффициент Sorensen-Dice;
- сходство с косинусом;
- ...
Здесь вы можете найти объяснение и реализацию java:
https://github.com/tdebatty/java-string-similarity
Ответ 6
Вы можете достичь этого, используя apache commons java library. Взгляните на эти две функции внутри него:
- getLevenshteinDistance
- getFuzzyDistance
Ответ 7
Теоретически вы можете сравнить редактировать расстояния.
Ответ 8
Обычно это делается с помощью параметра изменить расстояние. Поиск "edit distance java" вызывает ряд библиотек, таких как этот.
Ответ 9
Звучит как поиск плагиата для меня, если ваша строка превращается в документ. Возможно, поиск с этим термином окажется хорошим.
"Программирование коллективного интеллекта" содержит главу об определении того, похожи ли два документа. Код находится в Python, но он чист и легко переносится.
Ответ 10
Спасибо первому ответчику, я думаю, что есть 2 вычисления computeEditDistance (s1, s2). Благодаря высоким затратам времени, он решил улучшить производительность кода. Итак:
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]);
}
}
}