【PHP】標準入力・標準出力を楽に記述するための工夫【AtCoder】

## はじめに

PHPでAtCoderを解いていると標準入力の受け取りが大変面倒に感じます。しかし、工夫次第で快適に標準入力を受け取れるようになりますので、そのための方法をまとめていきます。（ついでに標準出力も少しだけ工夫します。）

## 問題

### [PracticeA - Welcome to AtCoder](https://atcoder.jp/contests/abs/tasks/practice_1)

整数 $a,b,c$ と、文字列 $s$ が与えられます。
$a+b+c$ の計算結果と、文字列 $s$ を並べて表示してください。

* $1 \le a,b,c \le 1000$
* $1 \le |s| \le 100$

## 標準入力

標準入力は `fgets` 関数で取得します。

```PHP
<?php

$line1 = fgets(STDIN); // 1行目を取得
$line2 = fgets(STDIN); // 2行目を取得
$line3 = fgets(STDIN); // 3行目を取得

var_dump($line1); // string(3) "1\n"
var_dump($line2); // string(5) "2 3\n"
var_dump($line3); // string(6) "test\n"
```

`fgets` 関数を実行するごとに1行目、2行目…と標準入力を受け取ることができます。`STDIN` は標準入力のファイルポインタです。

さて、このままでは以下3つの問題点があります。

* 末尾に改行文字が含まれている
* 2行目の $b$ と $c$ の値が1つの文字列として結合されている
* 整数値が int 型ではなく string 型になっている

よって、この標準入力で取得した文字列を扱いやすい形に整形していきます。

まず、末尾の改行文字の削除には `trim` 関数を使用します。

```PHP
<?php

$line1 = trim(fgets(STDIN)); // 1行目を取得
$line2 = trim(fgets(STDIN)); // 2行目を取得
$line3 = trim(fgets(STDIN)); // 3行目を取得

var_dump($line1); // string(1) "1"
var_dump($line2); // string(3) "2 3"
var_dump($line3); // string(4) "test"
```

`trim` 関数は文字列の先頭および末尾にあるホワイトスペース（空白文字や改行文字など）を取り除いてくれます。これで改行文字を削除することができました。

次に、2行目の $b$ と $c$ の値をそれぞれ取り出すために `explode` 関数を使用します。

```PHP
<?php

$line1 = trim(fgets(STDIN));               // 1行目を取得
$line2 = explode(' ', trim(fgets(STDIN))); // 2行目を取得
$line3 = trim(fgets(STDIN));               // 3行目を取得

var_dump($line1); // string(1) "1"
var_dump($line2); // array(2) { [0] => string(1) "2" [1] => string(1) "3" }
var_dump($line3); // string(4) "test"
```

`explode` 関数は第1引数に区切り文字、第2引数に分割したい文字列を指定します。それによって、区切り文字ごとに分割された文字列を配列として取得することができます。

最後に、1行目と2行目の $a,b,c$ を int 型に変換するために**キャスト**と `array_map` 関数を使用します。

```PHP
<?php

$line1 = (int)trim(fgets(STDIN));                               // 1行目を取得
$line2 = array_map('intval', explode(' ', trim(fgets(STDIN)))); // 2行目を取得
$line3 = trim(fgets(STDIN));                                    // 3行目を取得

var_dump($line1); // int(1)
var_dump($line2); // array(2) { [0] => int(2) [1] => int(3) }
var_dump($line3); // string(4) "test"
```

1つの値に対してはキャスト、配列に対しては `array_map` 関数を使用しています。キャストは値の手前に `(int)` を記述することで int 型に変換できます。また、`array_map` 関数は第1引数に `'intval'`、第2引数に配列を指定することで、配列の各要素を int 型に変換できます。

型変換には以下2つのメリットがあります。

* 型の自動変換による予期せぬバグを防げる
* 型の自動変換が繰り返し行われるとき、その処理が標準入力を受け取るときの1回だけで済む

さて、これだけでも標準入力が十分扱いやすくなりましたが、毎回これらの記述を書くのは面倒です。よって、ここからは以下2つの工夫をすることで楽をしていきます。

* 関数化
* `list` の使用

まず、標準入力を受け取る処理を関数化します。

```PHP
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }

$line1 = ints();    // 1行目を取得
$line2 = ints();    // 2行目を取得
$line3 = strings(); // 3行目を取得

var_dump($line1); // array(1) { [0] => int(1) }
var_dump($line2); // array(2) { [0] => int(2) [1] => int(3) }
var_dump($line3); // array(1) { [0] => string(4) "test" }
```

関数を3つ作成しました。それぞれの処理は以下の通りです。

* `strings`：標準入力を string 型で1行受け取る
* `ints`：標準入力を int 型で1行受け取る
* `doubles`：標準入力を double 型で1行受け取る
※すべて、空白区切りで分割した文字列を要素に持つ配列として受け取ります。

これらの関数だけでは配列以外で値を受け取りたいときに困ってしまいます。そこで、`list` という言語構造を使用して、取得した配列の値を各変数に割り当てます。

```PHP
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }

list($a)     = ints();    // 1行目を取得
list($b, $c) = ints();    // 2行目を取得
list($s)     = strings(); // 3行目を取得

var_dump($a); // int(1)
var_dump($b); // int(2)
var_dump($c); // int(3)
var_dump($s); // string(4) "test"
```

これで、配列以外で値を受け取りたいときにも対応できるようになりました。標準入力の受け取りがとても楽になったと思います。

## 標準出力

標準出力は基本的に `echo` という言語構造を使用します。

```PHP
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }

list($a)     = ints();    // 1行目を取得
list($b, $c) = ints();    // 2行目を取得
list($s)     = strings(); // 3行目を取得

echo $a + $b + $c, ' ', $s, "\n"; // 出力
```

`echo` はカンマ区切りで値を並べることで、それぞれの値を出力することができます。改行文字には定数 `PHP_EOL` を使用してもよいですが、値を参照する処理がない分 `\n` を使用した方が速いです。

ただ、このままでは半角スペースや改行文字を毎回入力することになり面倒です。よって、標準出力用の便利な関数を作成します。

```PHP
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }
function output(...$args) { echo implode(' ', $args), "\n"; }

list($a)     = ints();    // 1行目を取得
list($b, $c) = ints();    // 2行目を取得
list($s)     = strings(); // 3行目を取得

output($a + $b + $c, $s); // 出力
```

標準出力用の関数 `output` を作成しました。引数の `...$args` は可変長引数で、引数を任意の数だけ指定することができます。指定された引数は `$args` に配列として渡されます。

関数 `output` では `implode` 関数が使用されています。`implode` は第1引数に区切り文字、第2引数に配列を指定することで、配列の各値を区切り文字で挟むように結合した文字列を取得できる関数です。つまり、`output` は**引数の値を空白スペース区切りで出力し、最後に改行文字を出力する関数**になります。

## テンプレート

最後にテンプレートを作成します。コードを書くたびに先ほどの関数を作成していては面倒なので、あらかじめ雛型を用意しておくと便利です。

```PHP
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }
function output(...$args) { echo implode(' ', $args), "\n"; }

function main() {

}

main();
```

これは個人的な好みですが、`main` 関数を作成してその中に処理を書くと見やすいと思います。

テンプレートをもとにこの問題のコードを作成すると以下のようになります。

```PHP:サンプルコード
<?php

function strings() { return explode(' ', trim(fgets(STDIN))); }
function ints() { return array_map('intval', strings()); }
function doubles() { return array_map('doubleval', strings()); }
function output(...$args) { echo implode(' ', $args), "\n"; }

function main() {

	list($a)     = ints();    // 1行目を取得
	list($b, $c) = ints();    // 2行目を取得
	list($s)     = strings(); // 3行目を取得

	output($a + $b + $c, $s); // 出力
}

main();
```

## まとめ

* 標準入力の問題点
  * 末尾に改行文字が含まれる：`trim` 関数で改行文字を削除する
  * 複数の値が1つの文字列として結合される：`explode` 関数で空白区切りで分割した文字列を要素に持つ配列に変換する
  * 型がすべて string 型になる：キャストと `array_map` 関数で型変換する
* 標準入力の工夫
  * 関数化をして記述量を減らす
  * `list` を使用して、配列以外でも値を受け取れるようにする
* 標準出力の工夫
  * 関数化をして、末尾に改行文字が自動で含まれるようにする
  * 可変長引数を使用して、1行に空白区切りで文字を出力できるようにする