[PHP]マルチバイト文字列を比較して類似の度合いを計算する

PHP には2つの文字がどれだけ似ているかを表す関数が2つ用意されています。
ひとつは similar_text() で、もうひとつは levenshtein() です。

similar_text() は類似度をパーセントで返し、
levenshtein() は、両者を一致させるのにかかる作業量を返します。

後者のレーベンシュタイン距離を調べるアルゴリズムは Wikipedia に掲載されているので、
それを参考にマルチバイト対応版 mb_levenshtein を作ってみました。

<?php
function mb_levenshtein($str1, $str2, $encoding){
  $length1 = mb_strlen($str1, $encoding);
  $length2 = mb_strlen($str2, $encoding);
  
  $str1 = mb_str_split($str1, $encoding);
  $str2 = mb_str_split($str2, $encoding);
  
  $distance = array();
  for($i=0;$i<=$length1;$i++){
    $distance[$i] = array();
    $distance[$i][0] = $i;
  }
  for($i=0;$i<=$length2;$i++){
    $distance[0][$i] = $i;
  }
  $cost = 0;
  for($i=1;$i<=$length1;$i++){
    for($j=1;$j<=$length2;$j++){
      $cost = ($str1[$i - 1] === $str2[$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[$length1][$length2];
}

function mb_str_split($str, $encoding){
  $arr = array();
  $length = mb_strlen($str, $encoding);
  for($i=0;$i<$length;$i++){
    $arr[] = mb_substr($str, $i, 1, $encoding);
  }
  return $arr;
}

$str1 = "sample";
$str2 = "example";

header("Content-type:text/html; charset=utf-8");

echo mb_levenshtein($str1, $str2, 'utf-8');

[PHP]ヒアドキュメント内で関数を使う

PHP で HTML などを複数行表示させたい時、ヒアドキュメントを用いることが多いですが、
出力時に htmlspecialchars() 等の関数を使っておく必要が有る場合が殆どで、
現実的には次のようなソースになってしまいます。

<?php
//実際には動作しません
$str1 = getSomeText();
$str2 = getSomeText();

$str1 = htmlspecialchars($str1, ENT_QUOTES);
$str2 = htmlspecialchars($str2, ENT_QUOTES);

echo <<<EOD
<p>{$str1}</p>
<p>{$str2}</p>
EOD;
exit;

「<p>{htmlspecialchars($str1)}</p>」のように出来れば楽なのですが、ヒアドキュメント内で関数は直接使えないため、
前もって処理しておく必要があります。
例では2つしか無いのでこれでもいいですが、何十個も処理するのはあまり綺麗とはいえません。

解決方法は2つあり、ひとつはこちらの記事(http://blog.half-moon.org/archives/390)で知ったメンバ関数を経由する方法です。

<?php
class Foo {
  function h($str){
    return htmlspecialchars($str, ENT_QUOTES);
  }
}

$foo = new Foo();
$str = "<'sample'>";
echo <<<EOD
	<p>{$foo->h($str)}</p>
EOD;
exit;

このようにするとメンバ関数は展開されるので見た目をすっきり書くことができます。

もう一つは可変変数を使う方法です

<?php
$h='htmlspecialchars';
$str = "<'test'>";
echo <<<EOD
<p>{$h($str, ENT_QUOTES)}</p>
EOD;
exit;

クラスを作らなくていい分短いソースでできますが、
なんとなく落ち着かないのは私だけでしょうか?

どちらの方法もややトリッキーな方法であるため、複数人で作業する場合などは
通常通り出力前に変換しておく方法をとったほうが無難かもしれません。
あくまでこういう方法もあるという紹介にとどめておきます。

[PHP]携帯サイト向けに文字コードを一括変換する

携帯サイトを UTF-8 で書ければ余計な手間がかからなくていいのですが、
古い機種もカバーするとなると未だに Shift-JIS を利用しなければなりません。
加えて全角カタカナを半角カタカナに統一するとなると結構面倒です。

モバイル専用サイトなら全て Shift-JIS で統一するのも手ですが、データベースを
PCサイトと共有しているケースは変換する必要がどうしても出てきます。

かといって echo のたびに mb_convert_encoding と mb_convert_kana を
やっていたのでは埒が明かないので、出力を一時的にバッファリングして一括変換します。
下記のプログラムソースは UTF-8 で書かれ、 Shift-JIS として出力しています。

<?php
mb_internal_encoding("UTF-8");
ob_start('mobile_output');
header("Content-Type: text/html; charset=Shift_JIS");
?>
<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
    <title>タイトル</title>
  </head>
  <body>
  これはサンプルです。SAMPLE123
  </body>
</html>
<?
ob_end_flush();

function mobile_output($str){
	$str = mb_convert_kana($str, "ka", "UTF-8");
	$str = mb_convert_encoding($str, 'SJIS', 'UTF-8');
	return $str;
}

フォームから GET や POST で受け取るデータは Shift-JIS なので
データベースに格納する場合などは適宜変換して下さい。