[英]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 - 1
和i + 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.Lenght
和t.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 查找应用程序瓶颈。
编辑
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.