ここは、技術情報、身の回りに起こった出来事を、「もしかしたらみんなの役に立つかもしれない」と思って書き留めておく場所です。

Image

【paiza】プログラミングで彼女をつくったPHP編

2016年2月13日
 

みなさんは彼女をつくりましたか?

僕はつくりました!

後ほど追加された猫耳、メイド服は→【poh7】プログラミングで猫耳とメイド服の彼女をつくった(PHP編その2)

POH8は→【paiza】プログラミングでアイドルをプロデュースPHP編

 

なれそめ

いつものように、適当にインターネットをさまよっていましたら、こんなページを見つけました。

恋愛SLG: プログラミングで彼女をつくる|paizaオンラインハッカソン7 https://paiza.jp/poh/ando

え~!

やってみよう。

 

どんなサイト?

最近ちょくちょくある、プログラミング学習サイトなのですが、画面の中に女の子が立ってまして!?

問題を解くと女の子のパーツ(?)が変更できる

といった具合のものです。

プログラミングが少しでもできる人ならば、チャレンジしたくならないですか?

 

出題される問題について

簡単なものから難しいものまであります。

プログラミングを使ったパズルのような問題なので、一般的に使えるようなものではありませんが、考える過程の思考や工夫、マニュアルの参照は、今後のプログラミングにかなり役立つと思います。

実行結果さえ合えばいいので、その結果を出力するプログラムは無数に作れます。

実行時間、コードのバイト数が出ますので、なるべく効率のよいプログラムを作っていく目安になります。

 

言語について

メジャーどころから新しい言語まで、かなりの数の言語が選択できます。(言語によってはβ版となっています)

ですので、ほとんどの人がやろうと思えばできるでしょう。

ちなみに、今回私はPHPを選択しています。

理由は

  • コードを短くできる
  • 考えることを少なくできる

の2つです。

PHPは標準関数が豊富なので、それらを使えばコードをかなり削ることができます。

あのライブラリとか使えないの?とか思わなくてもいいですし。

そして、配列が思いついたときに適当に使える、数値・文字列が厳格でない等が、この「仕事じゃないプログラミング」に強力に役立ちます(笑)

 

このページでやりたかったこと

ネット上にPHPの解答例みたいなのがなかったので、置いておきたいです(笑)

簡単なやつを除いてですが。

模範解答って会員登録したら見れるのかなあ?※2/13時点ではありませんでした。

 

縞ニーソ

縞々文字列を作り出す問題です。

<?php
$r = trim(fgets(STDIN));
$m = trim(fgets(STDIN));
echo substr(str_repeat(str_repeat('R', $r) . str_repeat('W', $r), $m / $r), 0, $m);
?>

 

解説

いきなりPHP関数全開ですが、関数と同じ働きのコードを書いても仕方がないので。

本来4行目の$m / $rは$m / $r / 2(切り上げ)です。

 

ロングヘア

入力されたyes、noの多い方を出力する問題です。

素直版

<?php
$s = array('yes' => 0, 'no' => 0);
for ($i = 0; $i < 5; $i ++) {
    $s[trim(fgets(STDIN))] ++;
}
arsort($s);
echo key($s);
?>

 

解説

素直に作るとこんな感じです。ひねりがないですね。

arsortで「キーと値の関係を保ったまま」逆順ソートし、最初のキーを表示しています。

 

文字列汎用版

<?php
for ($i = 0; $i < 5; $i ++) {
    $s[] = trim(fgets(STDIN));
}
$s = array_count_values($s);
arsort($s);
echo key($s);
?>

 

解説

yes、noをコード中に書かないようにすることにより、汎用化してみました。

array_count_valuesとかあんまり使わないですね。

コードを少なくするために3行目で、PHP特有の「初期化していない変数を配列のように使うと配列になるよ」を使用しています。

普通に作るときは、必ず変数は初期化しましょう。

 

身も蓋もない版

<?php
for ($i = 0; $i < 5; $i ++) {
    $s[] = trim(fgets(STDIN));
}
sort($s);
echo $s[2];
?>

 

解説

5個しかないので、真ん中は多い方でしょという抜け道的なやつです。

 

眼帯

古本屋で本を買う問題です。

<?php
fgets(STDIN);
fgets(STDIN);
$h = explode(' ', trim(fgets(STDIN)));
fgets(STDIN);
$o = array_diff(explode(' ', trim(fgets(STDIN))), $h);
sort($o);
echo empty($o) ? 'None' : implode(' ', $o);
?>

 

解説

他の言語では必要な場合もあるのですが、PHPでは持っている本と売っている本以外の入力は捨てます(笑)

そして、explodeで、すかさず配列化します。

array_diff関数で配列の差分を返してもらって、小さい順にって書いてあったのでsortをかけます。

emptyならNone、あったらくっつけて出力です。

PHPのsort関数は必ず変数を渡す必要があり、かつ戻り値もbooleanなので少し長くなってしまいます。

 

サンタ服

四角いケーキを切る問題です。

スタンダード版

<?php
fscanf(STDIN, '%d %d %d %d', $x, $y, $z, $n);
$q = array(array(0, $x), array(0, $y));
for ($i = 0; $i < $n; $i ++) {
    fscanf(STDIN, '%d %d', $e, $d);
    $q[$e][] = $d;
}
for ($i = 0; $i < 2; $i ++) {
    sort($q[$i]);
    for ($j = count($q[$i]) - 1;$j > 0; $j --) {
        $m[$i][] = $q[$i][$j] - $q[$i][$j - 1];
    }
}
echo min($m[0]) * min($m[1]) * $z;
?>

 

解説

普通に縦と横の入力を配列に入れます。

ソートさせることにより、切り込みまでの幅をループを使って算出できるので、その結果を配列に入れます。

最後に縦と横の最小値に高さを掛けて終わりです。

問題どおりに作ってみました。

こんなゴチャゴチャしたプログラムが嫌いなので、直します。

 

シンプル版

<?php
fscanf(STDIN, '%d %d %d %d', $x, $y, $z, $n);
$q = array(array($x), array($y));
for ($i = 0; $i < $n; $i ++) {
    fscanf(STDIN, '%d %d', $e, $d);
    $q[$e][] = $d;
}
foreach($q as $t) {
    sort($t);
    $m = 999;
    $w = 0;
    foreach($t as $u) {
        $m = min($m, $u - $w);
        $w = $u;
    }
    $z *= $m;
}
echo $z;
?>

 

解説

スタンダード版からゴチャッとしたところを削っていったら、シンプルなものになりました。

10行目は当初PHP_INT_MAXとしていましたが、入力値の最大が100なので999にしてみました。

 

めがね

画像の一致したピクセルパターンを探すといった問題です。

スタンダード版

<?php
for ($i = trim(fgets(STDIN)); $i > 0 ; $i --) {
    $g[] = str_replace(' ', '', trim(fgets(STDIN)));
}
for ($i = trim(fgets(STDIN)); $i > 0; $i --) {
    $p[] = str_replace(' ', '', trim(fgets(STDIN)));
}
$q = array_shift($p);
$u = count($p);
for ($i = 0; $i < count($g); $i ++) {
    for ($c = strpos($g[$i], $q, 0); $c !== false; $c = strpos($g[$i], $q, $c + 1)) {
        for ($j = 0; $j < $u; $j ++) {
            if (substr($g[$i + $j + 1], $c, strlen($q)) != $p[$j]) {
                break;
            }
        }
        if ($j == $u) {
            echo $i . ' ' . $c;
            exit;
        }
    }
}
?>

 

解説

コンセプトは「文字列入力なので、文字列比較しよう」です。

exitに苦しいものがありますが、無難な作りじゃないでしょうか。

11行目の1番目のstrposでお互いの一行目を探ります。

それが一致した場合、その位置から次の行、次の行と比較していきます。(12行目~16行目のループ)

 

割り切り版

<?php
$g = '';
$n = trim(fgets(STDIN));
for ($i = $n; $i > 0 ; $i --) {
    $g .= str_replace(' ', '', fgets(STDIN));
}
$m = trim(fgets(STDIN));
for ($i = $m; $i > 0; $i --) {
    $p[] = str_replace(' ', '', trim(fgets(STDIN)));
}
$s = strlen($g) / $n;
$c = -1;
do {
    $a = $p[0];
    $c = strpos($g, $a, $c + 1);
    for ($j = 1; $j < $m; $j ++) {
        $a .= substr($g, $c + $s * $j, strlen($p[0]);
    }
} while ($a != implode('', $p);
echo floor($c / $s) . ' ' . $c % $s;
?>

 

解説

「もう文字列なんだからいいじゃん」とばかりに割り切ったバージョンです。

5行目でtrimせず、入力文字列の改行を生かしたまま、一つの文字列としています。

これにより、15行目のstrposがfalseを返す事がなくなり、判定が不要となります。

あとは、計算で切り出した後、お互いを文字列として比較しています。

普段あまり使わないdo~whileを使えたことで満足です(笑)

 

見直したら短いの思いついた版(2016年2月21日追記)

<?php
for ($i = trim(fgets(STDIN)); $i > 0 ; $i --) {
    $g[] = str_replace(' ', '', trim(fgets(STDIN)));
}
for ($i = trim(fgets(STDIN)); $i > 0; $i --) {
    $p[] = str_replace(' ', '', trim(fgets(STDIN)));
}
$s = strlen($g[0]) + 1;
preg_match('/' . implode('.{' . ($s - strlen($p[0])) . '}', $p) . '/', implode('2', $g), $m, PREG_OFFSET_CAPTURE);
echo floor($m[0][1] / $s) . ' ' . $m[0][1] % $s;
?>

 

解説

書いた文章を見直していたら、「あれ?めがねってこんな長いコードだっけ?」と思ったので、短くしました。

そもそも一つの文字列にすれば、正規表現で一発検索できますね。

その場合、改行コードは邪魔なので、使っていない'2'としました。

preg_matchにPREG_OFFSET_CAPTUREを指定すると、位置も返してくれるので、その位置から計算します。

検索パターンは「.{n}」を記述し、任意の文字+文字数指定を行っています。

 

水着

階乗の結果を出すだけですが、罠が仕掛けてあります。

コレジャナイ版

<?php
echo ltrim(substr(rtrim(gmp_strval(gmp_fact(fgets(STDIN))), '0'), -9), '0');
?>

 

解説

問題を素直に解いたと言っていいのかどうか???

出題からは、こんなコードを意図してはいないと思うのですが、PHPではこれでもできます。

このコードのイマイチな点は

  • GMPは標準インストールじゃない
  • 素直にやってるので遅い(CPUパワーに任せてる)

です。

コードバイトが少ないですが、遅いです。

そりゃそうですよね。

かといって、PHPコードでまともに階乗をやっていこうとすると、タイムアウトでテストが通りません!

 

コレだよコレ版

<?php
$a = 1;
for ($i = trim(fgets(STDIN)); $i > 1; $i --) {
    $a = substr(rtrim($a * $i, '0'), -10);
}
echo substr($a, -9);
?>

 

解説

問題に、「最下位桁から続く0 をすべて除いた」とあるので、0に何掛けても0じゃーんとばかりに、計算直後にバッサリ切ります。

さらに下位9桁とありますので、その後9桁までしか計算しない…

と行きたいところですが、一桁誤差(といってもいいのか?)が出るようなので、10桁残します。

この文字列なんだか数値なんだかわからん感じがPHPですね。

これでやってみましょう。

さすがに高速になりました。

PHPなら、速度はこんなもんじゃないでしょうか。

 

最後に

彼女の最終形です。

 

後ほど追加された猫耳、メイド服は→【poh7】プログラミングで猫耳とメイド服の彼女をつくった(PHP編その2)