the code is here
the issue i'm trying to solve
there's find_map: consume an iterator, evaluate a FnMut, first time a Some gets returned abort and return it.
- ✓ no need to loop through the entire range
- ✓ if you need to do some computation with the found result and the computation is somewhat related to the search criterion, you don't need to do work twice, you can do
findandmapin one call
now what if i have >1 independent things that need to be find_maped? I could do
let a = iter.find_map(fn1);
let b = iter.find_map(fn2);
but that would loop through the start of the range twice. I also don't know if both finds will actually find anything (a and/or b might be None) and i don't know if a or b will be found first.
side quest: let me know if i'm missing an existing solution.
what i did
I thought that could be reasonably be done with a try_fold
- keep in the accumulator an optional for each of the searches
- abort when all elements are
Some
like this
fn find_map_two<P1, P2, T1, T2>(mut self, map1: P1, map2: P2) -> (Option<T1>, Option<T2>)
where
Self: Sized,
Self::Item: Clone,
P1: FnMut(&Self::Item) -> Option<T1>,
P2: FnMut(&Self::Item) -> Option<T2>,
{
let mut maps = (map1, map2);
let init = (None, None);
let rv = self.try_fold(init, |mut old, element| {
use seq_macro::seq;
seq!(i in 0..2 {
old.i = old.i.or_else(||maps.i(&element));
});
let mut abort = true;
seq!(i in 0..2 { abort = abort && old.i.is_some();});
if abort {
std::ops::ControlFlow::Break(old)
} else {
std::ops::ControlFlow::Continue(old)
}
});
match rv {
std::ops::ControlFlow::Break(rv) | std::ops::ControlFlow::Continue(rv) => rv,
}
// .into_value();
}
some odd things to remark
- i mutate
old.i = old.i.or_elsebecause of what comes next - same for
let mut abort = true; seq! … abort = abort && …, that's justold.0.is_some() && old.1.is_some()
side quest: anything to remark at this point? hints about how to do impl … MultiFindIterator correctly?
the real question
how do i generalize this over multiple FnMut?
- I believe internally the
FnMutand theOptionshould stay in a tuple and not a fixed size array because the functions might have different signatures (cf my poor little test). - afaik rust macros can't loop, and i might have to instantiate 1…10 tuples and functions.
- i accept that for larger numbers of functions, something with boxing dyns might be necessary, that's out of scope
I switched to a macro for most of the code
macro_rules! find_map_many_impl {
($size:literal, $iter:expr, $init:expr, $maps:expr) => {
{
let rv = $iter.try_fold($init, |mut old, element| {
use seq_macro::seq;
seq!(i in 0..$size {
old.i = old.i.or_else(||$maps.i(&element));
});
let mut abort = true;
seq!(i in 0..$size { abort = abort && old.i.is_some();});
if abort {
std::ops::ControlFlow::Break(old)
} else {
std::ops::ControlFlow::Continue(old)
}
});
match rv {
std::ops::ControlFlow::Break(rv) | std::ops::ControlFlow::Continue(rv) => rv,
}
// .into_value();
}
};
}
And then the non-generic part that remains is
fn find_map_3<P1, P2, P3, T1, T2, T3>(
mut self,
map1: P1,
map2: P2,
map3: P3,
) -> (Option<T1>, Option<T2>, Option<T3>)
where
Self: Sized,
Self::Item: Clone,
P1: FnMut(&Self::Item) -> Option<T1>,
P2: FnMut(&Self::Item) -> Option<T2>,
P3: FnMut(&Self::Item) -> Option<T3>,
{
let mut maps = (map1, map2, map3);
let init = (None, None, None);
find_map_many_impl!(3, self, init, maps)
}
}
Can I do better than that? All the Ti and Pi look like they could do with some deduplication.