Repository: rust-itertools/itertools (language: rust)

Issue:
Implement ExactSizeIterator for CircularTupleWindows
`.circular_tuple_windows()` will produce the same amount of elements as the upstream iterator, so I believe the implementation should be pretty simple.

Speaking about the practical example, I want to convert a vector of 2D points to pairs of normalized vectors.

```rust
  let vecs = shape
    .into_iter()
    .circular_tuple_windows::<(_, _)>()
    .map(|((x1, y1), (x2, y2)): (Point, Point)| (x2 - x1, y2 - y1) as Point)
    .circular_tuple_windows(); //  trait `ExactSizeIterator` is not satisfied
```


Discussion / hints:

Fix patch:
diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs
index 0aea97716..ba1c69df6 100644
--- a/src/tuple_impl.rs
+++ b/src/tuple_impl.rs
@@ -3,7 +3,6 @@
 use std::iter::Cycle;
 use std::iter::Fuse;
 use std::iter::FusedIterator;
-use std::iter::Take;
 use std::marker::PhantomData;
 
 // `HomogeneousTuple` is a public facade for `TupleCollect`, allowing
@@ -185,6 +184,20 @@ where
         }
         None
     }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        // At definition, `T::num_items() - 1` are collected
+        // so each remaining item in `iter` will lead to an item.
+        self.iter.size_hint()
+    }
+}
+
+impl<I, T> ExactSizeIterator for TupleWindows<I, T>
+where
+    I: ExactSizeIterator<Item = T::Item>,
+    T: HomogeneousTuple + Clone,
+    T::Item: Clone,
+{
 }
 
 impl<I, T> FusedIterator for TupleWindows<I, T>
@@ -208,7 +221,8 @@ where
     I: Iterator<Item = T::Item> + Clone,
     T: TupleCollect + Clone,
 {
-    iter: Take<TupleWindows<Cycle<I>, T>>,
+    iter: TupleWindows<Cycle<I>, T>,
+    len: usize,
     phantom_data: PhantomData<T>,
 }
 
@@ -219,10 +233,11 @@ where
     T::Item: Clone,
 {
     let len = iter.len();
-    let iter = tuple_windows(iter.cycle()).take(len);
+    let iter = tuple_windows(iter.cycle());
 
     CircularTupleWindows {
         iter,
+        len,
         phantom_data: PhantomData {},
     }
 }
@@ -236,10 +251,35 @@ where
     type Item = T;
 
     fn next(&mut self) -> Option<Self::Item> {
-        self.iter.next()
+        if self.len != 0 {
+            self.len -= 1;
+            self.iter.next()
+        } else {
+            None
+        }
+    }
+
+    fn size_hint(&self) -> (usize, Option<usize>) {
+        (self.len, Some(self.len))
     }
 }
 
+impl<I, T> ExactSizeIterator for CircularTupleWindows<I, T>
+where
+    I: Iterator<Item = T::Item> + Clone,
+    T: TupleCollect + Clone,
+    T::Item: Clone,
+{
+}
+
+impl<I, T> FusedIterator for CircularTupleWindows<I, T>
+where
+    I: Iterator<Item = T::Item> + Clone,
+    T: TupleCollect + Clone,
+    T::Item: Clone,
+{
+}
+
 pub trait TupleCollect: Sized {
     type Item;
     type Buffer: Default + AsRef<[Option<Self::Item>]> + AsMut<[Option<Self::Item>]>;


Test patch:
diff --git a/tests/quick.rs b/tests/quick.rs
index 1a19d60e8..3dc23d704 100644
--- a/tests/quick.rs
+++ b/tests/quick.rs
@@ -1117,6 +1117,10 @@ quickcheck! {
         true
     }
 
+    fn circular_tuple_windows_exact_size(a: Vec<u8>) -> bool {
+        exact_size(a.iter().circular_tuple_windows::<(_, _, _, _)>())
+    }
+
     fn equal_tuple_windows_1(a: Vec<u8>) -> bool {
         let x = a.windows(1).map(|s| (&s[0], ));
         let y = a.iter().tuple_windows::<(_,)>();
@@ -1141,6 +1145,14 @@ quickcheck! {
         itertools::equal(x, y)
     }
 
+    fn tuple_windows_exact_size_1(a: Vec<u8>) -> bool {
+        exact_size(a.iter().tuple_windows::<(_,)>())
+    }
+
+    fn tuple_windows_exact_size_4(a: Vec<u8>) -> bool {
+        exact_size(a.iter().tuple_windows::<(_, _, _, _)>())
+    }
+
     fn equal_tuples_1(a: Vec<u8>) -> bool {
         let x = a.chunks(1).map(|s| (&s[0], ));
         let y = a.iter().tuples::<(_,)>();