【paiza】プログラミングで彼女をつくったPHP編
みなさんは彼女をつくりましたか?
僕はつくりました!
後ほど追加された猫耳、メイド服は→【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)