Reputation: 139
I would like to avoid experimental features so I am considering other options before generators.
I have a vector v
which is delimited into groups by 0. The sum of these delimited groups can be accumulated through a for loop or an iterator:
fn main() {
let v = vec![55, 23, 75, 0, 12, 34, 0, 97, 71, 23, 0];
// for loop
let mut acc = 0;
for &vi in &v {
acc += vi;
if vi == 0 {
println!("for yield {}", acc);
acc = 0;
}
}
// iter 'loop'
v.iter()
.scan(0, |acc, &vi| {
*acc += vi;
Some(if vi == 0 {
let total = *acc;
*acc = 0;
Some(total)
} else {
None
})
})
.filter_map(|acc| acc)
.for_each(|acc| println!("iter yield {:?}", acc));
}
scan
is used as an ad hoc coroutine which returns Some(value)
when the iterator has produced a value and None
when the iterator is still processing. None
is filtered out and the sums are printed.
The example above is somewhat trivial as both operations produce the same result; however, my project requires digesting a stream of data (cannot be collected into vector) and conditionally folding them (e.g. a condition here could be if the acc
is divisible by 10, instead of 0 delimited).
The folding condition itself is pipelined such that each pipeline may provide an iterator (think nested coroutines). I'd like to see if there is an alternative to Iterator::scan
-> Iterator::filter_map
whether they use iterators or not. Keep in mind, collecting the entire stream of data is not possible as the stream is potentially unlimited.
Upvotes: 5
Views: 2147
Reputation: 154846
There is nothing wrong with either the for
loop or the version with scan()
. If I were looking for a clearer alternative, I'd consider using group_by
from the itertools
crate. This version is elegant and does almost what you need:
use itertools::Itertools;
v.iter()
.group_by(|&&vi| vi != 0)
.into_iter()
.map(|(_, group)| group.sum::<u32>())
.for_each(|s| println!("sum {}", s));
The problem is that it outputs the numbers 153, 0, 46, 0, 191, and 0. The 0s come from the fact that it considers the element 0 as just another group for which the key (result of the expression vi != 0
) happens to be false rather than true. To fix this, you need to change map
to filter_map
similar to how you did with scan()
:
v.iter()
.group_by(|&&vi| vi != 0)
.into_iter()
.filter_map(|(filt, group)| if filt { Some(group.sum::<u32>()) } else { None })
.for_each(|s| println!("sum {}", s));
Not perfect, but still slightly more elegant than the original formulation because it makes the grouping explicit.
Upvotes: 4