Java'da Benzerlik Dize Karşılaştırması


111

Birkaç dizgeyi birbiriyle karşılaştırmak ve en çok benzer olanları bulmak istiyorum. Hangi dizelerin diğer dizelere daha benzer olduğunu bana döndürecek herhangi bir kitaplık, yöntem veya en iyi uygulama olup olmadığını merak ediyordum. Örneğin:

  • "Hızlı tilki atladı" -> "Tilki atladı"
  • "Hızlı tilki atladı" -> "Tilki"

Bu karşılaştırma, birincinin ikinciye göre daha benzer olduğunu döndürecektir.

Sanırım aşağıdaki gibi bir yönteme ihtiyacım var:

double similarityIndex(String s1, String s2)

Bir yerde böyle bir şey var mı?

DÜZENLEME: Bunu neden yapıyorum? Bir MS Project dosyasının çıktısını, görevleri yerine getiren bazı eski sistemin çıktılarıyla karşılaştıran bir betik yazıyorum. Eski sistem çok sınırlı bir alan genişliğine sahip olduğundan, değerler eklendiğinde açıklamalar kısaltılır. MS Project'ten hangi girdilerin sistemdeki girdilere benzediğini bulmak için yarı otomatik bir yol istiyorum, böylece oluşturulan anahtarları alabilirim. Hala manuel olarak kontrol edilmesi gerektiğinden dezavantajları vardır, ancak çok fazla işten tasarruf sağlar

Yanıtlar:


82

Evet, aşağıdakiler gibi iyi belgelenmiş birçok algoritma vardır:

  • Kosinüs benzerliği
  • Jaccard benzerliği
  • Zar katsayısı
  • Eşleşen benzerlik
  • Çakışan benzerlik
  • vs vs

İyi bir özet ("Sam's String Metrics") burada bulunabilir (orijinal bağlantı ölü, dolayısıyla İnternet Arşivine bağlanır)

Ayrıca şu projeleri kontrol edin:


18
+1 Simmetrics sitesi artık aktif görünmüyor. Ancak, kodu sourceforge'da buldum: sourceforge.net/projects/simmetrics İşaretçi için teşekkürler.
Michael Merchant

7
"Bunu kontrol edebilirsiniz" bağlantısı kopmuştur.
Kiril

1
Michael Merchant bu yüzden yukarıda doğru bağlantıyı yayınladı.
emilyk

2
Sourceforge'daki simetrik kavanozu biraz modası geçmiş, github.com/mpkorstanje/simmetrics , maven eserleri içeren güncellenmiş github sayfası
tom91136

@MichaelMerchant adlı kullanıcının yorumunu eklemek için Proje ayrıca mevcuttur github . Orada da çok aktif değil ama sourceforge'dan biraz daha yeni.
Ghurdyl

163

Birçok kitaplıkta kullanıldığı gibi, iki dizge arasındaki benzerliği% 0-% 100 biçiminde hesaplamanın yaygın yolu , daha kısaya dönüştürmek için uzun dizeyi ne kadar (% olarak) değiştirmeniz gerektiğini ölçmektir:

/**
 * 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


Hesaplanıyor editDistance():

Yukarıdaki editDistance()işlevin , iki dizgi arasındaki düzenleme mesafesini hesaplaması beklenir . Bu adım için birkaç uygulama vardır , her biri belirli bir senaryoya daha iyi uyabilir. En yaygın olanı Levenshtein mesafe algoritmasıdır ve onu aşağıdaki örneğimizde kullanacağız (çok büyük dizeler için, diğer algoritmalar muhtemelen daha iyi performans gösterecektir).

Düzenleme mesafesini hesaplamak için iki seçenek aşağıda verilmiştir:


Çalışma örneği:

Çevrimiçi demoyu buradan izleyin.

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");
  }

}

Çıktı:

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
Levenshtein mesafe yöntemi org.apache.commons.lang3.StringUtils.
Cleankod

@Cleankod Artık ortak metnin bir parçası: commons.apache.org/proper/commons-text/javadocs/api-release/org/…
Luiz

15

Ben tercüme Levenshtein mesafe algoritması JavaScript içine:

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

İki dizge arasındaki farkı hesaplamak için Levenshtein mesafesini kullanabilirsiniz. http://en.wikipedia.org/wiki/Levenshtein_distance


2
Levenshtein birkaç dizge için harikadır, ancak çok sayıda dizi arasındaki karşılaştırmalara göre ölçeklenmeyecektir.
harcayan

Java'da Levenshtein'ı bir miktar başarıyla kullandım. Büyük listeler üzerinde karşılaştırmalar yapmadım, bu yüzden bir performans düşüşü olabilir. Ayrıca, biraz basittir ve olması gerekenden daha benzer görülme eğiliminde olan daha kısa sözcükler (3 veya 4 karakter gibi) için eşiği yükseltmek için biraz ince ayar yapılabilir (kediden köpeğe yalnızca 3 düzenlemedir). aşağıda önerilenler hemen hemen aynı şeydir - Levenshtein, düzenleme mesafelerinin özel bir uygulamasıdır.
Ravent

İşte Levenshtein'ı verimli bir SQL sorgusuyla nasıl birleştirdiğini gösteren bir makale: literatejava.com/sql/fuzzy-string-search-sql
Thomas W

10

Gerçekten de birçok dizi benzerlik ölçüsü var:

  • Levenshtein düzenleme mesafesi;
  • Damerau-Levenshtein mesafesi;
  • Jaro-Winkler benzerliği;
  • En Uzun Ortak Sonraki düzenleme mesafesi;
  • Q-Gram (Ukkonen);
  • n-Gram mesafesi (Kondrak);
  • Jaccard indeksi;
  • Sorensen-Dice katsayısı;
  • Kosinüs benzerliği;
  • ...

Bunların açıklamasını ve java uygulamasını burada bulabilirsiniz: https://github.com/tdebatty/java-string-similarity





3

İntihal bulucu gibi geliyor bir belgeye geliyor bana. Belki bu terimle arama yapmak iyi bir şey ortaya çıkarır.

"Kolektif Zekanın Programlanması", iki belgenin benzer olup olmadığının belirlenmesiyle ilgili bir bölüm içerir. Kod Python'da, ancak temiz ve taşınması kolay.


3

İlk cevaplayana teşekkürler, sanırım 2 hesaplama computeEditDistance (s1, s2) var. Yüksek zaman harcaması nedeniyle, kodun performansını iyileştirmeye karar verdi. Yani:

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]);
    }


 }
}

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.