※ パクレゼルヴではWeb開発エンジニアを大募集中!詳細はこちら

Archive

Archive for the ‘PHP’ Category

PHPのfor文の条件式にcount関数を使用すると・・・  

2010/7/26 月曜日 19:12:22

はじめましてー
今月からお世話になりますうっち〜です。
よろしくお願いします!

さて、最初から何を書こうか迷ってしまいましたが・・・

前々から気になってたPHPのfor文の挙動について調べてみることにしました。

配列の要素をfor文で取り出す時に
for文の条件式でcount関数を使用している例をよく見るのですが

for ($i = 0; $i < count($ary); $i++) {

ループする度にcount関数が毎回処理されてそうで・・・
僕は事前にcount関数を使用する形を使用しています。

$cnt = count($ary);
for ($i = 0; $i < $cnt; $i++) {

でも、本当にそのような挙動になっているのか!?
実はPHPが最適化してくれているのかも??
と、確信がなかったのでいつも気になっていました。
(for文にcount入れた方が楽なので無駄なことしてるんじゃないかと・・・)

ということで、ちょっと調べてみました。

下記A,Bの2パターンのコードを用意し、
gdbでcount関数内にブレークポイントを設定し
それぞれ実行してみてcount関数が呼ばれる回数を調べてみました。

// パターンA
$ary = array(1, 2, 3, 4, 5);
$cnt = count($ary);
for ($i = 0; $i < $cnt; $i++) {
echo $ary[$i];
}
// パターンB
$ary = array(1, 2, 3, 4, 5);
for ($i = 0; $i < count($ary); $i++) {
echo $ary[$i];
}

■結果
count関数が呼ばれた回数は

パターンA:1回
パターンB:7回

でしたー。

無駄ではなかったようです!

ただ、
実際にどのくらいの処理時間の差が出るのか?
気にするほどの時間なのか?
が気になるところです。

なのでそれはまたの機会にでも!

うっち〜 PHP

PHPで都道府県、市区町村、町域名以降の住所分割を高速に行う方法  

2010/7/26 月曜日 14:51:41

macbook-black
マカーです。

ECのように住所を扱うシステムで、一つに結合された住所文字列から、『都道府県』、『市区町村』、『町域名以降』を高速に分割したい時があります。
簡単に思いつく方法としては、日本郵便で公開されている住所データを元に1行づつ比較していく方法が考えられますが、生成に時間が掛かってしまいます。

こんな時にmecabを使うと簡単且つ高速に分割できます。

住所辞書の作成

まずは住所辞書を作成します。
そのために住所辞書の元になるデータ用意する必要がありますが、今回は日本郵便で公開されている郵便番号データを使いました。

公開されているデータファイルはlzh形式となっているので、lhaで解凍します。

wget http://www.post.japanpost.jp/zipcode/dl/kogaki/lzh/ken_all.lzh
lha e ken_all.lzh

ken_all.csvというcsvファイルが解凍されます。
中身はこんな感じ。

01101,"060  ","0600000","ホッカイドウ","サッポロシチュウオウク","イカニケイサイガナイバアイ","北海道","札幌市中央区","以下に掲載がない場合",0,0,0,0,0,0
01101,"064  ","0640941","ホッカイドウ","サッポロシチュウオウク","アサヒガオカ","北海道","札幌市中央区","旭ケ丘",0,0,1,0,0,0
01101,"060  ","0600041","ホッカイドウ","サッポロシチュウオウク","オオドオリヒガシ","北海道","札幌市中央区","大通東",0,0,1,0,0,0
01101,"060  ","0600042","ホッカイドウ","サッポロシチュウオウク","オオドオリニシ(1-19チョウメ)","北海道","札幌市中央区","大通西(1〜19丁目)",1,0,1,0,0,0
01101,"064  ","0640820","ホッカイドウ","サッポロシチュウオウク","オオドオリニシ(20-28チョウメ)","北海道","札幌市中央区","大通西(20〜28丁目)",1,0,1,0,0,0
〜〜〜中略〜〜〜
47381,"90714","9071433","オキナワケン","ヤエヤマグンタケトミチョウ","ハイミナカ","沖縄県","八重山郡竹富町","南風見仲",0,0,0,0,0,0
47381,"90717","9071751","オキナワケン","ヤエヤマグンタケトミチョウ","ハテルマ","沖縄県","八重山郡竹富町","波照間",0,0,0,0,0,0
47381,"90715","9071544","オキナワケン","ヤエヤマグンタケトミチョウ","ハトマ","沖縄県","八重山郡竹富町","鳩間",0,0,0,0,0,0
47382,"90718","9071800","オキナワケン","ヤエヤマグンヨナグニチョウ","イカニケイサイガナイバアイ","沖縄県","八重山郡与那国町","以下に掲載がない場合",0,0,0,0,0,0
47382,"90718","9071801","オキナワケン","ヤエヤマグンヨナグニチョウ","ヨナグニ","沖縄県","八重山郡与那国町","与那国",0,0,0,0,0,0

都道府県名と市区町村だけを抜き出し、重複を取り除いた後、mecabの辞書として登録できる形式に変換します。

nkf -S -w ken_all.csv|cut -f 4-5,7-8 -d ','|sed -e 's/^"\([^"]*\)","\([^"]*\)","\([^"]*\)","\([^"]*\)"/\3 \1\
\4 \2/g'|sort|uniq|gawk '{c=int(-400 * (length($1) ^ 1.5)); if (c < -36000) c=36000; print $1 ",-1,-1," c ",名詞,固有名詞,地域,一般,*,*," $1 "," $2 "," $2}' > addr.csv

以下のようなcsvファイルができあがります。

$ cat addr.csv
あきる野市,-1,-1,-4472,名詞,固有名詞,地域,一般,*,*,あきる野市,アキルノシ,アキルノシ
あま市,-1,-1,-2078,名詞,固有名詞,地域,一般,*,*,あま市,アマシ,アマシ
あわら市,-1,-1,-3200,名詞,固有名詞,地域,一般,*,*,あわら市,アワラシ,アワラシ
いすみ市,-1,-1,-3200,名詞,固有名詞,地域,一般,*,*,いすみ市,イスミシ,イスミシ
いちき串木野市,-1,-1,-7408,名詞,固有名詞,地域,一般,*,*,いちき串木野市,イチキクシキノシ,イチキクシキノシ
〜〜〜中略〜〜〜
蕨市,-1,-1,-1131,名詞,固有名詞,地域,一般,*,*,蕨市,ワラビシ,ワラビシ
檜山郡厚沢部町,-1,-1,-7408,名詞,固有名詞,地域,一般,*,*,檜山郡厚沢部町,ヒヤマグンアッサブチョウ,ヒヤマグンアッサブチョウ
檜山郡江差町,-1,-1,-5878,名詞,固有名詞,地域,一般,*,*,檜山郡江差町,ヒヤマグンエサシチョウ,ヒヤマグンエサシチョウ
檜山郡上ノ国町,-1,-1,-7408,名詞,固有名詞,地域,一般,*,*,檜山郡上ノ国町,ヒヤマグンカミノクニチョウ,ヒヤマグンカミノクニチョウ
諫早市,-1,-1,-2078,名詞,固有名詞,地域,一般,*,*,諫早市,イサハヤシ,イサハヤシ

変換したcsvデータを元にmecabの辞書を生成します。
以下の例ではaddr.dicという辞書ファイルが生成されます。

mecab-dict-indexおよびipadicのパスは環境によりことなると思いますので適当に置き換えてください。

/usr/local/libexec/mecab/mecab-dict-index -d/usr/local/lib/mecab/dic/ipadic -u addr.dic -f utf8 -t utf8 addr.csv

辞書テスト

実際にうまく分割されるか試してみます。

まずは生成した辞書ファイルを使わない場合の例。
東京都港区芝公園4-2-8の場合、『東京』『都』『港』『区』と分割されます。

$ echo '東京都港区芝公園4-2-8' | mecab
東京	名詞,固有名詞,地域,一般,*,*,東京,トウキョウ,トーキョー
都	名詞,接尾,地域,*,*,*,都,ト,ト
港	名詞,固有名詞,地域,一般,*,*,港,ミナト,ミナト
区	名詞,接尾,地域,*,*,*,区,ク,ク
芝公園	名詞,固有名詞,地域,一般,*,*,芝公園,シバコウエン,シバコーエン
4	名詞,数,*,*,*,*,*
-	名詞,サ変接続,*,*,*,*,*
2	名詞,数,*,*,*,*,*
-	名詞,サ変接続,*,*,*,*,*
8	名詞,数,*,*,*,*,*
EOS

デフォルトの辞書の場合、
『東京都』が、『東京』『都』
『港区』が『港』『区』
と分かれてしまうので、このままでは使用できません。

次に、今回生成した辞書ファイルを使用した場合の例です。
期待通り『東京都』『港区』と分割されます。

$ echo '東京都港区芝公園4-2-8' | mecab -u./addr.dic
東京都	名詞,固有名詞,地域,一般,*,*,東京都,トウキョウト,トウキョウト
港区	名詞,固有名詞,地域,一般,*,*,港区,ミナトク,ミナトク
芝公園	名詞,固有名詞,地域,一般,*,*,芝公園,シバコウエン,シバコーエン
4	名詞,数,*,*,*,*,*
-	名詞,サ変接続,*,*,*,*,*
2	名詞,数,*,*,*,*,*
-	名詞,サ変接続,*,*,*,*,*
8	名詞,数,*,*,*,*,*
EOS

変換スクリプト

上記で生成した辞書を使うことで、都道府県、市区町村までは期待どおり分割できます。
分割は、『都道府県』、『市区町村』、『町域名以降』の三つで良いので、町域名以降は分割されたものを結合します。

rskyさんのphp_mecabを使わせていただき、PHPで処理してみました。

<?php
$dictfile = './addr.dic';
$addrfile = 'testdata.lst';

$options = array('-u', $dictfile);

$mecab = mecab_new($options);

$handle = @fopen($addrfile, 'r');
while (!feof($handle))
{
	$addr = fgets($handle);
	if (!$addr)
		continue;

	$addr_splits = array();
	$node = mecab_sparse_tonode($mecab, $addr);
	while($node = mecab_node_next($node))
	{
		$addr_splits[] = mecab_node_surface($node);
	}
	$addr1 = array_shift($addr_splits);
	$addr2 = array_shift($addr_splits);
	$addr3 = implode("", $addr_splits);

	echo "$addr1,$addr2,$addr3\n";
}
fclose($handle);
mecab_destroy($mecab);

上記のスクリプトをconvaddr.phpという名前で保存します。
$addrfileで指定したファイルに改行で区切った住所データを用意し実行すると、カンマ区切りで3つに区切られた住所データとして出力されます。

$ cat testdata.lst|wc -l
5000
$ time (php convaddr.php > output.csv)

real	0m0.448s
user	0m0.352s
sys	0m0.096s
$ head output.csv
千葉県,木更津市,下郡1-18-16ハウス下郡405
群馬県,桐生市,仲町3-3-7ステーション仲町210
埼玉県,吉川市,吉川2-16-2吉川パレス103
東京都,小平市,上水南町4-8-9
滋賀県,彦根市,平田町4-14-6
秋田県,大館市,幸町1-7-19メゾン幸町119
岡山県,苫田郡鏡野町,下森原3-5-11
沖縄県,浦添市,港川2-6-5
大分県,中津市,三光森山3-6三光森山アパート414
香川県,観音寺市,木之郷町3-17-7

お名前.com VPSにて5000件のレコードを使いテストしたところ0.458秒で変換できました。
用途にもよると思いますが、速度的に実用十分ではないかと思います。

尚、この方法を応用することで住所から郵便番号を高速に調べることもできますが、また別の機会に紹介したいと思います。

マカー PHP

HTMLの事とか  

2010/7/5 月曜日 14:33:29

先月はWebデザインを主にしていました。
プログラムを全く触っていないわけじゃないですよ・・・?
まぁともかく!HTMLとかCSSとか、後PHPですこし気付いた事でも。
自分メモ的な感じで。

array_shift()とarray_pop()

array_shift()
array_pop()
てのがあるんですが。この関数、例えばarray_shift()だったら

$data = array('青','赤','黄','白','緑','紫','金','銀');
$data1 = array_shift($data);
echo $data1;
foreach($data1 as $val){
    echo $val;
}

ってやれば

//$data1
青
//$data
赤黄白緑紫金銀

となる。配列の先頭の要素を抜き出す。
array_pop()は最後の要素を抜き出す。
元の配列が変わってしまうという点に注意さえすれば、便利な関数なんですが、
元の配列が変わってしまった事で起きた出来事が。

配列に都道府県毎の料金を入れて、最大値と最低値、都道府県毎の料金を表示させるって事をやってたんですが、
料金の最大値と最低値が表示されない!なんで!
と思って調べたら、array_shift()とarray_pop()を使っていたためでした。
料金の配列を降順でソートして、array_shift()で先頭(最大値)を、array_pop()で最後(最低値)を
抜いていたのです。

そこで、reset()、end()の登場。

reset()は内部ポインタを最初の要素まで戻し、その値を返す。
end()はその逆。

array_shift()とarray_pop()と似たような動きをする関数です。
array_shift()等とは違って、元の配列が変わらない。
処理速度はというと・・・そんなに変わらないそうです;;(若干reset()のほうが早いようですが)

しかし、max()、min()でやったら?って気がするかも・・・

因みにこのreset()関数、同じソースで、同じ配列を複数回使う場合などに使えます。というか使います。

$array = array('1', '2', '3', '4');

next($array);
echo current($array) ; // "2"

//ポインタをリセットして再度実行
reset($array);
echo current($array) ; // "1"

こんな感じかな?

以降はhtml関係のお話
メイリオについて
>メイリオとは、和文ゴシック体フォントの一つで、Vista以降のマイクロソフト製OSに標準で搭載されてる。
まぁXPとか2003とかで使ってたMS UI Gothicに代わるフォントなんですけど、今回これに苦しめられました。

font-family:"メイリオ",Meiryo,"Hiragino Kaku Gothic Pro","ヒラギノ角ゴ Pro W3","MS P Gothic","MS Pゴシック",Verdana,Arial,sans-serif;

てな感じで指定してるんですけど、OSがVistaと7だとデザインが崩れるんですよね。

なぜかというと、メイリオは全角文字がすべて固定幅のフォントであり、行間も従来フォントに比べて広いから。
なのでゴシックなどを想定して作ってるとデザインが崩れる。

font-familyでメイリオはずして、ゴシックを先頭にするとかで解決。
line-heightで行間を短くするのでもOKかな?
僕は今回line-heightで行間短くして対処しました。ゴシックだとなんか字が汚いんですよね・・・

つっしー HTMLとか, PHP, Tips

htmlメール  

2010/7/1 木曜日 3:54:13

おいすー^^

とかやってみる

こんばんは のびーにょです

HTMLメールについて

phpでHTMLメールを送る場合、

//送信元アドレス
$from "";
//BCCアドレス
$bcc ="";
//タイトル
$title ="";
//本文
$body ="";
//エラー時の戻り先
$replay="";

mb_language('Japanese');
mb_internal_encoding("UTF-8");
mb_detect_order("ASCII, JIS, UTF-8, EUC-JP, SJIS");
$header = "FROM: ".$from."\n";
$header.= "BCC: ".$bcc."\n";
$header.= "Return-Path: ".$from."\n";
$header.= 'MIME-Version: 1.0' . "\n";
$header.= "Content-Type: text/html; charset=ISO-2022-JP";

$title = mb_convert_kana($title,"KV");
$body = mb_convert_kana($body,"KV");
$body = mb_convert_encoding($body, 'JIS', 'auto');

mb_send_mail($mail,$title,$body,$header,"-f ".$replay);

ってやってやればbodyにhtml書けば送れるらしいよ。

$header.= 'MIME-Version: 1.0' . "\n";
$header.= "Content-Type: text/html; charset=ISO-2022-JP";

要するにヘッダーに上記を追加してやれば良いと言う話。
text/plainじゃなくてtext/htmlにしてやるべし。
MIMEもお忘れなく。

まぁPEARのMailとか使ったほうがいいけど。

と、まぁそんなことは割とどうでもよくて。

CSSで背景指定した場合

background-image:url(hogehoge);

とかね

HTMLメールだと背景画像が表示されないらしい。
主にhotmailとGmail

と、言うことで、じゃぁどうしようか ってなる。

ここで懐かしのテーブル背景!

<table background="hogehoge">

って奴

これならhotmailでもGmailでも背景として画像が使えるらしい

初めて知った

まぁ携帯ばっかり作ってたのでHTMLメールは知らないのです。

携帯のHTMLメールはマルチパート部分が結構複雑な上にキャリアでちょっと違う部分もあるので注意です。

機会があればそのへんも書きたいと思います。

のびーにょ HTMLとか, PHP

sessionについて  

2010/6/30 水曜日 18:54:04

はじめまして。
6月からソリューション事業部にお世話になっております。
よろしくお願い致します。

基本的なことですが、sessionについて悩んだことを書きます。

下記の処理を実行するとエラーがでます。
問題点はセッションに値が入ってないからです。
session_start関数を呼べば$_SESSIONに値を入れなくても
エラーなく判定できると思い込んでいました。

session_start();

if(strcmp($_SESSION['proposal']['complete'],'test') == 0){
}

対処法は下記のようにセッションに値をいれておくか
array_key_existsなどの関数を使うことにより、
エラーなく実行することができます。

$_SESSION['proposal']['complete'] ='';

または

if(array_key_exists('proposal', $_SESSION) &&
    array_key_exists('complete', $_SESSION['proposal']) &&
      strcmp($_SESSION['proposal']['complete'],'test') == 0){
}

Manocchi PHP