Peek filter iterator

Is there a filter iterator that can also peek one item ahead? For example, if I have a function that says whether a byte should be skipped (based on both the byte itself and the next byte):

// Returns true if `byte` should be skipped.
// Or false otherwise.
fn skip_byte(byte: u8, next: Option<u8>) -> bool {
    match next {
        Some(n) => byte == n,
        _ => false
    }
}

Then can I use it to filter a list, like this:

fn main() {
     let bytes = [0u8, 1,2, 3, 3, 4, 4, 5];
     // Uses a hypothetical `peek_filter` iterator.
     let result = bytes.iter().peek_filter(skip_byte).collect();
}

https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.peekable

The above method converts(?) the Iterator into a Peekable which adds the method .peek() which you could then use with the regular filter() method.

But you can’t use peek inside the filter function.

    let a = [5u8,7,9,42,1,8];
    let mut peekable = a.iter().peekable();
    let mut iter = peekable.filter(|x| {
        match peekable.peek() {
            Some(n) => **n == **x,
            None => false
        }
    });
    iter.next();

The borrow checker will complain about this code.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=fc8bf83eb030329c2686c7d1c6c168e9

fn main() {
     let a = [5u8,7,9,42,1,8];
    let mut peekable = a.iter().peekable();
    while let Some(x) = peekable.next() {
        match peekable.peek() {
            Some(n) => *n == x,
            None => false
        };
    }
}

According to SO, it wasn’t possible in 2017 and I have a feeling it’s still not possible. The above code works and I guess you could somehow hack on an addition to the Peekable or Iterator to create a callable fn (maybe?)

Instead of looking into the future you could look into the past.

fn main() {
    let a = [5u8, 7, 9, 9, 42, 1, 8, 9];
    let mut prev = None::<&u8>;
    let b: Vec<_> = a
        .iter()
        .filter(|x| match prev {
            Some(ref n) if n == x => false,
            _ => {
                prev = Some(x);
                true
            }
        })
        .collect();

    println!("{:?}", a);
    println!("{:?}", b);
}
2 Likes

Thanks, I think that’ll work.

In case it interests anyone, here’s a version that uses scan to pass state instead of a local variable:

fn main() {
    let a = [5u8, 7, 9, 9, 42, 1, 8, 9];
    let b: Vec<_> = a
        .iter()
        .scan((None::<u8>, None::<u8>), |state, &x| { 
            *state = (state.1, Some(x));
            Some(*state)
        })
        .filter_map(|data| {
            if data.0 == data.1 {
                None
            } else {
                data.1
            }
        })
        .collect();

    println!("{:?}", a);
    println!("{:?}", b);
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6dd444841445d2f0985d62ab0b5294f0

3 Likes

My slightly improved version

fn main() {
    let a = [5u8, 7, 9, 9, 42, 1, 8, 9];
    let b: Vec<_> = a
        .iter()
        .scan(None, |state, x| {
            let prev = state.take();
            *state = Some(x);
            Some((prev, Some(x)))
        })
        .filter_map(|(prev, x)| {
            if prev == x {
                None
            } else {
                x
            }
        })
        .collect();

    println!("{:?}", a);
    println!("{:?}", b);
}
2 Likes