こんにちは。
Rust のイテレータまわりでちょっと手こずったので、後日のためのメモです。
まずは次のコードを見てください:
fn main() { let vec = vec![vec![0], vec![1, 2], vec![3, 4, 5]]; for sub_vec in vec.iter() { for x in sub_vec.iter() { println!("{}", x); } } }
実行したい方はこちら:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=9eb206547a1d0390ad22b5137bae4086
これを実行すると 0, 1, 2, 3, 4, 5 の順に表示されます。vec がちょうど入れ子のコンテナになっていて、二重ループで中身をひとつひとつ取り出している形です。
これを、以下のようなコードで実現することを考えます:
struct MyContainer(Vec<Vec<u32>>); impl MyContainer { fn iter(&self) -> ??? { ??? } } fn main() { let vec = vec![vec![0], vec![1, 2], vec![3, 4, 5]]; let con = MyContainer(vec); for x in con.iter() { println!("{}", x); } }
iter メソッドの返り値となる型を定義する必要があり、そこでは Iterator
私なりの答えはこうです↓
struct MyContainer(Vec<Vec<u32>>); impl MyContainer { fn iter(&self) -> MyIter<'_> { MyIter::new(self) } } struct MyIter<'a> { con: &'a MyContainer, iter: Option<(std::slice::Iter<'a, Vec<u32>>, std::slice::Iter<'a, u32>)>, } impl<'a> MyIter<'a> { fn new(con: &'a MyContainer) -> Self { Self { con, iter: None } } } impl<'a> Iterator for MyIter<'a> { type Item = &'a u32; fn next(&mut self) -> Option<Self::Item> { let Some(iter) = &mut self.iter else { // self.iter を準備する let mut outer = self.con.0.iter(); let Some(inner) = outer.next() else { return None; // 内側の Vec がひとつも入っていなかった場合: おしまい }; self.iter = Some((outer, inner.iter())); return self.next(); // 仕切り直し }; let outer = &mut iter.0; let inner = &mut iter.1; // inner から取り出す let Some(item) = inner.next() else { // なかった場合は, outer からとってきて inner に入れる let Some(new_inner) = outer.next() else { return None; // outer が品切れパターン: おしまい }; iter.1 = new_inner.iter(); return self.next(); // 仕切り直し }; Some(item) } } fn main() { let vec = vec![vec![0], vec![1, 2], vec![3, 4, 5]]; let con = MyContainer(vec); for x in con.iter() { println!("{}", x); } }
実行したい方はこちら:
play.rust-lang.org
中間状態を保存するために、外側のイテレータと内側のイテレータを補完するためのフィールドを準備しています。あえて Option にすることでコンストラクタを呼ぶタイミングではイテレータをセットせず、next のタイミングですっからかん判定ができるようになっています。これをしないと「あれっ??取り出せると思ってた内側のイテレータ準備できない・・・」ってなってつらくなります。
三重・四重の入れ子でも似たような実装で実現できると思うんですが、多少面倒くさいので、もっと楽な方法 (あるいは↑を書かなくてもさっと実現できるクレートとか) があれば教えてください。