登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  php教程

PHP levenshtein 怎样设置插入替换删除的不同代价

来源:17golang原创

时间:2026-10-29 03:23:03 337浏览 收藏

PHP levenshtein 的第三、第四、第五个参数依次是插入、替换、删除代价。为三项传入不同的正整数,就可以计算加权编辑距离;返回的是最便宜的编辑方案总成本,不保证采用你直觉中的那种操作。我会先固定“原字符串变成目标字符串”的方向,再决定代价,避免调参后连比较方向也变了。

官方文档:https://www.php.net/manual/en/function.levenshtein.php

先固定字符串方向,再给代价命名

假设正在处理短英文标识符,业务更愿意接受漏输一个字符,却不希望轻易接受多余字符。这里可以用不等权代价表达业务偏好,但参数名称需要从 string1 变成 string2 的角度理解。本文只讨论短 ASCII 字符串,不把结论扩展为中文语义相似度。

调用形式是 levenshtein($from, $to, $insertCost, $replaceCost, $deleteCost)。插入是在原串中补字符,删除是移除原串字符,替换是改变原串字符。默认三项都是 1。如果插入和删除不同,交换两个字符串就可能得到不同的成本,所以缓存键、日志和排序代码都要保留顺序。

原字符串、目标字符串和三项代价与编辑距离函数的静态关系
图1:参数契约结构图。输入分组和代价分组分别连接计算函数,连线只表示参数依赖。
项目本文示例配置含义
插入2补入一个字符的成本
替换7直接替换一个字符的成本
删除3移除一个字符的成本

这些数字是用于解释规则的自定义配置,没有“通用最优值”的含义。我的建议是先用业务里的误输入样本确定相对偏好,再讨论是否上线。

替换代价很高,结果也未必等于替换代价

把单字符 p 变成 q,直接替换要花 7;另一种合法方案是删掉 p,再插入 q,总成本是 3 加 2,等于 5。函数选择最小成本,所以按这套配置推导的结果是 5。这并不表示第四个参数没有生效,而是另一类编辑组合更便宜。

同样,p 变成 pq 只需插入 q,推导成本为 2;pq 变成 p 则删除 q,推导成本为 3。这些是按定义计算的预期值,不是本文执行得到的日志。

直接替换成本与删除加插入成本共同约束最小成本
图2:成本候选关系图。两个候选方案都约束最小成本;这是静态说明图,不是运行结果。

我更倾向于把这种现象视为配置语义问题:如果业务要求一个字符替换无论如何都必须支付 7,那么允许插入和删除的普通编辑距离未必表达得了这个要求。不能单靠提高替换参数,强迫算法忽略更便宜的合法方案。

把输入范围和配置一起收进函数

下面是 PHP 8 CLI 示例:限制 ASCII 输入最长 64 字节,代价只接受 1 到 100 的整数。这是示例的资源与业务约束,并非 PHP 内置函数的固定长度或数值限制。没有读取文件、访问网络或修改外部数据;本文未执行该代码。

 64 || preg_match('/[^\x00-\x7F]/', $text) !== 0) {
            throw new InvalidArgumentException('仅接受不超过64字节的ASCII字符串');
        }
    }

    // 三项都必填,拒绝字符串数字、零代价和缺失配置。
    foreach (['insert', 'replace', 'delete'] as $key) {
        if (!isset($cost[$key]) || !is_int($cost[$key])
            || $cost[$key]  100) {
            throw new InvalidArgumentException('编辑代价必须是1到100的整数');
        }
    }

    return levenshtein(
        $from, $to, $cost['insert'], $cost['replace'], $cost['delete']
    );
}

try {
    $cost = ['insert' => 2, 'replace' => 7, 'delete' => 3];
    // 每组明确保留原串与目标串顺序,不把这两项排序。
    foreach ([['p', 'q'], ['p', 'pq'], ['pq', 'p']] as [$from, $to]) {
        printf("%s => %s: %d\n", $from, $to, weightedCost($from, $to, $cost));
    }
} catch (InvalidArgumentException $e) {
    // 参数错误写入标准错误,并让调用方看到非零退出码。
    fwrite(STDERR, $e->getMessage() . PHP_EOL);
    exit(1);
}

这里没有使用命名实参,调用位置与代价数组键一一对应,便于代码审阅。示例接受空字符串和 ASCII 控制字符;若业务处理的是标识符,还应在调用前增加标识符允许字符规则。大写与小写是否等同,也应由业务明确决定,不能让预处理悄悄改变规则。

调参出问题时,恢复整套配置

当同一组候选的排序突然改变,我会先看三件事:字符串方向是否倒置、配置是否换了版本、是否混用了旧阈值。加权成本不是百分制相似度;过去用单位代价设定的“距离小于等于 2”不能直接搬到新配置。

建议给配置取一个版本标识,例如 ascii-cost-v1,把三项代价、方向约定、输入预处理和接受阈值一起保存。若效果不符合业务目标,恢复这整套配置及对应缓存命名空间。只把 replace 从 7 改回 1,未必恢复旧排序。

是否启用新配置,由维护者拿实际业务样本判断:同一输入比较哪些候选、并列时用什么稳定次序、哪些差异应当接受。上线记录应保留配置版本和决策依据,不必把敏感原始输入完整写入普通日志。

几个容易混淆的边界

返回 0 就代表字符串一定相同吗?本文限制所有代价为正数,在这个范围内零成本表示无需编辑。不要将它推广到任意自定义零代价方案。

可以把结果直接称为相似度吗?更准确的名称是编辑成本。数值越小表示这套规则下变换成本越低,不能直接解释为语义相似概率。

长文本能否取消长度限制直接比较?不建议直接放开。官方标明算法复杂度与两串长度乘积有关;大量候选比较还要叠加候选数量。本文的 64 字节限制只是保守示例,真实上限应按负载另定。

PHP 8 是否仍只有 255 字符上限?官方变更记录说明,超过该长度返回 -1 的旧行为属于 PHP 8 之前。业务仍应自己控制资源预算,不把取消旧限制当作无限输入承诺。

我会先让参数含义、方向和阈值形成一套可解释的契约,再去提高某个编辑动作的代价。只要记住“求最小总成本”,替换参数看似没有生效的问题通常就能说清。

参考:PHP levenshtein 官方手册。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>