Rustのイテレータについて

イテレータとは

イテレータは連続したコレクション(配列、ベクタ、ハッシュマップなど)を走査するためのメカニズムで、インデックスを使用せずに要素を処理できます。

forループとイテレータ

forループとイテレータは似ていますが、重要な違いがあります。forループではインデックスを使用して要素にアクセスしますが、イテレータはコレクション全体を直接走査します。 JavaScriptでの例:

let arr = [1, 2, 3];
for (let i = 0; i < arr.length; i++) {
  console.log(arr[i]);
}
Rustでの例:

let arr = [1, 2, 3];
for v in arr {
    println!("{}", v);
}
Rustのforループは実際にはイテレータを使用しており、コンパイラによって自動的に変換されます。配列そのものはイテレータではありませんが、IntoIteratorトレイトを実装することでイテレータとして扱うことができます。 範囲を使ったイテレーションも一般的です:

for i in 1..10 {
    println!("{}", i);
}

イテレータの遅延初期化

イテレータは遅延初期化されます。つまり、イテレータを作成してもすぐに要素を走査しません。要素の消費はイテレータを使用したときに初めて行われます。

let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();

for val in v1_iter {
    println!("{}", val);
}

nextメソッド

イテレータの要素を取得するためにnextメソッドを使用します。nextメソッドはOption型を返し、コレクションの終わりに達するとNoneを返します。 Iteratorトレイトの定義:

pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option;
}
手動でイテレータを使用する例:

fn main() {
    let arr = [1, 2, 3];
    let mut arr_iter = arr.into_iter();

    assert_eq!(arr_iter.next(), Some(1));
    assert_eq!(arr_iter.next(), Some(2));
    assert_eq!(arr_iter.next(), Some(3));
    assert_eq!(arr_iter.next(), None);
}

IntoIterator トレイト

IntoIteratorトレイトはコレクションをイテレータに変換します。イテレータ自体もこのトレイトを実装しています。

impl IntoIterator for I {
    type Item = I::Item;
    type IntoIter = I;

    fn into_iter(self) -> I {
        self
    }
}

into_iter, iter, iter_mut

これらのメソッドはコレクションをイテレータに変換しますが、それぞれ異なるアクセス権限を持ちます。 - into_iter: 所有権を移動 - iter: 不変な参照 - iter_mut: 可変な参照 例:

fn main() {
    let values = vec![1, 2, 3];

    for v in values.into_iter() {
        println!("{}", v)
    }

    let values = vec![1, 2, 3];
    let _values_iter = values.iter();
    println!("{:?}", values);

    let mut values = vec![1, 2, 3];
    let mut values_iter_mut = values.iter_mut();

    if let Some(v) = values_iter_mut.next() {
        *v = 0;
    }

    println!("{:?}", values);
}

消費者とアダプタ

イテレータの消費者はイテレータの要素を消費し、結果を返します。アダプタは新しいイテレータを生成します。

消費者アダプタ

sumメソッドはイテレータの要素を集計します。

fn main() {
    let v1 = vec![1, 2, 3];
    let v1_iter = v1.iter();
    let total: i32 = v1_iter.sum();
    assert_eq!(total, 6);
}

イテレータアダプタ

mapメソッドはイテレータの各要素に対して関数を適用します。

let v1: Vec<i32> = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
assert_eq!(v2, vec![2, 3, 4]);

クロージャーとしてのアダプターパラメータ

クロージャーはイテレータアダプターのパラメータとして使用され、環境から値をキャプチャできます。 例:

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

Iterator トレイトの実装

独自のイテレータを作成するには、Iteratorトレイトを実装します。 例:

struct Counter {
    count: u32,
}

impl Counter {
    fn new() -> Counter {
        Counter { count: 0 }
    }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option {
        if self.count < 5 {
            self.count += 1;
            Some(self.count)
        } else {
            None
        }
    }
}

イテレータのパフォーマンス

イテレータはゼロコスト抽象であり、ランタイムオーバーヘッドを追加せずに効率的に動作します。

イテレータアダプタと消費者アダプタの一覧

イテレータアダプタ

  • map()
  • filter()
  • zip()
  • enumerate()
  • chain()
  • take()
  • skip()
  • inspect()
  • cycle()

消費者アダプタ

  • next()
  • count()
  • sum()
  • product()
  • fold()
  • reduce()
  • last()
  • any()
  • all()
  • find()
  • for_each()
  • collect()

タグ: rust イテレータ トレイト クロージャー パフォーマンス

5月18日 21:03 投稿