8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

D言語で競プロする時の標準入力どうしよう

Last updated at Posted at 2018-03-06

D言語。
ローレベルな事も抽象的な事もできて、読みやすくて、使いやすい標準関数が揃っていて、速度もそこそこ速い。
競プロで better C++ くらいの使い方するのにとても良いと思っています。

なのですが、Dで標準入力から取得する時、皆さんどうしてます?

方法としては3種類くらいあるかなぁと思っています。

  1. readlnを酷使する
  2. readfを酷使する
  3. stdin.byLineを酷使する
  4. core.stdc.stdioを活用する

他にもやり方はあるかもしれないのですが、さっと思いつくのはこんなところですかね。
自分が楽なものを使えば良いと思うのですが、しかし競プロで利用するならば速度(とメモリ消費)を考えないといけません。

……これらの方法で、どれが一番速いのでしょうか?

サンプル問題

適当にこんな問題を考えました。

N行M列の数列があります。
全ての数字の合計の、下3桁を求めなさい。

条件:
1 <= N, M <= 10^3
0 <= 全ての数字 < 10^3

入力:
N M
X1,1 X1,2 ... X1,M
:
XN,1 XN,2 ... XN,M

これを、3種類の標準入力で解いてみましょう。

テストケースを作る

それ用のスクリプトを雑に書きました。

N = 10 ** 3
M = 10 ** 3

puts "#{N} #{M}"

nums = [*0...1000]

N.times do
  puts M.times.map { nums.sample }.join(" ")
end

Rubyで書きました。

解答のコードを書く

こっちは勿論 D で書きましょう。

readlnを使う

import std.stdio, std.conv, std.array;

void main()
{
    auto N = readln.split[0].to!int;

    int c;
    int[] ns;
    foreach (_; 0..N)
        foreach (n; readln.split) c = (c + n.to!int) % 1000;
    writeln(c);
}

readln 関数を N+1 回呼んで、全部の行を取得しています。
先頭以外の行は split 関数で分割し(この関数は引数無しで呼ぶと空白で分割してくれます)、 foreach で回して to 関数で int 型に変換しつつ下3桁を足していきます。

readlnを使う(バッファを活用する)

readln 関数は、バッファを取る事ができます。読み込んだ行を返り値として返すと、その度に文字列のメモリのアロケーションが発生するのに対し、バッファを使うとそのコストを抑える事ができます(と、関数の説明に書いてあります)。
というわけでバッファを使う版も書いてみましょう。

import std.stdio, std.conv, std.array;

void main()
{
    auto N = readln.split[0].to!int;

    int c;
    int[] ns;
    char[] buf;
    foreach (_; 0..N) {
        readln(buf);
        foreach (n; buf.split) c = (c + n.to!int) % 1000;
    }
    writeln(c);
}

readfを使う

writef の逆バージョンですね。フォーマットで指定した形式で文字列を読み込む事ができます。何となく便利そうですよね。

import std.stdio, std.conv, std.array;

void main()
{
    int N, M;
    readf("%d %d", &N, &M);

    int c, X;
    foreach (_1; 0..N) foreach (_2; 0..M) {
        readf(" %d", &X);
        c = (c + X) % 1000;
    }
    writeln(c);
}

最新のphobosだとポインタで渡す必要が無かったり、フォーマット文字列をコンパイル時に渡せたりする版のreadf(やwritef)が入っているのですが、AtCoderで使えるDだとまだその機能が入っていませんでしたね。残念。

byLine関数を使う

ファイルを1行単位の range として扱う関数です。

import std.stdio, std.conv, std.array;

void main()
{
    int c = -1;
    foreach (line; stdin.byLine) {
        if (c == -1)
            c = 0;
        else
            foreach (n; line.split) c = (c + n.to!int) % 1000;
    }
    writeln(c);
}

やってる事は readln 版とそんなに変わりませんね。

比較する

簡単なスクリプトを書いて速度を比較します。
Dには std.datetime.stopwatch.benchmark という関数があるので使ってみました。
test${N}.txt という名前で10個くらいテストファイルを作って、確認してみます。

import std.stdio, std.conv, std.process, std.datetime.stopwatch;

void test(string filename)()
{
    static foreach (i; 1..11)
        executeShell("./" ~ filename ~ " < test" ~ i.to!string ~ ".txt");
}

void main()
{
    auto r = benchmark!(test!"readln_test", test!"readln_buf_test", test!"readf_test", test!"byline_test")(1);

    writefln("readln_test:\t%s", r[0]);
    writefln("readln_buf_test:\t%s", r[1]);
    writefln("readf_test:\t%s", r[2]);
    writefln("byline_test:\t%s", r[3]);
}

結果がこうなりました。

readln_test:     1 sec, 323 ms, and 520 μs
readln_buf_test: 1 sec, 197 ms, 249 μs, and 9 hnsecs
readf_test:      4 secs, 501 ms, 440 μs, and 2 hnsecs
byline_test:     1 sec, 246 ms, 363 μs, and 3 hnsecs

見てみる

readf 関数を使った場合が明白に遅いですね。その他は、まぁどんぐりの背比べでしょうか。
バッファを使った readln が一番速いように見えますが、何度か試行すると byLine 版と逆転したりするので、同じくらいという事でしょうか。ただ、バッファを使わない場合よりは間違いなく速いです。

実は byLine 関数は中でバッファを使った readln を使っているので、速度にほとんど差が無いのは当然なんですね。
一方 readf は少しややこしい事をしています。
その辺りが速度の違いに現れてるんじゃないですかね。

結論

どれを使えば良いんでしょうね?
個人的には readln で1行取得して、 chomp なり split なりしてから to でよしなに変換してやるのが楽でわかりやすいので多用しています。

readln.split.to!(int[]); // intの配列に変換

readln.chomp.to!long; // 何か大きな数を取得

調査結果からバッファを使った方が多少速い事が分かりますが、標準入力からの取得なんてそこまで何度もループする処理ではないので、僅かな時間すら惜しいような問題でも無い限りそんなに気にする事じゃないかなぁと思っています。
最初の1行読み取る、みたいな処理ならば、 readf 使ったって良いと思います。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?