Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cycle a Rust iterator a given number of times

How do I cycle through an iterator a finite number of times?

I would expect the output of something like this to be 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3 and then stop:

vec![1, 2, 3].iter().cycle(4)
//                         ^ but .cycle() doesn't take an argument...

I don't know the length of the iterator to begin with.

like image 502
Jean-François Corbett Avatar asked Dec 19 '19 16:12

Jean-François Corbett


People also ask

What does ITER () do in Rust?

The iter method produces an iterator over immutable references. If we want to create an iterator that takes ownership of v1 and returns owned values, we can call into_iter instead of iter . Similarly, if we want to iterate over mutable references, we can call iter_mut instead of iter .

How do you use an iterator in Rust?

fn last(self) -> Option<Self::Item> Consumes the iterator, returning the last element. This method will evaluate the iterator until it returns None . While doing so, it keeps track of the current element. After None is returned, last() will then return the last element it saw.

Are iterators slow Rust?

The code only looks slow when un-commenting the for loop because it does not do anything otherwise. Iterators are lazy, and only perform some activity when consumed. A for loop is an example of a construct which consumes the iterator.

How do you loop through an array in Rust?

Starting in the 2021 edition, array. into_iter() uses IntoIterator normally to iterate by value, and iter() should be used to iterate by reference like previous editions.


2 Answers

There is no such an iterator in the std lib.

If you know the iterator size, you can take your number times the size of the iterator:

fn cycle_n_times<T: Clone>(slice: &[T], count: usize) -> impl Iterator<Item = &T> {
    slice.iter().cycle().take(slice.len() * count)
}

Or you can write your own that is more general:

pub struct Ncycles<I> {
    orig: I,
    iter: I,
    count: usize,
}

impl<I: Clone> Ncycles<I> {
    fn new(iter: I, count: usize) -> Ncycles<I> {
        Ncycles {
            orig: iter.clone(),
            iter,
            count,
        }
    }
}

impl<I> Iterator for Ncycles<I>
where
    I: Clone + Iterator,
{
    type Item = <I as Iterator>::Item;

    #[inline]
    fn next(&mut self) -> Option<<I as Iterator>::Item> {
        match self.iter.next() {
            None if self.count == 0 => None,
            None => {
                self.iter = self.orig.clone();
                self.count -= 1;
                self.iter.next()
            }
            y => y,
        }
    }
}

#[test]
fn it_work() {
    Ncycles::new(vec![1, 2, 3].iter(), 4).eq(&[1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3]);
}
like image 187
Boiethios Avatar answered Oct 22 '22 23:10

Boiethios


One simple way is to repeat the iterator itself, take the first 4 and flatten:

fn main() {
    let v = vec![1, 2, 3];
    let res = std::iter::repeat(v.iter())
        .take(4)
        .flatten()
        .collect::<Vec<_>>();
    dbg!(res);
}

Some micro-benchmark result using code in this gist comparing 3 different approaches:

  • repeat-take-flatten in this answer
  • hand-rolled loop
  • a cycle_n implementation mimicking Iterator::cycle.

Kudos to rustc, cycle_n consistently outperforms the other two when the input is reasonably large whereas repeat-take-flatten performs the best for small input.

Micro-benchmark between repeat-take-flatten, hand-rolled loop and <code>cycle_n</code>

like image 20
edwardw Avatar answered Oct 23 '22 00:10

edwardw