So sánh chuỗi tương tự trong Java


111

Tôi muốn so sánh một số chuỗi với nhau và tìm những chuỗi giống nhau nhất. Tôi đã tự hỏi liệu có bất kỳ thư viện, phương pháp hoặc phương pháp hay nhất nào sẽ trả về cho tôi những chuỗi nào giống với các chuỗi khác không. Ví dụ:

  • "The quick fox jump" -> "The fox jump"
  • "The quick fox jump" -> "The fox"

So sánh này sẽ trả lại rằng cái đầu tiên giống hơn cái thứ hai.

Tôi đoán tôi cần một số phương pháp như:

double similarityIndex(String s1, String s2)

Có một điều như vậy ở đâu đó?

CHỈNH SỬA: Tại sao tôi làm điều này? Tôi đang viết một tập lệnh so sánh đầu ra của tệp MS Project với đầu ra của một số hệ thống kế thừa xử lý các tác vụ. Bởi vì hệ thống kế thừa có độ rộng trường rất hạn chế, khi các giá trị được thêm vào, các mô tả sẽ được viết tắt. Tôi muốn một số cách bán tự động để tìm các mục nhập từ MS Project tương tự với các mục nhập trên hệ thống để tôi có thể lấy các khóa đã tạo. Nó có nhược điểm, vì nó vẫn phải được kiểm tra thủ công, nhưng nó sẽ tiết kiệm rất nhiều công việc

Câu trả lời:


82

Có, có rất nhiều thuật toán được ghi chép đầy đủ như:

  • Cosine tương tự
  • Jaccard tương tự
  • Hệ số của xúc xắc
  • Đối sánh sự giống nhau
  • Tương tự chồng chéo
  • Vân vân

Bạn có thể tìm thấy một bản tóm tắt hay ("Số liệu chuỗi của Sam") tại đây (liên kết gốc đã chết, vì vậy nó liên kết đến Kho lưu trữ Internet)

Cũng kiểm tra các dự án này:


18
+1 Trang web mô phỏng dường như không còn hoạt động nữa. Tuy nhiên, tôi đã tìm thấy mã trên sourceforge: sourceforge.net/projects/simmetrics Cảm ơn vì con trỏ.
Michael Merchant

7
Liên kết "bạn có thể kiểm tra điều này" bị hỏng.
Kiril

1
Đó là lý do tại sao Michael Merchant đăng đúng liên kết ở trên.
emilyk

2
Jar cho simmetrics trên SourceForge là một chút lỗi thời, github.com/mpkorstanje/simmetrics là trang github cập nhật với hiện vật maven
tom91136

Để thêm vào bình luận của @MichaelMerchant, dự án cũng có sẵn trên github . Mặc dù không hoạt động nhiều ở đó nhưng gần đây hơn sourceforge một chút.
Ghurdyl

163

Cách phổ biến để tính độ giống nhau giữa hai chuỗi theo kiểu 0% -100% , như được sử dụng trong nhiều thư viện, là đo lường mức độ (tính bằng%) bạn phải thay đổi chuỗi dài hơn để biến nó thành ngắn hơn:

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


Tính toán editDistance():

Các editDistance()chức năng trên được dự kiến để tính toán chỉnh sửa khoảng cách giữa hai dây. Có một số cách triển khai cho bước này, mỗi cách có thể phù hợp với một tình huống cụ thể hơn. Phổ biến nhất là thuật toán khoảng cách Levenshtein và chúng tôi sẽ sử dụng nó trong ví dụ dưới đây (đối với các chuỗi rất lớn, các thuật toán khác có khả năng hoạt động tốt hơn).

Đây là hai tùy chọn để tính toán khoảng cách chỉnh sửa:


Ví dụ làm việc:

Xem demo trực tuyến tại đây.

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

}

Đầu ra:

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
Phương pháp khoảng cách Levenshtein có sẵn trong org.apache.commons.lang3.StringUtils.
Cleankod

@Cleankod Bây giờ nó là một phần của commons-text: commons.apache.org/proper/commons-text/javadocs/api-release/org/...
Luiz

15

Tôi đã dịch thuật toán khoảng cách Levenshtein sang 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

Bạn có thể sử dụng khoảng cách Levenshtein để tính toán sự khác biệt giữa hai chuỗi. http://en.wikipedia.org/wiki/Levenshtein_distance


2
Levenshtein là tuyệt vời cho một vài chuỗi, nhưng sẽ không mở rộng để so sánh giữa một số lượng lớn các chuỗi.
tiêu

Tôi đã sử dụng Levenshtein trong Java với một số thành công. Tôi đã không thực hiện so sánh trên các danh sách khổng lồ vì vậy có thể có một cú đánh hiệu suất. Ngoài ra, nó cũng hơi đơn giản và có thể sử dụng một số điều chỉnh để nâng cao ngưỡng cho các từ ngắn hơn (như 3 hoặc 4 ký tự) có xu hướng được xem là giống hơn so với bình thường (chỉ có 3 chỉnh sửa từ mèo sang chó) Lưu ý rằng Chỉnh sửa Khoảng cách được đề xuất dưới đây khá giống nhau - Levenshtein là một triển khai cụ thể của các khoảng cách chỉnh sửa.
Rhubarb

Dưới đây là một bài báo cho thấy cách kết hợp Levenshtein với một truy vấn SQL hiệu quả: literatejava.com/sql/fuzzy-string-search-sql
Thomas W

10

Thực sự có rất nhiều biện pháp tương tự chuỗi:

  • Levenshtein chỉnh sửa khoảng cách;
  • Khoảng cách Damerau-Levenshtein;
  • Tương tự Jaro-Winkler;
  • Khoảng cách chỉnh sửa Thứ tự chung dài nhất;
  • Q-Gram (Ukkonen);
  • khoảng cách n-Gram (Kondrak);
  • Chỉ số Jaccard;
  • Hệ số Sorensen-Dice;
  • Cosine tương tự;
  • ...

Bạn có thể tìm thấy giải thích và triển khai java về những điều này tại đây: https://github.com/tdebatty/java-string-similarity





3

Tôi nghe có vẻ giống như một công cụ tìm đạo văn nếu chuỗi của bạn biến thành một tài liệu. Có thể tìm kiếm với cụm từ đó sẽ tạo ra một cái gì đó tốt.

"Lập trình Trí tuệ Tập thể" có một chương về xác định xem hai tài liệu có giống nhau hay không. Mã bằng Python, nhưng nó sạch sẽ và dễ chuyển.


3

Cảm ơn người trả lời đầu tiên, tôi nghĩ có 2 phép tính của computeEditDistance (s1, s2). Do thời gian sử dụng cao, quyết định cải thiện hiệu suất của mã. Vì thế:

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


 }
}

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.