はじめに
昨今,CodeIQやpaizaといったサービスが登場し,自動採点型のプログラミングの問題に取り組む人が増えている(と思う).
自動採点型のプログラミングの問題では,基本的に標準入力から読み込みを行い,をれに応じて適切な出力をするというものだ.
入力ケースは何パターンか与えられ,その度に送信されたプログラムを実行する仕組みとなっている.
このような標準入力から読み込みを行い,標準出力がちゃんとしたのであるかどうかを判定し,自動採点を行う形式のプログラミングの問題は,AOJやAtCoder,yukicoderなどの競技プログラミングサイトでは一般的なものであったが,CodeIQ,paizaなどの台頭により,より広く認知されるようになったのではないかと思う.
この記事では,僕の忘備録も兼ねて,各種言語における標準入力と標準出力のサンプルを掲載する.
というのも,CodeIQの入出力サンプルにはヒドい例が掲載されている言語もあったからだ.
特に,Javaのサンプルは,
import java.io.*;
class Main {
public static void main(String[] args) throws IOException {
int c;
while ((c = System.in.read()) != -1)
System.out.println(c);
}
}
となっており,何故か1文字ごとに読み込みを行うようになっている.
また,この手の入出力サンプルは1行だけ読み込んで,それをそのまま処理というケースしか書かれていない.
とりあえず,
- 標準入力をEOFまで読み込み,
- 入力をトークン分割し,
- トークンを整数型に変換して.
- 標準出力に出力する
という単純なサンプルを掲載する.
具体的な入力としては,
10 20
30 40
50 60
70 80
のようなものだ.
ただ,実際にはデータ数(読み込めばよい行数)が1行目に与えられる問題が多く,EOFまで読み込む必要はないが,たまにEOFまで読み込まなければならない問題があるので,EOFまでの読み込みの例を記載するわけだ.
指定回数を読み込む方法については,各言語におけるループの仕方を調べればよいだけであり,ググれば簡単にヒットするし,ループが記述できないようでは問題自体も解けないであろうから割愛する.
言語
本記事で取り扱う言語は以下の通りである.
まずはC言語から.
まぁ,C言語で問題を解く人はいないだろうが,サンプルとして掲載する.
#include <stdio.h>
#include <stdlib.h>
#define LINE_BUF_SIZE 1024
int
main(void)
{
static char line[LINE_BUF_SIZE];
while (fgets(line, sizeof(line), stdin) != NULL) {
int a, b;
if (sscanf(line, "%d %d", &a, &b) != 2) {
fputs("sscanf: Convert error\n", stderr);
return EXIT_FAILURE;
}
}
return EXIT_SUCCESS;
}
競プロにおける入力はある程度入力される文字列の長さが想定できるので,それなりのバッファ容量を確保し, scanf()
を用いても問題はないだろうが, scanf()
を使ってるだけで気持ち悪いと感じるので, fgets()
と sscanf()
を用いることにする.
EOFに到達した場合, fgets()
は NULL
を返却するので,それをEOFの判定に利用する.
また, sscanf()
の返り値は,変換に成功した個数なので,うまく整数に変換できたかどうかの確認に利用する.
なお,64bit整数を sscanf()
で読み込むにあたってはやや問題があり, "%lld"
といった書式指定文字列を用いないとうまくいかないかもしれない.
これはC99から正式に採用され,それ以前ではコンパイラによっては非標準として実装されているレベルなので,やや気持ち悪いかもしれない.
C++を用いている人は多いので,最早説明の必要はないだろう.
#include <cstdlib>
#include <iostream>
int
main()
{
std::cin.tie(0);
std::ios::sync_with_stdio(false);
int a, b;
while (std::cin >> a >> b) {
std::cout << a << " " << b << std::endl;
}
return EXIT_SUCCESS;
}
一応解説しておくと,
std::cin.tie(0);
で std::cout
と std::cin
の結び付きを解除し,
std::ios::sync_with_stdio(false);
で, stdio
との同期を切る.
これによって, std::cout
と std::cin
を用いた入出力を高速化できる.
これは, std::cout
/ std::cin
を用いる場合には有効である.
もっとも, std::printf()
を用いた方がフォーマット出力は楽なので, std::cout
の代わりに std::printf
を用いている人も多いだろう.
<iostream>
と <cstdio>
系の関数を混在させる場合は,前述の高速化を用いてはいけない.
上記の例では,EOFの判定は std::cin
のbool変換演算子を用いているが, std::cin.eof()
が true
かどうかでEOFを判定するという手段もある.
なお,ガチ勢は using namespace std;
と膨大なマクロとインクルードを記述したものを用いるだろう.
基本的な部分の解説の必要は無いだろう.
C#の場合, Console.WriteLine()
はデフォルトだと自動的にフラッシュされる設定になっているので,この自動フラッシュをオフにし,最後にまとめてフラッシュすることで,出力の部分の高速化が期待できる.
using System;
using System.IO;
class MainClass {
static void Main() {
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false });
string line;
while ((line = Console.ReadLine()) != null) {
string[] tokens = line.Split(' ');
int a = int.Parse(tokens[0]);
int b = int.Parse(tokens[1]);
Console.WriteLine(a + " " + b);
}
Console.Out.Flush();
}
}
IEnumerable
の操作を用いて,文字列から整数への変換を以下のように書くのもよいだろう.
using System;
using System.IO;
class MainClass {
static void Main() {
Console.SetOut(new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false });
string line;
while ((line = Console.ReadLine()) != null) {
int[] tokens = line.Split(' ').Select(int.Parse).ToArray();
int a = tokens[0];
int b = tokens[1];
Console.WriteLine("{0} {1}", a, b);
}
Console.Out.Flush();
}
}
VB.NETはよくわからないので,C# のものを単純に翻訳してみただけだ.
Line
は予約語らしいので,読み込んだ一行は inputtedLine
という名前の変数に格納する.
Imports System
Imports System.IO
Public Class MainClass
Shared Sub Main(args As String())
Console.SetOut(New StreamWriter(Console.OpenStandardOutput()) With { .AutoFlush = false })
Dim inputtedLine As String = Console.ReadLine()
While (Not inputtedLine Is Nothing)
Dim tokens As Integer() = inputtedLine.Split(" ").Select(Function(token) Integer.Parse(token)).ToArray()
Dim a As Integer = tokens(0)
Dim b As Integer = tokens(1)
Console.WriteLine("{0} {1}", a, b)
inputtedLine = Console.ReadLine()
End While
Console.Out.Flush()
End Sub
End Class
Javaはバージョン毎に進化しているので,Java7の場合とJava8の場合に分けて入出力サンプルを掲載する.
Java6以下はもう滅びたと考えてよいので,サンプルを掲載する必要はないだろう.
なお,基本的に例外処理は記述せず, main()
メソッドからthrowする形で記述する.
というのも,基本的に例外が発生した時点で,問題に正答することはできなくなっていると考えられるからだ.
Java7からtry-with-resource文が実装されたので, System.in
の読み込み用のオブジェクトのクローズは自動的に行わせる.
import java.io.*;
public class Main {
public static void main(String[] args) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
String line;
while ((line = br.readLine()) != null) {
String[] tokens = line.split(" ");
int a = Integer.parseInt(tokens[0]);
int b = Integer.parseInt(tokens[1]);
System.out.println(a + " " + b);
}
}
}
}
java.util.Scanner
を用いた場合,split()
メソッドによるトークン分割,整数型等への変換処理を自前で書かなくてもよくなる.
ただし,java.util.Scanner
は遅いという話があるので,入力数が多い場合は気をつけなくてはならない.
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
try (Scanner sc = new Scanner(System.in)) {
while (sc.hasNextInt()) {
int a = sc.nextInt();
int b = sc.nextInt();
System.out.println(a + " " + b);
}
}
}
}
Java8になって, java.io.BufferedReader
に lines()
メソッドという,Streamを生成するAPIが追加された.
このメソッドを用いることで,標準入力からの読み込みをカッコよく書ける.
Java8を用いて問題を解く場合,Streamを用いて楽に記述することも多いだろうから,そういう意味でも親和性が高いはずだ.
import java.io.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
br.lines()
.map(line -> Arrays.stream(line.split(" "))
.mapToInt(Integer::parseInt)
.toArray())
.forEach(tokens -> {
int a = toknes[0];
int b = toknes[1];
System.out.println(a + " " + b);
});
}
}
}
もし,分割されたトークンを何らかのコンストラクタに突っ込み,目的とするオブジェクトの型の配列に変換したいのであれば,以下のようにするとよいだろう.
この例では,各トークンから BigDecimal
のインスタンスを生成している.
map()
メソッドから返却されるストリームの toArray()
メソッドは,引数が無い場合, Object
型の配列を返却するので,引数に目的の型の配列を生成する new
の参照(メソッド参照?)を渡す必要がある.
import java.io.*;
import java.math.*;
import java.util.*;
public class Main {
public static void main(String[] args) throws Exception {
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
br.lines()
.map(line -> Arrays.stream(line.split(" "))
.map(BigDecimal::new)
.toArray(BigDecimal[]::new))
.forEach(tokens -> {
BigDecimal a = toknes[0];
BigDecimal b = toknes[1];
System.out.println(a + " " + b);
});
}
}
}
なお,1行目にデータ数nが与えられる場合は,
try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in))) {
int n = Integer.parseInt(br.readLine());
br.lines()
}
のように,事前に1回 readLine()
メソッドを呼び出し,ファイルストリームの読み書き位置を移動しておくとよい.
なお, java.util.Scanner
にStreamを生成するものはないらしいので,Stream APIを用いたいなら, java.io.BufferedReader
を使おう.
Pythonは2と3で大きく仕様が変更されているので,別々に記述する.
ただ, fileinput
をimportし,標準入力を 1行1行読み取るようにした場合,標準入力からの読み込みに関しては,Python2とPython3 の両対応ができる.
import fileinput
if __name__ == '__main__':
for line in fileinput.input():
tokens = line.strip().split()
a, b = int(tokens[0]), int(tokens[1])
fileinput.input()
を用いる場合は以下のようになる.
import fileinput
if __name__ == '__main__':
for line in fileinput.input():
tokens = map(int, line.strip().split())
a, b = tokens[0], tokens[1]
print a, b
ただ,標準入力といえば, raw_input()
を用いるのが楽である.raw_input()
は1行の入力末尾の改行文字を削除してくれるので,自分で strip()
メソッドを呼び出す必要が無くなる.
入力データ数 n
が与えられる場合,リスト内包表記を用いて [raw_input() for i in range(n)]
とすることで,指定された行数の行を一気にリストとして取得できる.
if __name__ == '__main__':
n = int(raw_input())
for line in [raw_input() for i in range(n)]:
tokens = map(int, line.strip().split())
a, b = tokens[0], tokens[1]
print '%d %d' % (a, b)
整数に変換する部分をまとめて,以下のようにまとめるのもよいだろう.
if __name__ == '__main__':
n = int(raw_input())
for tokens in map(lambda line: map(int, line.strip().split()), [raw_input() for i in range(n)]):
a, b = tokens[0], tokens[1]
print '%d %d' % (a, b)
Python3になってから,print
文が廃止され, print()
関数になってしまった.
また, map()
の返り値がイテレータになったので,リストに変換するために list()
関数をかませる必要がある.
import fileinput
if __name__ == '__main__':
for line in fileinput.input():
tokens = list(map(int, line.strip().split()))
a, b = tokens[0], tokens[1]
print(a, b)
Python3では raw_input()
の代わりに input()
を用いないといけない.
Python2の例と同様に,リスト内包表記を用いることで,指定行数だけ入力をリストとして一気に受け取ることができる.
if __name__ == '__main__':
n = int(input())
for tokens in map(lambda line: list(map(int, line.strip().split())), [input() for i in range(n)]):
a, b = tokens[0], tokens[1]
print('%d %d' % (a, b))
特に解説することはない.
#!/usr/bin/env ruby
if __FILE__ == $0
while line = STDIN.gets
tokens = line.chomp!.split.map!(&:to_i)
a, b = tokens[0], tokens[1]
puts "#{a} #{b}"
end
end
Perlはほとんど書いたことはないが,以下のような形になるだろう.
$line =~ s/\s+$//;
とすることで,入力のstripを行うことができる.
#!/usr/bin/env perl
use strict;
use utf8;
use warnings;
if ($0 eq __FILE__) {
while (defined(my $line = <STDIN>)) {
$line =~ s/\s+$//;
my @tokens = split(/ +/, $line);
my $a = $tokens[0];
my $b = $tokens[1];
printf("%d %d\n", $a, $b);
}
}
PHPには詳しくないので,以下のような形でよいのかどうかも怪しい.
#!/usr/bin/env php
<?php
if (basename(__FILE__) == basename($_SERVER['PHP_SELF'])) {
while ($line = fgets(STDIN)) {
$tokens = split(' +', $line);
echo $tokens[0];
echo $tokens[1];
}
}
?>
最近,何かと人気のGo言語である.
fmt.Scan()
を用いるのであれば,以下のようにするとよいだろう.
package main
import (
"fmt"
)
func main() {
var a int
var b int
for _, err := fmt.Scan(&a, &b); err == nil; _, err = fmt.Scan(&a, &b) {
fmt.Println(a, b)
}
}
bifio
が利用可能な環境であれば,以下のようにしてもよさそうだ.
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
tokens := strings.Split(scanner.Text(), " ")
a, _ := strconv.Atoi(tokens[0])
b, _ := strconv.Atoi(tokens[1])
fmt.Println(a, b)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
}
Luaは文字列の分割が無いので,自分で実装する必要がある.
なかなかゴチャゴチャとした見た目になる.
なお,Luaのコメント形式は -- foo
もしくは --[[bar]]
という形式であるが,1行目のみshebangが許容されるようになっているので,ありきたりなshebangを記述している.
if ... then
module(..., package.seeall)
end
function split(str, delim)
if string.find(str, delim) == nil then
return { str }
end
local result = {}
local pat = '(.-)' .. delim .. '()'
local lastPos
for part, pos in string.gfind(str, pat) do
table.insert(result, part)
lastPos = pos
end
table.insert(result, string.sub(str, lastPos))
return result
end
if not ... then
line = io.read()
while line do
tokens = split(line, ' +')
print(tonumber(tokens[1]))
print(tonumber(tokens[2]))
line = io.read()
end
end
Node.jsは,標準入力をイベントとして感知することができるようになっている.
ただ,受け取った時点で順次処理をしていくのはかなり限定的なケースになると思われるので,入力を配列に格納し,EOFのイベントを検知したら,処理を行うという汎用的な形にして記載する.
(function() {
'use strict';
var lines = [];
require('readline').createInterface({
input: process.stdin,
output: process.stdout
}).on('line', function(line) {
lines.push(line);
});
process.stdin.on('end', function() {
lines.forEach(function(line) {
var tokens = line.split(' ').map(Number);
console.log(tokens[0] + ' ' + tokens[1]);
});
});
})();
何となくスコープ化しておいたが,ライブラリを作るわけでもないので,じゃんじゃかグローバル変数,関数を生やしても問題はないはずではある.
しかし,気持ちの問題として,スコープ化したくなってしまうものだ.
AtCoderのサンプルのように,
(function(input) {
'use strict';
input.split("\n").forEach(function(line) {
var tokens = line.split(' ').map(Number);
console.log(tokens[0] + ' ' + tokens[1]);
});
})(require('fs').readFileSync('/dev/stdin', 'utf8'));
としてもよいが,手元の環境がWindowsであるなら,このサンプルは /dev/stdin
が無いため動作しない.
したがって,Windows環境でも動作する前者のサンプルをオススメする.
標準入力には read
関数を用いることで,分割されたトークンを読み込むことができる.
ただし,read
は Lisp式を読み込む関数なので, 半角カッコがある入力の受け取りに用いることはできない.
(define (main-function)
(letrec ((main-loop (lambda ()
(let ((a (read)) (b (read)))
(if (or (eof-object? a)
(eof-object? b))
#f
(begin (display a)
(display " ")
(display b)
(newline)
(main-loop)))))))
(main-loop)))
(main-function)
Schemeと同様に read
関数を用いて,読み込みを行うことで,トークン毎の読み込みが可能となる.
Common Lispでは末尾再帰最適化の保証がされないため,ループマクロが好んで用いられるという点も踏まえると,以下のようにするのがよいのだろうか?
(defun main ()
(loop for a = nil then b
and b = nil then
(progn (princ a)
(princ b))
until (or (null (setq a (read)))
(null (setq b (read))))))
(main)
もし,1行読み込んで,半角スペースでsplitするなら,以下のようにするのがよいだろう.
(defun split-by-one-space (string)
(loop for i = 0 then (1+ j)
as j = (position #\Space string :start i)
collect (subseq string i j)
while j))
(defun main ()
(loop for line = nil then
(let ((tokens (mapcar #'parse-integer
(split-by-one-space line))))
(princ tokens))
until (null (setq line (read-line)))))
(main)
なお,上記の2つの例でコメントアウトしている部分(optimize宣言をしている部分)は,最適化指示をしている部分である.
テストがうまく通った後はコメントアウトを外すと,高速化が期待できるだろう.
Clojureはほぼ触ったことが無いが,Common Lispと同じノリで書けると信じて,書いてみる.
(defn main
[]
(loop [line (read-line)]
(if (nil? line)
nil
(do (let [tokens (map #(Long/parseLong %) (.split line " +"))]
(println tokens))
(recur (read-line))))))
(main)
多分,普段からClojureを書いている人から見れば,ツッコみどころが満載だが,とりあえずはこの形式で動作させることができると思う.
CodeIQのサンプルでは再帰を用いているが,Clojureでは末尾再帰最適化は保証されないと聞いたような気がするので, loop
と recur
を用いた方が良いのではないかと思った.
なかなか用いる機会は少ないが,bashで問題を解く場合は以下のようになる.
declare -i a b
while read a b; do
echo "$a $b"
done
read
コマンドはEOFを読み込んだとき,非0を返却するので,それを利用して,EOFまでの読み込みを行う.
declare -i a b
の行は無くても動作するが,シェル変数 a
, b
を整数型として宣言しておくと気分が良いと思う.
環境はかなり限られるが,bashからVim Scriptを実行することもできるらしい.
vim -u NONE -i NONE -N -n -e -s -S <(cat <<EOF
function! s:main(lines) abort
let answer = ''
for line in a:lines
let tokens = map(split(line, ' '), 'str2nr(v:val)')
let [a, b] = [tokens[0], tokens[1]]
answer .= printf("%d %d\n", a, b)
endfor
return answer
endfunction
let s:lines = getline(1, '$')
enew
put =s:main(s:lines)
1 delete _
%print
EOF
) <(cat)
CodeIQではbashが使えるが,vimを用いることができなかった.
しかし,AtCoderでは利用可能であるらしい.
今後
D言語やScalaやHaskell,RやSwift等についても調べたい.
余裕があればAWKも....
おそらく,この記事に追記する形で記載していくと思う.
参考