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

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

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


person Oliver Hanappi    schedule 26.02.2010    source источник


Ответы (5)


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

«Расстояние Левенштейна между двумя строками определяется как минимальное количество изменений, необходимых для преобразования одной строки в другую, при этом допустимые операции редактирования включают вставку, удаление или замену одного символа». - 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 # здесь.

person D'Arcy Rittich    schedule 26.02.2010
comment
В этом случае мне придется возразить против расстояния Левенштейна. Хотя это отлично подходит для выяснения того, насколько разные две строки, орфографические ошибки чаще всего сохраняют правильную фонетику. Например, алгоритм LD, вероятно, не укажет, что cool cat и kool kat похожи (что, как я полагаю, желает плакат), тогда как Soundex и Metaphone с большей вероятностью укажут на сходство между этими словами / фразы. - person casperOne; 26.02.2010
comment
@casperOne: сложно сказать, не зная, к какому набору данных он применяется, но согласитесь, что универсального подхода не существует. Я большой поклонник двойного метафона. - person D'Arcy Rittich; 26.02.2010
comment
@RedFilter привет .. Я использовал расстояние Левенштейна ... но на самом деле я сравниваю страны или регионы мира. поэтому, если я сохраню допуск равным 2, то Австрия и Австралия будут показаны одинаково. при этом США и США показаны по-разному. что я могу сделать с этой проблемой? - person Jay Nirgudkar; 10.06.2015
comment
@JayNirgudkar В этом случае у меня были бы дополнительные данные для никнейков / сокращений, с которыми я также сравниваю. - person D'Arcy Rittich; 10.06.2015

В платформе .NET нет ничего, что могло бы помочь вам с этим "из коробки".

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

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

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

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

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

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

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

person casperOne    schedule 26.02.2010

Вот реализация метода 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)));
}
person Ben Gripka    schedule 23.11.2016

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

person Jonathan Wood    schedule 14.01.2011
comment
Ссылка мертвая. - person Gert Arnold; 15.06.2021
comment
@GertArnold: У меня работает. - person Jonathan Wood; 15.06.2021
comment
Забавно, я могу добраться до него только тогда, когда перееду в США через VPN. В противном случае время истекает. - person Gert Arnold; 16.06.2021

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

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

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

    /// <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];
    }
person Samuel Neff    schedule 26.02.2010