イテレータ
Rustには様々な便利なイテレータがあります。ここでは、しばしば使うものの、頻繁には使わないイテレータであるscanとfoldを紹介します。
どちらも、「いままでの情報」をもちつつ、ループを走査したいような場合に有効なイテレータです。「いままでの情報」をもてると何がうれしいかというと、mut
な変数をループの外に用意する必要がなくなり、コードの見通しが良くなります。
fold
まずは、特に癖もなく、扱いやすいfold
からです。例えば、イテレータの合計を求めたい場合などに使用することができます。
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum = v.iter().fold(0, |sum, x| sum + x);
println!("{}", sum); // 55
基本的には見たままかと思いますが、第一引数に0
を与えています。max
などのような、イテレータが空であった場合に対応するためにOption
でラップするようなインターフェースにしないためだと思います。
ちなみに、合計を計算するためにはsumというメソッドも使うことができますが、こちらは出力に型を明示する必要があります。また、Add
トレイトとDefault
トレイトが定義されていればいいというようなインターフェースにはなっておらず、usize
などのプリミティブな型にしか使えないようです(参照)
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let sum: u64 = v.iter().sum();
println!("{}", sum); // 55
scan
次に、scan
です。こちらは少し癖があります。先ほどのfold
がイテレータから一つの値を返すメソッドだったのに対して、こちらのscan
はイテレータの要素数と同じ数だけ値を返すようなメソッドになります。たとえば、NumPyのcumsum関数のように接頭辞和を求めたい場合は、次のように書くことができます。
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let cumsum: Vec<_> = v.iter().scan(0, |cum, x|{
*cum += x;
Some(*cum)
}).collect();
println!("{:?}", cumsum); // [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
fold
同様第一引数に初期状態を与えるほか、scan
については、クロージャの中の処理が主に2つ必要になります。
- 次の要素の処理に使うための情報 (
cum
の参照先への代入に当たる部分) - イテレータとして返す値 (
Some(*cum)
というクロージャの返り値に当たる部分)
まずは、1. について、クロージャの第一引数にはmut
な参照が与えれるようになっており、これは、つまるところ、普通にループで書いたときにループの外に用意するようなmut
な変数にあたります。ただし、所有権は持っておらず、あくまで参照なので、参照先の値を変更するためには*
によって参照を外してやる必要があります。
初期状態をBox
でラップし、Copy
トレイトによって変数のアドレスが変わらないようにして少し覗いてみると、ループを通して参照先が変化していないことがわかりそうな気がします。
let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let cumsum: Vec<_> = v.iter().scan(Box::new(0), |cum, x|{
println!("{:p}", *cum);
**cum += *x;
Some(**cum)
}).collect();
println!("{:?}", cumsum); // [1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
0x5586a9ad7ad0
[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]
そして、2.の、クロージャの返り値について、これはこのイテレータに対して、next
メソッドを呼んだ際の返り値になります。next
メソッドの返り値はOption
でラップされており、None
が返ってくるとそのイテレータは終了したとみなす実装となっていることが多いです。ここでも例にもれず、Some
でラップした値を返すことで、イテレータを続けます。Some
の中で*
によって参照外しをしているのは、ここで参照を返してしまうとライフタイムの制約に違反するためです。明示的にclone
メソッドを呼んでもいいと思います。
さいごに
fold
とscan
の中間というか、scan
のmut
な参照をなくし、fold
のようにクロージャの前の返り値を次のクロージャの第一引数に与えるというようなイテレータがあれば微妙に便利な気もするのですが、そういうのは(少し探した限りでは)ないようです。
何か思い出したら追記する可能性があります