こんにちは!最近、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の理解を深めていきたいと思います。