Сравнение строк с допуском

Я ищу способ, чтобы сравнить строку с массивом строк. Сделать точный поиск, конечно, довольно легко, но я хочу, чтобы моя программа терпела орфографические ошибки, отсутствующие части строки и так далее.

есть ли какая-то структура, которая может выполнять такой поиск? У меня есть что-то в виду, что алгоритм поиска вернет несколько результатов в порядке процента совпадения или что-то вроде этого.

6 ответов


можно использовать Левенштейна алгоритм.

"расстояние Левенштейна между двумя строками определяется как минимальное количество изменений, необходимых для преобразования одной строки в другую, с допустимой редактирование операций вставки, удаления или замены одного символа." - Wikipedia.com

Это от dotnetperls.com:

using System;

/// <summary>
/// Contains approximate string matching
/// </summary>
static class LevenshteinDistance
{
    /// <summary>
    /// Compute the distance between two strings.
    /// </summary>
    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];
    }
}

class Program
{
    static void Main()
    {
        Console.WriteLine(LevenshteinDistance.Compute("aunt", "ant"));
        Console.WriteLine(LevenshteinDistance.Compute("Sam", "Samantha"));
        Console.WriteLine(LevenshteinDistance.Compute("flomax", "volmax"));
    }
}

вы можете фактически предпочесть использовать алгоритм расстояния Дамерау-Левенштейна, что также позволяет транспонировать символы, что является распространенной человеческой ошибкой при вводе данных. Вы найдете его реализацию на C#здесь.


в .NET framework нет ничего, что поможет вам с этим из коробки.

наиболее распространенными орфографическими ошибками являются те, где буквы являются достойным фонетическим представлением слова, но не правильным написанием слова.

например, можно утверждать, что слова sword и sord (да, это слово) имеют те же фонетические корни (они звучат одинаково, когда вы их произносите).

Это, как говорится, есть количество алгоритмов, которые можно использовать для перевода слов (даже неправильно написанных) в фонетические варианты.

первое-это Soundex. Это довольно просто реализовать, и есть достаточное количество .NET реализации этого алгоритма. Это довольно просто, но это дает вам реальные ценности, которые вы можете сравнить друг с другом.

другое метафон. Хотя я не могу найти родную .NET-реализацию Metaphone, ссылка даны ссылки на несколько других реализаций, которые могут быть преобразованы. Самым простым для преобразования, вероятно, будет Java реализация алгоритма Метафоны.

следует отметить, что алгоритм Метафоны прошел через изменения. Есть Двойной Метафон (который имеет реализация .NET) и метафон 3. Метафона 3 коммерческое применение, но имеет показатель точности 98% сравненный с точностью 89% для алгоритма двойной Метафоны при запуске с базой данных общих английских слов. В зависимости от вашей потребности вы можете искать (в случае двойной Метафоны) или покупать (в случае Метафоны 3) источник для алгоритма и конвертировать или обращаться к нему через слой P/Invoke (существует множество реализаций c++).

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


ваш другой вариант-сравнить фонетически с помощью Soundex или Metaphone. Я только что закончил статью, в которой представлен код C# для обоих алгоритмов. Вы можете просмотреть его на http://www.blackbeltcoder.com/Articles/algorithms/phonetic-string-comparison-with-soundex.


вот два метода, которые вычисляют Расстояние Левенштейна между строк.

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

Как только вы получите результат, вам нужно будет определить, какое значение вы хотите использовать в качестве порога для соответствия или не. Запустите функцию на куче выборочных данных, чтобы получить хорошее представление о том, как она работает, чтобы помочь решить ваш конкретный порог.

    /// <summary>
    /// Calculates the Levenshtein distance between two strings--the number of changes that need to be made for the first string to become the second.
    /// </summary>
    /// <param name="first">The first string, used as a source.</param>
    /// <param name="second">The second string, used as a target.</param>
    /// <returns>The number of changes that need to be made to convert the first string to the second.</returns>
    /// <remarks>
    /// From http://www.merriampark.com/ldcsharp.htm
    /// </remarks>
    public static int LevenshteinDistance(string first, string second)
    {
        if (first == null)
        {
            throw new ArgumentNullException("first");
        }
        if (second == null)
        {
            throw new ArgumentNullException("second");
        }

        int n = first.Length;
        int m = second.Length;
        var d = new int[n + 1, m + 1]; // matrix

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

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

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

        for (int i = 1; i <= n; i++)
        {

            for (int j = 1; j <= m; j++)
            {
                int cost = (second.Substring(j - 1, 1) == first.Substring(i - 1, 1) ? 0 : 1); // cost
                d[i, j] = Math.Min(
                    Math.Min(
                        d[i - 1, j] + 1,
                        d[i, j - 1] + 1),
                    d[i - 1, j - 1] + cost);
            }
        }

        return d[n, m];
    }

вот реализация метода LevenshteinDistance, который использует гораздо меньше памяти при получении тех же результатов. Это адаптация c# псевдо-кода, найденного в этом статья в Википедии под заголовком" итеративный с двумя строками матрицы".

public static int LevenshteinDistance(string source, string target)
{
    // degenerate cases
    if (source == target) return 0;
    if (source.Length == 0) return target.Length;
    if (target.Length == 0) return source.Length;

    // create two work vectors of integer distances
    int[] v0 = new int[target.Length + 1];
    int[] v1 = new int[target.Length + 1];

    // initialize v0 (the previous row of distances)
    // this row is A[0][i]: edit distance for an empty s
    // the distance is just the number of characters to delete from t
    for (int i = 0; i < v0.Length; i++)
        v0[i] = i;

    for (int i = 0; i < source.Length; i++)
    {
        // calculate v1 (current row distances) from the previous row v0

        // first element of v1 is A[i+1][0]
        //   edit distance is delete (i+1) chars from s to match empty t
        v1[0] = i + 1;

        // use formula to fill in the rest of the row
        for (int j = 0; j < target.Length; j++)
        {
            var cost = (source[i] == target[j]) ? 0 : 1;
            v1[j + 1] = Math.Min(v1[j] + 1, Math.Min(v0[j + 1] + 1, v0[j] + cost));
        }

        // copy v1 (current row) to v0 (previous row) for next iteration
        for (int j = 0; j < v0.Length; j++)
            v0[j] = v1[j];
    }

    return v1[target.Length];
}

вот функция, которая даст вам процент сходства.

/// <summary>
/// Calculate percentage similarity of two strings
/// <param name="source">Source String to Compare with</param>
/// <param name="target">Targeted String to Compare</param>
/// <returns>Return Similarity between two strings from 0 to 1.0</returns>
/// </summary>
public static double CalculateSimilarity(string source, string target)
{
    if ((source == null) || (target == null)) return 0.0;
    if ((source.Length == 0) || (target.Length == 0)) return 0.0;
    if (source == target) return 1.0;

    int stepsToSame = LevenshteinDistance(source, target);
    return (1.0 - ((double)stepsToSame / (double)Math.Max(source.Length, target.Length)));
}

вы можете найти реализации soundex и алгоритмов расстояния Левенштейна в открытом исходном коде CommonLibrary.NET проект.