「第20回オフラインリアルタイムどう書くの問題」をPHPで解く


http://qiita.com/Nabetani/items/5791f8ae1bb5d069a49b
http://nabetani.sakura.ne.jp/hena/ord20meetime/

会議に都合のいい時間を探します。

<?php

    class MEETING{
        /**
        * ミーティングの時間
        * @param String 「A1000-1130,B1230-1800,Z0800-2200」みたいな文字列
        * @return String 「1130-1230」みたいな文字列
        */
        public function get($input){
            // 初期値
            $all1 = str_repeat('1', 480);
            $freetime = ['A'=>$all1, 'B'=>$all1, 'I'=>$all1, 'J'=>$all1, 'Z'=>$all1];
            $starttime = mktime(10, 0, 0);

            // 入力値を整形
            $input = explode(',', $input);

            // 都合の悪い時間帯は0にする
            foreach($input as $val){
                $start = (mktime(substr($val, 1, 2), substr($val, 3, 2), 0) - $starttime)/60;
                $end   = (mktime(substr($val, 6, 2), substr($val, 8, 2), 0) - $starttime)/60;
                if($start < 0){ $start = 0; }elseif($start>480){ $start=480; }
                if($end < 0){ $end = 0; }elseif($end>480){ $end=480; }
                for($i=$start; $i<$end; $i++){
                    $freetime[$val[0]][$i] = 0;
                }
            }

            // GMPにする
            foreach($freetime as $key=>$val){
                $freetime[$key] = gmp_init($val, 2);
                // Zのみビット反転
                if($key==='Z'){
                    $freetime[$key] = gmp_xor($freetime[$key], gmp_init($all1, 2));
                }
            }

            // 全員'1'が60回続く箇所を見つける
                // ABIZで一番早い時間
                $andi = sprintf('%0480s', gmp_strval(gmp_and(
                    gmp_and($freetime['A'], $freetime['B']),
                    gmp_and($freetime['I'], $freetime['Z']) ), 2));
                $starti = strpos($andi, str_repeat('1', 60));
                // ABJZで一番早い時間
                $andj = sprintf('%0480s', gmp_strval(gmp_and(
                    gmp_and($freetime['A'], $freetime['B']),
                    gmp_and($freetime['J'], $freetime['Z']) ), 2));
                $startj = strpos($andj, str_repeat('1', 60));
                // どっちも無ければ解なし
                if($starti === false && $startj === false){ return '-'; }

            // 整形して返却
                $start = $starti===false ? $startj : ( $startj===false ? $starti : min($starti, $startj));
                $return date('Hi', $starttime + $start*60) . '-' . date('Hi', $starttime + $start*60+3600);
        }

    }

    // 以下はテスト
    $test = [
        ['A1050-1130,B1400-1415,I1000-1400,I1600-1800,J1100-1745,Z1400-1421,Z1425-1800', '1425-1525'],
        ['A1000-1200,B1300-1800,Z1000-1215,Z1230-1800', '-'],
        /* 省略 */
    ];

    $meeting = new MEETING();
    foreach($test as $key=>$data){
        $answer = $meeting->get($data[0]);
        if($answer !== $data[1]){
            print('えらー');
        }
    }

いやあ……なんだこれ。

暇な時間は1、用事があれば0を入れたビット列を作り、1が60回続くところを探しています。
発想は簡単ですが、文字列に対して論理演算を使うと想定外の結果になるのでビット操作が面倒なことになっていたり、日付を文字列にするところが妙にややこしかったり、あと最後の三項演算子は絶対もっと簡単な書き方があるだろ。

なんともすっきりしない解答。
時間も無駄にかかっていて3時間くらい。
何故か日時をDateTimeにしようとしたり文字列のまま処理しようとしたりで大混乱。