Порівняння рядків подібності в Java


111

Я хочу порівняти кілька рядків один з одним і знайти ті, які є найбільш схожими. Мені було цікаво, чи є бібліотека, метод чи найкраща практика, які б повернули мені, які струни більше схожі на інші рядки. Наприклад:

  • "Швидка лисиця стрибнула" -> "Лисиця стрибнула"
  • "Швидка лисиця стрибнула" -> "Лисиця"

Це порівняння поверне, що перше схоже, ніж друге.

Я думаю, мені потрібен такий метод, як:

double similarityIndex(String s1, String s2)

Чи є десь таке?

EDIT: Чому я це роблю? Я пишу сценарій, який порівнює вихідний файл проекту MS з результатом деякої застарілої системи, яка обробляє завдання. Оскільки застаріла система має дуже обмежену ширину поля, при додаванні значень описи скорочуються. Мені потрібно якийсь напівавтоматизований спосіб виявити, які записи з MS Project схожі на записи в системі, щоб я міг отримати згенеровані ключі. Він має недоліки, оскільки його потрібно перевірити вручну, але це дозволить заощадити багато роботи

Відповіді:


82

Так, існує багато добре задокументованих алгоритмів, таких як:

  • Косину схожість
  • Схожість Жакарда
  • Коефіцієнт кістки
  • Відповідність подібності
  • Подібність перекриття
  • тощо

Хороший підсумок ("Рядок метрики Сама") можна знайти тут (оригінальна посилання мертва, тому вона посилається на Інтернет-архів)

Також перевірте ці проекти:


18
+1 Сайт симетрики більше не здається активним. Однак я знайшов код на sourceforge: sourceforge.net/projects/simmetrics Дякую за покажчик.
Майкл Мерчант

7
Посилання "Ви можете перевірити це" порушено.
Кирило

1
Ось чому Майкл Мерчант розмістив правильне посилання вище.
emilyk

2
Баночка для симетрії на sourceforge трохи застаріла, github.com/mpkorstanje/simmetrics - оновлена ​​сторінка github з артефактами Maven
tom91136,

Щоб додати коментар до @MichaelMerchant, проект також доступний на github . Там не дуже активно, але трохи пізніше, ніж sourceforge.
Гурділ

163

Загальний спосіб обчислення схожості двох рядків способом 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()наведена вище функція обчислить відстань редагування між двома рядками. На цьому кроці є кілька реалізацій , кожна може краще відповідати конкретному сценарію. Найпоширенішим є алгоритм відстані Левенштейна, і ми будемо використовувати його в нашому прикладі нижче (для дуже великих рядків інші алгоритми, ймовірно, працюють краще).

Ось два варіанти обчислення відстані редагування:


Робочий приклад:

Дивіться онлайн-демонстрацію тут.

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"

11
Метод дистанції Левенштейна доступний в org.apache.commons.lang3.StringUtils.
Клеанод

@Cleankod Зараз це частина commons-text: commons.apache.org/proper/commons-text/javadocs/api-release/org/…
Луїз

15

Я переклав алгоритм відстані Левенштейна в 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];
};

11

Ви можете використовувати відстань Левенштейна, щоб обчислити різницю між двома рядками. http://en.wikipedia.org/wiki/Levenshtein_distance


2
Левенштейн чудово підходить для декількох струн, але не зможе порівняти велику кількість струн.
витрачайте

Я з певним успіхом використовував Левенштайн на Яві. Я не робив порівнянь над величезними списками, тому може бути хіт продуктивності. Крім того, це трохи просто, і ви можете скоригуватися, щоб підняти поріг для коротших слів (наприклад, 3 або 4 символи), які, як правило, схожі на звичайні (це лише 3 редагування від кота до собаки). Зауважте, що редагувати відстані запропоновані нижче - це майже те саме - Левенштайн - це особлива реалізація дистанцій редагування.
Ревінь

Ось стаття, що показує, як поєднувати Левенштайн з ефективним SQL-запитом: literatejava.com/sql/fuzzy-string-search-sql
Thomas W

10

Дійсно існує багато заходів подібності рядків:

  • Левенштайн редагувати відстань;
  • Відстань Дамерау-Левенштейн;
  • Схожість з Джаро-Вінклером;
  • Найдовша відстань редагування загальної послідовності;
  • Q-Грам (Укконен);
  • n-Грам відстань (Кондрак);
  • Індекс Жакарда;
  • Коефіцієнт Соренсена-Кубика;
  • Подібність косину;
  • ...

Ви можете знайти пояснення та реалізацію цих програм тут: https://github.com/tdebatty/java-string-s similarity


8

Домогтися цього можна за допомогою бібліотеки Java apache commons . Погляньте на ці дві функції всередині нього:
- getLevenshteinDistance
- getFuzzyDistance


3
Станом на жовтень 2017 року пов'язані методи застаріли. Використовуйте класи Відстань Левенштейна і FuzzyScore з Загально текстової бібліотеки замість
vatbub



3

Звучить як плагіат шукач для мене , якщо ваша рядок перетворюється в документ. Можливо, пошук за цим терміном виявить щось хороше.

"Програмування колективної розвідки" містить розділ, який визначає, чи схожі два документи. Код є в Python, але він чистий і простий у портуванні.


3

Дякую першому відповідачу, я думаю, є 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]);
    }


 }
}

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.