tey*_*non 5 php optimization levenshtein-distance
我试图用一点插件来实现levenshtein算法.我想优先考虑具有连续匹配字母的值.我已经尝试使用下面的代码实现我自己的形式:
function levenshtein_rating($string1, $string2) {
$GLOBALS['lvn_memo'] = array();
return lev($string1, 0, strlen($string1), $string2, 0, strlen($string2));
}
function lev($s1, $s1x, $s1l, $s2, $s2x, $s2l, $cons = 0) {
$key = $s1x . "," . $s1l . "," . $s2x . "," . $s2l;
if (isset($GLOBALS['lvn_memo'][$key])) return $GLOBALS['lvn_memo'][$key];
if ($s1l == 0) return $s2l;
if ($s2l == 0) return $s1l;
$cost = 0;
if ($s1[$s1x] != $s2[$s2x]) $cost = 1;
else $cons -= 0.1;
$dist = min(
(lev($s1, $s1x + 1, $s1l - 1, $s2, $s2x, $s2l, $cons) + 1),
(lev($s1, $s1x, $s1l, $s2, $s2x + 1, $s2l - 1, $cons) + 1),
(lev($s1, $s1x + 1, $s1l - 1, $s2, $s2x + 1, $s2l - 1, $cons) + $cost)
);
$GLOBALS['lvn_memo'][$key] = $dist + $cons;
return $dist + $cons;
}
Run Code Online (Sandbox Code Playgroud)
您应该注意$cons -= 0.1;我是添加值的部分,以确定连续值的优先级.该公式将针对大型字符串数据库进行检查.(高达20,000 - 50,000)我已经完成了PHP内置的基准测试levenshtein
Message Time Change Memory
PHP N/A 9300128
End PHP 1ms 9300864
End Mine 20ms 9310736
Array
(
[0] => 3
[1] => 3
[2] => 0
)
Array
(
[0] => 2.5
[1] => 1.9
[2] => -1.5
)
Run Code Online (Sandbox Code Playgroud)
基准测试代码:
$string1 = "kitten";
$string2 = "sitter";
$string3 = "sitting";
$log = new Logger("PHP");
$distances = array();
$distances[] = levenshtein($string1, $string3);
$distances[] = levenshtein($string2, $string3);
$distances[] = levenshtein($string3, $string3);
$log->log("End PHP");
$distances2 = array();
$distances2[] = levenshtein_rating($string1, $string3);
$distances2[] = levenshtein_rating($string2, $string3);
$distances2[] = levenshtein_rating($string3, $string3);
$log->log("End Mine");
echo $log->status();
echo "<pre>" . print_r($distances, true) . "</pre>";
echo "<pre>" . print_r($distances2, true) . "</pre>";
Run Code Online (Sandbox Code Playgroud)
我认识到PHP的内置功能可能总是比我的本质更快.但我想知道是否有办法加快我的速度?
所以问题是:有没有办法加快速度?我在这里的另一种选择是运行levenshtein,然后搜索最高的X结果并另外优先考虑它们.
根据Leigh的评论,复制PHP内置的Levenhstein形式将时间缩短到3ms.(编辑:发布连续字符扣除的版本.这可能需要调整,似乎工作.)
function levenshtein_rating($s1, $s2, $cons = 0, $cost_ins = 1, $cost_rep = 1, $cost_del = 1) {
$s1l = strlen($s1);
$s2l = strlen($s2);
if ($s1l == 0) return $s2l;
if ($s2l == 0) return $s1l;
$p1 = array();
$p2 = array();
for ($i2 = 0; $i2 <= $s2l; ++$i2) {
$p1[$i2] = $i2 * $cost_ins;
}
$cons = 0;
$cons_count = 0;
$cln = 0;
$tbl = $s1;
$lst = false;
for ($i1 = 0; $i1 < $s1l; ++$i1) {
$p2[0] = $p1[0] + $cost_del;
$srch = true;
for($i2 = 0; $i2 < $s2l; ++ $i2) {
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
if ($srch && $s2[$i2] == $tbl[$i1]) {
$tbl[$i1] = "\0";
$srch = false;
$cln += ($cln == 0) ? 1 : $cln * 1;
}
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) $c0 = $c1;
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) $c0 = $c2;
$p2[$i2 + 1] = $c0;
}
if (!$srch && $lst) {
$cons_count += $cln;
$cln = 0;
}
$lst = $srch;
$tmp = $p1;
$p1 = $p2;
$p2 = $tmp;
}
$cons_count += $cln;
$cons = -1 * ($cons_count * 0.1);
return $p1[$s2l] + $cons;
}
Run Code Online (Sandbox Code Playgroud)
我认为你的函数的主要减速是它是递归的。
正如我在评论中所说,PHP 函数调用对于引擎来说是一项非常繁重的工作。
PHP 本身实现levenshtein为一个循环,保存插入、替换和删除所产生的成本的运行总计。
我确信如果您也将代码转换为循环,您会看到性能的大幅提升。
我不知道你的代码到底在做什么,但我已将本机 C 代码移植到 PHP 中,为你提供一个起点。
define('LEVENSHTEIN_MAX_LENGTH', 12);
function lev2($s1, $s2, $cost_ins = 1, $cost_rep = 1, $cost_del = 1)
{
$l1 = strlen($s1);
$l2 = strlen($s2);
if ($l1 == 0) {
return $l2 * $cost_ins;
}
if ($l2 == 0) {
return $l1 * $cost_del;
}
if (($l1 > LEVENSHTEIN_MAX_LENGTH) || ($l2 > LEVENSHTEIN_MAX_LENGTH)) {
return -1;
}
$p1 = array();
$p2 = array();
for ($i2 = 0; $i2 <= $l2; $i2++) {
$p1[$i2] = $i2 * $cost_ins;
}
for ($i1 = 0; $i1 < $l1; $i1++) {
$p2[0] = $p1[0] + $cost_del;
for ($i2 = 0; $i2 < $l2; $i2++) {
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) {
$c0 = $c1;
}
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) {
$c0 = $c2;
}
$p2[$i2 + 1] = $c0;
}
$tmp = $p1;
$p1 = $p2;
$p2 = $tmp;
}
return $p1[$l2];
}
Run Code Online (Sandbox Code Playgroud)
我做了一个快速基准测试,比较了你的、我的和 PHP 的内部函数,每个函数 100,000 次迭代,时间以秒为单位。
float(12.954766988754)
float(2.4660499095917)
float(0.14857912063599)
Run Code Online (Sandbox Code Playgroud)
显然它还没有得到你的调整,但我确信他们不会减慢它那么多。
如果您确实需要更多的速度提升,一旦您弄清楚如何更改此函数,就应该很容易将您的更改移植回 C,复制 PHP 函数定义,并实现您自己的本机 C 版本你修改后的函数。
有很多关于如何制作 PHP 扩展的教程,因此如果您决定沿着这条路走下去,应该不会遇到太大困难。
编辑:
我注意到正在寻找进一步改进的方法
$c0 = $p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep);
$c1 = $p1[$i2 + 1] + $cost_del;
if ($c1 < $c0) {
$c0 = $c1;
}
$c2 = $p2[$i2] + $cost_ins;
if ($c2 < $c0) {
$c0 = $c2;
}
Run Code Online (Sandbox Code Playgroud)
是相同的
$c0 = min(
$p1[$i2 + 1] + $cost_del,
$p1[$i2] + (($s1[$i1] == $s2[$i2]) ? 0 : $cost_rep),
$c2 = $p2[$i2] + $cost_ins
);
Run Code Online (Sandbox Code Playgroud)
min我认为这与代码中的块直接相关。然而,这会显着减慢代码速度。(我猜这是额外函数调用的开销)
min()以区块作为第二计时的基准。
float(2.484846830368)
float(3.6055288314819)
Run Code Online (Sandbox Code Playgroud)
你对第二个$cost_ins不属于的看法是对的——我的复制/粘贴失败了。