Create peek_while for iterator

Viewed 189

I'm trying to create a peek_while method for my iterator which should basically do the same as take_while but only consume the character after the predicate matched. I've taken some inspiration from https://stackoverflow.com/a/30540952/10315665 and from the actual take_while source code https://github.com/rust-lang/rust/blob/2c7bc5e33c25e29058cbafefe680da8d5e9220e9/library/core/src/iter/adapters/take_while.rs#L42-L54 and arrived at this result:

pub struct PeekWhile<I: Iterator, P> {
    iter: Peekable<I>,
    predicate: P,
}

impl<I, P> fmt::Debug for PeekWhile<I, P>
where
    I: Iterator + Debug,
    <I as Iterator>::Item: Debug,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PeekWhile")
            .field("iter", &self.iter)
            .finish()
    }
}

impl<I, P> Iterator for PeekWhile<I, P>
where
    I: Iterator,
    P: FnMut(&I::Item) -> bool,
{
    type Item = I::Item;

    fn next(&mut self) -> Option<I::Item> {
        let n = self.iter.peek()?;
      
        while let Some(n) = self.iter.peek() {
            if (self.predicate)(&n) {
                return self.iter.next();
            } else {
                break;
            }
        }
        None
    }
}

pub trait PeekWhileExt: Iterator {
    fn peek_while<P>(self, predicate: P) -> PeekWhile<Self, P>
    where
        Self: Iterator,
        Self: Sized,
        P: FnMut(&Self::Item) -> bool,
    {
        PeekWhile {
            iter: self.peekable(),
            predicate,
        }
    }
}

impl<I: Iterator> PeekWhileExt for I {}

This is causing an infinite loop, and while I'm not sure why, I see that the take_while::next method does not have a loop, so I changed that to:

let n = self.iter.peek()?;

if (self.predicate)(n) {
    Some(n)
} else {
    None
}

which now gives me:

mismatched types
expected associated type `<I as Iterator>::Item`
         found reference `&<I as Iterator>::Item`

So how would I go about creating such an iterator? Is the code so far correct, and how to I complete it? I know that itertools has https://docs.rs/itertools/0.10.1/itertools/trait.Itertools.html#method.peeking_take_while which sounds promising, but this is a learning project, both in programming concepts and rust itself (I'm creating a JSON parser btw), so I would very much be interested in completing that portion of the code without any libraries.

Example use case (not tested):

let chars = "keyword:".chars();

assert_eq!(chars.peek_while(|c| c.is_alphabetic()).collect::<String>(), "keyword");
assert_eq!(chars.next().unwrap(), ':');
//                                 ^
// Very important that the next char doesn't get lost

Appreciate any help!

0 Answers
Related