1. 導入
実務において、検索機能の「もしかして」提案や、ユーザー入力の揺らぎ検知、データクレンジングを行う際、文字列同士がどれだけ似ているかを判定したい場面は多々あります。PHP標準の levenshtein 関数は非常に高速ですが、残念ながらマルチバイト文字(日本語など)に対応しておらず、そのまま利用すると意図しない挙動になります。本記事では、日本語環境でも正しく動作する「マルチバイト対応版レーベンシュタイン距離」の実装方法を解説します。
2. 基礎知識
レーベンシュタイン距離(Levenshtein distance)とは、ある文字列を別の文字列に変形するために必要な「挿入」「削除」「置換」の最小回数を表す指標です。値が0に近いほど2つの文字列は一致しており、値が大きいほど異なる文字列であることを示します。
一方で、similar_text 関数は、2つの文字列の類似度をパーセントで算出しますが、計算コストが比較的高いため、大量のデータを比較する際にはレーベンシュタイン距離の方が効率的です。
3. 実装/解決策
マルチバイト文字列を扱う場合、文字列をバイト単位ではなく「文字単位」に分割する必要があります。PHPの `mb_str_split`(PHP 7.4以降)や `mb_substr` を活用して文字列を配列に変換し、各文字を比較対象として行列計算を行うことで、正確な距離を算出します。
4. サンプルプログラム
以下は、現場でそのまま利用可能なマルチバイト対応のレーベンシュタイン距離計算関数です。
/
function mb_levenshtein(string $str1, string $str2, string $encoding = ‘UTF-8’): int
{
// 文字単位の配列に変換
$chars1 = mb_str_split($str1, 1, $encoding);
$chars2 = mb_str_split($str2, 1, $encoding);
$len1 = count($chars1);
$len2 = count($chars2);
// 行列の初期化
$distance = [];
for ($i = 0; $i <= $len1; $i++) $distance[$i][0] = $i;
for ($j = 0; $j <= $len2; $j++) $distance[0][$j] = $j;
// 動的計画法による計算
for ($i = 1; $i <= $len1; $i++) {
for ($j = 1; $j <= $len2; $j++) {
// 文字が一致していればコスト0、異なればコスト1
$cost = ($chars1[$i - 1] === $chars2[$j - 1]) ? 0 : 1;
$distance[$i][$j] = min(
$distance[$i - 1][$j] + 1, // 削除
$distance[$i][$j - 1] + 1, // 挿入
$distance[$i - 1][$j - 1] + $cost // 置換
);
}
}
return $distance[$len1][$len2];
}
// 使用例
$s1 = "プログラミング";
$s2 = "プログラム";
echo "距離: " . mb_levenshtein($s1, $s2); // 距離: 3
?>
5. 応用・注意点
パフォーマンスの罠:
レーベンシュタイン距離の計算量は O(m n) です。非常に長い文字列(文章全体など)を比較する場合、処理が重くなるため注意が必要です。比較対象が長い場合は、あらかじめ文字列を短く切り出す、あるいは閾値を設けて計算を途中で打ち切るなどの最適化を検討してください。
実務での活用:
この関数は、単なる比較だけでなく、スペルミス検知や、似たような商品名がDBに登録されていないかを確認する際の重複チェックに非常に有効です。また、完全一致ではない検索を実現するための「曖昧検索」のアルゴリズムとしても活用できます。実装時は必ずエンコーディングを指定し、文字化けや意図しないカウントを防ぐように徹底しましょう。