为mysql /模糊搜索实现Levenshtein距离?

And*_*ark 44 mysql database algorithm search levenshtein-distance

我希望能够按照以下方式搜索一个表格,因为它可以获得1个方差范围内的所有内容.

数据:

O'Brien
Smithe
Dolan
Smuth
Wong
Smoth
Gunther
Smiht

我已经研究过使用Levenshtein距离有没有人知道如何实现它?

Nic*_*son 11

为了使用levenshtein距离进行有效搜索,您需要一个有效的专用索引,例如bk-tree.不幸的是,我所知道的数据库系统,包括MySQL,都没有实现bk-tree索引.如果您正在寻找全文搜索,而不是每行只有一个术语,这将更加复杂.另一方面,我想不出任何可以以允许基于levenshtein距离进行搜索的方式进行全文索引的方式.


Hon*_*eng 7

有一个Levenshtein距离函数的mysql UDF实现

https://github.com/jmcejuela/Levenshtein-MySQL-UDF

它在C中实现,并且具有比schnaader提到的"MySQL Levenshtein距离查询"更好的性能

  • @talsibony 为什么不自己尝试一下呢? (2认同)

小智 5

damerau-levenshtein距离的实现可以在这里找到: Damerau-Levenshtein算法:具有转置 的Levenshtein对纯Levenshtein距离的改进是考虑字符的交换.我在schnaader链接的评论中找到了它,谢谢!


sto*_*the 5

上面为 levenshtein <= 1 给出的函数是不正确的——它给出了不正确的结果,例如“床”和“出价”。

我修改了上面给出的“MySQL Levenshtein 距离查询”,在第一个答案中,接受一个“限制”,这会稍微加快速度。基本上,如果您只关心 Levenshtein <= 1,请将限制设置为“2”,如果为 0 或 1,该函数将返回精确的 Levenshtein 距离;或 2 如果精确的 levenshtein 距离为 2 或更大。

这个 mod 让它快 15% 到 50%——你的搜索词越长,优势就越大(因为算法可以提前保释。)例如,在针对 200,000 个词的搜索中找到距离 1 的词的所有匹配项“咯咯笑”,原始版本在我的笔记本电脑上需要 3 分 47 秒,而“限制”版本则为 1:39。当然,这些对于任何实时使用来说都太慢了。

代码:

DELIMITER $$
CREATE FUNCTION levenshtein_limit_n( s1 VARCHAR(255), s2 VARCHAR(255), n INT) 
  RETURNS INT 
  DETERMINISTIC 
  BEGIN 
    DECLARE s1_len, s2_len, i, j, c, c_temp, cost, c_min INT; 
    DECLARE s1_char CHAR; 
    -- max strlen=255 
    DECLARE cv0, cv1 VARBINARY(256); 
    SET s1_len = CHAR_LENGTH(s1), s2_len = CHAR_LENGTH(s2), cv1 = 0x00, j = 1, i = 1, c = 0, c_min = 0; 
    IF s1 = s2 THEN 
      RETURN 0; 
    ELSEIF s1_len = 0 THEN 
      RETURN s2_len; 
    ELSEIF s2_len = 0 THEN 
      RETURN s1_len; 
    ELSE 
      WHILE j <= s2_len DO 
        SET cv1 = CONCAT(cv1, UNHEX(HEX(j))), j = j + 1; 
      END WHILE; 
      WHILE i <= s1_len and c_min < n DO -- if actual levenshtein dist >= limit, don't bother computing it
        SET s1_char = SUBSTRING(s1, i, 1), c = i, c_min = i, cv0 = UNHEX(HEX(i)), j = 1; 
        WHILE j <= s2_len DO 
          SET c = c + 1; 
          IF s1_char = SUBSTRING(s2, j, 1) THEN  
            SET cost = 0; ELSE SET cost = 1; 
          END IF; 
          SET c_temp = CONV(HEX(SUBSTRING(cv1, j, 1)), 16, 10) + cost; 
          IF c > c_temp THEN SET c = c_temp; END IF; 
            SET c_temp = CONV(HEX(SUBSTRING(cv1, j+1, 1)), 16, 10) + 1; 
            IF c > c_temp THEN  
              SET c = c_temp;  
            END IF; 
            SET cv0 = CONCAT(cv0, UNHEX(HEX(c))), j = j + 1;
            IF c < c_min THEN
              SET c_min = c;
            END IF; 
        END WHILE; 
        SET cv1 = cv0, i = i + 1; 
      END WHILE; 
    END IF;
    IF i <= s1_len THEN -- we didn't finish, limit exceeded    
      SET c = c_min; -- actual distance is >= c_min (i.e., the smallest value in the last computed row of the matrix) 
    END IF;
    RETURN c;
  END$$
Run Code Online (Sandbox Code Playgroud)