Iterator: need to identify the last element


#1
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

Does split_last work for you?

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


#3

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

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()?


#5

@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?


#6

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


#7

Just to be sure, thank you.


#8

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() {
     ...
}

#9

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.


#10

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())
}

#11

@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')));
}