Damerau-Levenshtein距离算法,禁用删除计数

时间:2012-08-19 14:34:47

标签: c# algorithm levenshtein-distance fuzzy

如何在Damerau-Levenshtein距离算法的实现中禁用删除计数,或者如果已经实现了其他算法,请指向我。

示例(禁用删除计数):

string1:你好吗?

string2:oyu怎么样?

距离: 1(换位,4次删除不计算

这是算法:

    public static int DamerauLevenshteinDistance(string string1, string string2, int threshold)
    {
        // Return trivial case - where they are equal
        if (string1.Equals(string2))
            return 0;

        // Return trivial case - where one is empty
        if (String.IsNullOrEmpty(string1) || String.IsNullOrEmpty(string2))
            return (string1 ?? "").Length + (string2 ?? "").Length;


        // Ensure string2 (inner cycle) is longer_transpositionRow
        if (string1.Length > string2.Length)
        {
            var tmp = string1;
            string1 = string2;
            string2 = tmp;
        }

        // Return trivial case - where string1 is contained within string2
        if (string2.Contains(string1))
            return string2.Length - string1.Length;

        var length1 = string1.Length;
        var length2 = string2.Length;

        var d = new int[length1 + 1, length2 + 1];

        for (var i = 0; i <= d.GetUpperBound(0); i++)
            d[i, 0] = i;

        for (var i = 0; i <= d.GetUpperBound(1); i++)
            d[0, i] = i;

        for (var i = 1; i <= d.GetUpperBound(0); i++)
        {
            var im1 = i - 1;
            var im2 = i - 2;
            var minDistance = threshold;
            for (var j = 1; j <= d.GetUpperBound(1); j++)
            {
                var jm1 = j - 1;
                var jm2 = j - 2;
                var cost = string1[im1] == string2[jm1] ? 0 : 1;

                var del = d[im1, j] + 1;
                var ins = d[i, jm1] + 1;
                var sub = d[im1, jm1] + cost;

                //Math.Min is slower than native code
                //d[i, j] = Math.Min(del, Math.Min(ins, sub));
                d[i, j] = del <= ins && del <= sub ? del : ins <= sub ? ins : sub;

                if (i > 1 && j > 1 && string1[im1] == string2[jm2] && string1[im2] == string2[jm1])
                    d[i, j] = Math.Min(d[i, j], d[im2, jm2] + cost);

                if (d[i, j] < minDistance)
                    minDistance = d[i, j];
            }

            if (minDistance > threshold)
                return int.MaxValue;
        }

        return d[d.GetUpperBound(0), d.GetUpperBound(1)] > threshold
            ? int.MaxValue
            : d[d.GetUpperBound(0), d.GetUpperBound(1)];
    }

3 个答案:

答案 0 :(得分:6)

 public static int DamerauLevenshteinDistance( string string1
                                            , string string2
                                            , int threshold)
{
    // Return trivial case - where they are equal
    if (string1.Equals(string2))
        return 0;

    // Return trivial case - where one is empty
    // WRONG FOR YOUR NEEDS: 
    // if (String.IsNullOrEmpty(string1) || String.IsNullOrEmpty(string2))
    //      return (string1 ?? "").Length + (string2 ?? "").Length;

    //DO IT THIS WAY:
    if (String.IsNullOrEmpty(string1))
        // First string is empty, so every character of 
        // String2 has been inserted:
        return (string2 ?? "").Length;
    if (String.IsNullOrEmpty(string2))
        // Second string is empty, so every character of string1 
        // has been deleted, but you dont count deletions:
        return 0;

    // DO NOT SWAP THE STRINGS IF YOU WANT TO DEAL WITH INSERTIONS
    // IN A DIFFERENT MANNER THEN WITH DELETIONS:
    // THE FOLLOWING IS WRONG FOR YOUR NEEDS:
    // // Ensure string2 (inner cycle) is longer_transpositionRow
    // if (string1.Length > string2.Length)
    // {
    //     var tmp = string1;
    //     string1 = string2;
    //     string2 = tmp;
    // }

    // Return trivial case - where string1 is contained within string2
    if (string2.Contains(string1))
        //all changes are insertions
        return string2.Length - string1.Length;

    // REVERSE CASE: STRING2 IS CONTAINED WITHIN STRING1
    if (string1.Contains(string2))
        //all changes are deletions which you don't count:
        return 0;

    var length1 = string1.Length;
    var length2 = string2.Length;


    // PAY ATTENTION TO THIS CHANGE!
    // length1+1 rows is way too much! You need only 3 rows (0, 1 and 2)
    // read my explanation below the code!
    // TOO MUCH ROWS: var d = new int[length1 + 1, length2 + 1];
    var d = new int[2, length2 + 1];

    // THIS INITIALIZATION COUNTS DELETIONS. YOU DONT WANT IT
    // or (var i = 0; i <= d.GetUpperBound(0); i++)
    //    d[i, 0] = i;

    // But you must initiate the first element of each row with 0:
    for (var i = 0; i <= 2; i++)
        d[i, 0] = 0;


    // This initialization counts insertions. You need it, but for
    // better consistency of code I call the variable j (not i):
    for (var j = 0; j <= d.GetUpperBound(1); j++)
        d[0, j] = j;


    // Now do the job:
    // for (var i = 1; i <= d.GetUpperBound(0); i++)
    for (var i = 1; i <= length1; i++)
    {
        //Here in this for-loop: add "%3" to evey term 
        // that is used as first index of d!

        var im1 = i - 1;
        var im2 = i - 2;
        var minDistance = threshold;
        for (var j = 1; j <= d.GetUpperBound(1); j++)
        {
            var jm1 = j - 1;
            var jm2 = j - 2;
            var cost = string1[im1] == string2[jm1] ? 0 : 1;

            // DON'T COUNT DELETIONS!  var del = d[im1, j] + 1;
            var ins = d[i % 3, jm1] + 1;
            var sub = d[im1 % 3, jm1] + cost;

            // Math.Min is slower than native code
            // d[i, j] = Math.Min(del, Math.Min(ins, sub));
            // DEL DOES NOT EXIST  
            // d[i, j] = del <= ins && del <= sub ? del : ins <= sub ? ins : sub;
            d[i % 3, j] = ins <= sub ? ins : sub;

            if (i > 1 && j > 1 && string1[im1] == string2[jm2] && string1[im2] == string2[jm1])
                d[i % 3, j] = Math.Min(d[i % 3, j], d[im2 % 3, jm2] + cost);

            if (d[i % 3, j] < minDistance)
                minDistance = d[i % 3, j];
        }

        if (minDistance > threshold)
            return int.MaxValue;
    }

    return d[length1 % 3, d.GetUpperBound(1)] > threshold
        ? int.MaxValue
        : d[length1 % 3, d.GetUpperBound(1)];
}

我的解释为什么你只需要3行:

看看这一行:

var d = new int[length1 + 1, length2 + 1];

如果一个字符串的长度为n而另一个字符串的长度为m,那么您的代码需要一个(n + 1)*(m + 1)个整数的空格。每个整数需要4个字节。如果您的字符串很长,这会浪费内存。如果两个字符串都是35.000字节长,则需要超过4 GB的内存!

在此代码中,您为d[i,j]计算并写入新值。为此,您从其上邻居(d[i,jm1]),其左邻居(d[im1,j]),其左上角邻居(d[im1,jm1])读取值,最后从其双倍读取值-upper-double-left neighbor(d[im2,jm2])。所以你只需要实际行和前两行的值。

您永远不需要来自任何其他行的值。那么你为什么要存储它们呢?三行就足够了,我的变化就变成了shure,你可以使用这三行而不会在任何时候读取任何错误的值。

答案 1 :(得分:2)

我建议不要重写此特定算法来处理“免费”编辑的特定情况。其中许多从根本上简化了问题的概念,使得度量标准不会传达任何有用的信息。

例如,当替换是自由的时,所有字符串之间的距离是它们的长度之间的差异。只需将较小的字符串转换为较大字符串的前缀并添加所需的字母即可。 (您可以保证没有较小的距离,因为编辑距离的每个字符都需要插入一个。)

当转置是免费的时,问题会减少到确定字母数量差异的总和。 (由于所有字谜之间的距离为0,因此对每个字符串中的字母进行排序以及交换或删除较大字符串的非公共元素是最佳策略。数学参数类似于前一个示例。)

在插入和删除是空闲的情况下,任意两个字符串之间的编辑距离为零。如果只有插入或删除是自由的,这会破坏距离度量的对称性 - 自由删除,从a到aa的距离是1,而从aa到a的距离是1.根据应用,这可能是理想的;但我不确定这是否是你感兴趣的东西。你需要大大改变所提出的算法,因为它使得所提到的一个字符串的假设总是比另一个更长。

答案 2 :(得分:0)

尝试将var del = d[im1, j] + 1;更改为var del = d[im1, j];,我认为这可以解决您的问题。