ラベル PHP の投稿を表示しています。 すべての投稿を表示
ラベル PHP の投稿を表示しています。 すべての投稿を表示
2013/03/12

[PHP]実用的なメールアドレスの正規表現

メールアドレスは仕様がヤバイ

正規表現辞典正規表現辞典を読んでループ展開も覚えた私に怖いものなどない!と
メールアドレスの正規表現を書いてみた。…ものは漏れがあった。
メールアドレスの正規表現なんて決定版があるんじゃないの?と調べてみたら
404 Blog Not Found:「PHP使いはもう正規表現をblogに書くな」と言わせないでくれ
こぶたのラッパ » 「PHP使いはもう正規表現をblogに書くな」のメールアドレスチェック用正規表現をPHP用に書きなおす
「danコガいはもう正規表現をblogに書くな」と言わせないでくれ | へぼい日記
404 Blog Not Found:regexp - 'test@[127.0.0.1' . "¥¥¥x1f]" はRFC2822準拠
などの記事が見つかった。正規表現がヤバイと思ったらまず仕様がヤバイらしい。
Perlメモ
Jeffrey E. F. Friedl氏原著による 「詳説 正規表現」にはメールアドレスはネストしたコメントを持つことができるので正規表現で表わすのは不可能であると書いてあります。
メールアドレス - Wikipedia
Wikipedia 見るとわかるが実は色々と記号を使えたり、" " でくくればなんでもありという凄い仕様なのである。

そこで一番理解できそうな以下の記事の正規表現を使うことにした。
メールアドレス(addr-spec)の正規表現 | へぼい日記
<?php
$wsp           = '[\x20\x09]'; // 半角空白と水平タブ
$vchar         = '[\x21-\x7e]'; // ASCIIコードの ! から ~ まで
$quoted_pair   = "\\\\(?:$vchar|$wsp)"; // \ を前につけた quoted-pair 形式なら \ と " が使用できる
$qtext         = '[\x21\x23-\x5b\x5d-\x7e]'; // $vchar から \ と " を抜いたもの。\x22 は " , \x5c は \
$qcontent      = "(?:$qtext|$quoted_pair)"; // quoted-string 形式の条件分岐
$quoted_string = "\"$qcontent*\""; // " で 囲まれた quoted-string 形式。量指定子が*ということは空文字を許容している
$atext         = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; // 通常、メールアドレスに使用出来る文字
$dot_atom_text = "$atext+(?:[.]$atext+)*"; // ドットが連続しない RFC 準拠形式をループ展開で記述
$dot_atom      = $dot_atom_text; // RFC5322 では dot-atom と呼ぶ
$local_part    = "(?:$dot_atom|$quoted_string)"; // local-part は dot-atom 形式 または quoted-string 形式のどちらか
$domain        = $dot_atom; // ドメインは英数字と記号 を ドット区切りにした文字
$addr_spec     = "${local_part}[@]$domain"; // 変数展開で配列として処理されるため可変変数として書いている

// 昔の携帯電話メールアドレス用
$dot_atom_loose   = "$atext+(?:[.]|$atext)*"; // 連続したドットと @ の直前のドットを許容する
$local_part_loose = "(?:$dot_atom_loose|$quoted_string)"; // 昔の携帯電話メールアドレス用。昔の携帯電話は quoted-string 形式のメールアドレスを取得できたのか?
$addr_spec_loose  = "${local_part_loose}[@]$domain"; // 変数展開で配列として処理されるため可変変数として書いている

$input_addr_spec = 'foo@example.com';

if ( preg_match("/\A$addr_spec\z/", $input_addr_spec) ) {
    print "valid addr-spec\n";
}
?>
理解せずにコピペするのは嫌だったのでコメントをつけて理解できるようにした。
リンク先の方針は引用していないけど読んでおいてください。
RFC 5322 準拠で昔の携帯電話のメールアドレスで取得できた
「連続したドット」と「 @ の直前のドット」を持つメールアドレスも許容する正規表現を書いてくれている。
\ が \\\\ と表記されている理由は正規表現事典のP.115を見るべし。
簡単にいえば文字列のエスケープと正規表現のエスケープでそれぞれ倍増するから。

あと 多分バグで quoted-string 形式の正規表現の量指定子が * になっていて ""@domain を通してしまう。

^-^@-.- はRFC準拠のメールアドレスです

メールアドレス(addr-spec)の正規表現 | へぼい日記ではローカルパートのテストは十分行われているけど
ドメイン部分のテストは不十分に感じる。その証拠に ^-^@-.- なども通してしまう。
流石にこんな顔文字みたいなメールアドレスを DB で見かけたら目を疑ってしまう。
その理由はRFC 5322 - Internet Message Formatにほとんどドメインについて書かれていないからである。
ドットとハイフンは連続しないと思われるが連続を許す正規表現だし、記号も色々使えるのでおかしい気がする。
そう思って調べてみると Wikipedia にRFC 5321 - Simple Mail Transfer Protocolにも
メールアドレスについて書かれている、と書いてあった。
軽くキーワードだけ抽出して読んでみた感じでは
ドメインはドット区切りのサブドメインの繰り返しからなり、
サブドメインはハイフン区切りの英数字の繰り返しからなる。
読む人は RFC で使われる ABNF の仕様(RFC 5234 - Augmented BNF for Syntax Specifications: ABNF)が
わからないと読みにくいかも。まぁなんとなく予想はつくだろうけど。

実用的なメールアドレスの形式チェック関数

<?php
function isValidEmailFormat($email, $supportPeculiarFormat = true){
    $wsp              = '[\x20\x09]'; // 半角空白と水平タブ
    $vchar            = '[\x21-\x7e]'; // ASCIIコードの ! から ~ まで
    $quoted_pair      = "\\\\(?:{$vchar}|{$wsp})"; // \ を前につけた quoted-pair 形式なら \ と " が使用できる
    $qtext            = '[\x21\x23-\x5b\x5d-\x7e]'; // $vchar から \ と " を抜いたもの。\x22 は " , \x5c は \
    $qcontent         = "(?:{$qtext}|{$quoted_pair})"; // quoted-string 形式の条件分岐
    $quoted_string    = "\"{$qcontent}+\""; // " で 囲まれた quoted-string 形式。
    $atext            = '[a-zA-Z0-9!#$%&\'*+\-\/\=?^_`{|}~]'; // 通常、メールアドレスに使用出来る文字
    $dot_atom         = "{$atext}+(?:[.]{$atext}+)*"; // ドットが連続しない RFC 準拠形式をループ展開で構築
    $local_part       = "(?:{$dot_atom}|{$quoted_string})"; // local-part は dot-atom 形式 または quoted-string 形式のどちらか
    // ドメイン部分の判定強化
    $alnum            = '[a-zA-Z0-9]'; // domain は先頭英数字
    $sub_domain       = "{$alnum}+(?:-{$alnum}+)*"; // hyphenated alnum をループ展開で構築
    $domain           = "(?:{$sub_domain})+(?:[.](?:{$sub_domain})+)+"; // ハイフンとドットが連続しないように $sub_domain をループ展開
    $addr_spec        = "{$local_part}[@]{$domain}"; // 合成
    // 昔の携帯電話メールアドレス用
    $dot_atom_loose   = "{$atext}+(?:[.]|{$atext})*"; // 連続したドットと @ の直前のドットを許容する
    $local_part_loose = $dot_atom_loose; // 昔の携帯電話メールアドレスで quoted-string 形式なんてあるわけない。たぶん。
    $addr_spec_loose  = "{$local_part_loose}[@]{$domain}"; // 合成
    // 昔の携帯電話メールアドレスの形式をサポートするかで使う正規表現を変える
    if($supportPeculiarFormat){
        $regexp = $addr_spec_loose;
    }else{
        $regexp = $addr_spec;
    }
    // \A は常に文字列の先頭にマッチする。\z は常に文字列の末尾にマッチする。
    if(preg_match("/\A{$regexp}\z/", $email)){
        return true;
    }else{
        return false;
    }
}
俺は変数展開は{}でくくる派なんだよお!
あとRFC 5321 - Simple Mail Transfer Protocolではサブドメインは1回でも良いので hoge@fuga を
許容するのだけど感覚として最低でもドットは1回は含まれるだろ?ってことで15行目の量指定子を + にした。

引数で昔の携帯電話のメールアドレスの形式をサポートするか選べるようにした。
なので以下のように書ける。普通こんな事しないだろうけど。
<?php
if(isValidEmailFormat($email)){
    if(isValidEmailFormat($email, false)){
        // RFC準拠の正しいメールアドレスです
    }else{
        // キャリアの独自形式なのでメールが届かない可能性があるよ的な注意書きが出せる
    }
}else{
    // 不正な形式なのでエラーです
}
ちなみに昔の携帯電話のメールアドレスの形式をサポートしないなら filter_var を使うこともできる。
if(filter_var($email, FILTER_VALIDATE_EMAIL)){
    // 正しい(?)形式です
}
(?)をつけたのはドメイン部分のハイフンとドット周りで変なのを許容するから。
処理時間は isValidEmailFormat より 6倍遅い上に、filter_var のメールアドレスチェックは微妙なので
isValidEmailFormat 関数を使って行きましょう!

追記: 2013/08/11

Perl - PHPしか書けないザコがメールアドレス正規表現でガチ勢に挑んでみた - Qiita [キータ]
色々なパターンをテストしてもらいました。
基本的にWebサービスでの利用を想定していたので
はてブコメントにあるようなドメインパートが localhost のメールアドレスや
IPv4, IPv6 のメールアドレスは通らなくても良いかな?
a@a.0, a@0.0, a@0.a が通ってるのが微妙感あるけどどうなんだろ?
他は大体想定通りなので良かった。
文字数チェックは行なっていないので別途チェックしましょう。
2013/03/06

[PHP]簡易json2xml関数を無名関数を使って作ってみた

Twitter API が v1.1 から JSON のみを返すようになりましたね。
XML で保存していた過去のレスポンスと形式を揃えておくために
JSON から XML に変換したいと思ったら意外とめんどくさいですよん。
数行で出来るんじゃないのとか思って json2xml 関数を作ってみた。

php - How to convert array to SimpleXML - Stack Overflow
array_walk_recursive を使って
SimpleXMLElement::addChild で配列の中身を1個ずつ突っ込めばいいよ。
ただし key と value は反転するけどな!って回答があったので
無名関数で正しい順に渡してやればいいのでは?と書き始めた。
<?php
function json2xml($root, $json){
    $array = json_decode($json, true);
    $xml = new SimpleXMLElement("<{$root}/>");
    return array2xml($array, $xml);
}

function array2xml($array, &$xml){
    $addChild = function($value, $key) use($xml){
        if(is_array($value)){
            if(is_numeric($key)){
                array2xml($value, $xml);
            }else{
                array2xml($value, $xml->addChild($key));
            }
        }else{
            $xml->addChild($key, $value);
        }
    };
    array_walk($array, $addChild);
    return $xml->asXML();
}
そしたら添字配列と連想配列で微妙に処理を変えないとタグ名が意図した通りにならなくて
php - How to convert array to SimpleXML - Stack Overflow を見ると
再起処理で変換していかなくてはならなくて、 array2xml なんかも作ってしまったり。
実行結果は以下のような感じ。
<?php
$json = '{"hashtags":[{"indices":[32,36],"text":"lol"}]}'; // https://dev.twitter.com/docs/platform-objects/entities
$xml = json2xml('response', $json); 
/*
<?xml version="1.0"?>
<response><hashtags><indices><0>32</0><1>36</1></indices><text>lol</text></hashtags></response>
*/
添字配列のところが数値タグになっていて XML 的にアレ。
以下の xml2json で再変換したらエラーで死ぬ。
<?php
function xml2json($xmlString){
    $xml = simplexml_load_string($xmlString);
    $json = json_decode(json_encode($xml), true);
    return $json;
}
やってみると意外と考えなきゃいけないことが多いというわけ。
自分で変換とか茨の道過ぎてオススメできない。
先人がPHPでJSONとArrayとXMLを一発で相互変換するツール - JAX.php [ゼロと無限の間に]
作成しているのでありがたく使わせていただこう。
しかし、少しだけバグがあって JSON に XML でエスケープすべき文字が含まれている場合、
エスケープしていないことによるエラーが出る。
_toXmlString 関数で $s = $data; の代入している際にデータをエスケープしていないので
_xmlEscape でエスケープした $s = $this->_xmlEscape($data); に書き換えてから使おう。
この便利な JAXクラスで json2xml すると上でちゃんと変換できなかった JSON も
以下のように正しい XML が返ってくる。
<?xml version="1.0"?>
<response><hashtags><hashtags><indices><indices>32</indices><indices>36</indices></indices><text>lol</text></hashtags></hashtags></response>
先人様様である。感謝して使おう。
2013/02/24

PHP5.3の無名関数がcreate_function関数より便利!

PHPで配列から特定の値を除去する関数を作っていたのだけど
create_functionに値を渡して判定してarray_filterで除去するのが良いかと思った。
が、create_functionに値を渡すことができなかった…!

参考
無名関数(クロージャ) « PHP6.jp
無名関数/クロージャ基礎 | PHPサンプル実験室
Tricorn Labs » PHP 5.3を使ってみました。の機能1. クロージャ(無名関数)

PHP: 無名関数 - Manualでも混同されているけど無名関数とクロージャは別物なので注意。

ということで作った関数がこちら。
function array_remove_all($array, $value){
    $callback = function($v) use($value){
        return $v !== $value;
    };
    return array_filter($array, $callback);
}
use を使うことで外からの変数を無名関数に渡すことができる。
array_filterでコールバック関数にcreate_functionを指定すると
変数と処理を文字列で記述しなくてはならなかったので IDEの支援なしで複雑なものを書くのは厳しかった。
せいぜい簡単なワンライナーで書ける処理だけだったので無名関数でPHPのコードとして書けるのは嬉しい。
今度から無名関数を使っていきたい。
2013/02/12

[SublimeText2]PHPDoc形式のコメントを補完してくれるPhpDoc

SublimeText/PhpDoc · GitHub
Docコメント/**を入力すると改行した時に次の行に * が補完される。
PHPDocのタグ(Tags - PHPDoc - Wikipedia, the free encyclopedia)の補完も
サポートしているようで@を入力してctrl + spaceを押すと
アノテーションの一覧が出るのでtabで確定すると色々補完される。
IDE でもここまでの補完はしてくれないかも?PHPを使う人は入れておくと便利。
2013/02/10

[競技プログラミング][PHP][AtCoder]アキレスと亀

B - アキレスと亀

時間制限 : 2sec / スタック制限 : 16MB / メモリ制限 : 64MB

問題文

高橋君は、カメが大好きです。高橋君は L メートル先にカメを見つけたので、
このカメを追いかけて、捕まえようと思いました。
ですが、カメは高橋君が苦手です。
カメは、高橋君から追いかけられ始めた瞬間、高橋君と反対の方向に逃げていきます。
高橋君の追いかける速度は秒間 va メートルで、カメの逃げる早さは秒間 vb メートルです。

ここで高橋君はふと疑問に思いました。
高橋君が、今カメのいる場所までたどり着いた時、カメはさらに少し先に進んでいます。
そのカメがいる場所まで高橋君がたどり着くと、カメはその少し先にまた進んでいます。
これでは何度繰り返しても永遠にカメに追いつかないのではないでしょうか。

高橋君は、この事実を調査するため、
この動作を N 回繰り返した時に、カメと高橋君の距離がどれだけ縮まっているかを調べたいです。
高橋君とカメの距離を出力するプログラムを作成してください。
なお、高橋君はカメがいるところまでまっすぐ最短距離を進みます。
また、動作を開始した時点でカメがいたところまで高橋君が移動することを 1 回の移動として数えるので、
それぞれの移動にかかる時間が異なることに気をつけてください。

入力

入力は以下の形式で標準入力から与えられる。
N va vb L
1 行目に、高橋君の移動回数を表す整数 N(1≦N≦100) 、高橋君の秒間移動距離を表す整数 va(1≦va≦100)、
カメの秒間移動距離を表す整数 vb(1≦vb<va)、高橋君とカメの最初の距離を表す整数 L(1≦L≦100) が、
空白区切りで与えられる。

出力

カメのいる場所に高橋君が移動する動作を N 回行った後の、高橋君とカメの距離を出力せよ。
出力は標準出力におこない、末尾には改行をいれること。
出力は絶対誤差あるいは相対誤差の少なくとも片方が 10−6 以下であれば許容される。

出典

B: アキレスと亀 - AtCoder Regular Contest #012 | AtCoder

回答

AtCoder/arc012_2.php at master · wada811/AtCoder · GitHub
入力例1でよく考えれば簡単。

[競技プログラミング][PHP][AtCoder]週末

A - 週末

時間制限 : 2sec / スタック制限 : 16MB / メモリ制限 : 64MB

問題文

高橋君は、週末が大好きです。
高橋君は英語のデジタルカレンダーを使っているのですが、高橋君は英語の曜日を読むことができません。
カレンダーに表示されている曜日が与えられるので、
あと何日で週末かを計算するプログラムを作成してください。

入力

入力は以下の形式で標準入力から与えられる。
day
1 行目に day が与えられる。
day は曜日を表す文字列で Sunday(日曜日)、Monday(月曜日)、Tuesday(火曜日)、
Wednesday(水曜日)、Thursday(木曜日)、Friday(金曜日)、Saturday(土曜日) のいずれかである。

出力

週末までの日数を出力せよ。なお、週末とは、土曜日および日曜日のことを表す。
もし、すでに週末であれば、 0 と出力すること。
出力は標準出力におこない、末尾には改行をいれること。

出典

A: 週末 - AtCoder Regular Contest #012 | AtCoder

回答

AtCoder/arc012_1.php at master · wada811/AtCoder · GitHub
結果を配列を持っていて出力するだけ。
2013/01/23

SublimeText2でPHPファイルをビルド&Build Systemを使いこなす

SublimeText2でPHPを実行できると何かと便利だと思うので設定する。
PHPはインストール済みとする。
MacならPHPがインストールされていると思うので大丈夫だと思う。

参考: Build Systems — Sublime Text Unofficial Documentation

設定

Sublime Text 2 >Tools > Build System > New Build System... から
設定ファイルを新規作成して以下のJSONをコピペすると良い。
{
    "cmd": ["php", "$file"],
    "selector": "source.php"
}
$file が現在のファイルを指し示す。
selector で source.php と書いておくとPHPファイルで Tools > Build System > Automatic の際に
自動的にPHPと認識してビルドしてくれる。

ビルド

PHPファイルを開いて command + B でビルドできる。

標準入力の処理があるPHPファイルを実行する

競技プログラミングのテストケースをファイルとして持っておき、
それを実行したいPHPファイルにパイプで渡してCLI(Command Line Interface)で実行すれば
コードを書き換えることなく競技プログラミング用のテストが出来る。
{
    "cmd": ["cat $file_path/test.txt | php $file | diff - $file_path/answer.txt -u"],
    "selector": "source.php",
    "shell": true
}
$file_path は実行したファイルのディレクトリ。コマンドが急に俺のスキルを越えていったので解説。
  1. cat $file_path/test.txtでテストケースを標準出力に流しこむ
  2. パイプで標準入力として受け取りphp $fileで実行
  3. 結果の標準出力をパイプで受け取り、diff - $file_path/answer.txt -uで正誤チェック
標準入力とファイルの diff を取る場合は - を指定するらしい。オプションで diff 表示形式変更可能。
参考: UNIXの部屋 コマンド検索:diff (*BSD/Linux)

上のようにコマンドを区切っていたらどうにもエラーになるので
"shell": true にしてコマンドを全部いっぺんに渡している。

答えまで準備するのは面倒な場合は以下のように途中で渡すのをやめれば良い。
{
    "cmd": ["cat $file_path/test.txt | php $file"],
    "selector": "source.php",
    "shell": true
}
これでシームレスにコーディングと実行ができ、快適な競技プログラミングができるでしょう!

Build Systemを使いこなす

{
    "selector": "source.php",
    "variants": [
        {
            "cmd": ["php $file"],
            "name": "php build",
            "shell": true
        },
        {
            "cmd": ["cat $file_path/test.txt | php $file"],
            "name": "php run test case",
            "shell": true
        },
        {
            "cmd": ["cat $file_path/test.txt | php $file | diff - $file_path/answer.txt -u"],
            "name": "php check answer",
            "shell": true
        }
    ]
}
上で書いたコマンドを使い分けたい場合は1ファイルに"variants"に配列で指定すれば良い。
ここでは1階層目に"cmd"を記述しなかったので command + B を押しても何も起こらない。
コマンドパレットで Build: php build とか候補が出るようになるのでこちらで使い分けると良い。
2013/01/20

[競技プログラミング][PHP][AtCoder]鉛筆リサイクルの新技術

A - 鉛筆リサイクルの新技術

時間制限 : 2sec / スタック制限 : 64MB / メモリ制限 : 256MB

問題文

世界的大手鉛筆会社のファイバーカステラ社が、
小さくなって使えなくなってしまった鉛筆を再利用する画期的な新技術を発明した。
この技術は小さくなった鉛筆 m 本から新しい鉛筆を n 本 (m > n) 作り出すものである。
ファイバーカステラ社が N 本の鉛筆を製造・販売し、その全てが使用されて回収され、
回収された使えなくなった鉛筆から新しい鉛筆を作る。
これらを販売し、やはり全てが使用後回収されて新たな鉛筆の原料となる。
これを繰り返した結果として、
ファイバーカステラ社が総計何本の鉛筆を販売できるか計算するプログラムを作成せよ。
再利用する際に、回収されたにもかかわらず新しい鉛筆の原料とされなかった鉛筆を保持しておき、
任意のタイミングで回収した鉛筆に加えても良い。
販売できる本数には、はじめの N 本も忘れずに加えること。
また、 N > m とし、m と n が互いに素であるとする。

入力

入力は以下の形式で標準入力から与えられる。 自然数 m 、 n 、 N がこの順に半角空白区切りで入力される。
m n N
1 行目には整数 m 、 n 、N が与えられる。
m は小さくなって使えなくなってしまった鉛筆の数である。
n はファイバーカステラ社が作り出す新しい鉛筆の本数である。
N はファイバーカステラ社が最初に販売する鉛筆の本数である。
(1 ≦ n < m < N ≦ 1,000) であり、m と n が互いに素であることは保証されている。

出力

ファイバーカステラ社が販売する鉛筆の総数を標準出力に 1 行で出力すること。
この数には使い終わった後に再度製造された鉛筆も含まれる。
また、出力の最後には改行をいれること。

出典

A: 鉛筆リサイクルの新技術 - AtCoder Regular Contest #011 | AtCoder

回答

AtCoder/arc011_1.php at master · wada811/AtCoder · GitHub
<?php
fscanf(STDIN, "%d %d %d", $consume, $create, $number);
$total = $number;
$rest = 0;
while($number >= $consume){
    $rest = $number % $consume;
    $number = (int)($number/ $consume) * $create;
    $total += $number;
    $number += $rest;
}
println($total);
function println($var, $line = null){
    if(is_null($line)){
        echo $var . PHP_EOL;
    }else{
        echo $line . ':' . $var . PHP_EOL;
    }
}
?>
A問題くらい飲みながらでも解けるだろーとか思ってたら
いつものA問題よりちょっと難しくてヤベってなった。
シャキッとして出力例2の解説を読みつつ組んだ。
途中で競技プログラミング用に結果出力とデバッグ出力を兼ねた関数を作り始めて、
SublimeText2 のスニペット登録してたら時間終わってた。
2013/01/19

[PHP]DateTimeオブジェクトをforeach文で回せるDatePeriodクラス

[競技プログラミング][PHP][AtCoder]超大型連休 | DevAchieveDateTimeオブジェクトを
for 文で日付を回そうと思ったのだけど、以下のようにとんでもないことになってしまった。
<?php
for(
    $date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo')),
    $end = DateTime::createFromFormat('Y/m/d', '2012/12/31', new DateTimeZone('Asia/Tokyo'));
    $date->diff($end)->format('%R') === '+';
    $date->add(new DateInterval('P1D'))
){
    // 2012/01/01 から 2012/12/31 まで1日毎に日付を回すfot文
}
これはいくらなんでも微妙すぎだと思っていたんだけど、その後DatePeriodクラスというものを知った。
期間を設定してやれば foreach 文で日付を回せるらしい。百聞は一見に如かず。
<?php
$start = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$end = clone $start; // clone でオブジェクトを複製
$end->add(new DateInterval('P1Y'));
$period = new DatePeriod($start, new DateInterval('P1D'), $end);
foreach($period as $date){
    // 2012/01/01 から 2012/12/31 まで1日毎に日付を回すfoteach文
}
とってもスッキリして読みやすくなりました。
しかし地味にバグがあって、終了日の時刻が"00:00:00"だと終了日がイテレーションに含まれないらしいです。
バグレポートはされていてオプションで対応するみたいですが、まだ使えないみたいです。
参考: DatePeriod::INCLUDE_END_DATE - ゆっくり*ゆっくり

そのため上記の例では1日毎に日付を回すので+1の 2013/01/01 ということで 1年足しました。
さりげなく DateTime::createFromFormat では作らずに clone でオブジェクトを複製しました。
PHP: オブジェクトのクローン作成 - Manual
細かい説明は抜きに下のコードをhttp://codepad.viper-7.com/で試してみてください。
<?php
$date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$copy = $date;
$copy->add(new DateInterval('P1Y'));
echo $date->format('Y/m/d'); // 2013/01/01
echo $copy->format('Y/m/d'); // 2013/01/01

$date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo'));
$copy = clone $date;
$copy->add(new DateInterval('P1Y'));
echo $date->format('Y/m/d'); // 2012/01/01
echo $copy->format('Y/m/d'); // 2013/01/01
clone を使わないと $copy の変更で $date も変更されてしまっていたでしょう?
つまりそういうことです。

ということで、ちょっと脱線してしまったけど DatePeriod クラスの使い方はこれでおしまい。
綺麗にforeach 文が書けてチョーイイネ!サイコー!
2013/01/10

[競技プログラミング][PHP][AtCoder]超大型連休

B - 超大型連休

時間制限 : 2sec / スタック制限 : 10MB / メモリ制限 : 64MB

問題文

2011 年、AtCoder 国の高橋首相はある重大な決定を行った。
その決定とは...法改正である。国民の祝日に関する法律を変更し、休日を増やすことにした!!
国民の創造性を尊重するその決定が、霞が関を魔境へと変えたッ!

あなたは霞が関の国土交通省に勤務する職員であり、この法改正により上司から新たな任務を与えられた。
その任務とは、2012 年の「連休の最大日数」を計算することである。
連休の大きさを事前に計算することで国民の行動を予想し、
高速道路の部分的な値下げを行い、経済を活性させるためだ。
したがって、あなたに失敗することは許されない。国民の行動を正確に予想できなくなるからだ。

以下に、「連休」の定義と諸注意を記す。
AtCoder 国が使用する暦はグレゴリオ暦に従う。
「連休」とは、「休日」が連続して続くことである。
「土曜日」「日曜日」「祝日」「振替休日」が「休日」に相当する。
「祝日」が他の休日と同じ日に指定されていた場合、「振替休日」を必ず設置する。
「振替休日」は祝日の時系列順に決定していき、その祝日以降最も近い平日(休日を除いた日)となる。
2012 年 1 月 1 日は日曜日である。

入力

入力は以下の形式で標準入力から与えられる。
N
m1/d1
m2/d2
:
:
mn/dn
1 行目には祝日の日数を表す整数 N が与えられ、 0≦N≦366 を満たす。
2 行目から N+1 行目までの N 行で祝日の日付が与えられる。
mi は i(1≦i≦366) 番目に与えられる祝日の月で、 1≦mi≦12 を満たす。
di は i(1≦i≦366) 番目に与えられる祝日の日で、
mi=2 のとき、 1≦di≦29 を満たす。
mi=(4,6,9,11) のとき、 1≦di≦30 を満たす。
mi=(1,3,5,7,8,10,12) のとき、 1≦di≦31 を満たす。
mi と di はともに整数である。
mi と di は必ず/で区切られて与えられる。
祝日は時系列順に与えられるとは限らないことに注意せよ。
ただし、同じ日付が複数与えられないことは保証されている。

出力

法改正後における 2012 年の連休の最大日数を出力せよ。
出力は標準出力におこない、末尾には改行をいれること。

出典

B: 超大型連休 - AtCoder Regular Contest #010 | AtCoder

回答

Submission #65857 - AtCoder Regular Contest #010 | AtCoder
AtCoder/arc010_2.php at master · wada811/AtCoder · GitHub
<?php
// 土日の設定
$holidays = array();
for(
    $date = DateTime::createFromFormat('Y/m/d', '2012/01/01', new DateTimeZone('Asia/Tokyo')),
    $end = DateTime::createFromFormat('Y/m/d', '2012/12/31', new DateTimeZone('Asia/Tokyo'));
    $date->diff($end)->format('%R') === '+';
    $date->add(new DateInterval('P1D'))
){
    if($date->format('w') === '0' || $date->format('w') === '6'){
        $holidays[$date->format('Y/m/d')] = true;
    }else{
        $holidays[$date->format('Y/m/d')] = false;
    }
}
// 祝日の取得・設定
fscanf(STDIN, "%d", $n);
for($i = 0; $i < $n; $i++){
    fscanf(STDIN, "%d/%d", $month, $day);
    $date = DateTime::createFromFormat('Y/m/d', "2012/{$month}/{$day}", new DateTimeZone('Asia/Tokyo'));
    // 振替休日の設定
    while($holidays[$date->format('Y/m/d')]){
        $date->add(new DateInterval('P1D'));
    }
    $holidays[$date->format('Y/m/d')] = true;
}
// 最大連休日数の取得
$max = 0;
$count = 0;
foreach($holidays as $holiday){
    if($holiday){
        $count++;
    }else{
        $count = 0;
    }
    $max = $count > $max ? $count : $max;
}
echo $max . PHP_EOL;
?>
通らなかったコード。
AtCoderの回答を見るとわかるのだが一部の問題は何故か通らない。
サンプルなどは通ってしまって何が行けないのか全くわからなくなってしまった。
それにしてもfor文の初期化部でDateTime型の変数を宣言するというのはまぁ酷い。
そしてこれは解決しなそうな感じがする。
誰かわかったらコメントお願いします。
2013/01/09

[競技プログラミング][PHP][AtCoder]名刺交換

A - 名刺交換

時間制限 : 2sec / スタック制限 : 10MB / メモリ制限 : 64MB

問題文

青木君は就職活動をおこなっている大学生で、名刺を N 枚持っています。
これから M 日間の就職活動を予定しており、 i 日目には名刺を ci 枚消費することがわかっています。
就職活動を行うにあたり、名刺が足りなくなると非常に困ります。
そこで、青木君はそれぞれの日のはじめに名刺の所持枚数を確認し、
A 枚以下ならば B 枚名刺を補充することにしました。
B 枚補充しても A 枚以下にしかならないような場合でも、それ以上の補充はできません。

最初から持っている N 枚とこのような補充で、就職活動の最後の日まで乗りきれるかどうか判定してください。
もし、足りなくなる場合は、その日付を青木君に教えてあげてください。

入力

入力は以下の形式で標準入力から与えられる。
N M A B
c1
c2
:
:
cM
1 行目に N , M , A , B が半角スペースで区切られて与えられる。
N は持っている名刺の枚数で 1≦N≦1,000 を満たす。
M は就職活動の日数で 0≦M≦100 を満たす。
A は名刺を補充するタイミングの枚数を示す数で 0≦A≦1,000 を満たす。
B は補充する名刺の枚数で 0≦B≦1,000 を満たす。
N , M , A , B は全て整数である。
2 行目から M+1 行目までの M 行間で、名刺を配る枚数がそれぞれ与えられる。
ci は i(1≦i≦M) 日目に配る名刺の枚数を表す整数で 0≦ci≦1,000 を満たす。

出力

就職活動の最後の日まで乗り切ることができればcompleteと出力すること。
もし、名刺を配りきってしまった場合は、足りなくなった日の日付を出力すること。
出力は標準出力におこない、末尾には改行をいれること。

出典

A: 名刺交換 - AtCoder Regular Contest #010 | AtCoder

回答

AtCoder/arc010_1.php at master · wada811/AtCoder · GitHub
<?php
fscanf(STDIN, "%d %d %d %d", $num, $days, $limit, $purchase);
for($i = 1; $i <= $days; $i++){
    $distribute = trim(fgets(STDIN));
    if($limit >= $num){
        $num += $purchase;
    }
    if($num >= $distribute){
        $num -= $distribute;
    }else{
        echo $i . PHP_EOL;
        exit();
    }
}
echo 'complete' . PHP_EOL;
?>
補充チェックして配布チェックして出力するだけの簡単な問題。
2012/12/30

2012年に @wada811 が読んだ技術書まとめ

2012年も終わりに近づいているので@wada811が読んだ技術書を振り返ってみようかと思います。

2012/01/14 [書評]Web標準の教科書―XHTMLとCSSでつくる“正しい”Webサイト | DevAchieve

趣味でやっていたHTML/CSSを業務レベルに上げるために基礎からおさらいしました。
最近HTML5が仕様策定完了したのでHTML5/CSS3バージョンで出して欲しいですね。
CSS3まだまだぐちゃぐちゃしてて無理かもしれませんが…。(linear-gradientとか使ってみたけど酷い…)

2012/01/15 [書評]WEB+DB PRESS 総集編 [Vol.1~60] | DevAchieve

ちょっとずつ読んでいって最近全部読み終わりました。
流石に古くかったり、やってない言語などは参考にはならなかったけど
普遍的な内容は結構勉強になった。効率が良いかと問われると微妙だと言わざるをえないけど…。
最近の号はやはり得るものはそこそこ多いので普通に買うのはありかもしれません。

2012/01/15 [書評]良いコードを書く技術 読みやすく保守しやすいプログラミング作法 | DevAchieve

ある程度良いコードを書けていればリーダブルコードを読んだ方が得るものもあるんじゃないかと思います。
原典にあたる重要性を教えてくれるのと参考文献を紹介してくれるのでそこはいいと思います。

2012/01/15 [書評]コンピュータはなぜ動くのか ~知っておきたいハードウエア&ソフトウエアの基礎知識~ | DevAchieve

2012/01/15 [書評]プログラムはなぜ動くのか 第2版 知っておきたいプログラムの基礎知識 | DevAchieve

2012/01/15 [書評]ネットワークはなぜつながるのか 第2版<br>知っておきたいTCP/IP、LAN、光ファイバの基礎知識 | DevAchieve

なぜ〜なのかシリーズ。レビューを書いたのがこの日というだけでさすがに1日で3冊読めるものじゃない。
基本情報技術者を取るのと、ソフトウェア会社でエンジニアになるにあたって
非情報学科の大学生だった俺がとりあえず基礎知識を仕入れておかねばと読んだ本です。
まぁそこまで無駄ではなかったと思いたいです。ネットワークはちんぷんかんぷんのままだったけど…。

2012/03/31 [書評]できる逆引き Excel VBAを極める 勝ちワザ700 2010/2007/2003/2002対応 (「できる逆引き」シリーズ) | DevAchieve

内定先アルバイトでExcel VBAをやるのに読んだ本です。なかったら結構きつかったかも。

2012/03/30 [書評]よくわかるPHPの教科書 | DevAchieve

レビュー日だから日付が前後してますが、読んだのはこっちが後です。
プログラミング言語はもういくつか使っていたのでPHPでどんな事するのかの概要を知るために買ってみました。
プログラミング経験があればプロになるためのWeb技術入門
プロになるための PHPプログラミング入門で十分な内容だったかも。

2012/04/15 基本情報技術者試験を受験するまでに使った参考書3冊 | DevAchieve

基本情報技術者試験に受かったので使った参考書のレビューをしました。
改訂新版 基本情報技術者 らくらく突破 表計算はオススメだと思います。
後は過去問やっておけばなんとかなるような気がしますね。
そのくらい過去問でやってたことが形を変えて出てきます。

2012/04/21 [書評]プログラミングの宝箱 アルゴリズムとデータ構造 第2版 | DevAchieve

基本情報技術者試験に受かったはいいものの、情報科出身ではないのでアルゴリズムとデータ構造について
まともに学んだことがなくてイマイチな得点だったので勉強のために読んでみました。
アルゴリズムとデータ構造について学んだことがない人は読んでおくといいと思います。

2012/04/25 [Twitter][API]改訂で2012年5月14日までに開発者がするべきこと | DevAchieve

Twitter API ポケットリファレンスの事もちょっと書いている記事なので。
TwitterのAPIを叩くなら結構必須だと思います。かなり役に立ちました。
TwitterのAPIが1.1にバージョンアップしますが読んでおくと1.1のこともわかりやすいかもしれません。

2012/05/12 [書評]よくわかるiPhoneアプリ開発の教科書【iOS 5&Xcode 4.2対応版】 | DevAchieve

iPhoneアプリを作ることになってXcodeやInterface Builderの使い方がぜんぜんわからなかったので
そのへんを解説している一番簡単そうな本を読んでみました。
iPhoneを使っていなかったのでどんな機能、どんなパーツがあるかを知ることができたのは良かったと思います。

2012/05/09 [書評]詳解 Objective-C 2.0 第3版 | DevAchieve

iPhoneアプリを作るにはObjective-Cで書くのだけれど結構他の言語とは異なっていたので
よく理解するために読んでみました。使いこなせてはいないけど理解することはできました。
もっとObjective-CをマスターしてiPhone/Android両方できますと胸を張って言えるようになりたいです。

2012/06/02 [書評]知る、読む、使う! オープンソースライセンス | DevAchieve

昔、図書館で読んだソフトウェアライセンスの話が電子書籍になって出版されていたので読んでみました。
ソフトウェアライセンスも大事な話ですから一回は学んでおいたほうがいいですね。

2012/06/03 [書評]「プロになるためのWeb技術入門」なぜ,あなたはWebシステムを開発できないのか | DevAchieve

アプリばっかり作っていてWebとかPHPとかのあたりが全然わかってなかったのでコレで勉強しました。
コレはもっと早く読んでおきたかったというくらいの基礎知識でした。

2012/06/09 [書評]iPhoneプログラミングUIKit詳解リファレンス | DevAchieve

話はまたiPhoneに戻って今度はUIKitを解説した本です。
コレがなかったらリファレンスを読むしかなかったかもしれません。
この本はiOS4ベースで今使い物になるか不安になるかもしれませんが、
iOS5/6で追加はされましたが既存のものはそんなに変わっていないと思うので大丈夫かと思います。

2012/06/30 [書評]iPhoneアプリ設計の極意 ―思わずタップしたくなるアプリのデザイン | DevAchieve

iPhoneに限らずAndroidアプリのデザイン設計をするのにも役立つ本です。
こういうの本の内容を理解していない人がアプリって
すごく微妙だったりするので使いやすいアプリを作りたいなら必読です!

2012/07/23 [書評]達人プログラマー システム開発の職人から名匠への道 | DevAchieve

名著だと言われていたので読んでました。
実際のプログラミング、システム開発に近い所でのプログラマがどうあるべきかを説いている本です。
プログラマとしてどうあるべきか不安がある人は読んでおくといいかもしれません。

2012/07/08 日経ソフトウエアをまとめ買いしてやったぜぇ~ワイルドだろぅ? | DevAchieve

2012/08/09 [書評]日経ソフトウエア 2012年 01月号 | DevAchieve

2012/08/12 [書評]日経ソフトウエア 2012年 02月号 | DevAchieve

2012/08/20 [書評]日経ソフトウエア 2012年 03月号 | DevAchieve

2012/09/03 [書評]日経ソフトウエア 2012年 04月号 | DevAchieve

2012/09/09 [書評]日経ソフトウエア 2012年 05月号 | DevAchieve

2012/09/10 [書評]日経ソフトウエア 2012年 06月号 | DevAchieve

2012/09/16 [書評]日経ソフトウエア 2012年 07月号 | DevAchieve

2012/09/18 [書評]日経ソフトウエア 2012年 08月号 | DevAchieve

2012/09/25 [書評]日経ソフトウエア 2012年 09月号 | DevAchieve

忙しい中で効率的に最新情報を手に入れるには雑誌がいいのでは?
ということで日経ソフトウエアを購読してみました。
最初は興味が有ることが多くて良かったのだけど、
終盤は読みたい記事も少なくなりコスパ悪かったので購読やめちゃました。
他にも読みたい本あるし必要な時にちゃんとした本で情報を仕入れた方が確実な知識が手に入ると思いました。

2012/08/24 [書評]必ず結果が出るブログ運営テクニック100 プロ・ブロガーが教える“俺メディア”の極意 | DevAchieve

ネット上でよくブログ論、ブログテクニックの記事を読んでいたので目新しいことはなかったのですが、
逆にそういうのを読んでいない人は本書を読んでおくと良いかもしれません。ブログで稼ぎたい人向です。
僕はセルフブランディングと備忘録が出来ればいいのでブログで直接お金を稼ぎたいわけではないので
この本を読んでどうこうしようというのはありませんでした。技術ブログで記事量産とか無理ですし…。

2012/08/26 [書評]リーダブルコード The Art of Readable Code | DevAchieve

コードは理解しやすくなければならないという考えのもと、
理解するための時間を最短にするための実践的なテクニック集を解説している本です。
変数名には説明的な名前をつけるべきとか
どういうコメントを書くべきかとかを解説しているので読んでマスターしてください!

2012/09/04 [書評]ウェブデザインのつくり方、インターフェイスデザインの考え方。 | DevAchieve

デザインというから敬遠されがちですが、スタイリングとかデコレーション的なアートな感じではなく
設計なのでWeb系の開発者は読んでおくと良いと思います。(プログラマーもデザインを理解しておくべき論)

2012/09/11 [書評]マンガでわかるデータベース | DevAchieve

データベースの概念、基礎知識をマンガで解説した本です。
なんとなくSQLを書いたことがある人がしっかりデータベースを勉強しようと思ったら
この本から学んでいくのが良いと思います。情報処理技術者試験のデータベースあたりの勉強にも使えます。
ちなみにごっちゃになりやすい第二正規形と第三正規形の違いは連鎖があるかどうかです。(詳しくは記事へ)

2012/09/26 [書評]SQL ゼロからはじめるデータベース操作 | DevAchieve

標準SQLでRDBMSによらないSQLが書けるようになる本です。
コレ一冊でOracle, SQL Server, DB2, PostgreSQL, MySQLで動くSQLが書けるようになります。
この本をマスターしたら後のSQLは応用するだけで組めるようになるので是非マスターしましょう!

2012/10/02 [書評]プログラミングコンテストチャレンジブック [第2版] | DevAchieve

競技プログラミングをたまにやっているので勉強用にと読んでみました。
難しいこと書いてあって睡眠導入剤として優秀すぎたので読むのは諦めました…。

2012/11/03 [書評]Android SDK開発のレシピ | DevAchieve

第1版と第2版ともに読んだ本です。第2版はカメラ周りが実装にかなり役に立ちました。
その他のサンプルも役に立っているので手元において開発をしていました。

1年で書籍25冊と雑誌9冊を読んだ!

正確には書評を書いていない書籍とかあったり、つまみ読みで通して読んでない本とかあったり、
途中で読むのをやめた書籍があったりで書籍と雑誌あわせて40冊くらいだと思います。
今年読んだけど書評を書いてない本は以下のとおり。
Webを支える技術情熱プログラマー入門gitObjective-C逆引きハンドブック
PHP 逆引きレシピ平成25年度【春期】【秋期】 応用情報技術者 合格教本
12月はレビューを一冊も書いていないのですが、
これは応用情報技術者の勉強を始めたので他の本は全然読んでいないからです。

これだけ本を読んで身につけたのがObjective-CとPHPとSQLくらいでしょうか。
まだまだユニットテストとかTDDとかJenkinsでCIとかの高位な部分や、
JavaScriptやRubyなどの他の言語などをもっと学びたいです。
もっと色々なことを身につけられる機会、環境、時間が欲しいです。速さが足りない!
2012/12/28

Webプログラマーになる前に学ぶべきこと、知っておきたかったこと

新米Webプログラマー@wada811がプログラマーになる前に学ぶべきこと、知っておきたかったことを
これからWebプログラマーになる人に向けてまとめてみました。

心構え

プログラマとして生きるための心構えとして情熱プログラマーを読んでおくのはオススメだと思います。
研修先から帰る新幹線の中で読み終わる程度の分量だったのでぜひ読んでおいて欲しいです。
さらっと読めてしまうけど学ぶことが多いので何度も読んでしまうお気に入りの本です。
コードについてはリーダブルコードを読みましょう。[書評]リーダブルコード The Art of Readable Code
志高く、良いコードを書こうという気概があれば技術者として伸びていけると思っています。こちらもぜひ!
あと、ブログを書こう!私がプログラマとしてブログを書く事をオススメする8つの理由 | DevAchieve

スキル

HTML/CSS

Web系なら基本中の基本です。
HTML5/CSS3とかアツいですし、HTML5の仕様策定も完了したのですが
ユーザーが使用するブラウザが対応していないことも多いのでまずは基本のHTML/CSSを抑えましょう。
ということで、Web標準の教科書―XHTMLとCSSでつくる“正しい”Webサイトはオススメです。
結構習うより慣れろ的なところがあるので既存のページをいじくりまわすだけでも勉強になるかと思います。

プログラミング

あんまり好きじゃないですけど最初はやっぱりC言語からなのかなぁという気がします。
最初はCの絵本新版 明解C言語 入門編なんかを半年くらい挫折を繰り返したり
プログラミングの宝箱 アルゴリズムとデータ構造 第2版でちゃんとアルゴリズムを学んだり
AIZU ONLINE JUDGEのVolume100のものを一通りやったりとある程度できるようになるまで頑張りました。
今は3分動画でマスターする初心者向けプログラミング学習サイト - ドットインストールがあって
通信添削もしてくれるそうですからプログラミング学習については困らなそうですね。

JavaScript/jQuery

軽く知っていると実装に幅が出て捗ります。
ガッツリ習得してなくても何とかなっているけど基本くらいは知っておきたいですね。
プロになるためのJavaScript入門が最近出て、書評とか見る限り良さげだったので読むと良さそうです。
ガチでやるならJavaScript 第6版JavaScriptリファレンス 第6版は役立ちそうです。

正規表現

もしかしたらJavaScriptより先にこっちを習得しておくべきかもしれません。
めんどくさいテキスト処理などを一発で片付けることができたりとかなり捗ります。
習うより慣れろで覚えてしまったのでどの書籍が参考になるとかは挙げられないのですが頑張って覚えましょう!

PHP

全く知らないのでは実務で困ることが多いと思うので概要をプロになるためのWeb技術入門
プロになるための PHPプログラミング入門で抑えておけばなんとかなるはずです。
プログラミング自体が初めてでなければめっちゃ便利なC言語の上位版みたいな感じなので大丈夫だと思います。
あとはPHP: PHP マニュアル - Manualが詳しいので関数とかをマニュアル見れば使いこなせるかと思います。
フレームワークはCakePHPがよく使われているますが、入社時に習得してなくても
CakePHP Cookbookというドキュメントがあるので入社後に習得していけば大丈夫です。

SQL

PHPに合わせてSQLも学ぶ必要がありますが、入社時にはデータベースの概要がわかっていれば十分でしょう。
マンガでわかるデータベースがわかりやすかったです。[書評]マンガでわかるデータベース | DevAchieve
SQL ゼロからはじめるデータベース操作も読んでガリガリSQL書けるようになっていると凄いです。
[書評]SQL ゼロからはじめるデータベース操作 | DevAchieve

バージョン管理システム

SubversionかGitのどちらかを知っていて軽くでも使ってみていると実務で使う際に理解が早いかと思います。
個人で使うならGitだと思うので入門gitとか読んで実際に使ってみると良いんじゃないでしょうか?
GitならGitHubでソースコードを公開できるので何か作って公開しておくとヘッドハンティングとか来るかも?

資格

資格を持っていなくても基本情報レベルの知識はあった方が良いんじゃないかと思います。
僕は基本情報は入社直後の春に取りました。基本情報技術者試験を受験するまでに使った参考書3冊
基本情報が取れればちょっと勉強すれば応用情報も取れると最近聞いて来春受験に向けて勉強してます。
資格を取ったら祝い金とか貰えるとこなら頑張るのもいいと思います。

おわりに

こう見るとWebプログラマーに求められる技術は結構幅広いですね。
広く深い知識を習得していかなければなりませんので日々勉強が必要です。
万能選手でなければならないけどもスペシャリストでもなくてはならない、
それでいて一番の下手くそでもいたい。(情熱プログラマー)
こんな師匠がいて導いてくれていたらと思い、記事を書いてみました。
僕のWeb系の師匠になってくれる人を探しています!
2012/12/27

[PHP]年末年始に働きたくないPHPerのためのタイマー予約機能の実装

もう仕事納めをして16連休を謳歌している@wada811です。
皆さん年末年始をいかがお過ごしの予定でしょうか?
え?クリスマスや年越し・新年の対応のために仕事しているんです?
そんな仕事はタイマー仕込んでさっさと年末年始をまったり過ごしましょう!

DateTimeクラスやDateInterval::formatを使っていて
PHP5.3以上でないと動かないのでideone.comなどでは動作が確認できないので
codepad.viper-7.comで確認してください。
<?php
/**
 * タイマー処理用判定クラス
 */
class Timer {

   /**
    * タイマー処理: 指定の日時からはtrueを返す
    * @param string $dateTime 'Y-m-d H:i:s'フォーマットな日時
    * @return boolean 判定結果
    */
   public static function fromDateTime($dateTime){
       $today = new DateTime();
       $limitDateTime = DateTime::createFromFormat('Y-m-d H:i:s', $dateTime, new DateTimeZone('Asia/Tokyo'));
       return $today->diff($limitDateTime)->format('%R') === '-';
   }

   /**
    * タイマー処理: 指定の日時まではtrueを返す
    * @param string $dateTime 'Y-m-d H:i:s'フォーマットな日時
    * @return boolean 判定結果
    */
   public static function toDateTime($dateTime){
       $today = new DateTime();
       $limitDateTime = DateTime::createFromFormat('Y-m-d H:i:s', $dateTime, new DateTimeZone('Asia/Tokyo'));
       return $today->diff($limitDateTime)->format('%R') === '+';
   }

}
使い方は簡単!Timerクラスを require するなどしたら後は日時を渡して呼び出すだけ!
if(Timer::toDateTime('2012-12-25 23:59:59')){
    // クリスマスまでの処理
}
if(Timer::fromDateTime('2013-01-01 00:00:00')){
    // 来年からの処理
}
DateTime::diffに渡される日時が同じである場合は'+'を返すので
Timer::toDateTimeは境界を含みますが、Timer::fromDateTimeは境界を含みません。
まぁ1秒だけだから細かいことは気にしなくても大丈夫だと思いますので
普通に切りの良い時間をそれぞれ指定してもいいかと思います。
2012/12/11

[競技プログラミング][PHP][第2回早稲田大学プログラミングコンテスト]団子とうさぎ

A - 団子とうさぎ

時間制限 : 2sec / スタック制限 : 64MB / メモリ制限 : 64MB

古来より,月にはうさぎが住んでおり,餅をついていると言われている.
我々が月を見上げるとき,うさぎ達もまた地球を見ているのである.

問題

今,うさぎたちの目の前には N 段に積まれた団子がある.
今回の積み方では,上から x 段目には,x2 個の団子がある.
これらの団子を M 匹のうさぎたちで平等に分けることを考えよう.
あなたの仕事は,団子を平等に分けた時,いくつ団子が余るかを求めるプログラムを書くことである.

入力

入力は以下の形式で標準入力から与えられる.
N M
団子の段数を表す整数 N(1≦N≦100) とうさぎの数 M(1≦M≦100)が半角スペース区切りで与えられる.

出力

団子を平等に分けた時,余る団子の数を標準出力に 1 行で出力せよ.
なお、最後には改行を出力せよ.

出典

A: 団子とうさぎ - 第2回早稲田大学プログラミングコンテスト | AtCoder

回答

AtCoder/wupc_01.php at master · wada811/AtCoder · GitHub
<?php
fscanf(STDIN, "%d %d", $n, $rabbit);
$count = 0;
for($i = 1; $i <= $n; $i++){
    $count += pow($i, 2);
}
echo $count % $rabbit . PHP_EOL;
?>
$count を求めるもっと上手い方法あったかな?
2012/12/10

[競技プログラミング][PHP][東京大学プログラミングコンテスト2012]2012年12月02日

A - 2012年12月02日

時間制限 : 2sec / スタック制限 : 64MB / メモリ制限 : 256MB

問題

今日は2012年12月02日であって, 月日を構成する数字1202を並べ替えると西暦を表す数字2012になる.
このような日を良い日という事にする. 与えられた日付が良い日かどうかを答えよ.

入力

yyyy/mm/dd という形の文字列が一行で与えられる.
yyyyは,4桁の整数,mmは先頭が0でありうる2桁の整数,ddは先頭が0でありうる2桁の整数である.

yyyyが西暦,mmが月,ddが日を表している.

出力

与えられた日付けが良い日であるならば,yes 良い日でないならば,no を一行に出力せよ.

制約

1000≤yyyy≤9999
与えられる日付は,正しい日付を表している.

出典

A: 2012年12月02日 - 東京大学プログラミングコンテスト2012 | AtCoder

回答

AtCoder/utpc2012_01.php at master · wada811/AtCoder · GitHub
<?php
$date = trim(fgets(STDIN));
$date1 = array($date[0], $date[1], $date[2], $date[3]);
$date2 = array($date[5], $date[6], $date[8], $date[9]);
echo array_count_values($date1) == array_count_values($date2) ? 'yes' : 'no';
echo PHP_EOL;
?>
あんまり良い名前が思いつかなかったから適当な変数名。使い捨てのコード。ひどい。
2012/12/09

[競技プログラミング][PHP][DigitalArts プログラミングコンテスト2012]Password

B - Password

時間制限 : 2sec / スタック制限 : 64MB / メモリ制限 : 64MB

問題文

セキュリティに興味がある高橋君は、デジタルアーツ株式会社に就職したい青年です。
そこで、高橋君は自分が運営するサービスであるAtCoderのセキュリティについて見なおしてみることにしました。

現在AtCoderのシステムでは、パスワードは 1 文字以上 20 文字以下の英小文字のみとしています。
そして、文字列 s に対してハッシュ値( hash(s) )を求める以下の式があり、
パスワードと入力した文字列に対して、それぞれこの式で算出したハッシュ値が一致すると、
入力した文字列は正しいとみなします。

hash(s)=Num(c1)+Num(c2)+…+Num(cN) (ci は文字列 s の i 番目の文字を意味する)

なお、上記の式の関数 Num() とは英小文字を数字に変換する関数で、
Num(a)=1, Num(b)=2, …., Num(z)=26 というように、
a から z に対して順に 1 から 26 までの数字を返します。

高橋君は、このシステムではパスワードと違う文字列でも
簡単にハッシュ値が一致してしまうことに気づきました。
例えば、文字列 abc のハッシュ値は、1+2+3=6 となりますが、
文字列 bbb のハッシュ値も 2+2+2=6 ですし、f も 6 になってしまいます。

高橋君は、現在使っているパスワードに対してどのような文字列が
正しいパスワードとして認識されてしまうか知りたいです。
正しいパスワード以外で条件を満たすものを 1 つ出力しなさい。
条件を満たすものが複数ある場合は、どの文字列を出力しても構いません。
もし条件を満たすパスワードが存在しない場合は NO と出力しなさい。
なお、AtCoderのシステムで入力できるパスワードは 1 文字以上 20 文字以下の英小文字のみなので、
答えとして出力する文字列もその条件をみたします。

入力

入力は以下の形式で標準入力から与えられる。
c1c2‥‥cN
入力には正しいパスワードを表す長さ N(1≦N≦20) の文字列が 1 行で与えられる。
正しいパスワードの i 番目の文字を表す ci は英小文字 (a-z) である。

出力

与えられた正しいパスワードを表す文字列と等しいハッシュ値になる英小文字 1 文字以上 20 文字以下の文字列を、
正しいパスワード以外のいずれか 1 つ出力せよ。
また、そのような文字列が存在しない場合は NO と出力せよ。
なお、出力は 1 行のみとし、最後には改行を出力せよ。

出典

B: Password - DigitalArts プログラミングコンテスト2012 | AtCoder

回答

AtCoder/digitalarts_2.php at master · wada811/AtCoder · GitHub
<?php
$password = trim(fgets(STDIN));
$ascii_a = ord('a') - 1;
$numbars = array();
for($i = 0, $end = strlen($password); $i < $end; $i++){
    $numbars[] = ord($password[$i]) - $ascii_a;
}

$sum = array_sum($numbars);
// special case
if($sum === 1 || $sum === 26 * 20){
    // 'a' or 'zzzzzzzzzzzzzzzzzzzz'
    echo 'NO' . PHP_EOL;
    exit();
}

$answers = array();
if(count($numbars) === 1){
    // 'b' - 'z'
    $answers[0] = $numbars[0] - 1;
    $answers[1] = 1;
}else{
    while($sum){
        $answers[] = min(26, $sum);
        $sum -= min(26, $sum);
    }
}
// ex. 'za' -> 'az'
while($answers == $numbars){
    shuffle($answers);
}

$another_password = '';
for($i = 0, $count = count($answers); $i < $count; $i++){
    $another_password .= chr($answers[$i] + $ascii_a);
}
echo $another_password . PHP_EOL;
?>
2行目でtrimし忘れて意図しない改行コードで答えが合わなくて泣きそうになった。
文字列を配列でアクセスできるので1文字ずつアスキーコード変換して配列に入れる。
答えが NO になる例外を弾き、1文字のパスワードとそうでないもので処理を分ける。
後は28行目に書いたようなやつのためにshuffleして入れ替えておく。コレで確実。
あとはアスキーコードを逆変換して出力!
2012/12/08

[競技プログラミング][PHP][DigitalArts プログラミングコンテスト2012]C-Filter

A - C-Filter

時間制限 : 2sec / スタック制限 : 64MB / メモリ制限 : 64MB

問題文

セキュリティに興味がある高橋君は、デジタルアーツ株式会社に就職したい青年です。
彼は、面接で自分をアピールするために、
得意なプログラミングでフィルタリングソフト「C-Filter」を作ろうと考えています。
「C-Filter」は、与えられた文字列 s に、
あらかじめ登録しておいた「NGワード」と一致する文字列が存在する場合、
その文字列の長さと等しい数の *に置き換えて出力するソフトウェアです。
この文字列を置き換える処理をフィルタリングと呼びます。
「NGワード」とは、半角英小文字と*から構成されます。
*はすべての半角英小文字 1 文字と一致します。
例えば myonmyon は、NGワードmyo*myonと一致します。
ただし、NGワードは単語ごとに適用されるため、myo myon はNGワード myo*myonとは一致しません。
また、NGワードはある単語に対して完全に一致する必要があります。
例えば、abcdeは、NGワードabcやbcd、cdeに一致しません。

文字列 s と、NGワードが与えられるので、C-Filterの出力する文字列を答えてください。

入力

入力は以下の形式で標準入力から与えられる。
s
N
t1
:
:
tN
入力は N+2 行からなる。
1 行目には 1 文字以上 1,000 文字以下の文字列 s が与えられる。
s はフィルタリングする対象の文字列を半角スペースで区切って繋げた文字列である。
2 行目にはNGワードの個数を表す整数 N(0≦N≦50) が与えられる。
3 行目から N+2 行目までNGワードを表す文字列 ti(1≦i≦N)が与えられる。
文字列 ti は半角英小文字と * から構成される。
文字列 ti の長さは 1 文字以上、 20 文字以下である。
文字列 ti に含まれる * は、半角スペースを除くすべての半角英小文字 1 文字をフィルタリングの対象とします。

出力

入力された文字列 s をC-Filterでフィルタリングした結果を 1 行で出力せよ。
なお、最後には改行を出力せよ。

出典

A: C-Filter - DigitalArts プログラミングコンテスト2012 | AtCoder

回答

AtCoder/digitalarts_1.php at master · wada811/AtCoder · GitHub
<?php
$sentence = trim(fgets(STDIN));
fscanf(STDIN, "%d", $n);
$filters = array();
for($i = 0; $i < $n; $i++){
    $filters[] = '/^' . str_replace('*', '.', trim(fgets(STDIN))) . '$/';
}
$words = explode(' ', $sentence);
$censored = preg_replace_callback(
                $filters,
                create_function(
                    '$matches',
                    'return str_repeat("*", strlen($matches[0]));'
                ),
                $words
            );
echo implode(' ', $censored) . PHP_EOL;
?>
fgetsで1行取得。この際にtrimしておかないと思わぬ改行コードでハマる。
6行目で取得しつつ正規表現に入れ込む。 間の空白を置換しないようにするのが面倒なので空白で分割。 preg_replaceのe修飾子が PHP 5.5.0 で非推奨になったので
AtCoder の PHP のバージョンは 5.3.6だけど使わないでおく。
代わりにpreg_replace_callbackでうまいことやる。
第二引数のコールバック関数にマッチした文字の文字数だけstr_repeatで繰り返す。
後は文字列に直して出力!テクニカルな感じでカッコいい!
2012/11/10

PHP で ASCII コードを利用して CSS の list-style-type を再現する

CSS の list-style-type は ul 要素や ol 要素などに指定することができて、
以下のように表示したければ
  1. あいうえお
  2. かきくけこ
  3. さしすせそ
以下のように指定します。
ol {
    list-style-type: upper-alpha;
}
<ol>
    <li>あいうえお</li>
    <li>かきくけこ</li>
    <li>さしすせそ</li>
</ol>
しかし、このABCなどのリストマーカー部分をリスト部分とは異なるデザインにしたい場合、
特に表示位置、色、大きさを調節したい場合はリストでは難しくなります。

HTML/CSSでは難しいのですが、動的なサイトだったためPHPで書くことができました。
リストマーカー部分が数値であれば簡単に for ループのインデックスが使えるのですが
リストマーカーが英字なので少しどうしたものかと思いました。
パッと思い浮かんだ選択肢は、アルファベットを持った配列を定義しておきインデックスで配列の要素を出力、
またはアルファベットを文字列で持ちインデックスでsubstrで1文字だけ出力、という2つ。

定義するのがめんどくさいし、ダサい実装でどうしたものかと考えていたら
C言語の入門書でアスキーコードを利用したことを思い出しました。
PHPでアスキーコードを利用したことがなかったのですが、
C言語でできることはPHPでもできるだろと探してみたらありました。

PHPでアスキーコードを利用する方法!

<?php
for($i = 0; $i < 26; $i++){
    echo chr(ord('A') + $i) . '.';
}
?>
chrがアスキーコードから文字を返してくれて、ordが文字のアスキーコードを返してくれます。
2012/10/28

[競技プログラミング][PHP][AtCoder]おとぎの国の高橋君

B - おとぎの国の高橋君

時間制限 : 2sec / スタック制限 : 10MB / メモリ制限 : 64MB

問題文

高橋君の住むAtCoder国では、
私達が普段使用する数字と同様に 10 個のアラビア数字 (0−9) の 10 進数が使われています。
しかし、私達が普段使用する数字は大小関係が 0<1<2<3<4<5<6<7<8<9 の順になっているのに対して、
AtCoder国の数字ではその大小関係が異なっています。
例えば、AtCoder国の数字では 0<9<8<7<6<5<4<3<2<1 の順になっている場合、
AtCoder国では 9 よりも 8 の方が大きいことになります。また、97 よりも 72 の方が大きいことになります。

AtCoder国の数字の大小関係といくつかの数が与えられるので、
AtCoder国の数字の大小関係で昇順に並び替えてください。
なお、私達が普段使用する数字同様、AtCoder国で最も小さい数字は 0 であることは決まっています。

入力

入力は以下の形式で標準入力から与えられる。
b0 b1 ‥‥ b9
N
a0
a1
:
:
aN−1
入力は N+2 行ある。
1 行目には、AtCoder国での 1 桁の数字の大小関係が与えられる。
AtCoder国では b0<b1<…<b9 であることを表している。
b0 は必ず 0 である。
重複する数字は存在せず、0 から 9 までの数字が 1 度ずつ現れる。
2 行目には並び替える数の個数を表す整数 N(1≦N≦777) が与えられる。
3 行目からの N 行には、j+3 行目に並び替える数を表す整数 aj(1≦aj≦777,777,777) が与えられる。

出力

与えられた数をAtCoder国の数字の大小関係にあわせて昇順に並び替え、
標準出力に 1 行に 1 つの数字ずつ出力せよ。
なお、最後には改行を出力せよ。

出典

B: おとぎの国の高橋君 - AtCoder Regular Contest #009 | AtCoder

回答

AtCoder/arc009_2.php at master · wada811/AtCoder
<?php
// デバッグ用関数
function echos($array, $line){
    echo $line . '::k: ' . implode(' ', array_keys($array)) . PHP_EOL;
    echo $line . '::v: ' . implode(' ', $array) . PHP_EOL;
}
// 変換処理はここから
$cardinalNumber = trim(fgets(STDIN));
$cardinalNumbers = explode(' ', $cardinalNumber);
fscanf(STDIN, '%d', $n);
for($i = 0; $i < $n; $i++){
    fscanf(STDIN, '%d', $targetNumbers[]);
}
// echos($targetNumbers, __LINE__);
// 14::k: 0 1 2 3 4 5 6 7 8 9
// 14::v: 1 2 3 4 5 6 7 8 9 10
// targetNumbers の数字をアルファベットに置換
$alphabets = array('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j');
$numbers = array_keys($alphabets);
$targetAlphabets = str_replace($numbers, $alphabets, $targetNumbers);
// echos($targetAlphabets, __LINE__);
// 21::k: 0 1 2 3 4 5 6 7 8 9
// 21::v: b c d e f g h i j ba
// このアルファベットを cardinalNumbers の順になるようにソート
$cardinalAlphabets = array();
for($i = 0, $end = count($cardinalNumbers); $i < $end; $i++){
    $cardinalAlphabets[$i] = $alphabets[$cardinalNumbers[$i]];
}
// echos($cardinalAlphabets, __LINE__);
// 29::k: 0 1 2 3 4 5 6 7 8 9
// 29::v: a i b d f e j h g c
// targetAlphabets を cardinalAlphabets の k に置換
$numbers = array_keys($cardinalAlphabets);
$atCoderNumbers = str_replace($cardinalAlphabets, $numbers, $targetAlphabets);
// echos($atCoderNumbers, __LINE__);
// 35::k: 0 1 2 3 4 5 6 7 8 9
// 35::v: 2 9 3 5 4 8 7 1 6 20
// この数値を昇順に並べ替える
asort($atCoderNumbers);
// echos($atCoderNumbers, __LINE__);
// 40::k: 7 0 2 4 3 8 6 5 1 9
// 40::v: 1 2 3 4 5 6 7 8 9 20
// この順の k で targetNumbers を得る
$results = array();
$orders = array_keys($atCoderNumbers);
foreach($orders as $order){
    $results[] = $targetNumbers[$order];
}
// echos($results, __LINE__);
// 49::k: 0 1 2 3 4 5 6 7 8 9
// 49::v: 8 1 3 5 4 9 7 6 2 10
// 出力!
echo implode(PHP_EOL, $results) . PHP_EOL;
?>
変換してソートして逆変換するだけだとか思ってやってみたら結構大変なことになった。
数字をAtCoder国の大小関係の数字に変換しようとしたら
str_replaceで配列を渡すと多重変換されてしまうようだった。
仕方ないので適当にアルファベットを間に挟んで変換していったら混乱したので
デバッグ用関数を作ったら結構すんなり解けた。
間にアルファベットを挟む分だけわかりにくくなっているが、
変換してしまえばasortでソートするだけだし、
ソートすればインデックスがわかるので逆変換しなくても
元の数字をそのインデックスの順に出力するだけで良い。

普段よりB問題から面倒くさかったかも。C問題も難しかったし。

タグ(RSS)