PHPにおけるプログレスバーの実現

プログレスバーとは作業の進行状況をバーの長さで表示してくれるアレです。ファイルのコピーとかする時よく見かけますよね。あれをPHPで実現しようと四苦八苦した結果をまとめておきたいと思います。

(別のまとめも作りました:PHPにおけるプログレスバーの実現2

用意するHTMLは、
 
Wait a minute ...
こんなです。ソースは、
<style type="text/css">
#p_bar {background: #565656; font-size: 3mm; width: 0; padding: 0; margin: 0; border-spacing: 0;}
#i_p_bar {color: #ffffff; text-align: right;}
#out_bar {background: #cfcfcf; width: 300px; border: 1px #565656 solid; padding: 0; margin: 0; border-spacing: 0;}
.under_p_bar {color: #565656; font-size: 3mm;}
</style>

<table id="out_bar" width="300">
<tbody><tr><td>

<table id="p_bar">
<tbody><tr><td id="i_p_bar">&nbsp;</td></tr>
</tbody></table>

</td></tr>
</tbody></table>
<span id="msg" class="under_p_bar">Wait a minute ...</span>
<span id="rest" class="under_p_bar"></span>

こんな感じで書けますね。まぁデザイン等はお好みで。次にJavaScript部分は、

// メッセージを変える
function changeMsg(msg) {
    $('msg').innerHTML =msg;
}

// プログレスバーを動かす
function setProgress(i ,all) {
    Element.setStyle($('p_bar'), {'width': ((i/all)*300)+'px'});
    $('i_p_bar').innerHTML = Math.round(100*(i/all))+"%";
    $('rest').innerHTML = i + "/" + all + "件を処理完了";
}

こんなになってます…、あ、ちなみにprototype.js使ってます。まぁ見ての通りchangeMsg()ではidがmsgのspanの中身を変える関数、setProgressではプログレスバーの横幅を変更、処理状況をidがrestのspanに書きだしています。んじゃぁ、この関数をどうやって実行するのか、ですが、これはPHP側で行います。
echo "<script type=\"text/javascript\">changeMsg(\"解析開始...\")</script>\n";
ob_flush();
flush();
こんな感じでです。flush()ってのはバッファをフラッシュしているらしいです。バッファ?フラッシュ?良く分かんないですがwなんか、ob_flush()とflush()を両方コールすることでで向こう側にある処理途中の情報も送信してくれて(普通は処理し終わってから全部こっちに送ってくれるのかな?)ブラウザに表示してくれる、と解釈してます。まぁ動けばよし!ということで…。知ってる人いたら教えてください。そいでもって、プログレスバーを進めるには、
echo "<script type=\"text/javascript\">setProgress(".$i.",".$this->cntData. ")</script>\n";
こんな感じで行います。あ、ちなみに$this->cntDataには解析するデータ総数、$iには解析中のデータの番号を格納しています。こんな感じにすることでプログレスバーが実現できちゃいます。もうちょい詳しく書くと、
$i=0; $all = count($data);
foreach($data) {
    sleep(1); //ここが重い処理をする場所。

    echo "<script type=\"text/javascript\">setProgress(".$i.",".$all. ")</script>\n";
    ob_flush();
    flush();
    $i++;
}
こんな感じですかね。実装すると
 
Wait a minute ...
こんな感じになります。流れとしては、
① 重い処理をする
② 画面にプログレスバーを進めるJavaScriptを書き出す。
③ flushしないと画面に反映されないので、ob_flush()とflush()をする。
という感じです。例が分かりにくかったかもしれませんが参考になれば幸いです。

IE6 standalone in Windows Vista

競馬の解析の方は若干離れてました。今は、とある紹介で企業から頂いたサイト作りの仕事に専念してます。

その際、CrossBrowserに対応するよう努めているのですが、今日バイト先のPCから見たらレイアウトが崩れてました。なぜに!?と思ったのですが、そのPCに入っていたのはIE6。うちのvistaに入っているのはIE7と狐だけなので気づきませんでした。これはヤバイな、と思って家に帰ってきてから昔動かしたことのあるIE6 standaloneを起動しましたが動きません。…なぜ、と思ったのですが、昔動かしていたのは今はセーフモードでしか起動しなくなったXPの方のパソコンでした。あー、なぜー、と思って調べてみるとInternet Explorer 6 in Windows Vista (IE6) - part 1なる記事を発見。苦手な英語も狐のアドオンDictionary Tooltipの力を借りて何とか読みました。簡単に最後の部分を訳すと
HTMLとCSSは成功したけどJavaScriptはまだダメヨ。
ってことらしいです。なんかごちゃごちゃいじらなきゃいけないみたいなんで、自分には無理!とあきらめました。仕方ないのでXPが入ってるノートPCを併用して確認して作業していきたいと思います…、無念。

配列関数

今日は競馬解析プログラムを作ってて使えると思った配列関数の紹介です。全部は紹介しきれない上に分かってないのも多いので、PHPのサイトの方を見て頂いた方がいいかも・・・。

1. list()
配列を変数に分解できる関数です。
$fruit = array("apple", "banana");
$list($fruit1, $fruit2) = $fruit;
なんて感じで分解できます。これを利用してよく使われるのがeach()との組み合わせで、
$fruit = array(
    "佐藤" => "先生",
    "鈴木" => "友達",
    "伊藤" => "上司"
);
while(list($name, $rel) = each($fruit)) {
    echo $name . "は僕の" . $rel . "です。<br />\n";
}

/* 出力
    佐藤は僕の先生です。
    鈴木は僕の友達です。
    伊藤は僕の上司です。
*/
てな感じになります。each()はキーとその値を分けてくれる関数です。なお、この状態でもう一度実行しても
while(list($name, $rel) = each($fruit)) {
    echo $name . "は僕の" . $rel . "です。<br />\n";
}
/* 出力
   
*/
となってしまいます。これは配列の内部にポインタってものがあって、今どこを参照しているか記憶しているものです。これを最初に戻してあげるために、
reset($fruit);
をしてあげてからwhileを実行しないと動作しませんので注意してください(僕はこれで生じたエラーで1時間悩みましたw)

2. array_count_values()
重複した項目をカウントしてくれる関数です。うちの競馬解析では
// 重複度合を調べる
$chk_cross = array_count_values($this->horse);
こんな感じで、クロス(同じ祖先を重複して持つ)を調べています。$this->horseは配列で馬の名前が格納されてます。最初はこの重複を、
$i=0;
foreach($this->horse as $hname) {
    $chk_cross[$this->horse[$i]]++;
    $i++;
}
で調べてたんですが、マニュアル見てたら見っけました。やっぱ欲しい関数って結構あるもんなんですね。

紹介と言いつつ2つしか書いてませんがw、あとはarray_search()くらいですかね、使ったのは。配列は結構いろんな場面で使うので、ざっとマニュアルに目を通しておくと面白いですよ。

競馬解析プログラム3

暫く、体調不良でした…、ちょっとレポートが大きかったんで。この間から進んだことは、

①上のサイアーラインにも色付けする。
この間の記事でいうと、Tom Foolの父親が緑になってませんが、あれも緑にするっていった感じです。

②全馬の父・母の頻度を調べる。
2007/11/11のエリザベス女王杯時点での去年までのデータでいうと、
FA :
19
7
2
2
2
1
MF :
12
9
5
3
2
2

こんな感じです。FAが父、MFが母父です。

③クロスを調べる。

スティルインラブ  Fa    Mo-Fa 2003/11/16 Turn-to: 5x5 Royal Charger: 6x6 Blue Swords: 6x6 
スティルインラブ サンデーサイレンス Halo Hail to Reason Turn-to Royal Charger
Source Sucree
Nothirdchance Blue Swords
Galla Colors
Cosmah Cosmic Bomb Pharamond
Banish Fear
Almahmoud Mahmoud
Arbitrator
Wishing Well Understanding Promised Land Palestinian
Mahmoudess
Pretty Ways Stymie
Pretty Jo
Mountain Flower Montparnasse Gulf Stream
Mignon
Edelweiss Hillary
Dowager
ブラダマンテ Roberto Hail to Reason Turn-to Royal Charger
Source Sucree
Nothirdchance Blue Swords
Galla Colors
Bramalea Nashua Nasrullah
Segula
Rarelea Bull Lea
Bleebok
Sulemeif Northern Dancer Nearctic Nearco
Lady Angela
Natalma Native Dancer
Almahmoud
Barely Even Creme dela Creme Olympia
Judy Rullah
Dodge Me The Doge
By Me

Frequency :
1.96875
1.9375
0.375
0.3125
0.21875
0.09375
0.09375


いまはこんな感じになってます。あ、ちなみに色も濃くしました。まだまだやることは多いっすね。目標の自動予想までの道のりは長い…、って感じです。何か付けてほしい機能とかありましたらどんどん言ってください。出来るところから対応していきたいと思います、といいつつ公開はずっと先になりそうです(汗

競馬解析プログラム2

血統表の出力までは完成しました。あれから、HTMLの解析結果をSQLiteのデータベースに格納するプログラム、解析するプログラムと2つにわけ、なんとか!血統表の出力までこぎつけました。苦労したのは、血統表の組み立ててです。↓の方にもサンプル載せましたが、形を見て分かる通りめんどくさいテーブルです。しかも、取得した馬の配列データは、テーブルのtdを上から見てった順番!ウハ、どうしよ、って感じでした。結局、
// 並び替え用
var $change = array(2,4,8,16,32,33,17,34,35,9,18,36,37,        19,38,39,5,10,20,40,41,21,42,43,11,22,44,45,23,46,47,3,
6,12,24,48,49,25,50,51,13,26,52,53,27,54,55,7,14,28,56,
57,29,58,59,15,30,60,61,31,62,63);
こんなん作って、for文で処理しちゃいましたw
        for($i=0; $i<62; $i++) {
            $this->horse[$this->change[$i]] =$_blood[$i];
        }
んで、できたテーブルが


フサイチパンドラ 2006/11/12 entried.
フサイチパンドラ サンデーサイレンス Halo Hail to Reason Turn-to Royal Charger
Source Sucree
Nothirdchance Blue Swords
Galla Colors
Cosmah Cosmic Bomb Pharamond
Banish Fear
Almahmoud Mahmoud
Arbitrator
Wishing Well Understanding Promised Land Palestinian
Mahmoudess
Pretty Ways Stymie
Pretty Jo
Mountain Flower Montparnasse Gulf Stream
Mignon
Edelweiss Hillary
Dowager
ロッタレース Nureyev Northern Dancer Nearctic Nearco
Lady Angela
Natalma Native Dancer
Almahmoud
Special Forli Aristophanes
Trevisa
Thong Nantallah
Rough Shod
Sex Appeal Buckpasser Tom Fool Menow
Gaga
Busanda War Admiral
Businesslike
Best in Show Traffic Judge Alibhai
Traffic Court
Stolen Hour Mr. Busher
Late Date
2.21875
0.96875
0.875
0.75
0.09375
0.09375



こんなんです。色の付け方は、自分自身を①番として親を2代目、祖父母を3代目…、と見ていき順に②、③…と番号を振っていくと、ある特定の馬から見た父親と母親はそれぞれ自分の番号×2、自分の番号×2+1とすることができます。そこでこれを使って自分からスタートして再帰的に解析していくことによって全馬調べることができます。テーブルの作り方は、自分の番号が何世代目の番号か調べることによってtdのrowspanを指定して組み立てることができます。細かいことはちょっとメンドイのでw、おいておきます。すみません。とりあえず、これでエリザベス女王杯についての過去の血統表が完成しました。後は、クロスとかニックスとかの解析もしてみようかな。

なお、色付けに関してはブラッドバリエーション様のデータを参考にさせていただきました。ありがとうございます。

正規表現メモ - タグの検索 / URLの検索

データベース化するのは馬が38万頭もいることがわかり(笑)あきらめました。そこで、G1などのレースに絞ってそこに出場してきた馬の中で3着以内のものの血統を調べていこう!って方針になりました。

まぁ、その前に正規表現でHTMLタグ調べていった方が楽なんじゃないか、ということで正規表現について学ぼうと思います。まず、一般的なHTMLのタグをサーチするには、
.*?
という表現が重要になってきます。正規表現において
. : 何かの文字
* : 直前の文字が0文字以上
.* : ナンカ文字があるよ
? : 直前の文字が0個か1個→最小マッチング
とやることで、たとえば一般タグを検索の場合、
<.*?>
とすると、最小マッチングにより
<font color="red">赤いよ</font>
本当は_線部のところのみマッチしてほしいのに、太字にマッチしてしまうのを防げます。(PHPでは、基本的に最大マッチングを行います)
また、URLの検索をしたい場合には、
"http:\/\/[\w\d/%#$&?()~_.=+-]+"
とすることでOKです。\dは数字、\wはアルファベットを意味します。+は直前が1文字以上なので、[]内の文字が何文字かあるところまでURLとなるわけです。
最後はpreg_match()関数です。
int preg_match ( string $pattern, string $subject [, array &$matches [, int $flags [, int $offset]]] )
と与えられています。たとえば、
$pat = "<title>(.*?)<\/title>";
if ( preg_match ( "/".$pat."/i", $HTML, $match ) ){
  echo ( $match[1] );
}
とすれば、サイトのタイトルを抜き出せます。

さて、それじゃまたプログラムの作成の方に戻りま~す。  

競馬解析プログラム

このブログのデザインもだんだん飽きてきんで(笑)、そろそろ本筋に入って行こうかと思います。当面の目標は、「各馬・レースのデータベースの構築」⇒「自動的に次のレースの予想をはじき出す」というプログラムの作成です。データベースの構築といっても、一個一個手入力でやっていくには膨大な時間の無駄になってしまうのは、前に作った解析システム(高校の友達と一緒にやってました)から学びました。そこで今回は情報を配信しているサイトをクロールして、情報を収集するプログラムを作成しようと思います。言語はPHPでやって行こうと思っています。

最初に突き当たった壁が、どうやってHTMLを解析するのか。HTMLのパーサーのクラスライブラリとかないかな、と思ってところ、PEARパッケージのXML_HTMLSaxを紹介しているサイトが多かったので試してみました…が、PHPに触りたての自分にとってはちょっち無理w、ってことであきらめ、色んなクラスライブラリも試してみましたが、なかなかうまくいかず…。カッコイイ関数とか使わなくて、単純な仕組みでやってくれてるのないかな、と思ってたところ、オライリー社のSpidering Hacksという本のソースコードをDLできるページがあることを知り、早速使ってみました。…すると、どんぴしゃっすね。若干自分で改変しましたがstrpos()(:引数で指定した文字列のある場所を返す)を使うだけで特定の箇所を抜き出すことを実現していました。

function getBlock( $pStart, $pStop, $pSource, $offset) {
   $_data = null;
   $_start = stripos( $pSource, $pStart, $offset);
   $_stop = stripos($pSource, $pStop, $_start);
   $start = $_start + strlen($pStart);
   $length = $_stop - $start;
   if($_stop > $_start ) {
      $_data = trim( substr( $pSource, $start, $length ) );
   }
  
   return( array($_data, $_stop) );
}

こんな感じで、$pStartと$pStopで指定したものの間を抜き取るって感じです。まぁ同じタグが複数個所あったら抜き出すのは難しいですが、最近のHTMLはクラス、ID指定とかで特定の部位が探し出しやすくなっていることが多いので、以外に使えます。これなら、HTML⇒XHTML⇒XMLとして解析、とかキャパ越えなことしなくても大丈夫!ってことでいろいろ試してみました。その結果、とりあえず

No.0 テイエムオペラオー

栗毛
1996/03/13
オペラハウス
Blushing Groom

No.1 ディープインパクト

鹿毛
2002/03/25
サンデーサイレンス
Alzao

No.2ゼンノロブロイ

黒鹿毛
2000/03/27
サンデーサイレンス
マイニング
・・・
みたいな感じで、情報を抜き出していくことは可能になりました。まー、あとは抽出する項目を増やしていきましょ、って感じです。…ただし、対象のHTMLを直接読み込んでるんで、こちらも相当時間かかりますし(普通にタイムアウトしますww)、情報を抜き出させてもらっているサイト様にも迷惑かと…。問題は山積みですが、頑張って行こうと思います。