【问题标题】:Damerau - Levenshtein Distance, adding a thresholdDamerau - Levenshtein 距离,添加阈值
【发布时间】:2011-04-19 23:34:54
【问题描述】:

我有下面的实现,但是我想添加一个阈值,所以如果结果要大于它,就停止计算并返回。

我该怎么做?

编辑:这是我当前的代码,threshold 尚未使用...目标是使用它

    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
        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++)
        {
            for (var j = 1; j <= d.GetUpperBound(1); j++)
            {
                var cost = string1[i - 1] == string2[j - 1] ? 0 : 1;

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

                d[i, j] = Math.Min(del, Math.Min(ins, sub));

                if (i > 1 && j > 1 && string1[i - 1] == string2[j - 2] && string1[i - 2] == string2[j - 1])
                    d[i, j] = Math.Min(d[i, j], d[i - 2, j - 2] + cost);
            }
        }

        return d[d.GetUpperBound(0), d.GetUpperBound(1)];
    }
}

【问题讨论】:

标签: c# levenshtein-distance threshold


【解决方案1】:

这是关于你的回答:Damerau - Levenshtein Distance, adding a threshold (抱歉,我还没有 50 个代表,所以无法发表评论)

我认为你在这里犯了一个错误。你初始化了:

var minDistance = threshold;

你的更新规则是:

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

另外,您的提前退出标准是:

if (minDistance > threshold)
   return int.MaxValue;

现在,请注意上面的 if 条件永远不会成立!你应该将minDistance初始化为int.MaxValue

【讨论】:

    【解决方案2】:

    您还将此作为 SQL CLR UDF 问题提出,因此我将在特定上下文中回答:您最好的优化不是来自优化 Levenshtein 距离,而是来自减少比较的对数。是的,更快的 Levenshtein 算法会有所改善,但不如将比较次数从 N 平方(N 在数百万行中)减少到 N*some 因子。我的建议是仅比较在可容忍增量内具有长度差异的元素。在您的大表上,您在 LEN(Data) 上添加一个持久计算列,然后使用包含数据在其上创建一个索引:

    ALTER TABLE Table ADD LenData AS LEN(Data) PERSISTED;
    CREATE INDEX ndxTableLenData on Table(LenData) INCLUDE (Data);
    

    现在,如果您的数据的 LEN(Data) 变化很大,您可以通过加入最大长度差异(例如 5)来限制纯粹的问题空间:

    SELECT a.Data, b.Data, dbo.Levenshtein(a.Data, b.Data)
    FROM Table A
    JOIN Table B ON B.DataLen BETWEEN A.DataLen - 5 AND A.DataLen+5
    

    【讨论】:

    • 通过使用soundex 加入我的表,然后应用 levenshtein 距离,我能够显着提高性能
    • 您还应该尝试将持久列添加为 SOUNDEX,然后使用包含 (Data) 在 (SOUNDEX) 上添加索引。
    • 我同意减少比较次数更好;但是我已将我的比较从 1.2 exa 比较降低到 127.8 giga 比较。现在我需要一个更好的 Levenstien。此时我需要将计算时间从 3.5 天缩短到 10 小时内。
    【解决方案3】:

    终于明白了……虽然它没有我希望的那么有益

        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
            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)];
        }
    

    【讨论】:

    • 很容易看出为什么这不是很有用。您将 minDistance 设置为阈值,然后仅将其替换为较小的值,然后测试 minDistance 是否保持不变或超过了字符串成本的计算。为了使测试提前退出处理,d[i] 的每个索引都必须导致成本大于阈值,并且由于该算法永远不会减少它计算的成本,这是非常悲观的。
    • 据我所知,每个内部/j 循环都必须完成,否则结果将不正确。行中的最后一项是转换可以采用的最大距离。最小值,是当前可能的最小距离。这就是为什么我跟踪每行的最低值,如果它已经高于阈值,则返回。它应该防止在完全不同的字符串中出现多个外部/i 循环
    • 这比我当前计算精确值的实现差,即使阈值非常低。
    • 我建议改用 stackoverflow.com/a/9454016/461444。它速度极快,而且似乎给出了准确的结果。
    【解决方案4】:

    这是我能想到的最优雅的方式。在设置d的每个索引后,看看它是否超过了你的阈值。评估是恒定时间的,因此与整个算法的理论 N^2 复杂度相比,这是杯水车薪:

    public static int DamerauLevenshteinDistance(string string1, string string2, int threshold)
    {
        ...
    
        for (var i = 1; i <= d.GetUpperBound(0); i++)
        {
            for (var j = 1; j <= d.GetUpperBound(1); j++)
            {
                ...
    
                var temp = d[i,j] = Math.Min(del, Math.Min(ins, sub));
    
                if (i > 1 && j > 1 && string1[i - 1] == string2[j - 2] && string1[i - 2] == string2[j - 1])
                    temp = d[i,j] = Math.Min(temp, d[i - 2, j - 2] + cost);
    
                //Does this value exceed your threshold? if so, get out now
                if(temp > threshold) 
                  return temp;
            }
        }
    
        return d[d.GetUpperBound(0), d.GetUpperBound(1)];
    }
    

    【讨论】:

    • 几乎!我对其进行了调整以使其正常工作,出于某种原因,必须设置d[i,j],所以我只是同时设置了 temp ,然后检查了它,现在它可以正常工作了!谢谢!
    • 我弄错了,这不起作用......即使结果应该是1,如果我通过阈值2,结果是3
    猜你喜欢
    • 2014-04-14
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 2012-12-23
    • 1970-01-01
    相关资源
    最近更新 更多