やってみる

アウトプットすべく己を導くためのブログ。その試行錯誤すらたれ流す。

Rustのイテレータ

 要素を順番に返すヤツ。map, zip, filter, skipなどのコレクション操作もできる。

成果物

参考

イテレータ

 イテレータは要素を順に取り出す。Option<Some<T>,None>型を返す。これにより終了判断できる。

生成(Iterator.iter())

 Vec型のイテレータiter()関数の戻り値で得られる。

fn main() {
    let v = vec![1,2,3];
    let i = v.iter();
    println!("{:?}", i); // Iter([1, 2, 3])
}
メソッド 概要
Iterator.iter() 不変参照
Iterator.iter_mut() 可変参照
Iterator.into_iter() 所有権を奪う

for

fn main() {
    let v = vec![1,2,3];
    let i = v.iter();
    for value in v { println!("{}", value); } // 1\n2\n3
}

Iterator.next()

fn main() {
    let v = vec![1,2,3];
    let mut i = v.iter(); // next()を呼ぶならmutableにする必要がある。さもなくばerror[E0596]: cannot borrow `i` as mutable, as it is not declared as mutable
    assert_eq!(i.next(), Some(&1));
    assert_eq!(i.next(), Some(&2));
    assert_eq!(i.next(), Some(&3));
    assert_eq!(i.next(), None);
}

Iteratorトレイト

 Iteratorトレイトは標準ライブラリで以下のように定義されている。全てのイテレータはこれを実装している。

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ...
}

消費アダプタ(consuming adaptors)

 next()を呼び出すメソッドは消費アダプタと呼ばれる。

 sum()はその一例。

fn main() {
    let v = vec![1,2,3];
    let i = v.iter();
    let s:i32 = i.sum();
    assert_eq!(s, 6);

//    println!("{:?}", i); // error[E0382]: borrow of moved value: `i`
    println!("{:?}", v);
}

 なお、sum()の戻り値には型注釈が必須。さもなくば以下エラー。

error[E0282]: type annotations needed
 --> src/main.rs:4:9
  |
4 |     let s = i.sum();
  |         ^
  |         |
  |         cannot infer type
  |         consider giving `s` a type

 また、sum()は所有権を奪う。よってイテレータ変数isum()の呼出後、使えない。

let s:i32 = i.sum();
println!("{:?}", i); // error[E0382]: borrow of moved value: `i`

イテレータアダプタ(iterator adaptors)

 イテレータを別の種類のイテレータに変える。

src/main.rs

fn main() {
    let v = vec![1,2,3];
    v.iter().map(|x| x + 1);
}

 map()はまだ何もしていない。イテレータを消費するまで実行しない。collect()で消費する。

$ cargo run
...
warning: unused `std::iter::Map` that must be used
 --> src/main.rs:3:5
  |
3 |     v.iter().map(|x| x + 1);
  |     ^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: #[warn(unused_must_use)] on by default
  = note: iterators are lazy and do nothing unless consumed```

map()

src/main.rs

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

 map()は主語となるイテレータの各要素すべてに対して、引数で渡したクロージャの戻り値を要素としたVecを返す。 

filter()

 クロージャtrueを返せば、イテレータにその要素が含まれる。falseを返せば含まれない。

src/lib.rs

#[derive(PartialEq, Debug)]
struct Shoe {
    size: u32,
    style: String,
}
fn shoes_in_my_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter()
        .filter(|s| s.size == shoe_size)
        .collect()
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn filters_by_size() {
        let shoes = vec![
            Shoe { size: 10, style: String::from("sneaker") },
            Shoe { size: 13, style: String::from("sandal") },
            Shoe { size: 10, style: String::from("boot") },
        ];

        let in_my_size = shoes_in_my_size(shoes, 10);

        assert_eq!(
            in_my_size,
            vec![
                Shoe { size: 10, style: String::from("sneaker") },
                Shoe { size: 10, style: String::from("boot") },
            ]
        );
    }
}
$ cargo test

独自イテレータ実装

src/lib.rs

struct Counter {
    count: u32,
}
impl Counter {
    fn new() -> Counter { Counter { count: 0 } }
}
impl Iterator for Counter {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        self.count += 1;
        if self.count < 6 { Some(self.count) }
        else { None }
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn calling_next_directly() {
        let mut counter = Counter::new();
        assert_eq!(counter.next(), Some(1));
        assert_eq!(counter.next(), Some(2));
        assert_eq!(counter.next(), Some(3));
        assert_eq!(counter.next(), Some(4));
        assert_eq!(counter.next(), Some(5));
        assert_eq!(counter.next(), None);
    }
}

zip, skip

#[test]
fn using_other_iterator_trait_methods() {
    let sum: u32 = Counter::new().zip(Counter::new().skip(1))
                                 .map(|(a, b)| a * b)
                                 .filter(|x| x % 3 == 0)
                                 .sum();
    assert_eq!(18, sum);
}

 各メソッドは上記の意味である。よってzipは以下の2値がタプルとなったイテレータを返す。

a b
1 2
2 3
3 4
4 5
5 None
None None

 ただし、zipは少なくとも片方がNoneであればそのタプルを生成しない。つまりzipが返すのは以下4組のみ。

a b
1 2
2 3
3 4
4 5

 zipが返すイテレータnext()すると順次以下を返す。

返却値
(1,2)
(2,3)
(3,4)
(4,5)
None

 タプルの各値をかけ合わせ、3で割り切れるか否か。

a b a*b x%3==0
1 2 1*2=2 2%3=2 false
2 3 2*3=6 6%3=1 true
3 4 3*4=12 12%3=0 true
4 5 4*5=20 20%3=2 false

 3で割り切れるものは6, 12の2つ。これらをsumすると、18となる。assert_eq!(18, sum);で確認。

対象環境

$ uname -a
Linux raspberrypi 4.19.42-v7+ #1219 SMP Tue May 14 21:20:58 BST 2019 armv7l GNU/Linux

前回まで