1. 導入
ユーザーが検索窓や入力フォームでスペルミスをした際、「もしかして: ~」と修正候補を表示する機能は、UX向上において非常に有効です。PHPには文字列間の距離を計算する標準関数であるlevenshtein()が用意されており、これを利用することで比較的容易に類似文字列を特定できます。本記事では、この関数の仕組みと、実務で不可欠な日本語(マルチバイト)対応の実装方法を解説します。
2. 基礎知識
levenshtein()とは、2つの文字列間での「レーベンシュタイン距離」を計算する関数です。これは、一方の文字列をもう一方の文字列に変形するために必要な「挿入・削除・置換」の最小回数を指します。数値が小さいほど、文字列が似ていることを意味します。
ただし、標準のlevenshtein()はシングルバイト文字列を前提としており、日本語などのマルチバイト文字を正しく扱えません。実務環境で日本語を扱う場合は、文字単位で処理を行う独自のロジックが必要です。
3. 実装/解決策
実装の基本は、候補となる単語リストと入力値を比較し、距離が一定以下のものを抽出することです。
実務上の注意点として、単語リストが数千〜数万件に及ぶ場合、全件ループで距離計算を行うとサーバー負荷が急増します。以下の対策を推奨します。
・文字数による絞り込み:長さが極端に異なる単語は除外する。
・閾値による早期終了:距離が0(完全一致)または1の場合に処理を中断する。
・事前計算:検索頻度の高い単語については、あらかじめハッシュ化しておく。
4. サンプルプログラム
以下は、マルチバイト対応の独自関数を用いた「もしかして」機能のサンプルです。
/
// 候補リスト
$wordlist = [“アップル”, “オレンジ”, “グレープ”, “バナナ”, “ブルーベリー”];
$input = “アッブル”; // ユーザーの入力ミス
// 日本語対応のlevenshtein距離計算関数
function mb_levenshtein($str1, $str2) {
$s1 = mb_str_split($str1, ‘utf-8’);
$s2 = mb_str_split($str2, ‘utf-8’);
$n = count($s1);
$m = count($s2);
$d = [];
for ($i = 0; $i <= $n; $i++) $d[$i][0] = $i;
for ($j = 0; $j <= $m; $j++) $d[0][$j] = $j;
for ($i = 1; $i <= $n; $i++) {
for ($j = 1; $j <= $m; $j++) {
$cost = ($s1[$i - 1] === $s2[$j - 1]) ? 0 : 1;
$d[$i][$j] = min($d[$i - 1][$j] + 1, $d[$i][$j - 1] + 1, $d[$i - 1][$j - 1] + $cost);
}
}
return $d[$n][$m];
}
$results = [];
foreach ($wordlist as $word) {
$dist = mb_levenshtein($input, $word);
// 距離が2以下なら候補として保存
if ($dist > 0 && $dist <= 2) {
$results[$word] = $dist;
}
}
// 距離が近い順にソート
asort($results);
if (!empty($results)) {
echo "もしかして: " . key($results);
} else {
echo "該当する候補は見つかりませんでした。";
}
?>
5. 応用・注意点
・パフォーマンスの最適化:単語リストが膨大な場合は、PHP側で力技で計算するのではなく、Elasticsearchなどの検索エンジンが備える「Fuzzy Query」機能を利用することを強く推奨します。
・閾値の設定:許容距離(サンプル内の「2」)を大きくしすぎると、全く関係のない単語が候補として表示されてしまいます。対象とする単語の特性に合わせて調整してください。
・エンコーディング:mb_str_splitを使用する際は、必ずUTF-8で統一されていることを確認してください。文字化けが発生すると、正常に距離が計算されません。