Liner Note

情報(ユーザー中心デザイン・ユーザビリティ)と技術(ウェブプログラミング・ウェブサービス)についてのメモ書き

要約:ページタイトルをサイトタイトルと文書タイトルに分離する試み using PHP

タイトル名とサイト名って書式が決まっているようでバランバランなので、これらを分けてみようととなるとちょっとしんどい。

例えば title タグの中身がほげほげ日記ー××について:○○シリーズその1だったとしよう。この場合は、サイト名は「ほげほげ日記」で文書名は「××について:○○シリーズその1」なのだけど、上記の通りサイト名と文書名をつなぐデリミタ(区切り記号)はバラバラな上、デリミタが複数ある場合を考慮すると単純にデリミタでぶった切るわけにもいかん。本来的には<title><span class="site">サイト名</span><span class="delimeter"> - </span><span class="document">文書名</span>とかセマンティックを意識して書ければよいのだけど1

なので、分離の際には2つ以上のタイトルが必要になる。つまり、

  • ほげほげ日記ー××について:○○シリーズその1
  • ほげほげ日記(トップページのタイトル)

この場合、「ほげほげ日記」が2つのタイトルに共通の文字列なので、そこを抜き出してやればよい。で、残りの文字列が文書名になるわけだ(デリミタはいらない子なので消す)。

で、この「2つのタイトルに共通の文字列を探す」をどう機械語に訳すか。なんとなく思いつくのは、1文字ずつぶった切った後に配列に格納して、双方を比較していくというもの。

でも、もっといい方法ないかなと思って、はてなで質問したら、お二方にお答えいただきました。

ID:KeyKeyさんのお答えは片方の文字列を段階的に切り出していってもう片方と一致するかどうか見るというもの。ID:yoneto164さんは私の考えているところに近い気がします。最後の方はよく分かりませんでしたが^-^;

で、お二人の回答を元にしたテストコードが以下。コメントは一部勝手に書き加えたりしてます、解釈が間違っていたらごめんなさい。

PHPソースコード
<?php
header("Content-Type: text/html;charset=utf-8");
 
$s1 = "Liner Note - はてなブックマークの各エントリを見やすく表示する「はてなブックマークビューワ」を作った";
$s2 = "Liner Note";
 
echo "<h1 id="t7266e0">「{$s1}」と「{$s2}」</h1>";
 
echo "<h2 id="tff8ebf">[1]の結果</h2>";
echo getCommonString1($s1, $s2);
echo "<h2 id="t695b26">[2]の結果</h2>";
echo getCommonString2($s1, $s2);
echo "<h2 id="t0ac35b">[3]の結果</h2>";
echo getCommonString3($s1, $s2);
 
// 処理時間を測定
function getmicrotime(){
  
  list($msec, $sec) = explode(" ", microtime());
  return ((float)$sec + (float)$msec);
  
}
 
// ID:KeyKeyさんのコード
function getCommonString1($s1, $s2){
  $t[] = getmicrotime();
  
  $k = 0;
  
  //片方の文字列の長さを取得
  $len = mb_strlen($s1, "utf-8");
  
  for ($i=0; $i<$len; $i++){
    for ($j=1; $j<=$len-$i; $j++){
      //$i文字目から$j文字目までを切り出す
      $rest = mb_substr($s1, $i, $j, "utf-8");
      //$s2に含まれていれば配列に格納
      if (preg_match("/" . $rest . "/", $s2, $match)) {
        $results[] = $rest;
      }
    }
  }
  
  $longest = 0;
  foreach ($results as $result){
    $len = mb_strlen($result, "utf-8");
    if ($len > mb_strlen($longest, "utf-8")){
      $longest = $result;
    }
  }
  
  $t[] = getmicrotime();
  echo "<p>処理時間:". round($t[1] - $t[0], 7) . "秒</p>";
  
  return $longest;
  
}
 
// ID:yoneto164さんのコード2
function getCommonString2($s1, $s2){
  $t[] = getmicrotime();
  
  $al = mb_strlen($s1,"utf-8");
  $bl = mb_strlen($s2,"utf-8");
  
  for ($i=0; $i<$al; $i++){
    $aa[$i] = mb_substr($s1,$i,1,"utf-8");
  }
  for ($i=0; $i<$bl; $i++){
    $bb[$i] = mb_substr($s2,$i,1,"utf-8");
  }
  
  for ($i=0; $i<$al; $i++){
    for ($j=0; $j<$bl; $j++){
      // 文字が一致
      if ($aa[$i] == $bb[$j]){
        if ($match){
          // 以前にマッチした文字なら配列に加えない
          foreach ($match as $str){
            if ($aa[$i] == $str){
              $ng = 1;
            }
          }
        }
        
        if ($ng != 1){
          $match[$num] = $aa[$i];
        }
        
        $ng = 0;
        $num++;
        
      }
    }
  }
  
  foreach($match as $print){
    $msg .= $print;
  }
  
  $t[] = getmicrotime();
  echo "<p>処理時間:". round($t[1] - $t[0], 7) . "秒</p>";
  
  return $msg;
  
}
 
// ID:yoneto164さんのコード3
function getCommonString3($s1, $s2){
  $t[] = getmicrotime();
  
  $al = mb_strlen($s1, "utf-8");
  $bl = mb_strlen($s2, "utf-8");
  
  for ($i=0; $i<$al; $i++){
    $aa[$i] = mb_substr($s1,$i,1,"utf-8");
  }
  
  for ($i=0; $i<$bl; $i++){
    $bb[$i] = mb_substr($s2,$i,1,"utf-8");
  }
  
  for ($i=0, $num=0; $i<$al; $i++){
    for ($j=0; $j<$bl; $j++){
      if ($matchnum[$i][$j] != 1){
        // 文字が一致
        if ($aa[$i] == $bb[$j]){
          $match[$num] = $aa[$i];
          $matchnum[$i][$j] = 1;
          // さらに文字が一致したら追加
          for ($k=1; $k<= $al-$i-1 && $k <= $bl-$j-1; $k++){
            if ($aa[$i+$k] == $bb[$j+$k]){
              $match[$num] .= $aa[$i+$k];
              $matchnum[$i+$k][$j+$k] = 1;
            } else{
              $j = $j + $k - 1;
              break;
            }
          }
          $num++;
        }
      }
    }
  }
  
  $longest = 0;
  foreach ($match as $result){
    $len = mb_strlen($result, "utf-8");
    if ($len > mb_strlen($longest, "utf-8")){
      $longest = $result;
    }
  }
  
  $t[] = getmicrotime();
  echo "<p>処理時間:". round($t[1] - $t[0], 7) . "秒</p>";
  
  return $longest;
  
}
 
?>

実際に実行してみると、勝手に書き加えたコードの影響もあるでしょうが、ID:yoneto164さんのコードがより速かった2 ので、そちらを元に構成することにしました。それがこちら。

PHPソースコード
<?php
// ID:yoneto164さんのコード2を元に構成
function getTitle($s1, $s2){
  
  $al = mb_strlen($s1, "utf-8");
  $bl = mb_strlen($s2, "utf-8");
  
  for ($i=0; $i<$al; $i++){
    $aa[$i] = mb_substr($s1,$i,1,"utf-8");
  }
  
  for ($i=0; $i<$bl; $i++){
    $bb[$i] = mb_substr($s2,$i,1,"utf-8");
  }
  
  for ($i=0, $num=0; $i<$al; $i++){
    for ($j=0; $j<$bl; $j++){
      if ($matchnum[$i][$j] != 1){
        // 文字が一致
        if ($aa[$i] == $bb[$j]){
          $match[$num] = $aa[$i];
          $matchnum[$i][$j] = 1;
          // さらに文字が一致したら追加
          for ($k=1; $k<= $al-$i-1 && $k <= $bl-$j-1; $k++){
            if ($aa[$i+$k] == $bb[$j+$k]){
              $match[$num] .= $aa[$i+$k];
              $matchnum[$i+$k][$j+$k] = 1;
            } else{
              $j = $j + $k - 1;
              break;
            }
          }
          $num++;
        }
      }
    }
  }
  
  $longest = 0;
  foreach ($match as $result){
    $len = mb_strlen($result, "utf-8");
    if ($len > mb_strlen($longest, "utf-8")){
      $longest = $result;
    }
  }
  
  $site =  preg_replace("/[:-<>\/\|ー|:]/", "", chop($longest));
  $title = str_replace($longest, "", $s1);
  
  return array($site, $title);
  
}
?>

ちなみに比較に使うタイトルですが、一方は「サイト名と文書」のもの、もう一方は「サイト名」のみのものでないといけません。この処理だと共通する文字列の一番長いものを「サイト名」にしていますから。

これで、トップページが「無題ドキュメント」だとか「Welcome to Adobe GoLive 5」とかでない限り大丈夫なはず。あ、あともう1つありましたね。昔流行ったらしい短縮タイトルです、この文書ならば「[L] 2つのタイトルからタイトル名とサイト名を分離するPHPスクリプト」と記述するような。

この場合、先頭に[*](*は任意の1文字)が来ているかチェックして、あった場合に例外処理するのがよいのかな。

  1. むろん、こんなspan要素だらけのtitleタグはゴメンだが[戻る]
  2. といっても小数点3桁の世界ですが[戻る]

Popularity: 1% [?]

キーワード:

似たもの記事

読者の皆さんの反応サイト内コメントの更新情報(RSSフィード)

読者のコメント

4

ブックマークコメント

0

他サイトの関連記事

0

読者のコメント

  1. お名前

    名無しさん

    投稿日時
    2008年01月23日
    1時ごろ
    Comment No
    #1

    いるか賞ありがとうございました。
    2つ目のコードは分かり難いですよね。
    書いている自分も分からなくなるような状態でした。
    これは、「ABC」と「ABC」を比較したさい、
    「ABC」という答えを導きたいわけですが、
    「A」「AB」「BC」「ABC」という文字列が全て一致してしまうので、
    以前に一致した文字は一致と見なさない処理を加え、
    一致した最長記録だけを残すための処理を施しているためです。
    この処理を行うために回答が遅くなってしまいましたが、
    お役に立てたようなら幸いです。

  2. お名前

    tezcello

    投稿日時
    2008年01月23日
    22時ごろ
    Comment No
    #3

    一致部分を探す様な時は、先頭からでは無く、最後からスキャンをするとよいとどこかで読んだ気がします。先ずは一致する文字を探し、一致する文字を見つけてからは、一致しなくなればスキャンを終了出来るのでスキャンそのものが速いとか。(VC++関係のマニュアルかなんかだったような…)
    具体的なコードやサンプルのURLを示すことが出来ませんので、コメントで。

はてなブックマークでつけられたコメント

この記事はまだブックマークされていません

他サイトの関連記事

トラックバックはまだ寄せられていません


トラックバックとは
この記事に言及したサイトをこちらに掲載する仕組みをトラックバックと言います。ここでは、このサイトに頂いたトラックバックを一覧表示しています。
トラックバックしてくださる方へ
この記事への言及がない記事など、トラックバック受信方針に沿っていないものは、読者にお見せしても仕方ないこともあり削除させていただいることをご了承ください。
トラックバックを受け取るためのURI

コメント書き込みフォーム

  • メールアドレスはウェブ上で公開したり、連絡以外で使うことはありません
  • コメントを公開したくないが、作者に連絡を取りたい場合は メールで連絡してください
  • 本文中にHTMLコードは使用できません(URLはそのままお書きください)