Perbandingan String Kemiripan di Java


111

Saya ingin membandingkan beberapa string satu sama lain, dan menemukan string yang paling mirip. Saya bertanya-tanya apakah ada pustaka, metode, atau praktik terbaik yang akan mengembalikan saya string mana yang lebih mirip dengan string lain. Sebagai contoh:

  • "Rubah cepat melompat" -> "Rubah melompat"
  • "Rubah cepat melompat" -> "Rubah"

Perbandingan ini akan mengembalikan bahwa yang pertama lebih mirip daripada yang kedua.

Saya rasa saya membutuhkan beberapa metode seperti:

double similarityIndex(String s1, String s2)

Apakah ada hal seperti itu di suatu tempat?

EDIT: Mengapa saya melakukan ini? Saya menulis skrip yang membandingkan output dari file MS Project dengan output dari beberapa sistem warisan yang menangani tugas. Karena sistem lama memiliki lebar bidang yang sangat terbatas, saat nilai ditambahkan, deskripsinya disingkat. Saya ingin beberapa cara semi-otomatis untuk menemukan entri mana dari MS Project yang mirip dengan entri pada sistem sehingga saya bisa mendapatkan kunci yang dihasilkan. Ini memiliki kekurangan, karena masih harus diperiksa secara manual, tetapi akan menghemat banyak pekerjaan

Jawaban:


82

Ya, ada banyak algoritma yang terdokumentasi dengan baik seperti:

  • Kesamaan kosinus
  • Kesamaan Jaccard
  • Koefisien dadu
  • Kesamaan yang cocok
  • Kesamaan yang tumpang tindih
  • dll dll

Ringkasan yang bagus ("Sam's String Metrics") dapat ditemukan di sini (tautan asli mati, sehingga tertaut ke Arsip Internet)

Periksa juga proyek ini:


18
+1 Situs simmetrics sepertinya tidak aktif lagi. Namun, saya menemukan kode tersebut di sourceforge: sourceforge.net/projects/simmetrics Terima kasih atas penunjuknya.
Michael Merchant

7
Tautan "Anda dapat memeriksa ini" rusak.
Kiril

1
Itu sebabnya Michael Merchant memposting tautan yang benar di atas.
emilyk

2
Toples untuk simmetrics di sourceforge agak ketinggalan jaman, github.com/mpkorstanje/simmetrics adalah halaman github yang diperbarui dengan artefak maven
tom91136

Untuk menambah komentar @MichaelMerchant, proyek ini juga tersedia di github . Tidak terlalu aktif di sana juga tetapi sedikit lebih baru dari sourceforge.
Ghurdyl

163

Cara umum untuk menghitung kemiripan antara dua string dengan cara 0% -100% , seperti yang digunakan di banyak pustaka, adalah mengukur berapa banyak (dalam%) Anda harus mengubah string yang lebih panjang untuk mengubahnya menjadi lebih pendek:

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


Menghitung editDistance():

The editDistance()fungsi di atas diharapkan untuk menghitung mengedit jarak antara dua string. Ada beberapa implementasi untuk langkah ini, masing-masing mungkin lebih sesuai dengan skenario tertentu. Yang paling umum adalah algoritma jarak Levenshtein dan kami akan menggunakannya dalam contoh di bawah ini (untuk string yang sangat besar, algoritma lain cenderung berkinerja lebih baik).

Berikut dua opsi untuk menghitung jarak edit:


Contoh kerja:

Lihat demo online di sini.

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

}

Keluaran:

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
Metode jarak Levenshtein tersedia di org.apache.commons.lang3.StringUtils.
Cleankod

@Cleankod Sekarang menjadi bagian dari commons-text: commons.apache.org/proper/commons-text/javadocs/api-release/org/…
Luiz

15

Saya menerjemahkan algoritma jarak Levenshtein ke dalam 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

Anda dapat menggunakan jarak Levenshtein untuk menghitung perbedaan antara dua string. http://en.wikipedia.org/wiki/Levenshtein_distance


2
Levenshtein bagus untuk beberapa string, tetapi tidak akan diskalakan untuk perbandingan antara sejumlah besar string.
pemboros

Saya telah menggunakan Levenshtein di Java dengan beberapa keberhasilan. Saya belum melakukan perbandingan atas daftar besar sehingga mungkin ada kinerja yang sukses. Juga agak sederhana dan dapat menggunakan beberapa penyesuaian untuk meningkatkan ambang kata-kata yang lebih pendek (seperti 3 atau 4 karakter) yang cenderung terlihat lebih mirip dari yang seharusnya (hanya 3 suntingan dari kucing ke anjing) Perhatikan bahwa Edit Jarak disarankan di bawah ini kurang lebih sama - Levenshtein adalah implementasi khusus dari jarak edit.
Rhubarb

Berikut adalah artikel yang menunjukkan bagaimana menggabungkan Levenshtein dengan kueri SQL yang efisien: literatejava.com/sql/fuzzy-string-search-sql
Thomas W

10

Memang ada banyak ukuran kesamaan string di luar sana:

  • Levenshtein mengedit jarak;
  • Jarak Damerau-Levenshtein;
  • Kesamaan Jaro-Winkler;
  • Jarak edit Common Subsequence Terpanjang;
  • Q-Gram (Ukkonen);
  • jarak n-Gram (Kondrak);
  • Indeks Jaccard;
  • Koefisien Sorensen-Dice;
  • Kesamaan cosinus;
  • ...

Anda dapat menemukan penjelasan dan implementasi java di sini: https://github.com/tdebatty/java-string-similarity





3

Kedengarannya seperti pencari plagiarisme bagi saya jika string Anda berubah menjadi dokumen. Mungkin penelusuran dengan istilah itu akan menghasilkan sesuatu yang bagus.

"Programming Collective Intelligence" memiliki bab tentang menentukan apakah dua dokumen serupa. Kodenya menggunakan Python, tetapi bersih dan mudah dipindahkan.


3

Terima kasih kepada penjawab pertama, menurut saya ada 2 perhitungan computeEditDistance (s1, s2). Karena menghabiskan waktu yang tinggi, memutuskan untuk meningkatkan kinerja kode. Begitu:

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


 }
}

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.