Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to map over only the Some() values in an iterator?

Tags:

rust

Both of the following work (in 2 invocations), but they feel too verbose.


fn main() {
    let v = vec![Some(0), Some(1), None, Some(2)];
    assert_eq!(
        vec![0,2,4],
        v.iter()
            .filter(|x| x.is_some())
            .map(|x| x.unwrap() * 2)
            .collect::<Vec<u8>>());
    assert_eq!(
        vec![0,2,4],
        v.iter()
            .filter_map(|x| *x)
            .map(|x| x*2)
            .collect::<Vec<u8>>());
}

filter_map is close to what I want:

[filter_map] removes the Option layer automatically. If your mapping is already returning an Option and you want to skip over Nones, then filter_map is much, much nicer to use.

doc.rust-lang.org

But it doesn't unwrap the value in the closure because it expects an Option to be returned.

Is there a way to both filter on only the Some values, and map over those values with a single invocation? Such as:

// Fake, does not work
fn main() {
    let v = vec![Some(0), Some(1), None, Some(2)];
    assert_eq!(
        vec![0,2,4],
        v.iter()
            .map_only_some(|x| x * 2)
            .collect::<Vec<u8>>());
}
like image 354
Jordan Avatar asked Sep 18 '25 03:09

Jordan


1 Answers

Well I figured it out, and Iterator's next always returns an Option, so you just have to flatten it:

// Since v.iter().next() is Some(Some(0)), the following works
    assert_eq!(
        vec![0,2,4],
        v.iter()
            .flatten()
            .map(|x| x * 2)
            .collect::<Vec<u8>>());

It's not in a single invocation, but it's much much cleaner and, I think, idiomatic.

like image 80
Jordan Avatar answered Sep 19 '25 15:09

Jordan