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

cro*_*arp 2 c# algorithm fuzzy levenshtein-distance

如何在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)];
    }
Run Code Online (Sandbox Code Playgroud)

Hub*_*ast 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)];
}
Run Code Online (Sandbox Code Playgroud)

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

看看这一行:

var d = new int[length1 + 1, length2 + 1];
Run Code Online (Sandbox Code Playgroud)

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

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

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