Как я могу измерить сходство между двумя строками?

Учитывая две строки text1 а также text2

public SOMEUSABLERETURNTYPE Compare(string text1, string text2)
{
     // DO SOMETHING HERE TO COMPARE
}

Примеры:

  1. Первая строка: Stackru

    Вторая строка: StaqOverflow

    Возврат: сходство 91%

    Возврат может быть в% или что-то в этом роде.

  2. Первая строка: простой текстовый тест

    Вторая строка: сложный текстовый тест

    Возврат: значения можно считать равными

Есть идеи? Каков наилучший способ сделать это?

12 ответов

Решение

Существуют различные способы сделать это. Загляните на страницу Wikipedia "Показатели сходства строк" для ссылок на другие страницы с алгоритмами.

Однако я не думаю, что какой-либо из этих алгоритмов принимает во внимание звуки - поэтому "переполнение staq" будет так же похоже на "переполнение стека", как и "переполнение staw", несмотря на то, что первый более похож с точки зрения произношения.

Я только что нашел другую страницу, которая дает гораздо больше возможностей... в частности, алгоритм Soundex ( Википедия) может быть ближе к тому, что вы ищете.

Расстояние Левенштейна, вероятно, то, что вы ищете.

Вот код, который я написал для проекта, над которым я работаю. Мне нужно знать коэффициент сходства строк и коэффициент сходства, основанный на словах строк. Последнее, я хочу знать как коэффициент схожести слов самой маленькой строки (так что если все слова существуют и совпадают в большей строке, результат будет равен 100%), так и коэффициент схожести слов большей строки (который я называю RealWordsRatio). Я использую алгоритм Левенштейна, чтобы найти расстояние. Код пока не оптимизирован, но работает как положено. Я надеюсь, что вы найдете это полезным.

public static int Compute(string s, string t)
    {
        int n = s.Length;
        int m = t.Length;
        int[,] d = new int[n + 1, m + 1];

        // Step 1
        if (n == 0)
        {
            return m;
        }

        if (m == 0)
        {
            return n;
        }

        // Step 2
        for (int i = 0; i <= n; d[i, 0] = i++)
        {
        }

        for (int j = 0; j <= m; d[0, j] = j++)
        {
        }

        // Step 3
        for (int i = 1; i <= n; i++)
        {
            //Step 4
            for (int j = 1; j <= m; j++)
            {
                // Step 5
                int cost = (t[j - 1] == s[i - 1]) ? 0 : 1;

                // Step 6
                d[i, j] = Math.Min(
                    Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
                    d[i - 1, j - 1] + cost);
            }
        }
        // Step 7
        return d[n, m];
    }

double GetSimilarityRatio(String FullString1, String FullString2, out double WordsRatio, out double RealWordsRatio)
    {
        double theResult = 0;
        String[] Splitted1 = FullString1.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries);
        String[] Splitted2 = FullString2.Split(new char[]{' '}, StringSplitOptions.RemoveEmptyEntries);
        if (Splitted1.Length < Splitted2.Length)
        {
            String[] Temp = Splitted2;
            Splitted2 = Splitted1;
            Splitted1 = Temp;
        }
        int[,] theScores = new int[Splitted1.Length, Splitted2.Length];//Keep the best scores for each word.0 is the best, 1000 is the starting.
        int[] BestWord = new int[Splitted1.Length];//Index to the best word of Splitted2 for the Splitted1.

        for (int loop = 0; loop < Splitted1.Length; loop++) 
        {
            for (int loop1 = 0; loop1 < Splitted2.Length; loop1++) theScores[loop, loop1] = 1000;
            BestWord[loop] = -1;
        }
        int WordsMatched = 0;
        for (int loop = 0; loop < Splitted1.Length; loop++)
        {
            String String1 = Splitted1[loop];
            for (int loop1 = 0; loop1 < Splitted2.Length; loop1++)
            {
                String String2 = Splitted2[loop1];
                int LevenshteinDistance = Compute(String1, String2);
                theScores[loop, loop1] = LevenshteinDistance;
                if (BestWord[loop] == -1 || theScores[loop, BestWord[loop]] > LevenshteinDistance) BestWord[loop] = loop1;
            }
        }

        for (int loop = 0; loop < Splitted1.Length; loop++)
        {
            if (theScores[loop, BestWord[loop]] == 1000) continue;
            for (int loop1 = loop + 1; loop1 < Splitted1.Length; loop1++)
            {
                if (theScores[loop1, BestWord[loop1]] == 1000) continue;//the worst score available, so there are no more words left
                if (BestWord[loop] == BestWord[loop1])//2 words have the same best word
                {
                    //The first in order has the advantage of keeping the word in equality
                    if (theScores[loop, BestWord[loop]] <= theScores[loop1, BestWord[loop1]])
                    {
                        theScores[loop1, BestWord[loop1]] = 1000;
                        int CurrentBest = -1;
                        int CurrentScore = 1000;
                        for (int loop2 = 0; loop2 < Splitted2.Length; loop2++)
                        {
                            //Find next bestword
                            if (CurrentBest == -1 || CurrentScore > theScores[loop1, loop2])
                            {
                                CurrentBest = loop2;
                                CurrentScore = theScores[loop1, loop2];
                            }
                        }
                        BestWord[loop1] = CurrentBest;
                    }
                    else//the latter has a better score
                    {
                        theScores[loop, BestWord[loop]] = 1000;
                        int CurrentBest = -1;
                        int CurrentScore = 1000;
                        for (int loop2 = 0; loop2 < Splitted2.Length; loop2++)
                        {
                            //Find next bestword
                            if (CurrentBest == -1 || CurrentScore > theScores[loop, loop2])
                            {
                                CurrentBest = loop2;
                                CurrentScore = theScores[loop, loop2];
                            }
                        }
                        BestWord[loop] = CurrentBest;
                    }

                    loop = -1;
                    break;//recalculate all
                }
            }
        }
        for (int loop = 0; loop < Splitted1.Length; loop++)
        {
            if (theScores[loop, BestWord[loop]] == 1000) theResult += Splitted1[loop].Length;//All words without a score for best word are max failures
            else
            {
                theResult += theScores[loop, BestWord[loop]];
                if (theScores[loop, BestWord[loop]] == 0) WordsMatched++;
            }
        }
        int theLength = (FullString1.Replace(" ", "").Length > FullString2.Replace(" ", "").Length) ? FullString1.Replace(" ", "").Length : FullString2.Replace(" ", "").Length;
        if(theResult > theLength) theResult = theLength;
        theResult = (1 - (theResult / theLength)) * 100;
        WordsRatio = ((double)WordsMatched / (double)Splitted2.Length) * 100;
        RealWordsRatio = ((double)WordsMatched / (double)Splitted1.Length) * 100;
        return theResult;
    }

Я написал реализацию Double Metaphone в C# некоторое время назад. Вы найдете его значительно превосходящим Soundex и тому подобное.

Также было предложено расстояние Левенштейна, и это отличный алгоритм для многих применений, но фонетическое соответствие не совсем то, что оно делает; иногда это только кажется, потому что фонетически похожие слова обычно пишутся одинаково. Я провел анализ различных алгоритмов нечеткого сопоставления, которые вы также можете найти полезными.

Чтобы иметь дело с "подобными звуками", вы можете изучить кодирование с использованием фонетического алгоритма, такого как Double Metaphone или Soundex. Я не знаю, было бы полезно вычислять расстояния Левенштейна на фонетически закодированных строках или нет, но это возможно. В качестве альтернативы вы можете использовать эвристический метод: преобразовать каждое слово в строке в его закодированную форму, удалить все слова, встречающиеся в обеих строках, и заменить их одним представлением перед вычислением расстояния Левенштейна.

Вы можете искать строку "расстояния", например, расстояние Левенштейна.

Модуль Perl Text:: Phonetic имеет реализации различных алгоритмов.

Джефф Этвуд писал о поиске аналогичного решения для определения авторства постов вики, которое может помочь вам сузить область поиска.

Если вы сравниваете значения в базе данных SQL, вы можете использовать функцию SOUNDEX. Если вы запрашиваете у Google SOUNDEX и C#, некоторые люди написали аналогичную функцию для этого и VB.

Я также рекомендую Soundex, я использовал его в прошлом для обработки названий городов с ошибками. Вот хорошая ссылка для использования: http://whitepapers.zdnet.com/abstract.aspx?docid=352953

Metaphone 3 - это третье поколение алгоритма Metaphone. Это повышает точность фонетического кодирования с 89% Double Metaphone до 98%, что проверено на базе данных наиболее распространенных английских слов, а также имен и неанглоязычных слов, знакомых в Северной Америке. Это производит чрезвычайно надежное фонетическое кодирование для американского произношения.

Metaphone 3 был разработан и разработан Лоуренсом Филипсом, который разработал и разработал оригинальные алгоритмы Metaphone и Double Metaphone.

Если вы хотите сравнить фонетически, посмотрите алгоритмы Soundex и Metaphone: http://www.blackbeltcoder.com/Articles/algorithms/phonetic-string-comparison-with-soundex

Другие вопросы по тегам