Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add circular_tuple_windows #350

Merged
merged 1 commit into from Mar 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/lib.rs
Expand Up @@ -127,7 +127,7 @@ pub mod structs {
pub use crate::sources::{RepeatCall, Unfold, Iterate};
#[cfg(feature = "use_std")]
pub use crate::tee::Tee;
pub use crate::tuple_impl::{TupleBuffer, TupleWindows, Tuples};
pub use crate::tuple_impl::{TupleBuffer, TupleWindows, CircularTupleWindows, Tuples};
#[cfg(feature = "use_std")]
pub use crate::unique_impl::{Unique, UniqueBy};
pub use crate::with_position::WithPosition;
Expand Down Expand Up @@ -584,6 +584,40 @@ pub trait Itertools : Iterator {
tuple_impl::tuple_windows(self)
}

/// Return an iterator over all windows, wrapping back to the first
/// elements when the window would otherwise exceed the length of the
/// iterator, producing tuples of a specific size (up to 4).
///
/// `circular_tuple_windows` clones the iterator elements so that they can be
/// part of successive windows, this makes it most suited for iterators
/// of references and other values that are cheap to copy.
///
/// ```
/// use itertools::Itertools;
/// let mut v = Vec::new();
/// for (a, b) in (1..5).circular_tuple_windows() {
/// v.push((a, b));
/// }
/// assert_eq!(v, vec![(1, 2), (2, 3), (3, 4), (4, 1)]);
///
/// let mut it = (1..5).circular_tuple_windows();
/// assert_eq!(Some((1, 2, 3)), it.next());
/// assert_eq!(Some((2, 3, 4)), it.next());
/// assert_eq!(Some((3, 4, 1)), it.next());
/// assert_eq!(Some((4, 1, 2)), it.next());
/// assert_eq!(None, it.next());
///
/// // this requires a type hint
/// let it = (1..5).circular_tuple_windows::<(_, _, _)>();
/// itertools::assert_equal(it, vec![(1, 2, 3), (2, 3, 4), (3, 4, 1), (4, 1, 2)]);
/// ```
fn circular_tuple_windows<T>(self) -> CircularTupleWindows<Self, T>
where Self: Sized + Clone + Iterator<Item = T::Item> + ExactSizeIterator,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any issues with eliminating the ExactSizeIterator bound?

Copy link
Contributor Author

@ed-bassett ed-bassett Aug 4, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added ExactSizeIterator so that the call to len() on line 198 on in tuple_impl.rs would be performant. Using tuple_windows(iter.cycle()).take(len) was a neat way of getting the extra elements wrapped onto the end, but if removing the ExactSizeIterator constraint is preferable we could potentially go with something like the following, at the expense of a slight reduction in readability:

pub struct CircularTupleWindows<I, T: Clone>
    where I: Iterator<Item = T::Item> + Clone,
          T: TupleCollect + Clone,
{
    iter: TupleWindows<Chain<Chain<IntoIter<<T as TupleCollect>::Item>, I>, IntoIter<<T as TupleCollect>::Item>>, T>,
}

pub fn circular_tuple_windows<I, T>(mut iter: I) -> CircularTupleWindows<I, T>
    where I: Iterator<Item = T::Item> + Clone,
          T: TupleCollect + Clone,
          T::Item: Clone,
{
    let first_elements = iter.by_ref().take(<T as TupleCollect>::num_items() - 1).collect::<Vec<_>>();
    let iter = tuple_windows(first_elements.clone().into_iter().chain(iter).chain(first_elements.into_iter()));

    CircularTupleWindows {
        iter: iter,
    }
}

impl<I, T> Iterator for CircularTupleWindows<I, T>
    where I: Iterator<Item = T::Item> + Clone,
          T: TupleCollect + Clone,
          T::Item: Clone,
{
    type Item = T;

    fn next(&mut self) -> Option<T> {
        self.iter.next()
    }
}

what do you think?

T: tuple_impl::TupleCollect + Clone,
T::Item: Clone
{
tuple_impl::circular_tuple_windows(self)
}
/// Return an iterator that groups the items in tuples of a specific size
/// (up to 4).
///
Expand Down
45 changes: 45 additions & 0 deletions src/tuple_impl.rs
@@ -1,6 +1,9 @@
//! Some iterator that produces tuples

use std::iter::Fuse;
use std::iter::Take;
use std::iter::Cycle;
use std::marker::PhantomData;

// `HomogeneousTuple` is a public facade for `TupleCollect`, allowing
// tuple-related methods to be used by clients in generic contexts, while
Expand Down Expand Up @@ -184,6 +187,48 @@ impl<I, T> Iterator for TupleWindows<I, T>
}
}

/// An iterator over all windows,wrapping back to the first elements when the
/// window would otherwise exceed the length of the iterator, producing tuples
/// of a specific size.
///
/// See [`.circular_tuple_windows()`](../trait.Itertools.html#method.circular_tuple_windows) for more
/// information.
#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
#[derive(Debug)]
pub struct CircularTupleWindows<I, T: Clone>
where I: Iterator<Item = T::Item> + Clone,
T: TupleCollect + Clone
{
iter: Take<TupleWindows<Cycle<I>, T>>,
phantom_data: PhantomData<T>
}

pub fn circular_tuple_windows<I, T>(iter: I) -> CircularTupleWindows<I, T>
where I: Iterator<Item = T::Item> + Clone + ExactSizeIterator,
T: TupleCollect + Clone,
T::Item: Clone
{
let len = iter.len();
let iter = tuple_windows(iter.cycle()).take(len);

CircularTupleWindows {
iter: iter,
phantom_data: PhantomData{}
}
}

impl<I, T> Iterator for CircularTupleWindows<I, T>
where I: Iterator<Item = T::Item> + Clone,
T: TupleCollect + Clone,
T::Item: Clone
{
type Item = T;

fn next(&mut self) -> Option<T> {
self.iter.next()
}
}

pub trait TupleCollect: Sized {
type Item;
type Buffer: Default + AsRef<[Option<Self::Item>]> + AsMut<[Option<Self::Item>]>;
Expand Down