Iterator: need to identify the last element

let chunks = s
           .split_terminator(....)
           .map(|e| if E_IS_LAST_ELEMENT() {
                          do_something_special()
                     } else {
                          e
                     }

How can I recognize the last element: E_IS_LAST_ELEMENT()?

I manage to identify the first element with a preceding .enumerate(), but since I do not know how many elements there are in total it does not help here.

2 Likes

Does split_last work for you?

Perhaps something like split last, and collect elements and a do_something_special().

4 Likes

General solution, but you should prefer using an approach that doesn't involve iterating over the entire input needlessly, if possible.

pub trait IdentifyLast: Iterator + Sized {
    fn identify_last(self) -> Iter<Self>;
}

impl<It> IdentifyLast for It where It: Iterator {
    fn identify_last(mut self) -> Iter<Self> {
        let e = self.next();
        Iter {
            iter: self,
            buffer: e,
        }
    }
}

pub struct Iter<It> where It: Iterator {
    iter: It,
    buffer: Option<It::Item>,
}

impl<It> Iterator for Iter<It> where It: Iterator {
    type Item = (bool, It::Item);

    fn next(&mut self) -> Option<Self::Item> {
        match self.buffer.take() {
            None => None,
            Some(e) => {
                match self.iter.next() {
                    None => Some((true, e)),
                    Some(f) => {
                        self.buffer = Some(f);
                        Some((false, e))
                    },
                }
            },
        }
    }
}

fn main() {
    for (last, e) in "abc".chars().identify_last() {
        println!("{:?} ({:?})", e, last);
    }
}
4 Likes

I am not sure. For performance reasons I must avoid to scan through s twice. My .split_terminator(....) is very time consuming.

Is there a way to look ahead one element from inside a .map() or inside a .filter()?

@DanielKeep : thank you for code!

Does it really iterate only 1 time over "abc"?

Is it possible to add fn identify_first(self) -> Iter<Self>; without extra cost?

It can't iterate over the input more than once. That's just not how iterators work.

Just to be sure, thank you.

I like

a lot. It has has many applications!

I wonder if it is possible to generalise it a bit more, f. ex.: .identify_first_last()

for (element, is_first, is_last) in "abc".chars().identify_first_last() {
     ...
}

I like to use the builtin iter::Peekable for any iterator adapter that needs to only look one element ahead, makes the implementation much shorter normally. Here's a playground implementing identify_first_last via iter::Peekable.

1 Like

Also, if you have an ExactSizeIterator (which, unfortunately Chars is not) you can easily iterate over just the middle of the iterator and take the first and last elements separately

fn main() {
    let mut iter = &mut b"abc".iter();

    let len = iter.len();
    assert!(len >= 2);
    println!("first {:?}", iter.next().unwrap());

    for element in iter.take(len - 2) {
        println!("{:?}", element);
    }

    println!("last {:?}", iter.next().unwrap())
}

@Nemo157: This is exactly what I need. Thank you!

I repeat your code here for others to find it more easily:

//! This module contains general helper traits.
use std::{ iter, mem };

/// This trait defines the iterator adapter `identify_first_last()`.
/// The new iterator gives a tuple with an `(element, is_first, is_last)`.
/// `is_first` is true when `element` is the first we are iterating over.
/// `is_last` is true when `element` is the last and no others follow.
pub trait IdentifyFirstLast: Iterator + Sized {
    fn identify_first_last(self) -> Iter<Self>;
}

/// Implement the iterator adapter `identify_first_last()`
impl<I> IdentifyFirstLast for I where I: Iterator {
    fn identify_first_last(self) -> Iter<Self> {
        Iter(true, self.peekable())
    }
}

/// A struct to hold the iterator's state
/// Our state is a bool telling if this is the first element.
pub struct Iter<I>(bool, iter::Peekable<I>) where I: Iterator;


impl<I> Iterator for Iter<I> where I: Iterator {
    type Item = (bool, bool, I::Item);

    /// At `next()` we copy false to the state variable.
    /// And `peek()` adhead to see if this is the last one.
    fn next(&mut self) -> Option<Self::Item> {
        let first = mem::replace(&mut self.0, false);
        self.1.next().map(|e| (first, self.1.peek().is_none(), e))
    }
}


#[test]
fn test_iterator_adaptor_identify_first_last(){

    let mut iter = "abcde".chars().identify_first_last();
    assert_eq!(iter.next(), Some((true,  false, 'a')));
    assert_eq!(iter.next(), Some((false, false, 'b')));
    assert_eq!(iter.next(), Some((false, false, 'c')));
    assert_eq!(iter.next(), Some((false, false, 'd')));
    assert_eq!(iter.next(), Some((false, true , 'e')));

    let mut iter = "a".chars().identify_first_last();
    assert_eq!(iter.next(), Some((true,  true,  'a')));
}
1 Like

Thanks. I really like the point that we can extend the standard library whenever we think it is lacking something. This is sort of like subtyping but is more loosely written.

This topic was automatically closed 30 days after the last reply. We invite you to open a new topic if you have further questions or comments.