Rustの基礎 - 反復処理(for文)
概要
Rustにおいて、反復処理 (イテレーション) をするfor文、および、範囲(Range)とイテレータを使用した処理を記載する。
Rustのfor文は、イテレータを実装した型 (配列、ベクタ、範囲等) に対して反復処理を行う。
Rustのfor文は、他の言語と異なり、常にイテレータパターンを使用する。
for-in文で反復処理をする
for文を使用してループ処理を記述する場合は、forの後ろに繰り返し変数を記述する。
続けて、in キーワードを記述した後にイテラブルなオブジェクトを記述して、波括弧{}でブロックを囲む。
for 繰り返し変数 in イテラブルなオブジェクト {
実行コード
}
以下の例では、配列から各要素を取得している。
fn main() {
let lang = ["Rust", "C++", "Go"];
for i in lang {
println!("{}", i);
}
}
// 出力
Rust
C++
Go
ベクタ (vec) を使用する場合は、参照を使用する、または、iter メソッドを使用する。
fn main() {
let lang = vec!["Rust", "C++", "Go"];
for i in &lang {
println!("{}", i);
}
}
// 出力
Rust
C++
Go
次に、文字列の各文字を反復処理する。
chars メソッドを使用して、文字列を文字単位で反復処理する。
fn main() {
for s in "Rust".chars() {
println!("{}", s);
}
}
// 出力
R
u
s
t
break文でfor文を抜ける
break文を使用して反復処理を抜けることができる。
以下の例では、取得した文字が's'の時にfor文から抜ける処理をしている。
fn main() {
for s in "Rust".chars() {
if s == 's' {
break;
}
println!("{}", s);
}
}
// 出力
R
u
ラベル付きループを使用すると、入れ子になったループから外側のループを抜けることができる。
fn main() {
'outer: for i in 0..3 {
for j in 0..3 {
if i == 1 && j == 1 {
break 'outer;
}
println!("i = {}, j = {}", i, j);
}
}
}
// 出力
i = 0, j = 0
i = 0, j = 1
i = 0, j = 2
i = 1, j = 0
continue文で処理をスキップする
continue文を使用して、処理をスキップして次の反復に進むことができる。
以下の例では、指定した文字's'の場合のみ処理がスキップしている。
fn main() {
for s in "Rust".chars() {
if s == 's' {
continue;
}
println!("{}", s);
}
}
// 出力
R
u
t
ラベル付きループで、外側のループの次の反復に進むこともできる。
fn main() {
'outer: for i in 0..3 {
for j in 0..3 {
if j == 1 {
continue 'outer;
}
println!("i = {}, j = {}", i, j);
}
}
}
// 出力
i = 0, j = 0
i = 1, j = 0
i = 2, j = 0
for文とHashMap (keys / values / iterメソッド)
HashMap型をfor文で処理する場合を考える。
次のようなHashMap型変数を用意して、for文で処理する。
HashMapをそのまま反復処理すると、キーと値のタプルが取得される。
use std::collections::HashMap;
fn main() {
let mut foods = HashMap::new();
foods.insert("breakfast", "miso soup");
foods.insert("lunch", "sandwich");
foods.insert("dinner", "steak");
for food in &foods {
println!("{:?}", food);
}
}
// 出力
("breakfast", "miso soup")
("dinner", "steak")
("lunch", "sandwich")
※注意
HashMapは順序を保証しないため、出力順序は挿入順と異なる場合がある。
for文とHashMapのkeys()メソッド
HashMapのキーのみを反復処理する場合は、keys メソッドを使用する。
use std::collections::HashMap;
fn main() {
let mut foods = HashMap::new();
foods.insert("breakfast", "miso soup");
foods.insert("lunch", "sandwich");
foods.insert("dinner", "steak");
for food in foods.keys() {
println!("{}", food);
}
}
// 出力
breakfast
dinner
lunch
for文とHashMapのvaluesメソッド
HashMapの値を反復処理で取得する場合は、values メソッドを使用する。
以下の例では、valuesメソッドを使用してHashMapの値を取得している。
use std::collections::HashMap;
fn main() {
let mut foods = HashMap::new();
foods.insert("breakfast", "miso soup");
foods.insert("lunch", "sandwich");
foods.insert("dinner", "steak");
for food in foods.values() {
println!("{}", food);
}
}
// 出力
miso soup
steak
sandwich
for文とHashMapのiterメソッド
HashMapのキーと値をセットで反復処理する場合は、iter メソッドを使用する。
use std::collections::HashMap;
fn main() {
let mut foods = HashMap::new();
foods.insert("breakfast", "miso soup");
foods.insert("lunch", "sandwich");
foods.insert("dinner", "steak");
for (key, value) in &foods {
println!("({}, {})", key, value);
}
}
// 出力
(breakfast, miso soup)
(dinner, steak)
(lunch, sandwich)
また、以下の例では、キーと値を使って文章を出力している。
use std::collections::HashMap;
fn main() {
let mut foods = HashMap::new();
foods.insert("breakfast", "miso soup");
foods.insert("lunch", "sandwich");
foods.insert("dinner", "steak");
for (food, menu) in &foods {
println!("Yesterday, I had {} for {}.", menu, food);
}
}
// 出力
Yesterday, I had miso soup for breakfast.
Yesterday, I had steak for dinner.
Yesterday, I had sandwich for lunch.
範囲 (Range) で反復処理 (数値のシーケンス生成)
for文において、範囲 (Range) が使用されることがよくある。
範囲は、指定した範囲の数値を順に取得するイテラブルなオブジェクトである。
範囲の使用方法は、start..end (endは含まない)、または、start..=end (endを含む) という形で指定する。
fn main() {
for i in 0..5 {
println!("{}", i);
}
}
// 出力
0
1
2
3
4
終了値を含む範囲を使用する場合は、..=演算子を使用する。
fn main() {
for i in 0..=5 {
println!("{}", i);
}
}
// 出力
0
1
2
3
4
5
step メソッド や step_by メソッドを使用して、一定の間隔で反復処理を行うことができる。
fn main() {
for i in (1..10).step_by(3) {
println!("{}", i);
}
}
// 出力
1
4
7
範囲は、collect メソッドを使用してベクターに変換することができる。
fn main() {
let a: Vec<i32> = (1..10).step_by(3).collect();
println!("{:?}", a);
}
// 出力
[1, 4, 7]
逆順で反復処理を行う場合は、rev メソッドを使用する。
fn main() {
for i in (0..5).rev() {
println!("{}", i);
}
}
// 出力
4
3
2
1
0
enumerateメソッドで反復処理 (インデックス付き反復処理)
enumerate メソッドを使用すると、インデックスと値のペアを取得できる。
以下の例では、配列を enumerate メソッドで処理し、インデックスと値を出力している。
fn main() {
let lang = ["Rust", "C++", "Go"];
for (index, value) in lang.iter().enumerate() {
println!("{}: {}", index, value);
}
}
// 出力
0: Rust
1: C++
2: Go
開始インデックスを指定することもできる。
fn main() {
let lang = ["Rust", "C++", "Go"];
for (index, value) in lang.iter().enumerate() {
println!("{}: {}", index + 1, value);
}
}
// 出力
1: Rust
2: C++
3: Go
zipメソッドで反復処理 (複数のイテレータの反復処理)
zip メソッドを使用すると、複数のイテレータを並列に反復処理することができる。
以下の例では、複数の配列をzip()メソッドでまとめて、それぞれの位置に対応した値を出力している。
各配列の共通するインデックスの値が抜き出されて反復処理されている。
要素数が異なる場合は、一番小さい要素数に合わせた回数で反復処理がなされる。
fn main() {
let lang = ["Rust", "C++", "Go"];
let prefix = ["R", "C", "G"];
let suffix = ["t", "+", "o"];
for ((item1, item2), item3) in lang.iter().zip(prefix.iter()).zip(suffix.iter()) {
println!("{} {} {}", item1, item2, item3);
}
}
// 出力
Rust R t
C++ C +
Go G o
3つ以上のイテレータをzip()する場合は、izip! マクロ (itertoolsクレート) を使用するとより簡潔に記述できる。
標準ライブラリのみを使用する場合は、上記のようにzip()をネストさせる。
上記のサンプルコードを範囲とインデックスを使用して記述すると、以下のようになる。
実行すると同様の結果が出力される。
fn main() {
let lang = ["Rust", "C++", "Go"];
let prefix = ["R", "C", "G"];
let suffix = ["t", "+", "o"];
for i in 0..lang.len() {
println!("{} {} {}", lang[i], prefix[i], suffix[i]);
}
}
// 出力
Rust R t
C++ C +
Go G o
イテレータメソッドチェーンによる高度な反復処理
Rustのイテレータは、map、filter、fold メソッド等のメソッドをチェーンすることで、強力な反復処理を記述できる。
mapメソッドで要素を変換する
map メソッドは、各要素に対して関数を適用して新しいイテレータを生成する。
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let doubled: Vec<i32> = numbers.iter().map(|x| x * 2).collect();
println!("{:?}", doubled);
}
// 出力
[2, 4, 6, 8, 10]
filterメソッドで要素を絞り込む
filter メソッドは、条件に一致する要素のみを抽出する。
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let evens: Vec<&i32> = numbers.iter().filter(|x| *x % 2 == 0).collect();
println!("{:?}", evens);
}
// 出力
[2, 4]
mapメソッド と filterメソッド を組み合わせる
複数のイテレータメソッドをチェーンすることで、複雑な処理を簡潔に記述できる。
fn main() {
let numbers = vec![1, 2, 3, 4, 5];
let result: Vec<i32> = numbers.iter()
.filter(|x| *x % 2 == 0)
.map(|x| x * 2)
.collect();
println!("{:?}", result);
}
// 出力
[4, 8]