From 60d2ddfc6a652c0bc22e18a86060a073f41a3411 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 3 Jan 2026 20:45:19 +0100 Subject: [PATCH 1/2] Fix PadUsing::next_back (1) Test next/next_back --- tests/quick.rs | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index d3dd44ead..faae8d698 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -998,6 +998,45 @@ quickcheck! { } } +quickcheck! { + fn pad_using_next_next_back(v: Vec) -> () { + let only_oks = v.into_iter().map(Ok).collect::>(); + for elements_required in 0..128 { + let mut oks_then_errs_vector = only_oks.clone(); + while oks_then_errs_vector.len() < elements_required { + oks_then_errs_vector.push(Err(oks_then_errs_vector.len())); + } + let oks_then_errs_pad_using = only_oks.iter().copied().pad_using(elements_required, Err); + assert_eq!( + oks_then_errs_pad_using.clone().collect::>(), + oks_then_errs_vector + ); + // Check next, next_back (https://github.com/rust-itertools/itertools/issues/1065) + let mut iter_check = oks_then_errs_vector.into_iter(); + let mut iter_real = oks_then_errs_pad_using; + let mut rng = rand::thread_rng(); + loop { + let next_or_next_back = if rng.gen_bool(0.5) { + let element_next_check = iter_check.next(); + let element_next_real = iter_real.next(); + assert_eq!(element_next_check, element_next_real); + element_next_real + } else { + let element_next_back_check = iter_check.next_back(); + let element_next_back_real = iter_real.next_back(); + assert_eq!(element_next_back_check, element_next_back_real); + element_next_back_real + }; + if next_or_next_back.is_none() { + assert!(iter_real.next().is_none()); + assert!(iter_real.next_back().is_none()); + break; + } + } + } + } +} + quickcheck! { fn size_powerset(it: Iter) -> bool { // Powerset cardinality gets large very quickly, limit input to keep test fast. From 320edc9381e51b943f66561584776a2d7b86a1f7 Mon Sep 17 00:00:00 2001 From: Takashiidobe Date: Thu, 4 Dec 2025 19:20:57 -0500 Subject: [PATCH 2/2] Fix PadUsing::next_back (2) PadUsing counts elements_from_next and elements_from_next_back Remove fold/rfold: Correctness is more important than performance. Maybe we can come back and re-implement them later. Fixes: #1065 --- src/pad_tail.rs | 104 ++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 5595b42ba..e6705d92c 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -1,4 +1,3 @@ -use crate::size_hint; use std::iter::{Fuse, FusedIterator}; /// An iterator adaptor that pads a sequence to a minimum length by filling @@ -11,8 +10,9 @@ use std::iter::{Fuse, FusedIterator}; #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PadUsing { iter: Fuse, - min: usize, - pos: usize, + elements_from_next: usize, + elements_from_next_back: usize, + elements_required: usize, filler: F, } @@ -20,19 +20,26 @@ impl std::fmt::Debug for PadUsing where I: std::fmt::Debug, { - debug_fmt_fields!(PadUsing, iter, min, pos); + debug_fmt_fields!( + PadUsing, + iter, + elements_from_next, + elements_from_next_back, + elements_required + ); } /// Create a new `PadUsing` iterator. -pub fn pad_using(iter: I, min: usize, filler: F) -> PadUsing +pub fn pad_using(iter: I, elements_required: usize, filler: F) -> PadUsing where I: Iterator, F: FnMut(usize) -> I::Item, { PadUsing { iter: iter.fuse(), - min, - pos: 0, + elements_from_next: 0, + elements_from_next_back: 0, + elements_required, filler, } } @@ -46,38 +53,34 @@ where #[inline] fn next(&mut self) -> Option { - match self.iter.next() { - None => { - if self.pos < self.min { - let e = Some((self.filler)(self.pos)); - self.pos += 1; - e - } else { - None - } - } - e => { - self.pos += 1; - e - } + let total_consumed = self.elements_from_next + self.elements_from_next_back; + + if total_consumed >= self.elements_required { + self.iter.next() + } else if let Some(e) = self.iter.next() { + self.elements_from_next += 1; + Some(e) + } else { + let e = (self.filler)(self.elements_from_next); + self.elements_from_next += 1; + Some(e) } } fn size_hint(&self) -> (usize, Option) { - let tail = self.min.saturating_sub(self.pos); - size_hint::max(self.iter.size_hint(), (tail, Some(tail))) - } + let total_consumed = self.elements_from_next + self.elements_from_next_back; + + if total_consumed >= self.elements_required { + return self.iter.size_hint(); + } + + let elements_remaining = self.elements_required - total_consumed; + let (low, high) = self.iter.size_hint(); + + let lower_bound = low.max(elements_remaining); + let upper_bound = high.map(|h| h.max(elements_remaining)); - fn fold(self, mut init: B, mut f: G) -> B - where - G: FnMut(B, Self::Item) -> B, - { - let mut pos = self.pos; - init = self.iter.fold(init, |acc, item| { - pos += 1; - f(acc, item) - }); - (pos..self.min).map(self.filler).fold(init, f) + (lower_bound, upper_bound) } } @@ -87,25 +90,24 @@ where F: FnMut(usize) -> I::Item, { fn next_back(&mut self) -> Option { - if self.min == 0 { - self.iter.next_back() - } else if self.iter.len() >= self.min { - self.min -= 1; - self.iter.next_back() - } else { - self.min -= 1; - Some((self.filler)(self.min)) + let total_consumed = self.elements_from_next + self.elements_from_next_back; + + if total_consumed >= self.elements_required { + return self.iter.next_back(); } - } - fn rfold(self, mut init: B, mut f: G) -> B - where - G: FnMut(B, Self::Item) -> B, - { - init = (self.iter.len()..self.min) - .map(self.filler) - .rfold(init, &mut f); - self.iter.rfold(init, f) + let elements_remaining = self.elements_required - total_consumed; + self.elements_from_next_back += 1; + + if self.iter.len() < elements_remaining { + Some((self.filler)( + self.elements_required - self.elements_from_next_back, + )) + } else { + let item = self.iter.next_back(); + debug_assert!(item.is_some()); // If this triggers, we should not have incremented elements_from_next_back, and the input iterator mistakenly reported that it would be able to produce at least elements_remaining items. + item + } } }