こんにちは!最近、Rustで競技プログラミングに挑戦しようと奮闘中です。
プログラミングの学習において、競技プログラミングは非常に良いトレーニングになります。
「この後何をすればいいの?」という、学習の停滞期を打破するきっかけにもなるでしょう。
しかし、Rustで競技プログラミングを始めようとした時に、最初にぶつかる壁が…そう、標準入力の受け取りです!
「他の言語では簡単にできるのに、Rustだとどう書けばいいの…?」
「呪文みたいなコードが出てきたけど、これ何…?」
今回は、そんな悩みを解決すべく、Rustで競技プログラミングの入力を受け取るための、基本的なコードとその解説をまとめました。
私自身の学習メモも兼ねていますので、Rust初心者の方も、一緒に標準入力の壁を乗り越えましょう!
Rustで競技プログラミング:まずは標準入力を理解しよう
競技プログラミングでは、問題のデータが標準入力から与えられることがほとんどです。
例えば、AtCoder等で出題される問題では、
1 2
のように、スペース区切りの数値が入力されます。
この入力を受け取り、プログラムで処理できるようにするには、どうすれば良いのでしょうか?
Rustで標準入力を受け取るためのコードは、一見すると複雑に見えるかもしれません。
しかし、一つずつ分解して理解すれば、決して難しくありません!
Rustで標準入力を受け取るコード例 (スペース区切りで2つの整数)
まずは、以下のコードを見てください。
fn main() {
let (a, b) = {
let mut s = String::new();
std::io::stdin()
.read_line(&mut s)
.unwrap();
let mut iter = s.split_whitespace()
.map(|i| i.parse::()
.unwrap());
(iter.next().unwrap(), iter.next().unwrap())
};
println!("{}", a + b);
}このコードは、スペース区切りで2つの整数を受け取り、その合計を出力する、という処理を行います。
…たったこれだけのコードなのに、見慣れない記号やメソッドがたくさん出てきて、混乱してしまいますよね?
私も最初はそうでした!
でも、大丈夫!一つずつ丁寧に解説していきます。
(今回はこのコードについてAIに解説してもらいました。)
AI:
このコードは、大きく分けて以下の2つの部分から構成されています。
- 標準入力から2つの整数を受け取る部分
- 受け取った2つの整数の和を出力する部分
1. 標準入力から2つの整数を受け取る部分
let (a, b) = { let mut s = String::new(); std::io::stdin() .read_line(&mut s) .unwrap(); let mut iter = s.split_whitespace() .map(|i| i.parse::<i32>() .unwrap()); (iter.next().unwrap(), iter.next().unwrap()) };この部分をさらに細かく分解して解説します。
let (a, b) = { ... };これは、Rustの「パターンマッチ」と「ブロック」を組み合わせた構文です。
{ … } の中の処理結果(この場合はタプル)を、let (a, b) というパターンに束縛(代入)しています。
つまり、{ … } の中で計算された2つの値が、それぞれ変数 a と b に格納されます。let mut s = String::new();
String::new()は、新しい空の文字列 (String 型) を作成します。let mut sは、s という名前の変数を宣言し、その変数を「変更可能 (mutable)」にします。- Rustでは、変数はデフォルトで変更不可能 (immutable) です。
mutキーワードをつけることで、後から値を変更できるようになります。この
sは、標準入力から読み込んだ文字列を格納するために使われます。std::io::stdin()
- これは、標準入力を表すオブジェクトを取得します。
std::ioは、標準入出力に関する機能を提供するモジュールです。stdin()関数は、標準入力からの入力を扱うためのStdin構造体のインスタンスを返します。.read_line(&mut s)
- これは、
Stdin構造体のメソッドread_lineを呼び出しています。read_lineメソッドは、標準入力から1行分の文字列を読み込み、それを引数で指定された文字列 (s) に格納します。&mut sは、「sへの変更可能な参照」を渡しています。read_lineは、読み込んだ文字列をsに書き込む必要があるため、変更可能な参照を渡す必要があります。- このメソッドは
Result型を返します。.unwrap()
read_lineメソッドはResult型の値を返します。Resultは、処理が成功したか失敗したかを表す型です。
- 成功した場合:
Ok(読み込んだバイト数)- 失敗した場合:
Err(エラー情報).unwrap()は、ResultがOkの場合はその中身(この場合は読み込んだバイト数)を取り出し、Errの場合はプログラムを強制終了(パニック)させます。- 競技プログラミングでは、入力形式が保証されていることが多いため、エラー処理を省略して
unwrap()を使うことがよくあります。- しかし、本来はエラーハンドリングを適切に行うのが望ましいです。
- ここでは、
read_lineの結果は使用しないため、Resultの中身は捨てられますlet mut iter = s.split_whitespace()...;
s.split_whitespace()は、文字列sを空白文字(スペース、タブ、改行など)で区切って、イテレータ (iterator) を作成します。- イテレータは、要素を順番に取り出すことができるオブジェクトです。
split_whitespace()は、文字列を分割するたびに&str(文字列スライス) を生成するイテレータを返します。let mut iterは、このイテレータをiterという名前の変数に束縛しています。- ここでも
mutを付けているのは、イテレータから要素を取り出すたびにイテレータの状態が変化するためです。.map(|i| i.parse::<i32>().unwrap()) </i32>
.map(...)は、イテレータの各要素に対して、指定された処理を適用するメソッドです。|i| ...は、「クロージャ」と呼ばれる無名関数です。iは、イテレータから取り出された各要素(この場合は&str)を表す仮引数です。...の部分で、iに対する処理を記述します。i.parse::<i32>()は、文字列スライスiをi32型の整数に変換しようと試みます。parse()メソッドもResult型を返します。
- 成功した場合:
Ok(変換された整数)- 失敗した場合:
Err(エラー情報).unwrap()は、parse()の結果がOkの場合はその中身(変換された整数)を取り出し、Errの場合はプログラムをパニックさせます。(iter.next().unwrap(), iter.next().unwrap())
iter.next()は、イテレータから次の要素を取り出します。
- 次の要素がある場合:
Some(要素)- 次の要素がない場合:
None.unwrap()は、next()の結果がSomeの場合はその中身を取り出し、Noneの場合はプログラムをパニックさせます。- ここでは、入力が必ず2つの整数を含むことが前提となっているため、
unwrap()を使っています。(..., ...)は、2つの値からなるタプルを作成します。- ここでは、イテレータから取り出した2つの整数をタプルにまとめています。
2. 受け取った2つの整数の和を出力する部分
println!("{}", a + b);
println!(...)は、指定された文字列を標準出力に出力するマクロです。"{}"は、プレースホルダーと呼ばれるもので、後続の引数の値で置き換えられます。a + bは、変数aとbに格納された整数の和を計算します。最終的に、計算された和が標準出力に出力されます。
はい!AI先生、今回も丁寧な解説ありがとうございます!
おかげで、あの呪文のようなコードが、何をしているのか理解できました。(たぶん)
まとめ:Rustでの競技プログラミング、まずは入力から!
今回は、Rustで競技プログラミングに挑戦する上で避けて通れない、標準入力の受け取り方について解説しました。
一見複雑に見えるコードも、一つずつ分解して理解すれば、それほど難しくありません。
まずは、今回紹介したコードを「おまじない」として覚えてしまい、徐々にその意味を理解していく、という方法でも良いでしょう。
Rustには、まだまだたくさんの機能があります。
所有権、エラー処理、ジェネリクス、トレイト…などなど、学ぶべきことは山積みです。
でも、焦らず、一歩ずつ、着実に学習を進めていけば、きっとRustをマスターできるはず!
私も、競技プログラミングに挑戦しながら、Rustの理解を深めていきたいと思います。