繁体   English   中英

使用 Levenshtein 距离检测字符串相似度(慢)

[英]Detecting string similarity using Levenshtein distance (is SLOW)

查询返回 2100 万条记录

我循环遍历这张表的方式需要永远。 还有哪些其他解决方案?

SqlDataReader rd = DbInfo.DataRdr(Conn,
 "SELECT a.NAME AS ANAME, b.NAME AS BNAME, a.ID as AID, b.ID AS BUD " +
 "FROM myTable a JOIN myTable b ON a.NUM = b.NUM AND a.ID <> b.ID");

while (rd.Read())
{
   if (rd["ANAME"].ToString().LevenshteinDistance(rd["BNAME"].ToString()) <= 10)
   {

        Logging.Write(...);

   }
}



    public static int LevenshteinDistance(this string s, string t)
    {
        if (s == null)
            throw new ArgumentNullException("s");
        if (t == null)
            throw new ArgumentNullException("t");
        int n = s.Length;
        int m = t.Length;
        int[,] d = new int[n+1,m+1];

        if (n == 0 || m == 0)
            return Math.Max(m, n);

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

        for (int i = 0; i < n; i++)
        {
            for (int j = 0; j < m; j++)
            {
                int cost = (t[j] == s[i]) ? 0 : 1;

                d[i + 1, j + 1] = Math.Min(Math.Min(d[i, j + 1] + 1, d[i + 1, j] + 1), d[i, j] + cost);
            }
        }

        return d[n, m];
    }

您可以先使用以下内容作为查询,具体取决于NUM列实际相等的频率:

SELECT a.NAME AS ANAME, b.NAME AS BNAME, other things
FROM myTable a
JOIN myTable b
ON a.NUM = b.NUM
AND a.id < b.id

然后,您的 SQL 查询将为您提供具有匹配NUM的行对,您可以在这些行上调用LevenshteinDistance 就像是:

DataTable dt1 = new DataTable();
dt1.Load(DbInfo.DataRdr(Conn, "[the query I wrote above]"));

for (int i = 0; i < dt1.Rows.Count; i++)
{
   if (dt1.Rows[i]["ANAME"].ToString().LevenshteinDistance(dt1.Rows[i]["BNAME"].ToString()) <= 10)
   {
     Logging.Write(...[modify the query so it returns the things you want to log]...);
   }
}

即使在i == j时,您也正在将dt1.Rows[i]["Name"].ToString()dt1.Rows[j]["Name"].ToString()进行比较。

尝试循环0 <= i < dt1.Rows.Count - 1i + 1 <= j < dt1.Rows.Count

此外,仅当dt1.Rows[i]["NUM"].ToString() == dt1.Rows[i]["NUM"].ToString() ,这可能是一个更快的检查。 如果那是错误的,那么做 Levenshtein 就没有意义了。

编辑:@John 对dt1.Rows[i]["NUM"].ToString() == dt1.Rows[i]["NUM"].ToString() (都是i ?)是正确的。

优化:

1)查看您的数据。 也许您可以进行一些检查以更快地整理出无效对。 如果Name的长度变化超过 10,您可以检查s.Lenghtt.Length之间的差异是否大于 10 并立即返回一个高距离(可能是 int.MaxValue 或只是 100)。 无论如何,如果它明显超出 scope,则计算距离毫无意义。

2)寻找小的优化。 在 150k 行上循环两次意味着 225 亿次迭代。 微小的变化可能会产生很大的影响。 您可以尝试缓存到行对象并通过使用Equals()方法删除ToString() 我认为这比访问数据表的第 i 个元素 150000 次要快。

for (int i = 0; i < dt1.Rows.Count; i++)
{
   var outerRow = dt1.Rows[i];
   for (int j = 0; i + 1 < dt1.Rows.Count; j++)
   {
     var innerRow = dt1.Rows[j];
     if (Equals(outerRow["NUM"] == innerRow["NUM"]))
     {
        if (outerRow["Name"].ToString().LevenshteinDistance(innerRow.ToString()) <= 10)
        {
           Logging.Write(...);
        }
     }
  }

3)尝试减少/拆分数据集。 执行查询以从 myTable 中获取NUM select distinct NUM from myTable的所有可能值。 然后对于结果中的每个NUM执行原始查询,但使用 where 条件和 select 仅名称: SELECT name from myTable where NUM = currentNum

这样您t have to compare the NUM row and you don select 奇数数据。 您的代码可以优化为仅执行 levenshtein 距离,但使用 1+2 中所述的优化。

4)尝试不同的方法,如全文搜索。

我只是想解决一个类似的问题,在 120 万行表中查找匹配项。 我使用了 lucene.net ,它在搜索我的行的一个或多个属性时为我提供了实时结果。

他们也做 levenshtein,但也许它比你的实现更快;) MSSQL Server 也支持全文搜索。

您可以做出的最大改进是减少正在考虑的解决方案空间。 由于您希望最大距离为 10,因此长度差异超过 10 的任何字符串都不可能符合条件:

SELECT a.NAME AS ANAME, b.NAME AS BNAME, a.ID as AID, b.ID AS BUD 
 FROM myTable a JOIN myTable b ON a.NUM = b.NUM AND a.ID < b.ID
 WHERE length(a.NAME) - length(b.NAME) BETWEEN -10 AND 10;

接下来,分析您的代码并查看热点在哪里。 一篇不错的入门文章:使用 Visual Studio Profiler 查找应用程序瓶颈

并查看优化 C# 中的 Levenshtein 算法

编辑

Chris 还注意到,由于levenshtein(a,b) == levenshtein(b,a)您只需要连接 a.ID < b.ID 上的 select,因为矩阵是对称的。 这将立即将您的问题减半。

在完成此线程中提到的其他优化之后,您可以将 Levenshtein 计算移动到服务器,并且仅 SELECT 与您的编辑距离标准匹配的行。 我在一个项目中需要这个功能,所以我用它做了一个库,在这里 lib中使用的编辑距离方法只需要n * 2 memory而不是n * m。

例如,即使在服务器上,您也只想在字符串长度的差异 < 10 时进行 EditDistance 计算,因此请先检查一下。 就像是

SELECT a.NAME as NameA, b.NAME as NameB
FROM table a
JOIN table b ON a.NUM = b.NUM
WHERE a.Id < b.Id
AND length(a.NAME) - length(b.NAME) BETWEEN -10 AND 10 OR
    EditDistance(a.Name, b.Name) < 10

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM