From 39a17b30fe462c6d711cdc14af0357903f8a5650 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:15:47 +0200 Subject: [PATCH 1/6] `add_then_div` --- src/tuple_impl.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index ba1c69df6..7096d3d72 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -107,6 +107,12 @@ where } } +/// `(n + a) / d` avoiding overflow when possible, returns `None` if it overflows. +fn add_then_div(n: usize, a: usize, d: usize) -> Option { + debug_assert_ne!(d, 0); + (n / d).checked_add(a / d)?.checked_add((n % d + a % d) / d) +} + impl Tuples where I: Iterator, From 30b583f199f5af3eb6f23ad86eedb492616fcce9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 12:41:30 +0200 Subject: [PATCH 2/6] `TupleCollect::buffer_len` --- src/tuple_impl.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 7096d3d72..ca8843e89 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -290,6 +290,11 @@ pub trait TupleCollect: Sized { type Item; type Buffer: Default + AsRef<[Option]> + AsMut<[Option]>; + fn buffer_len(buf: &Self::Buffer) -> usize { + let s = buf.as_ref(); + s.iter().position(Option::is_none).unwrap_or(s.len()) + } + fn collect_from_iter(iter: I, buf: &mut Self::Buffer) -> Option where I: IntoIterator; From 9f20d4981c8d825b8e36465a832bfe4e563f45e2 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:16:42 +0200 Subject: [PATCH 3/6] `Tuples::size_hint` --- src/tuple_impl.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index ca8843e89..8be920d03 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -105,6 +105,14 @@ where fn next(&mut self) -> Option { T::collect_from_iter(&mut self.iter, &mut self.buf) } + + fn size_hint(&self) -> (usize, Option) { + let buf_len = T::buffer_len(&self.buf); + let (mut low, mut hi) = self.iter.size_hint(); + low = add_then_div(low, buf_len, T::num_items()).unwrap_or(usize::MAX); + hi = hi.and_then(|elt| add_then_div(elt, buf_len, T::num_items())); + (low, hi) + } } /// `(n + a) / d` avoiding overflow when possible, returns `None` if it overflows. From 36677359a544cf17241f51759652802193dda951 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:22:43 +0200 Subject: [PATCH 4/6] `ExactSizeIterator` for `Tuples` Only when `I` is exact itself. Note that in this case, `add_then_div` won't ever return `None` since what is in the buffer was previously in `iter` so `n+a` is less or equal to the iterator length. --- src/tuple_impl.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 8be920d03..7f3d9f894 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -121,6 +121,13 @@ fn add_then_div(n: usize, a: usize, d: usize) -> Option { (n / d).checked_add(a / d)?.checked_add((n % d + a % d) / d) } +impl ExactSizeIterator for Tuples +where + I: ExactSizeIterator, + T: HomogeneousTuple, +{ +} + impl Tuples where I: Iterator, From 84e774f1109ce76746b809950e6a2e5c78650749 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:24:29 +0200 Subject: [PATCH 5/6] `Tuples::size_hint` tests --- tests/quick.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 3dc23d704..c7791545b 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1184,6 +1184,18 @@ quickcheck! { assert_eq!(buffer.len(), a.len() % 4); exact_size(buffer) } + + fn tuples_size_hint_inexact(a: Iter) -> bool { + correct_size_hint(a.clone().tuples::<(_,)>()) + && correct_size_hint(a.clone().tuples::<(_, _)>()) + && correct_size_hint(a.tuples::<(_, _, _, _)>()) + } + + fn tuples_size_hint_exact(a: Iter) -> bool { + exact_size(a.clone().tuples::<(_,)>()) + && exact_size(a.clone().tuples::<(_, _)>()) + && exact_size(a.tuples::<(_, _, _, _)>()) + } } // with_position From 040c794ef4ba0833d03dba789af83e4347ec8809 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:59:56 +0200 Subject: [PATCH 6/6] `Tuples::size_hint`: add jswrenn's comments Co-authored-by: Jack Wrenn --- src/tuple_impl.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 7f3d9f894..1ce6a5d15 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -107,11 +107,19 @@ where } fn size_hint(&self) -> (usize, Option) { - let buf_len = T::buffer_len(&self.buf); - let (mut low, mut hi) = self.iter.size_hint(); - low = add_then_div(low, buf_len, T::num_items()).unwrap_or(usize::MAX); - hi = hi.and_then(|elt| add_then_div(elt, buf_len, T::num_items())); - (low, hi) + // The number of elts we've drawn from the underlying iterator, but have + // not yet produced as a tuple. + let buffered = T::buffer_len(&self.buf); + // To that, we must add the size estimates of the underlying iterator. + let (mut unbuffered_lo, mut unbuffered_hi) = self.iter.size_hint(); + // The total low estimate is the sum of the already-buffered elements, + // plus the low estimate of remaining unbuffered elements, divided by + // the tuple size. + let total_lo = add_then_div(unbuffered_lo, buffered, T::num_items()).unwrap_or(usize::MAX); + // And likewise for the total high estimate, but using the high estimate + // of the remaining unbuffered elements. + let total_hi = unbuffered_hi.and_then(|hi| add_then_div(hi, buffered, T::num_items())); + (total_lo, total_hi) } }