Rust イテレータ操作まとめ|よく使う16のメソッド+実例
はじめに
Rustを勉強して感じたのが、「イテレータって便利だけど、最初はちょっと難しい」ということでした。
.iter()
や .into_iter()
、.map()
や .filter()
など、よく使われるけれど混乱しやすいメソッドがたくさんあって、私自身もよくつまずきました。
この記事は、自分自身が勉強したこと・つまずいたこと・「これ分かりやすかった!」という気づきを整理してまとめたものです。
同じようにRustを学んでいる方の少しでもヒントになれば嬉しいです。
.iter()
/ .into_iter()
/ .iter_mut()
- 文字列をイテレータに変換
1. -
.iter()
- 中身を覗くだけのイテレータ- データを”借りて”読み取るだけで、元の値はそのまま残る。
- 「ちょっと中身を見て処理したいとき」に使う。
-
.iter()
は値の参照 (&i32) を返す。
-
.into_iter()
- 中身を持っていくイテレータ- ベクタや配列の中身(値そのもの)を取り出して処理する。
- 取り出したら元の変数は使えなくなる(使いきるイメージ)
-
.into_iter()
は値そのもの(所有権)を返す。
-
.iter_mut()
- 中身を書き換えるためのイテレータ- データを”借りて”、その場で編集(更新)できるようにする。
- 値を変えながら処理したいときに使う。
.iter_mut()
は値の可変参照(&mut i32)を返す。
【補足】イテレータって何だっけ?
コレクション(ベクタやハッシュマップなど)の要素を1つずつ取り出すための仕組み。
【補足】この場合の
&
はどういう意味?
「この値、借りていい?」という意味で、変数の参照(ポインタ)を作るために使います。それでmut
が後ろに付いた場合、「この値、借りて変更していい?」という意味になります。
サンプルプログラム
fn main() {
let v: Vec<i32> = vec![1, 2,3];
// .iter()の例
for x in v.iter() { // 値の参照 (&i32) を返す
println!("{}", x); // → 1, 2, 3(*をつけなくても動く)
println!("{}", *x); // → 1, 2, 3(明示的につける場合。説明下記参照)
}
// .into_iter()の例
for x in v.clone().into_iter() { // 値の所有権 (i32) を返す
println!("{}", x); // → 1, 2, 3
}
// .iter_mut()の例
let mut v = vec![1, 2, 3];
for x in v.iter_mut() { // 値の可変参照 (&mut i32) を返す
*x *= 2;
}
}
【補足】 *(アスタリスク)= 借りたものから中身を取り出す
v.iter()
が返す値は、 (&i32)です。そのため、値を取り出す場合は*
をつける必要がありますが、Rustの場合*をつけなくても、Display トレイトを実装している型であれば、中身を自動的に取り出して表示してくれるようです。
ユースケース
メソッド | 処理内容 | 主な用途 | 応用例 | 元のデータ |
---|---|---|---|---|
.iter() | 中身を覗く(借用) | 読み取りだけ | 合計・平均・検索 | 残る |
.into_iter() | 中身を持っていく | 値を使い切りたいとき | フィルタ、文字列操作、型変換 | 消える |
.iter_mut() | 中身をいじる(借用) | 値を変更したいとき | データ変換、条件付き修正、正規化 | 残る |
応用例
iter()
の応用:平均値を計算する(所有権を奪わず読み取る)
fn average(v: &Vec<i32>) -> f32 {
let sum: i32 = v.iter().sum();
sum as f32 / v.len() as f32 // キャストして返す
}
fn main() {
let v = vec![10, 20, 30];
println!("平均: {}", average(&v)); // → 20.0
}
.into_iter()
の応用:値の所有権ごと取り出す(「値そのものを取り出す=所有権を奪う」)
fn main() {
let data = vec![Some("apple".to_string()), None, Some("banana".to_string())];
let result: Vec<String> = data
.into_iter() // 所有権を奪う
.filter_map(|x| x) // Someだけ残す
.collect();
println!("{:?}", result); // → ["apple", "banana"]
}
.iter_mut()
の応用:値の書き換え(その1)
-
numbers.iter_mut()
はVec<i32>
に対して、要素へのミュータブルな参照のイテレータを返します。 - つまり、n は ベクタ内の i32 を“借用している”(=アドレスを受け取っている)
- nはアドレスなので、値に対して演算子する場合は、nの前に
*
を付ける必要がある。
fn main() {
let mut numbers = vec![1,2,3,4,5];
for n in numbers.iter_mut() { // numbers.iter_mut() は Vec<i32> に対して、要素へのミュータブルな参照のイテレータを返します。
if *n % 2 == 0 {
*n *= 10;
}
}
println!("{:?}", numbers); // → [1, 20, 3, 40, 5]
}
.iter_mut()
の応用:値の書き換え(その2)
n main() {
let mut words = vec!["hello".to_string(), "rust".to_string()];
for word in words.iter_mut() {
*word = word.to_uppercase();
}
println!("{:?}", words); // → ["HELLO", "RUST"]
}
.map()
- 各要素を変換
2. 下記のv
とsquared
の所有権は、独立している。
.collect()
は新しい Vec<i32>
を生成して、それを squared
に格納する。
fn main() {
// vとsquaredの所有権は、独立している。.collect() は新しい Vec<i32> を生成して、それを squared に格納
let v = vec![1, 2, 3];
let squared: Vec<_> = v.iter().map(|x| x * x).collect();
println!("{:?}", squared); // → [1, 4, 9]
}
ちなみに、mapの引数には&i32
が渡ってくるので、こういう書き方もできる。
let squared: Vec<_> = v.iter().map(|x| *x * *x).collect();
.filter()
- 条件に合うものだけ残す
3. .filter()
は、v
の要素(1, 2, 3, 4, 5)
を1つずつ取り出して使う。
残ったものを、新しいVecを生成して、それをevent
に格納する。
こちらも同様、v
とevent
の所有権は独立している。
fn main() {
let v = vec![1, 2, 3, 4, 5];
// vの要素(1, 2, 3, 4, 5)を1つずつ取り出して使う(所有権を奪う
// 残ったものをVecに再構成する
let event: Vec<_> = v.into_iter().filter(|x| x % 2 == 0).collect();
println!("{:?}", event); // → [2, 4]
}
.enumerate()
- (インデックス, 要素) に変換
4. ベクタのインデックスを参照したい場合に使用する。
fn main() {
let v = vec!["a", "b", "c"];
for (i, val) in v.iter().enumerate() {
println!("{}: {}", i, val); // → 0: a, 1: b, 2: c
}
}
ちなみに、こういう書き方もできる。
for (i, val) in v.iter().enumerate() {
println!("{}: {}", i, *val); // → 0: a, 1: b, 2: c
}
.enumerate()
を使用して、要素を更新したい場合は.iter_mut()
に変更する。
サンプルコード:偶数番目だけ2倍にする
fn main() {
let mut numbers = vec![1, 2, 3, 4, 5];
for (i, val) in numbers.iter_mut().enumerate() {
if i % 2 == 0 {
*val *= 2;
}
}
println!("{:?}", numbers); // → [2, 2, 6, 4, 10]
}
.fold()
- 初期値から集約(畳み込み)
5. iter.fold(初期値, |累積値, 要素| { 処理 })
- 下記のプログラムは、ベクタ
[1, 2, 3]
の要素をひとつずつ処理して、全部足し合わせている。 - accは、初期値0から始まり、各要素xを加算していく
- xは、vの要素
(1, 2, 3)
を1つずつ取り出して使う(所有権を奪う) -
fold()
は.into_iter()
でも問題なく動きます。into_inter()
とiter()
の違いは、実行後に元のVecのデータが消えるか、残るかの違い。-
into_iter()
は、元のVecのデータは消える -
iter()
は、元のVecのデータは残る
-
fn main() {
let sum = vec![1, 2, 3]
.iter() // → Iterator<Item = &i32>
.fold(
0, // 初期値:0
|acc, x| // acc: 累積値, x: &i32(今の要素)
acc + x,
); // 足し算して次のaccに渡す
println!("{}", sum); // → 6
}
実際の処理の流れ
初期値 acc = 0
1回目: acc = 0, x = &1 → acc + x = 1
2回目: acc = 1, x = &2 → acc + x = 3
3回目: acc = 3, x = &3 → acc + x = 6
→ 結果:6
.reduce()
- 「最初の2つの値からスタートして、順番に1つずつ合体していく」処理で、最終的に 1つの結果を返してくれる(なにもなければ None)。
6. reduce()
の戻り値は Option<T> になります。なぜ、Option<T>なのかというと、要素が1つもない場合に、返す値が存在しないからです。
fn main() {
let max: Option<i32> = vec![3, 6, 1, 8].into_iter().reduce(|a, b| a.max(b));
println!("{:?}", max); // → Some(8)
}
.take(n)
/ .skip(n)
- 要素を一部だけ処理
7. -
.take(n)
は、最初のn個の要素を取り出す。 -
.skip(n)
は、最初のn個の要素をスキップする。
fn main() {
let v = vec![1, 2, 3, 4, 5];
// take(n) は、最初のn個の要素を取り出す
println!("{:?}", v.iter().take(3).collect::<Vec<_>>()); // [1, 2, 3]
// skip(n) は、最初のn個の要素をスキップする
println!("{:?}", v.iter().skip(2).collect::<Vec<_>>()); // [3, 4, 5]
}
.any(
) / .all()
- 条件判定(bool)
8. -
.any()
は、イテレータの要素のうち、1つでも条件を満たすものがあれば**true
**を返す。 -
.all()
は、イテレータの要素が全て条件を満たす場合に**true
**を返す。
fn main() {
let v = vec![1,2,3];
println!("{}", v.iter().any(|&x| x > 2)); // → true
// 又は println!("{}", v.iter().any(|x| *x > 2)); // → true
println!("{}", v.iter().all(|&x| x > 2)); // → false
// 又は、println!("{}", v.iter().all(|x| *x > 2)); // → false
}
【補足】
&
を付けた引数で受け取る場合の挙動はどうなるの?
結論:引数に&
をつけると参照を外して、値そのものを受け取る意味合いになる。
- |x| → xは&32(数値への参照)
- |&x| → 渡してあげるときに「参照じゃなくて、値そのものを渡して」という意味
✅ もし |x| だけにしたら?
明示的に*x
して使う必要があります。v.iter().any(|x| *x % 2 == 0);
.find()
/ .postion()
- 条件に合う最初の要素/位置
9. -
.find()
は、条件を満たす最初の要素をSomeで包んで返す。 -
.postion()
は、条件を満たす最初の要素のインデックスをSomeで包んで返す。
fn main() {
let v = vec![10, 20, 30];
let found = v.iter().find(|&&x| x == 20);
// 又はlet found = v.iter().find(|x| **x == 20);
println!("{:?}", found); // → Some(&20)
let pos = v.iter().position(|&x| x == 20);
// 又は let pos = v.iter().position(|x| *x == 20);
println!("{:?}", pos); // → Some(1)
}
【補足】
&&x
って何だ??
.any
と.all
の補足で、&xは参照じゃなくて、もう中身だけ欲しい(=値そのもの)ということでした。ところが、.find
の場合は、要素そのものではなく、その要素への参照を意味するものを引数として渡し、処理したいので&&
がつきます。
というのも、find
は下記のような感じで実装されていて(イメージ↓)
find関数の処理の中で&item
を渡すようなことをやってます。
そのため、最終的にクロージャには&&i32
(参照の参照)が渡ってくるわけです。なのでクロージャの引数が|&x|
にするとエラーになる。また|x|
で受け取って処理する場合は、**
をつける必要がある。fn find<P>(&mut self, predicate: P) -> Option<Self::Item> where P: FnMut(&Self::Item) -> bool, { for item in self { if predicate(&item) { return Some(item); } } None }
.rev()
- 逆順に処理
10. fn main() {
let v = vec![1, 2, 3, 4, 5];
for x in v.iter().rev() {
println!("{}", x); // → 5, 4, 3, 2, 1
}
}
.zip()
- 2つのイテレータを並べて、同時に1つずつ取り出してペアにする
11. .zip()
は、2つのイテレータから1つずつ要素を取り出して、タプルにして返す。
2つのイテレータの要素数が異なる場合、短い方に合わせる。
fn main() {
let a = vec![1, 2, 3, 4];
let b = vec!["a", "b", "c"];
// zip() は、2つのイテレータから1つずつ要素を取り出して、タプルにして返す
// 2つのイテレータの要素数が異なる場合、短い方に合わせる
for (n, s) in a.iter().zip(b.iter()) {
println!("{} - {}", n, s); // → 1 - a, 2 - b, 3 - c
}
}
応用例:
✅ 2つのリストで「成績表」を作る
例えば、名前と点数のベクタから、メッセージを作りたい
fn main() {
let names = vec!["山田", "佐藤", "鈴木"];
let scores = vec![80, 75, 90];
for (name, score) in names.iter().zip(scores.iter()) {
println!("{}さんの点数は{}です", name, score); // → 山田さんの点数は80です, 佐藤さんの点数は75です, 鈴木さんの点数は90です
}
}
.chain()
- イテレータを連結
12. .chain()
は、2つのイテレータを連結して1つのイテレータにする。
2つのイテレータの要素数が異なる場合、最初のイテレータから順に取り出す。
fn main() {
let a = vec![1, 2, 3, 4];
let b = vec![5, 6, 7, 8];
// chain() は、2つのイテレータを連結して1つのイテレータにする
// 2つのイテレータの要素数が異なる場合、最初のイテレータから順に取り出す
let combined: Vec<_> = a.into_iter().chain(b.into_iter()).collect::<Vec<_>>();
println!("{:?}", combined); // → [1, 2, 3, 4, 5, 6, 7, 8]
}
.flat_map()
- ネストされたイテレータをフラットにする
13. .flat_map()
は、各要素をイテレータに変換して、それを1つのイテレータに連結する。
この場合、各要素を空白文字で分割して、それを1つのイテレータに連結している。
fn main() {
let words = vec!["a b", "c d"];
// flat_map() は、各要素をイテレータに変換して、それを1つのイテレータに連結する
// この場合、各要素を空白文字で分割して、それを1つのイテレータに連結している
let chars: Vec<_> = words.iter().flat_map(|s| s.split_whitespace()).collect();
println!("{:?}", chars); // ["a", "b", "c", "d"]
}
応用例:
✅ 文字列リストからすべての単語を取り出す
例えば、複数の文から、単語だけ全部取り出したい
💡 各文を単語に分け、それを平坦にひとつのリストにするのが flat_map!
fn main() {
let sentences = vec![
"Rust is a systems programming language",
"It is blazingly fast",
"It is memory efficient",
];
let words: Vec<&str> = sentences
.iter()
.flat_map(|s| s.split_whitespace())
.collect::<Vec<&str>>();
println!("{:?}", words); // → ["Rust", "is", "a", "systems", "programming", "language", "It", "is", "blazingly", "fast", "It", "is", "memory", "efficient"]
}
.collect()
- ベクタや文字列に変換
14. fn main() {
let v: Vec<_> = (1..=10).collect::<Vec<_>>();
println!("{:?}", v); // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let s : String = "Rust".chars().collect::<String>();
println!("{}", s); // → Rust
}
.count()
/ .sum()
/ .product()
15. -
.count()
は、イテレータの要素数を返す。 -
.sum()
は、イテレータの要素を足し合わせる。 -
.product()
は、イテレータの要素を掛け合わせる。
fn main() {
let v: Vec<i32> = vec![1, 2, 3, 4, 5];
// count() は、イテレータの要素数を返す
// sum() は、イテレータの要素を足し合わせる
// product() は、イテレータの要素を掛け合わせる
println!("{}", v.iter().count()); // → 5
println!("{}", v.iter().sum::<i32>()); // → 15
println!("{}", v.iter().product::<i32>()); // → 120
}
.inspect()
- デバッグ用の中間処理
16. .inspect()
は、各要素を取り出して、その値を表示する。用途はデバッグ。
fn main() {
let v = vec![1, 2, 3, 4, 5];
// inspect() は、各要素を取り出して、その値を表示する。用途はデバッグ
let doubled: Vec<_> = v.iter().inspect(|x| println!("値: {}", x)).map(|x| x * 2).collect();
println!("{:?}", doubled); // → [2, 4, 6, 8, 10]
}
応用例:
✅ map/filterチェーン中にデバッグしたい
例えば、今どういうデータが流れてるか確認したい時とかに便利
fn main() {
let result: Vec<_> = (1..=5)
.inspect(|x| println!("現在の値: {}", x))
.map(|x| x * 2)
.inspect(|x| println!("2倍の値: {}", x))
.filter(|&x| x > 5)
.collect::<Vec<_>>();
println!("{:?}", result); // → [6, 8, 10]
}
💡 .inspect() はログのように途中経過を観察できる最強デバッグツール!
しかもチェーンの流れを壊さない。
🎉 おわりに(まとめ)
この記事で紹介した内容は、私自身がRustを学びながら「これ、よく出てくるな」と思ったメソッドたちです。
特に .map()
や .filter()
、.zip()
、.inspect()
などは、実際に使いこなせるとコードの表現力がぐっと広がります。
まだまだ学び途中ではありますが、これからRustを触ってみたい方や、同じように勉強中の方と一緒に成長していけたらと思っています。
最後まで読んでいただき、ありがとうございました。
少しでも参考になっていれば幸いです。お互いRustライフ楽しんでいきましょう!
Discussion