LoginSignup
3
4

More than 3 years have passed since last update.

Rustでイテレータメソッドを使おう

Posted at
1 / 12

こちらLTにて発表させていただきました。
https://uniquevision.connpass.com/event/191020/

自己紹介

  • ユニークビジョン株式会社 1年目
  • プログラミングを学び始めたのは大学生から
  • Rustはだいたいその1年後からずっと触っています

イテレータとは

配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化である。
(Wikipediaより)


イテレータとは

配列やそれに類似する集合的データ構造(コレクションあるいはコンテナ)の各要素に対する繰り返し処理の抽象化である。
(Wikipediaより)

要するに、「いま何番目の要素を見ているか」を気にすることなく順番に各要素を用いた処理を行う方法のことを指しています。


配列へのアクセス

例えばC言語やJavaなどにおいて、配列に対してfor文を用いて順番に添え字でアクセスすると以下のようになります。

int[] list = new int[1, 2, 3]; // Javaの場合
for (int i = 0; i < list.length; i++) {
  println(list[i]); // => 1 2 3
}

これでは単に繰り返し各要素を用いたいだけなのに、変数iの状態まで気にしなくてはなりません。
そこで次のような構文が用意されていたりします。

int[] list = new int[1, 2, 3];
forEach (int v : list) {
  println(v); // => 1 2 3
}

添え字は用いておらず、各要素が一度だけ順番に渡されます。


イテレータとは

改めて...

イテレータとは、for文(for-each文、拡張for文)のように「一連の要素にある処理を適用させる方法」の一つです。


イテレータの特徴

配列やそれに類似する集合的データ構造に対し、「一連の要素を順番に適用させたい(引数に取る)処理」を渡すとその副作用を得られるメソッドがたくさん用意されている。

  • 要素の可変参照を受け取って変化させる
  • 特定の要素だけに絞り込みたい
  • 全く別の新しい構造体を作成する

などなど...


Rustにおけるイテレータの実装方法

RustにはIteratorトレイトというものが用意されています。

これを実装するのに必要なのは、nextメソッドの処理を記述することだけです。あとはそのnextメソッドを用いた様々なメソッドが実装されます。

Iteratorトレイトの実装
struct TenCounter {
  count: usize,
}

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

impl Iterator for TenCounter {
  type Item = usize;

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

// main
let mut counter = TenConter::new();
assert_eq!(counter.next(), Some(1));
assert_eq!(counter.next(), Some(2));
...
assert_eq!(counter.next(), Some(10));
assert_eq!(counter.next(), None);

便利なイテレータメソッドの紹介

map

各要素をクロージャに渡し、その返り値のイテレータを返す

let iter = (1..=3).iter().map(|x|x * 2);
assert_eq!(Some(&2), iter.next());
assert_eq!(Some(&4), iter.next());
assert_eq!(Some(&6), iter.next());

any, all

  • any:ある要素に対しクロージャがtrueを返したらtrue
  • all:すべての要素に対しクロージャがtrueを返したらtrue
let chars = "Rust".chars();
assert!( chars.any(|c|c.is_lowercase()));
assert!(!chars.all(|c|c.is_lowercase()));

enumerate

  • イテレータの各要素を、(インデックス, 要素)というタプルに置き換えたイテレータを返す
assert_eq!(
  Some(1),
  "foo".chars().enumerate().find(|&(i, c)|c == 'o').map(|it|it.0)
);

// ちなみにこっちでも同じ
assert_eq!(
  Some(1),
  "foo".chars().position(|&c|c == 'o')
);

inspect

  • メソッドチェーンしすぎると途中の状態がわかりづらくなる
  • そういった場合に、途中の要素に対して何か操作するわけではなく、単に参照を受け取って状態を確認するなどで用いる
  • 主にデバック用
let list: Vec<i32> = (1..=5)
    .map(|x|x + 2)
    .filter(|x|x >= 5)
    .inspect(|x|println!("{}", x))
    .map(|x|x * 3)
    .collect();

skip, skip_while

与えた回数、もしくはクロージャがtrueを返す間は要素をスキップ(切り捨て)していく

skip
let args: Vec<String> = std::env::args()
    .skip(1) // プログラム名をスキップ
    .collect();
skip_while
let list_over_5: Vec<i32> = (1..=10).skip_while(|x|**x < 5).collect();
assert_eq!(iter.next(), Some(5));
assert_eq!(iter.next(), Some(6));
...
assert_eq!(iter.next(), Some(10));
assert_eq!(iter.next(), None);

filter_map

  • Optionを返すクロージャを渡し、クロージャがSomeを返したものの中身のイテレータを作成する
let list = ["1", "2", "lol"];
let mut iter = a.iter().filter_map(|s| s.parse().ok()); // ok() : Result::Ok(T)をOption::Some(T)に変換する
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

flat_map

  • IntoIteratorトレイトを実装している型を返すクロージャを引数にする
  • 要素をそれぞれイテレータにしつなげていったイテレータを返す。
公式の例
let words = ["alpha", "beta", "gamma"];
let merged: String = words.iter()
    .flat_map(|s| s.chars())
    .collect();
assert_eq!(merged, "alphabetagamma");

filter_mapflat_mapの比較

filter_mapを使う
let list = ["1", "2", "lol"];
let mut iter = a.iter().filter_map(|s| s.parse().ok());
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);
flat_mapを使う
let a = ["1", "2", "lol"];
let mut iter = a.iter().flat_map(|s| s.parse()); // `.ok()` 要らず
assert_eq!(iter.next(), Some(1));
assert_eq!(iter.next(), Some(2));
assert_eq!(iter.next(), None);

Option, Result

  • 同じようにIteratorトレイトを実装している
    • 要素1のイテレータになる
  • 加えて、それぞれの型に実装されているメソッドもイテレータのような挙動をするものが多い

例)

let maybe_i32 = Option::Some(10);
assert_eq!(
  Some(10),
  maybe_i32.filter(|x|*x > 5)
)

イテレータを用いる際の注意点

  • 自身を消費するメソッドがけっこうある
    • 例えばcount()max()など
  • 参照のイテレータなのか、生の要素を持っているイテレータなのか
    • iter()into_iter()

Rustは頻繁にイテレータが用いられる

  • stdモジュールで実装されているメソッドの返り値が、Iteratorトレイトやそれに類似したトレイトを実装していることが多い
  • 複数のデータを扱うような場面ではおおよそ同じような処理で対応可能
  • 上手く扱えば、記述量や可読性を上げることができる

参考にさせていただいた記事

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4