ページ

2012/06/19

[競技プログラミング][PHP][AtCoder]割り切れる日付

[競技プログラミング][C言語][AtCoder]割り切れる日付 | DevAchieveをPHPで解き直した。

回答

AtCoder/arc002_2.php at master · wada811/AtCoder

今回はC言語みたいに計算でやらずにPHPの関数で頑張りました。
とりあえず2種類の方法で通したので両方載せる。
<?php
    fscanf(STDIN, "%s", $date);
    while(true){
        $splits = explode("/", $date);
        $year = $splits[0];
        $month = $splits[1];
        $day = $splits[2];
        $quotient = $year / $month / $day;
        if(floor($quotient) == ceil($quotient)){
            break;
        }
        $date = date("Y/m/d", mktime(0, 0, 0, $month, $day, $year) + 86400);
    }
    echo $date."\n";
?>
explode関数で文字列を分割して年月日をそれぞれ取得する。
割り算を計算して、割り切れない場合は商が小数になるので
floor関数とceil関数で小数点以下を切り捨て/切り上げした結果が異なる。
その場合はmktime関数でUNIXTIMEに変換して一日(24*60*60=86400秒)を足す。
新しいUNIXTIMEに対してdate関数でフォーマットを指定して日付を生成して繰り返す。
これを割り切れるまで(商が整数になりfloor関数とceil関数の結果が同じになるまで)やります。

UNIX時間 - Wikipedia
UNIX時間(ユニックスじかん)またはUNIX時刻(ユニックスじこく、UNIX time、POSIX time)とはコンピューターシステム上で日時を表す単位。UTCでの1970年1月1日真夜中(0時0分0秒)からの経過秒数(閏秒を加味しない)で表される。
UNIX系オペレーティングシステムだけでなく、他の多くのオペレーティングシステムにおいてもこの単位が用いられている。システム内部では32ビットまたは64ビットの符号付整数(signed int)で扱われていることが普通であり、特に32ビットで扱われている場合においては符号付整数が取れる最大値 2,147,483,647 を超える時点で時刻を扱えなくなるという問題がある。これを2038年問題という。

ということでUNIXTIMEで計算してるとそのうち面倒な事になるんじゃないの?ってことで違う方法でも解いてみた。
<?php
    fscanf(STDIN, "%s", $input);
    $splits = explode("/", $input);
    $date = new DateTime();
    $date->setDate($splits[0], $splits[1], $splits[2]);
    while(true){
        $year = $date->format("Y");
        $month = $date->format("m");
        $day = $date->format("d");
        $quotient = $year / $month / $day;
        if(floor($quotient) == ceil($quotient)){
            break;
        }
        $date->add(new DateInterval("P1D"));
    }
    echo $date->format("Y/m/d") . PHP_EOL;
?>
ロジックは同じ。DateTimeオブジェクトのインスタンスを生成して、setDateメソッドで年月日をセットする。
フォーマットを指定してformatメソッドで値を取得する。
日付の加算はaddメソッドの引数にDateIntervalオブジェクトを渡して行う。
DateIntervalの引数の書式はPHP: DateInterval::__construct - Manualを参照のこと。

どこでも動いたほうがいいよねってことでそのプラットフォームでの改行コードを表すPHP_EOLも使ってみた。

PHP 逆引きレシピ (PROGRAMMER’S RECiPE)も良かったけど日付関連が前者のやり方だったので
後者のやり方を解説しているというパーフェクトPHP(PERFECT SERIES 3)が欲しい。

参考
手を動かして覚えるPHP 5.3新機能 日付(DateTime,DateInterval)編 | Act as Professional
PHP 5.3 の DateTime オブジェクト関連の便利な新機能 - 肉とご飯と甘いもの @ sotarok

追記(2012/09/01):DateTime::createFromFormatの方が簡潔だった

<?php
fscanf(STDIN, "%s", $input);
$date = DateTime::createFromFormat('Y/m/d', trim($input), new DateTimeZone('Asia/Tokyo'));
while(true){
    $year = $date->format("Y");
    $month = $date->format("m");
    $day = $date->format("d");
    $quotient = $year / $month / $day;
    if(floor($quotient) == ceil($quotient)){
        break;
    }
    $date->add(new DateInterval("P1D"));
}
echo $date->format("Y/m/d") . PHP_EOL;
?>
まだまだ修行が足りない。PHP力が低いなードキュメントは見るようにしてたのだけども…。

パーフェクトPHP (PERFECT SERIES 3)
パーフェクトPHP (PERFECT SERIES 3)