ブログ名

競技プログラミングやお歌のお話をする高菜です。

proconio を使わない Rust の標準入力(Rust 1.61 ~ Rust 1.65 の一連のアップデートについて)

概要

Rust は標準入力が難しめの言語と言われがちです。たしかに競技プログラミング文脈に限れば C++ や Python に比べてややややこしいことは否めませんが、今の嫌煙され方は過剰だと思っております。そこ私が標準入力の「今」をご紹介して、コワクナイヨということをご理解いただきたいという趣旨でございます。

なおこの記事ではインタラクティブ問題のことは一切考慮していません。理由は私がよくわかっていないからです。気が向いたらしっかり調べてインタラクティブ版の記事も出すかもです。

またこの記事では事前にコードを用意しておくことは考えていません。事前にコードを用意する前提ならそれこそたとえば proconio みたいにやればよいです。

方法の分類

言語的な問題はさておき、入力の取得方法には大まかに分類しましょう。

まずプログラムが標準入力をどの単位で受け取るかです。

  1. すべて読む(EOL まで読む)
  2. 行ごとに読む(\r, \r\n まで読む)

次に、イテレータなどを使う場合は何を返すイテレータを作るかです。

  1. トークンを返すイテレータ
  2. 行を返すイテレータ

インタラクティブのことなどを特に考えないのであれば 1a が良いと私は思います。b を採用すると Vec の要素が 1 行に並んでいたり、さらに長さが与えられていなかったりするときに便利ではあるのですが、今の競プロ環境は C++ に都合の悪いことはしないはずなので、あまりないと思われます。

方法

正直最新の方法だけ把握していれば良いとは思うのですが、みなさまの頭の片隅にあるであろう「複雑」な標準入力がすでに過去のものになっているということをご理解いただくために、古い書き方もご紹介します。

またいまだに 1.41 からアップデートされていない某有名オンラインジャッジにも対応する必要があると思いますので、お役に立つかとです。

方法 1 最新機能をふんだんに使った方法(Rust 1.65)

現在の tier 1 な書き方の一つだと思います。強みは入力が一つの String にまとまっていて余計なメモリ確保がないこと、弱みは変数束縛が 2 回あることです。

std::io::read_to_string が 1.65 で安定化された機能であることに注意です。

See: Announcing Rust 1.65.0 | Rust Blog

fn main() {
    let stdin = std::io::read_to_string(std::io::stdin()).unwrap();
    let mut stdin = stdin.split_whitespace();
}

方法 2 std::io::read_to_string が使えない場合(Rust 1.62)

これも方法 1 に負けないくらいよい方法だと思います。強みは変数束縛が 1 回しかないこと、弱みは行ごとに Vec に集めるという余計なことをしていることです。

std::io::read_to_string の代わりに 1.62 で安定化された Stdin::lines() を使いましょう。

See: Announcing Rust 1.62.0 | Rust Blog

fn main() {
    let mut stdin = std::io::stdin()
        .lines()
        .map(Result::unwrap)
        .flat_map(|s| s.split_whitespace().map(str::to_string).collect::<Vec<_>>());
}

方法 3 Stdin::lines()が使えない場合(Rust 1.61)

この辺からただ古いだけの書き方になってきます。

Stdin::lines() の代わりに BufRead::lines() を使いましょう。Stdin::lock() が static な参照を返すようになったのが 1.61 からであることに注意です。

See: Announcing Rust 1.61.0 | Rust Blog

use std::io::BufRead;

fn main() {
    let mut stdin = std::io::stdin()
        .lock()
        .lines()
        .map(Result::unwrap)
        .flat_map(|s| s.split_whitespace().map(str::to_string).collect::<Vec<_>>());
}

方法 4 Stdin::lock() が static な参照を返さない場合(Rust 1.1)

一旦変数に束縛しましょう。ほぼ任意バージョンでコンパイルできます。(強いて言うならば std::split_whitespace() が 1.1 なことくらい)

use std::io::BufRead;

fn main() {
    let stdin = std::io::stdin();
    let mut stdin = stdin
        .lock()
        .lines()
        .map(Result::unwrap)
        .flat_map(|s| s.split_whitespace().map(str::to_string).collect::<Vec<_>>());
}

方法5 なぜかよく見る方法

本当になんでこれが流行っているのか謎なのですが、微妙な点がたくさんあります。これだけを見て Rust の標準入力は微妙なんて言わないでいただいてですね。

  • lock() で行分けている → 古い。1.61 のアップデートで分けなくて良くなりました。
  • Stdin::read_line() → 変数束縛は極力避けたい。イテレータを使うなど避ける方法はいくらでもあるはずです。
  • 行ごとに処理 → 競技プログラミングではトークンごとに処理することが多い(一概に悪いとまでは言えませんが)
use std::io::BufRead;

fn main() {
    let stdin = std::io::stdin();
    let mut stdin = stdin.lock();
    let mut s = String::new();
    stdin.read_line(&mut s).unwrap();
}

オマケ 各オンラインジャッジの実行環境(20240526 時点)

AOJェ~~~ がんばってくれいという気持ちになどです。標準入力周りもそうですけれども、[_; N]::into_iter() とか const generics とかいろいろ使えなくて、ライブラリ側で対応する気も失せてもう毎回手書きしています。

参考文献