From bacbac5e6ae9a301be9a685fddee11e8bcb3155c Mon Sep 17 00:00:00 2001 From: Mario Carneiro Date: Thu, 26 Nov 2020 02:55:20 -0500 Subject: [PATCH 001/633] Intra-doc links This commit changes all the documentation to use intra-doc links, which were stabilized in rust 1.48. This helps to avoid broken links in crates that depend on this one. fixes #496 --- src/adaptors/coalesce.rs | 12 +++++----- src/adaptors/map.rs | 8 +++---- src/adaptors/mod.rs | 34 ++++++++++++++-------------- src/adaptors/multi_product.rs | 2 +- src/combinations.rs | 2 +- src/combinations_with_replacement.rs | 3 ++- src/diff.rs | 4 ++-- src/either_or_both.rs | 4 ++-- src/format.rs | 4 ++-- src/free.rs | 2 +- src/group_map.rs | 2 +- src/groupbylazy.rs | 8 +++---- src/intersperse.rs | 4 ++-- src/kmerge_impl.rs | 4 ++-- src/lib.rs | 28 ++++++++--------------- src/merge_join.rs | 4 ++-- src/multipeek_impl.rs | 2 +- src/pad_tail.rs | 2 +- src/peek_nth.rs | 2 +- src/peeking_take_while.rs | 4 ++-- src/permutations.rs | 2 +- src/tee.rs | 2 +- src/tuple_impl.rs | 10 ++++---- src/unique_impl.rs | 4 ++-- src/with_position.rs | 6 ++--- src/zip_eq_impl.rs | 2 +- src/zip_longest.rs | 4 ++-- src/ziptuple.rs | 4 +--- 28 files changed, 79 insertions(+), 90 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index 116f688f5..bf8dd05b3 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -84,7 +84,7 @@ impl, T> FusedIterator for Coalesc /// An iterator adaptor that may join together adjacent elements. /// -/// See [`.coalesce()`](../trait.Itertools.html#method.coalesce) for more information. +/// See [`.coalesce()`](crate::Itertools::coalesce) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type Coalesce = CoalesceBy::Item>; @@ -111,7 +111,7 @@ where /// An iterator adaptor that removes repeated duplicates, determining equality using a comparison function. /// -/// See [`.dedup_by()`](../trait.Itertools.html#method.dedup_by) or [`.dedup()`](../trait.Itertools.html#method.dedup) for more information. +/// See [`.dedup_by()`](crate::Itertools::dedup_by) or [`.dedup()`](crate::Itertools::dedup) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DedupBy = CoalesceBy, ::Item>; @@ -165,7 +165,7 @@ where /// An iterator adaptor that removes repeated duplicates. /// -/// See [`.dedup()`](../trait.Itertools.html#method.dedup) for more information. +/// See [`.dedup()`](crate::Itertools::dedup) for more information. pub type Dedup = DedupBy; /// Create a new `Dedup`. @@ -179,8 +179,8 @@ where /// An iterator adaptor that removes repeated duplicates, while keeping a count of how many /// repeated elements were present. This will determine equality using a comparison function. /// -/// See [`.dedup_by_with_count()`](../trait.Itertools.html#method.dedup_by_with_count) or -/// [`.dedup_with_count()`](../trait.Itertools.html#method.dedup_with_count) for more information. +/// See [`.dedup_by_with_count()`](crate::Itertools::dedup_by_with_count) or +/// [`.dedup_with_count()`](crate::Itertools::dedup_with_count) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DedupByWithCount = CoalesceBy, (usize, ::Item)>; @@ -208,7 +208,7 @@ where /// An iterator adaptor that removes repeated duplicates, while keeping a count of how many /// repeated elements were present. /// -/// See [`.dedup_with_count()`](../trait.Itertools.html#method.dedup_with_count) for more information. +/// See [`.dedup_with_count()`](crate::Itertools::dedup_with_count) for more information. pub type DedupWithCount = DedupByWithCount; /// Create a new `DedupByWithCount`. diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index ff377f700..eee5cc35f 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -64,10 +64,10 @@ where /// An iterator adapter to apply a transformation within a nested `Result::Ok`. /// -/// See [`.map_ok()`](../trait.Itertools.html#method.map_ok) for more information. +/// See [`.map_ok()`](crate::Itertools::map_ok) for more information. pub type MapOk = MapSpecialCase>; -/// See [`MapOk`](struct.MapOk.html). +/// See [`MapOk`]. #[deprecated(note = "Use MapOk instead", since = "0.10.0")] pub type MapResults = MapOk; @@ -98,7 +98,7 @@ where /// An iterator adapter to apply `Into` conversion to each element. /// -/// See [`.map_into()`](../trait.Itertools.html#method.map_into) for more information. +/// See [`.map_into()`](crate::Itertools::map_into) for more information. pub type MapInto = MapSpecialCase>; impl, U> MapSpecialCaseFn for MapSpecialCaseFnInto { @@ -111,7 +111,7 @@ impl, U> MapSpecialCaseFn for MapSpecialCaseFnInto { #[derive(Clone)] pub struct MapSpecialCaseFnInto(PhantomData); -/// Create a new [`MapInto`](struct.MapInto.html) iterator. +/// Create a new [`MapInto`] iterator. pub fn map_into(iter: I) -> MapInto { MapSpecialCase { iter, diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 8a8697b36..47a467407 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1,6 +1,6 @@ //! Licensed under the Apache License, Version 2.0 -//! http://www.apache.org/licenses/LICENSE-2.0 or the MIT license -//! http://opensource.org/licenses/MIT, at your +//! or the MIT license +//! , at your //! option. This file may not be copied, modified, or distributed //! except according to those terms. @@ -24,7 +24,7 @@ use crate::size_hint; /// /// This iterator is *fused*. /// -/// See [`.interleave()`](../trait.Itertools.html#method.interleave) for more information. +/// See [`.interleave()`](crate::Itertools::interleave) for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Interleave { @@ -37,7 +37,7 @@ pub struct Interleave { /// /// `IntoIterator` enabled version of `i.interleave(j)`. /// -/// See [`.interleave()`](trait.Itertools.html#method.interleave) for more information. +/// See [`.interleave()`](crate::Itertools::interleave) for more information. pub fn interleave(i: I, j: J) -> Interleave<::IntoIter, ::IntoIter> where I: IntoIterator, J: IntoIterator @@ -80,7 +80,7 @@ impl Iterator for Interleave /// /// This iterator is *fused*. /// -/// See [`.interleave_shortest()`](../trait.Itertools.html#method.interleave_shortest) +/// See [`.interleave_shortest()`](crate::Itertools::interleave_shortest) /// for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -271,7 +271,7 @@ impl Iterator for PutBack /// /// Iterator element type is `(I::Item, J::Item)`. /// -/// See [`.cartesian_product()`](../trait.Itertools.html#method.cartesian_product) for more information. +/// See [`.cartesian_product()`](crate::Itertools::cartesian_product) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Product where I: Iterator @@ -366,7 +366,7 @@ impl Iterator for Product /// /// Iterator element type is *X*, if the return type of `F` is *Option\*. /// -/// See [`.batching()`](../trait.Itertools.html#method.batching) for more information. +/// See [`.batching()`](crate::Itertools::batching) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Batching { @@ -400,7 +400,7 @@ impl Iterator for Batching /// The iterator steps by yielding the next element from the base iterator, /// then skipping forward *n-1* elements. /// -/// See [`.step()`](../trait.Itertools.html#method.step) for more information. +/// See [`.step()`](crate::Itertools::step) for more information. #[deprecated(note="Use std .step_by() instead", since="0.8.0")] #[allow(deprecated)] #[derive(Clone, Debug)] @@ -475,7 +475,7 @@ impl MergePredicate for MergeLte { /// /// Iterator element type is `I::Item`. /// -/// See [`.merge()`](../trait.Itertools.html#method.merge_by) for more information. +/// See [`.merge()`](crate::Itertools::merge_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type Merge = MergeBy; @@ -503,7 +503,7 @@ pub fn merge(i: I, j: J) -> Merge<::IntoIter, where I: Iterator, @@ -591,7 +591,7 @@ impl Iterator for MergeBy /// An iterator adaptor that borrows from a `Clone`-able iterator /// to only pick off elements while the predicate returns `true`. /// -/// See [`.take_while_ref()`](../trait.Itertools.html#method.take_while_ref) for more information. +/// See [`.take_while_ref()`](crate::Itertools::take_while_ref) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct TakeWhileRef<'a, I: 'a, F> { iter: &'a mut I, @@ -640,7 +640,7 @@ impl<'a, I, F> Iterator for TakeWhileRef<'a, I, F> /// An iterator adaptor that filters `Option` iterator elements /// and produces `A`. Stops on the first `None` encountered. /// -/// See [`.while_some()`](../trait.Itertools.html#method.while_some) for more information. +/// See [`.while_some()`](crate::Itertools::while_some) for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct WhileSome { @@ -672,7 +672,7 @@ impl Iterator for WhileSome /// An iterator to iterate through all combinations in a `Clone`-able iterator that produces tuples /// of a specific size. /// -/// See [`.tuple_combinations()`](../trait.Itertools.html#method.tuple_combinations) for more +/// See [`.tuple_combinations()`](crate::Itertools::tuple_combinations) for more /// information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -822,7 +822,7 @@ impl_tuple_combination!(Tuple12Combination Tuple11Combination; A, A, A, A, A, A, /// An iterator adapter to filter values within a nested `Result::Ok`. /// -/// See [`.filter_ok()`](../trait.Itertools.html#method.filter_ok) for more information. +/// See [`.filter_ok()`](crate::Itertools::filter_ok) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct FilterOk { @@ -886,7 +886,7 @@ impl Iterator for FilterOk /// An iterator adapter to filter and apply a transformation on values within a nested `Result::Ok`. /// -/// See [`.filter_map_ok()`](../trait.Itertools.html#method.filter_map_ok) for more information. +/// See [`.filter_map_ok()`](crate::Itertools::filter_map_ok) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct FilterMapOk { iter: I, @@ -957,7 +957,7 @@ impl Iterator for FilterMapOk /// An iterator adapter to get the positions of each element that matches a predicate. /// -/// See [`.positions()`](../trait.Itertools.html#method.positions) for more information. +/// See [`.positions()`](crate::Itertools::positions) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Positions { @@ -1016,7 +1016,7 @@ impl DoubleEndedIterator for Positions /// An iterator adapter to apply a mutating function to each element before yielding it. /// -/// See [`.update()`](../trait.Itertools.html#method.update) for more information. +/// See [`.update()`](crate::Itertools::update) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Update { diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 6938014ce..9708ef4bd 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -11,7 +11,7 @@ use alloc::vec::Vec; /// /// An iterator element type is `Vec`. /// -/// See [`.multi_cartesian_product()`](../trait.Itertools.html#method.multi_cartesian_product) +/// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MultiProduct(Vec>) diff --git a/src/combinations.rs b/src/combinations.rs index 9231a7b41..312ae6c7a 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; /// An iterator to iterate through all the `k`-length combinations in an iterator. /// -/// See [`.combinations()`](../trait.Itertools.html#method.combinations) for more information. +/// See [`.combinations()`](crate::Itertools::combinations) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Combinations { indices: Vec, diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 7e7a80223..1f1bcf5b2 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -5,7 +5,8 @@ use super::lazy_buffer::LazyBuffer; /// An iterator to iterate through all the `n`-length combinations in an iterator, with replacement. /// -/// See [`.combinations_with_replacement()`](../trait.Itertools.html#method.combinations_with_replacement) for more information. +/// See [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) +/// for more information. #[derive(Clone)] pub struct CombinationsWithReplacement where diff --git a/src/diff.rs b/src/diff.rs index c196d8d2f..b8d26cdd3 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -1,14 +1,14 @@ //! "Diff"ing iterators for caching elements to sequential collections without requiring the new //! elements' iterator to be `Clone`. //! -//! - [**Diff**](./enum.Diff.html) (produced by the [**diff_with**](./fn.diff_with.html) function) +//! - [`Diff`] (produced by the [`diff_with`] function) //! describes the difference between two non-`Clone` iterators `I` and `J` after breaking ASAP from //! a lock-step comparison. use crate::free::put_back; use crate::structs::PutBack; -/// A type returned by the [`diff_with`](./fn.diff_with.html) function. +/// A type returned by the [`diff_with`] function. /// /// `Diff` represents the way in which the elements yielded by the iterator `I` differ to some /// iterator `J`. diff --git a/src/either_or_both.rs b/src/either_or_both.rs index a03a4f16e..3598f49f3 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -25,7 +25,7 @@ impl EitherOrBoth { } /// If Left, return true otherwise, return false. - /// Exclusive version of [`has_left`]. + /// Exclusive version of [`has_left`](Self::has_left). pub fn is_left(&self) -> bool { match *self { Left(_) => true, @@ -34,7 +34,7 @@ impl EitherOrBoth { } /// If Right, return true otherwise, return false. - /// Exclusive version of [`has_right`]. + /// Exclusive version of [`has_right`](Self::has_right). pub fn is_right(&self) -> bool { match *self { Right(_) => true, diff --git a/src/format.rs b/src/format.rs index ec2dc7a7c..d87cee950 100644 --- a/src/format.rs +++ b/src/format.rs @@ -6,7 +6,7 @@ use std::cell::RefCell; /// The format value can only be formatted once, after that the iterator is /// exhausted. /// -/// See [`.format_with()`](../trait.Itertools.html#method.format_with) for more information. +/// See [`.format_with()`](crate::Itertools::format_with) for more information. #[derive(Clone)] pub struct FormatWith<'a, I, F> { sep: &'a str, @@ -19,7 +19,7 @@ pub struct FormatWith<'a, I, F> { /// The format value can only be formatted once, after that the iterator is /// exhausted. /// -/// See [`.format()`](../trait.Itertools.html#method.format) +/// See [`.format()`](crate::Itertools::format) /// for more information. #[derive(Clone)] pub struct Format<'a, I> { diff --git a/src/free.rs b/src/free.rs index bfc5ab422..0520eb5e6 100644 --- a/src/free.rs +++ b/src/free.rs @@ -225,7 +225,7 @@ pub fn join(iterable: I, sep: &str) -> String /// /// `IntoIterator` enabled version of [`iterable.sorted()`][1]. /// -/// [1]: trait.Itertools.html#method.sorted +/// [1]: crate::Itertools::sorted /// /// ``` /// use itertools::sorted; diff --git a/src/group_map.rs b/src/group_map.rs index 4231de074..a2d0ebb2a 100644 --- a/src/group_map.rs +++ b/src/group_map.rs @@ -6,7 +6,7 @@ use std::iter::Iterator; /// Return a `HashMap` of keys mapped to a list of their corresponding values. /// -/// See [`.into_group_map()`](../trait.Itertools.html#method.into_group_map) +/// See [`.into_group_map()`](crate::Itertools::into_group_map) /// for more information. pub fn into_group_map(iter: I) -> HashMap> where I: Iterator, diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 5d4a0c92e..b57d4046b 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -284,7 +284,7 @@ impl GroupInner /// value. It should be stored in a local variable or temporary and /// iterated. /// -/// See [`.group_by()`](../trait.Itertools.html#method.group_by) for more information. +/// See [`.group_by()`](crate::Itertools::group_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct GroupBy where I: Iterator, @@ -354,7 +354,7 @@ impl<'a, K, I, F> IntoIterator for &'a GroupBy /// Iterator element type is `(K, Group)`: /// the group's key `K` and the group's iterator. /// -/// See [`.group_by()`](../trait.Itertools.html#method.group_by) for more information. +/// See [`.group_by()`](crate::Itertools::group_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Groups<'a, K: 'a, I: 'a, F: 'a> where I: Iterator, @@ -460,7 +460,7 @@ pub fn new_chunks(iter: J, size: usize) -> IntoChunks /// /// Iterator element type is `Chunk`, each chunk's iterator. /// -/// See [`.chunks()`](../trait.Itertools.html#method.chunks) for more information. +/// See [`.chunks()`](crate::Itertools::chunks) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct IntoChunks where I: Iterator, @@ -505,7 +505,7 @@ impl<'a, I> IntoIterator for &'a IntoChunks /// /// Iterator element type is `Chunk`. /// -/// See [`.chunks()`](../trait.Itertools.html#method.chunks) for more information. +/// See [`.chunks()`](crate::Itertools::chunks) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Chunks<'a, I: 'a> where I: Iterator, diff --git a/src/intersperse.rs b/src/intersperse.rs index a0d79b036..687726f9d 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -21,7 +21,7 @@ impl IntersperseElement for IntersperseElementSimple { /// /// This iterator is *fused*. /// -/// See [`.intersperse()`](../trait.Itertools.html#method.intersperse) for more information. +/// See [`.intersperse()`](crate::Itertools::intersperse) for more information. pub type Intersperse = IntersperseWith::Item>>; /// Create a new Intersperse iterator @@ -44,7 +44,7 @@ implItem> IntersperseElement for F { /// /// This iterator is *fused*. /// -/// See [`.intersperse_with()`](../trait.Itertools.html#method.intersperse_with) for more information. +/// See [`.intersperse_with()`](crate::Itertools::intersperse_with) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct IntersperseWith diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index a1b3d8e6c..a1dcec2a1 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -98,7 +98,7 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) /// /// Iterator element type is `I::Item`. /// -/// See [`.kmerge()`](../trait.Itertools.html#method.kmerge) for more information. +/// See [`.kmerge()`](crate::Itertools::kmerge) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type KMerge = KMergeBy; @@ -146,7 +146,7 @@ pub fn kmerge(iterable: I) -> KMerge<::IntoIter> /// /// Iterator element type is `I::Item`. /// -/// See [`.kmerge_by()`](../trait.Itertools.html#method.kmerge_by) for more +/// See [`.kmerge_by()`](crate::Itertools::kmerge_by) for more /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct KMergeBy diff --git a/src/lib.rs b/src/lib.rs index ba7085bfd..b91b3763a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,13 +5,13 @@ //! Extra iterator adaptors, functions and macros. //! //! To extend [`Iterator`] with methods in this crate, import -//! the [`Itertools` trait](./trait.Itertools.html): +//! the [`Itertools` trait](Itertools): //! //! ``` //! use itertools::Itertools; //! ``` //! -//! Now, new methods like [`interleave`](./trait.Itertools.html#method.interleave) +//! Now, new methods like [`interleave`](Itertools::interleave) //! are available on all iterators: //! //! ``` @@ -43,8 +43,6 @@ //! ## Rust Version //! //! This version of itertools requires Rust 1.32 or later. -//! -//! [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html #![doc(html_root_url="https://docs.rs/itertools/0.8/")] #[cfg(not(feature = "use_std"))] @@ -279,8 +277,6 @@ macro_rules! iproduct { /// Prefer this macro `izip!()` over [`multizip`] for the performance benefits /// of using the standard library `.zip()`. /// -/// [`multizip`]: fn.multizip.html -/// /// ``` /// # use itertools::izip; /// # @@ -346,8 +342,6 @@ macro_rules! izip { /// return a regular value of some other kind. /// [`.next_tuple()`](#method.next_tuple) is an example and the first regular /// method in the list. -/// -/// [`Iterator`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html pub trait Itertools : Iterator { // adaptors @@ -446,7 +440,7 @@ pub trait Itertools : Iterator { /// will return `None`. /// /// Iterator element type is - /// [`EitherOrBoth`](enum.EitherOrBoth.html). + /// [`EitherOrBoth`](EitherOrBoth). /// /// ```rust /// use itertools::EitherOrBoth::{Both, Right}; @@ -688,7 +682,7 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(1, 2, 3), (4, 5, 6)]); /// ``` /// - /// See also [`Tuples::into_buffer`](structs/struct.Tuples.html#method.into_buffer). + /// See also [`Tuples::into_buffer`]. fn tuples(self) -> Tuples where Self: Sized + Iterator, T: traits::HomogeneousTuple @@ -985,7 +979,7 @@ pub trait Itertools : Iterator { /// /// All provided iterators must yield the same `Item` type. To generate /// the product of iterators yielding multiple types, use the - /// [`iproduct`](macro.iproduct.html) macro instead. + /// [`iproduct`] macro instead. /// /// /// The iterator element type is `Vec`, where `T` is the iterator element @@ -1434,7 +1428,7 @@ pub trait Itertools : Iterator { /// ease special-case handling of the first or last elements. /// /// Iterator element type is - /// [`Position`](enum.Position.html) + /// [`Position`](Position) /// /// ``` /// use itertools::{Itertools, Position}; @@ -2759,8 +2753,6 @@ pub trait Itertools : Iterator { /// let a = [1, 1, -1, -1]; /// assert_eq!(a.iter().position_minmax(), MinMax(2, 1)); /// ``` - /// - /// [`MinMaxResult`]: enum.MinMaxResult.html fn position_minmax(self) -> MinMaxResult where Self: Sized, Self::Item: PartialOrd { @@ -2805,8 +2797,7 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_minmax_by_key(|x| x.abs()), MinMax(0, 3)); /// ``` /// - /// [`MinMaxResult`]: enum.MinMaxResult.html - /// [`position_minmax`]: #method.position_minmax + /// [`position_minmax`]: Self::position_minmax fn position_minmax_by_key(self, mut key: F) -> MinMaxResult where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K { @@ -2848,8 +2839,7 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_minmax_by(|x, y| x.cmp(y)), MinMax(2, 1)); /// ``` /// - /// [`MinMaxResult`]: enum.MinMaxResult.html - /// [`position_minmax`]: #method.position_minmax + /// [`position_minmax`]: Self::position_minmax fn position_minmax_by(self, mut compare: F) -> MinMaxResult where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering { @@ -3053,7 +3043,7 @@ pub fn partition<'a, A: 'a, I, F>(iter: I, mut pred: F) -> usize /// An enum used for controlling the execution of `.fold_while()`. /// -/// See [`.fold_while()`](trait.Itertools.html#method.fold_while) for more information. +/// See [`.fold_while()`](crate::Itertools::fold_while) for more information. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum FoldWhile { /// Continue folding with this value diff --git a/src/merge_join.rs b/src/merge_join.rs index 0f87ae42e..4c0048f68 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -7,7 +7,7 @@ use crate::either_or_both::EitherOrBoth; /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// -/// See [`.merge_join_by()`](trait.Itertools.html#method.merge_join_by) for more information. +/// See [`.merge_join_by()`](crate::Itertools::merge_join_by) for more information. pub fn merge_join_by(left: I, right: J, cmp_fn: F) -> MergeJoinBy where I: IntoIterator, @@ -23,7 +23,7 @@ pub fn merge_join_by(left: I, right: J, cmp_fn: F) /// An iterator adaptor that merge-joins items from the two base iterators in ascending order. /// -/// See [`.merge_join_by()`](../trait.Itertools.html#method.merge_join_by) for more information. +/// See [`.merge_join_by()`](crate::Itertools::merge_join_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MergeJoinBy { left: PutBack>, diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 986e5b405..93b02279f 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -3,7 +3,7 @@ use alloc::collections::VecDeque; use crate::size_hint; use crate::PeekingNext; -/// See [`multipeek()`](../fn.multipeek.html) for more information. +/// See [`multipeek()`] for more information. #[derive(Clone, Debug)] pub struct MultiPeek where I: Iterator diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 4ed83c3b0..18e666bad 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -6,7 +6,7 @@ use crate::size_hint; /// /// Iterator element type is `I::Item`. /// -/// See [`.pad_using()`](../trait.Itertools.html#method.pad_using) for more information. +/// See [`.pad_using()`](crate::Itertools::pad_using) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PadUsing { diff --git a/src/peek_nth.rs b/src/peek_nth.rs index d22258b35..48b44bb70 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -3,7 +3,7 @@ use crate::PeekingNext; use alloc::collections::VecDeque; use std::iter::Fuse; -/// See [`peek_nth()`](../fn.peek_nth.html) for more information. +/// See [`peek_nth()`] for more information. #[derive(Clone, Debug)] pub struct PeekNth where diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index 70ef988f1..f9f213421 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -5,7 +5,7 @@ use crate::PutBackN; /// An iterator that allows peeking at an element before deciding to accept it. /// -/// See [`.peeking_take_while()`](trait.Itertools.html#method.peeking_take_while) +/// See [`.peeking_take_while()`](crate::Itertools::peeking_take_while) /// for more information. /// /// This is implemented by peeking adaptors like peekable and put back, @@ -73,7 +73,7 @@ impl PeekingNext for PutBackN /// An iterator adaptor that takes items while a closure returns `true`. /// -/// See [`.peeking_take_while()`](../trait.Itertools.html#method.peeking_take_while) +/// See [`.peeking_take_while()`](crate::Itertools::peeking_take_while) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PeekingTakeWhile<'a, I: 'a, F> diff --git a/src/permutations.rs b/src/permutations.rs index d96bbac2f..3080f9d5c 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -7,7 +7,7 @@ use super::lazy_buffer::LazyBuffer; /// An iterator adaptor that iterates through all the `k`-permutations of the /// elements from an iterator. /// -/// See [`.permutations()`](../trait.Itertools.html#method.permutations) for +/// See [`.permutations()`](crate::Itertools::permutations) for /// more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Permutations { diff --git a/src/tee.rs b/src/tee.rs index 0b003027d..ea4752906 100644 --- a/src/tee.rs +++ b/src/tee.rs @@ -15,7 +15,7 @@ struct TeeBuffer { /// One half of an iterator pair where both return the same elements. /// -/// See [`.tee()`](../trait.Itertools.html#method.tee) for more information. +/// See [`.tee()`](crate::Itertools::tee) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug)] pub struct Tee diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 1d24b0bb3..871b7a5c8 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -19,8 +19,8 @@ impl HomogeneousTuple for T {} /// An iterator over a incomplete tuple. /// -/// See [`.tuples()`](../trait.Itertools.html#method.tuples) and -/// [`Tuples::into_buffer()`](struct.Tuples.html#method.into_buffer). +/// See [`.tuples()`](crate::Itertools::tuples) and +/// [`Tuples::into_buffer()`]. #[derive(Clone, Debug)] pub struct TupleBuffer where T: HomogeneousTuple @@ -75,7 +75,7 @@ impl ExactSizeIterator for TupleBuffer /// An iterator that groups the items in tuples of a specific size. /// -/// See [`.tuples()`](../trait.Itertools.html#method.tuples) for more information. +/// See [`.tuples()`](crate::Itertools::tuples) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Tuples @@ -130,7 +130,7 @@ impl Tuples /// An iterator over all contiguous windows that produces tuples of a specific size. /// -/// See [`.tuple_windows()`](../trait.Itertools.html#method.tuple_windows) for more +/// See [`.tuple_windows()`](crate::Itertools::tuple_windows) for more /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] @@ -191,7 +191,7 @@ impl Iterator for TupleWindows /// 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 +/// See [`.circular_tuple_windows()`](crate::Itertools::circular_tuple_windows) for more /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug)] diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 14c14fc6e..7073e8e2c 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -6,7 +6,7 @@ use std::fmt; /// An iterator adapter to filter out duplicate elements. /// -/// See [`.unique_by()`](../trait.Itertools.html#method.unique) for more information. +/// See [`.unique_by()`](crate::Itertools::unique) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct UniqueBy { @@ -138,7 +138,7 @@ impl DoubleEndedIterator for Unique /// An iterator adapter to filter out duplicate elements. /// -/// See [`.unique()`](../trait.Itertools.html#method.unique) for more information. +/// See [`.unique()`](crate::Itertools::unique) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Unique { diff --git a/src/with_position.rs b/src/with_position.rs index 1440fb6f5..c53b652c6 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -1,10 +1,10 @@ use std::iter::{Fuse,Peekable}; -/// An iterator adaptor that wraps each element in an [`Position`](../enum.Position.html). +/// An iterator adaptor that wraps each element in an [`Position`]. /// /// Iterator element type is `Position`. /// -/// See [`.with_position()`](../trait.Itertools.html#method.with_position) for more information. +/// See [`.with_position()`](crate::Itertools::with_position) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct WithPosition where I: Iterator, @@ -33,7 +33,7 @@ pub fn with_position(iter: I) -> WithPosition /// A value yielded by `WithPosition`. /// Indicates the position of this element in the iterator results. /// -/// See [`.with_position()`](trait.Itertools.html#method.with_position) for more information. +/// See [`.with_position()`](crate::Itertools::with_position) for more information. #[derive(Copy, Clone, Debug, PartialEq)] pub enum Position { /// This is the first element. diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 857465da4..864930bc0 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -2,7 +2,7 @@ use super::size_hint; /// An iterator which iterates two other iterators simultaneously /// -/// See [`.zip_eq()`](../trait.Itertools.html#method.zip_eq) for more information. +/// See [`.zip_eq()`](crate::Itertools::zip_eq) for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct ZipEq { diff --git a/src/zip_longest.rs b/src/zip_longest.rs index 1395c8428..553fe3b08 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -11,7 +11,7 @@ use crate::either_or_both::EitherOrBoth; /// /// This iterator is *fused*. /// -/// See [`.zip_longest()`](../trait.Itertools.html#method.zip_longest) for more information. +/// See [`.zip_longest()`](crate::Itertools::zip_longest) for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct ZipLongest { @@ -20,7 +20,7 @@ pub struct ZipLongest { } /// Create a new `ZipLongest` iterator. -pub fn zip_longest(a: T, b: U) -> ZipLongest +pub fn zip_longest(a: T, b: U) -> ZipLongest where T: Iterator, U: Iterator { diff --git a/src/ziptuple.rs b/src/ziptuple.rs index 8f4019363..fe9659ee5 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -1,6 +1,6 @@ use super::size_hint; -/// See [`multizip`](../fn.multizip.html) for more information. +/// See [`multizip`] for more information. #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Zip { @@ -23,8 +23,6 @@ pub struct Zip { /// Prefer [`izip!()`] over `multizip` for the performance benefits of using the /// standard library `.zip()`. Prefer `multizip` if a nameable type is needed. /// -/// [`izip!()`]: macro.izip.html -/// /// ``` /// use itertools::multizip; /// From 1bcc0e7628b2ef6cc3c51a9ed7632b938fbc36cf Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 4 Sep 2020 18:50:21 -0400 Subject: [PATCH 002/633] Update CHANGELOG.md for 0.10.0 --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 67633ab75..3dc91d87e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,40 @@ # Changelog +## 0.10.0 + - Undeprecate `fold_while` (#476) + + - Tuple-related adapters work for tuples of arity up to 12 (#475) + + - `use_alloc` feature for users who have `alloc`, but not `std` (#474) + + - Add `Itertools::k_smallest` (#473) + + - Add `Itertools::into_grouping_map` and `GroupingMap` (#465) + - Add `Itertools::into_grouping_map_by` and `GroupingMapBy` (#465) + + - Add `Itertools::counts` (#468) + + - Add implementation of `DoubleEndedIterator` for `Unique` (#442) + - Add implementation of `DoubleEndedIterator` for `UniqueBy` (#442) + + - Add `Itertools::multipeek` (#435) + + - Add `Itertools::dedup_with_count` and `DedupWithCount` (#423) + - Add `Itertools::dedup_by_with_count` and `DedupByWithCount` (#423) + + - Add `Itertools::intersperse_with` and `IntersperseWith` (#381) + + - Add `Itertools::filter_ok` and `FilterOk` (#377) + - Add `Itertools::filter_map_ok` and `FilterMapOk` (#377) + - Deprecate `Itertools::fold_results`, use `Itertools::fold_ok` instead (#377) + - Deprecate `Itertools::map_results`, use `Itertools::map_ok` instead (#377) + - Deprecate `FoldResults`, use `FoldOk` instead (#377) + - Deprecate `MapResults`, use `MapOk` instead (#377) + + - Add `Itertools::circular_tuple_windows` and `CircularTupleWindows` (#350) + + - Add `peek_nth` and `PeekNth` (#303) + ## 0.9.0 - Fix potential overflow in `MergeJoinBy::size_hint` (#385) - Add `derive(Clone)` where possible (#382) From a8d126f742046f3862f2e0f290a5b3a58de62983 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sat, 26 Dec 2020 12:59:46 -0500 Subject: [PATCH 003/633] Update CHANGELOG.md --- CHANGELOG.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3dc91d87e..0e1682b1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,38 +1,32 @@ # Changelog ## 0.10.0 - - Undeprecate `fold_while` (#476) - + - **Increase minimum supported Rust version to 1.32.0** + - Improve macro hygiene (#507) + - Add `Itertools::powerset` (#335) + - Add `Itertools::sorted_unstable`, `Itertools::sorted_unstable_by`, and `Itertools::sorted_unstable_by_key` (#494) + - Implement `Error` for `ExactlyOneError` (#484) + - Undeprecate `Itertools::fold_while` (#476) - Tuple-related adapters work for tuples of arity up to 12 (#475) - - `use_alloc` feature for users who have `alloc`, but not `std` (#474) - - Add `Itertools::k_smallest` (#473) - - Add `Itertools::into_grouping_map` and `GroupingMap` (#465) - Add `Itertools::into_grouping_map_by` and `GroupingMapBy` (#465) - - Add `Itertools::counts` (#468) - - Add implementation of `DoubleEndedIterator` for `Unique` (#442) - Add implementation of `DoubleEndedIterator` for `UniqueBy` (#442) - + - Add implementation of `DoubleEndedIterator` for `Zip` (#346) - Add `Itertools::multipeek` (#435) - - Add `Itertools::dedup_with_count` and `DedupWithCount` (#423) - Add `Itertools::dedup_by_with_count` and `DedupByWithCount` (#423) - - Add `Itertools::intersperse_with` and `IntersperseWith` (#381) - - Add `Itertools::filter_ok` and `FilterOk` (#377) - Add `Itertools::filter_map_ok` and `FilterMapOk` (#377) - Deprecate `Itertools::fold_results`, use `Itertools::fold_ok` instead (#377) - Deprecate `Itertools::map_results`, use `Itertools::map_ok` instead (#377) - Deprecate `FoldResults`, use `FoldOk` instead (#377) - Deprecate `MapResults`, use `MapOk` instead (#377) - - Add `Itertools::circular_tuple_windows` and `CircularTupleWindows` (#350) - - Add `peek_nth` and `PeekNth` (#303) ## 0.9.0 From f7d948b6c6479746d47b8ff9cbd5d30b9df8b403 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sat, 26 Dec 2020 13:20:26 -0500 Subject: [PATCH 004/633] Update README to reflect new release version. --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 4e370072b..b65c127c6 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ How to use with cargo: .. code:: toml [dependencies] - itertools = "0.9" + itertools = "0.10.0" How to use in your crate: From 686782cd4485c4d705220d9666dd733205c1cd3e Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 28 Dec 2020 13:17:54 -0500 Subject: [PATCH 005/633] ci: delete end-success https://github.com/bors-ng/bors-ng/issues/1115#issuecomment-751793439 --- .github/workflows/ci.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index efc779f49..143ac2b6d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,14 +38,3 @@ jobs: steps: - name: Mark the job as successful run: exit 0 - - end-failure: - name: bors build finished - if: "!success()" - runs-on: ubuntu-latest - needs: [msrv,stable] - - steps: - - name: Mark the job as a failure - run: exit 1 - From 5c86c2c5315b19ef771bc1fd404386967014f762 Mon Sep 17 00:00:00 2001 From: Ming Date: Thu, 31 Dec 2020 18:05:33 -0800 Subject: [PATCH 006/633] Implement partition_result --- src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0c6af37e4..45e3e60d3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2494,6 +2494,33 @@ pub trait Itertools : Iterator { (left, right) } + /// Partition a sequence of `Result`s into one list of all the `Ok` elements + /// and another list of all the `Err` elements. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let successes_and_failures = vec![Ok(1), Err(false), Err(true), Ok(2)]; + /// + /// let (successes, failures): (Vec<_>, Vec<_>) = successes_and_failures + /// .into_iter() + /// .partition_result(); + /// + /// assert_eq!(successes, [1, 2]); + /// assert_eq!(failures, [false, true]); + /// ``` + fn partition_result(self) -> (A, B) + where + Self: Iterator> + Sized, + A: Default + Extend, + B: Default + Extend, + { + self.partition_map(|r| match r { + Ok(v) => Either::Left(v), + Err(v) => Either::Right(v), + }) + } + /// Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values /// are taken from `(Key, Value)` tuple pairs yielded by the input iterator. /// From d4b7d867f98613bb188178895c21075ca6894464 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 10:45:39 +0100 Subject: [PATCH 007/633] Move ignore_ident to macro file --- src/impl_macros.rs | 4 ++++ src/tuple_impl.rs | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/impl_macros.rs b/src/impl_macros.rs index 04ab8e177..3da8c632f 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -22,3 +22,7 @@ macro_rules! clone_fields { } } } + +macro_rules! ignore_ident{ + ($id:ident, $($t:tt)*) => {$($t)*}; +} diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 871b7a5c8..c73d8822d 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -248,9 +248,6 @@ macro_rules! count_ident{ () => {0}; ($i0:ident, $($i:ident,)*) => {1 + count_ident!($($i,)*)}; } -macro_rules! ignore_ident{ - ($id:ident, $($t:tt)*) => {$($t)*}; -} macro_rules! rev_for_each_ident{ ($m:ident, ) => {}; ($m:ident, $i0:ident, $($i:ident,)*) => { From 118f49ffb1d59e1443e0d361487555b5066cf5a2 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 10:50:06 +0100 Subject: [PATCH 008/633] Avoid redundant macro arguments --- src/adaptors/mod.rs | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 47a467407..a9a8a9018 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -737,7 +737,7 @@ impl HasCombination for (I::Item,) { } macro_rules! impl_tuple_combination { - ($C:ident $P:ident ; $A:ident, $($I:ident),* ; $($X:ident)*) => ( + ($C:ident $P:ident ; $($X:ident)*) => ( #[derive(Clone, Debug)] pub struct $C { item: Option, @@ -766,11 +766,11 @@ macro_rules! impl_tuple_combination { } } - impl Iterator for $C - where I: Iterator + Clone, + impl Iterator for $C + where I: Iterator + Clone, I::Item: Clone { - type Item = ($($I),*); + type Item = (A, $(ignore_ident!($X, A)),*); fn next(&mut self) -> Option { if let Some(($($X),*,)) = self.c.next() { @@ -786,8 +786,8 @@ macro_rules! impl_tuple_combination { } } - impl HasCombination for ($($I),*) - where I: Iterator + Clone, + impl HasCombination for (A, $(ignore_ident!($X, A)),*) + where I: Iterator + Clone, I::Item: Clone { type Combination = $C>; @@ -800,25 +800,24 @@ macro_rules! impl_tuple_combination { // use itertools::Itertools; // // for i in 2..=12 { -// println!("impl_tuple_combination!(Tuple{arity}Combination Tuple{prev}Combination; {tys}; {idents});", +// println!("impl_tuple_combination!(Tuple{arity}Combination Tuple{prev}Combination; {idents});", // arity = i, // prev = i - 1, -// tys = iter::repeat("A").take(i + 1).join(", "), // idents = ('a'..'z').take(i - 1).join(" "), // ); // } // It could probably be replaced by a bit more macro cleverness. -impl_tuple_combination!(Tuple2Combination Tuple1Combination; A, A, A; a); -impl_tuple_combination!(Tuple3Combination Tuple2Combination; A, A, A, A; a b); -impl_tuple_combination!(Tuple4Combination Tuple3Combination; A, A, A, A, A; a b c); -impl_tuple_combination!(Tuple5Combination Tuple4Combination; A, A, A, A, A, A; a b c d); -impl_tuple_combination!(Tuple6Combination Tuple5Combination; A, A, A, A, A, A, A; a b c d e); -impl_tuple_combination!(Tuple7Combination Tuple6Combination; A, A, A, A, A, A, A, A; a b c d e f); -impl_tuple_combination!(Tuple8Combination Tuple7Combination; A, A, A, A, A, A, A, A, A; a b c d e f g); -impl_tuple_combination!(Tuple9Combination Tuple8Combination; A, A, A, A, A, A, A, A, A, A; a b c d e f g h); -impl_tuple_combination!(Tuple10Combination Tuple9Combination; A, A, A, A, A, A, A, A, A, A, A; a b c d e f g h i); -impl_tuple_combination!(Tuple11Combination Tuple10Combination; A, A, A, A, A, A, A, A, A, A, A, A; a b c d e f g h i j); -impl_tuple_combination!(Tuple12Combination Tuple11Combination; A, A, A, A, A, A, A, A, A, A, A, A, A; a b c d e f g h i j k); +impl_tuple_combination!(Tuple2Combination Tuple1Combination; a); +impl_tuple_combination!(Tuple3Combination Tuple2Combination; a b); +impl_tuple_combination!(Tuple4Combination Tuple3Combination; a b c); +impl_tuple_combination!(Tuple5Combination Tuple4Combination; a b c d); +impl_tuple_combination!(Tuple6Combination Tuple5Combination; a b c d e); +impl_tuple_combination!(Tuple7Combination Tuple6Combination; a b c d e f); +impl_tuple_combination!(Tuple8Combination Tuple7Combination; a b c d e f g); +impl_tuple_combination!(Tuple9Combination Tuple8Combination; a b c d e f g h); +impl_tuple_combination!(Tuple10Combination Tuple9Combination; a b c d e f g h i); +impl_tuple_combination!(Tuple11Combination Tuple10Combination; a b c d e f g h i j); +impl_tuple_combination!(Tuple12Combination Tuple11Combination; a b c d e f g h i j k); /// An iterator adapter to filter values within a nested `Result::Ok`. /// From dcc4b80dafe5bf9b0605dcc11ea104d65cc9686e Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 11:02:13 +0100 Subject: [PATCH 009/633] fn from delegates work to other implementation --- src/adaptors/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a9a8a9018..7de8d4e01 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -757,12 +757,7 @@ macro_rules! impl_tuple_combination { impl From for $C> { fn from(iter: I) -> Self { - let mut iter = iter.fuse(); - $C { - item: iter.next(), - iter: iter.clone(), - c: $P::from(iter), - } + Self::from(iter.fuse()) } } From bfa59bcc738d54efb96efb793999bd0d26952adc Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 16:42:12 +0100 Subject: [PATCH 010/633] Remove unnecessary phantom data --- src/adaptors/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 7de8d4e01..1bbaebf63 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -682,7 +682,6 @@ pub struct TupleCombinations { iter: T::Combination, _mi: PhantomData, - _mt: PhantomData } pub trait HasCombination: Sized { @@ -698,7 +697,6 @@ pub fn tuple_combinations(iter: I) -> TupleCombinations TupleCombinations { iter: T::Combination::from(iter), _mi: PhantomData, - _mt: PhantomData, } } From 43fe27965576b643e7527989d061c183f3251dec Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 17:33:42 +0100 Subject: [PATCH 011/633] Use Self instead of macro argument --- src/adaptors/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 1bbaebf63..81ee3e288 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -745,7 +745,7 @@ macro_rules! impl_tuple_combination { impl From for $C { fn from(mut iter: I) -> Self { - $C { + Self { item: iter.next(), iter: iter.clone(), c: $P::from(iter), From 077040ff8bb75be262fd0b25683189e7606ca262 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 2 Jan 2021 17:35:54 +0100 Subject: [PATCH 012/633] Use into instead of repeating macro argument --- src/adaptors/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 81ee3e288..dfc68978f 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -748,7 +748,7 @@ macro_rules! impl_tuple_combination { Self { item: iter.next(), iter: iter.clone(), - c: $P::from(iter), + c: iter.into(), } } } @@ -772,7 +772,7 @@ macro_rules! impl_tuple_combination { } else { self.item = self.iter.next(); self.item.clone().and_then(|z| { - self.c = $P::from(self.iter.clone()); + self.c = self.iter.clone().into(); self.c.next().map(|($($X),*,)| (z, $($X),*)) }) } From d118414806a8cb8c5c5d9a0f159fb8bf65c6b519 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sat, 9 Jan 2021 21:42:33 -0500 Subject: [PATCH 013/633] Add Itertools.contains --- src/lib.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0c6af37e4..8edfbe0fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1608,6 +1608,53 @@ pub trait Itertools : Iterator { None } + /// Check whether the iterator contains an item. + /// + /// If the iterator contains the item prior to its end, + /// this method will short-circuit and only partially + /// exhaust the iterator. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let data = vec![ + /// "foo".to_owned(), + /// "bar".to_owned(), + /// "baz".to_owned(), + /// ]; + /// // this could get expensive: + /// assert!(data.contains(&"foo".to_owned())); + /// // this, less so: + /// assert!(data.iter().contains("foo")); + /// + /// // now the not-as-motivating tests involving Copy data: + /// + /// let data = vec![4usize, 5, 1, 3, 0, 2]; + /// assert!(data.iter().contains(&4)); + /// assert!(!data.iter().contains(&6)); + /// for x in (0..50) { + /// assert_eq!(data.contains(&x), data.iter().contains(&x)); + /// } + /// + /// let mut it = data.iter(); + /// assert!(!it.contains(&6)); + /// assert_eq!(it.next(), None); + /// + /// let mut it = data.iter(); + /// assert!(it.contains(&3)); + /// assert_eq!(it.next(), Some(&0)); + /// + /// let data : Option = None; + /// assert!(!data.into_iter().contains(0)); + /// ``` + fn contains(&mut self, query: Q) -> bool + where + Self: Sized, + Self::Item: PartialEq, + { + self.any(|x| x == query) + } + /// Check whether all elements compare equal. /// /// Empty iterators are considered to have equal elements: From 20b7f57539909c6041894b7d401bbb29c90bec13 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 09:08:41 -0500 Subject: [PATCH 014/633] Add Itertools.counts_by --- src/lib.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0c6af37e4..7e3402a6d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3048,6 +3048,36 @@ pub trait Itertools : Iterator { self.for_each(|item| *counts.entry(item).or_default() += 1); counts } + + /// Collect the items in this iterator and return a `HashMap` which + /// contains each item that appears in the iterator and the number + /// of times it appears, + /// determining identity using a keying function. + /// + /// # Examples + /// ``` + /// # use itertools::Itertools; + /// # use std::collections::HashMap; + /// let counts: HashMap = vec![ + /// (1, "foo"), (1, "bar"), (1, "baz"), + /// (3, "spam"), (3, "eggs"), (5, "foo") + /// ].into_iter().counts_by(|(fst,snd)| fst); + /// assert_eq!(counts[&1], 3); + /// assert_eq!(counts[&3], 2); + /// assert_eq!(counts[&5], 1); + /// assert_eq!(counts.get(&0), None); + /// ``` + #[cfg(feature = "use_std")] + fn counts_by(self, mut f: F) -> HashMap + where + Self: Sized, + K: Eq + Hash, + F: FnMut(Self::Item) -> K, + { + let mut counts = HashMap::new(); + self.for_each(|item| *counts.entry(f(item)).or_default() += 1); + counts + } } impl Itertools for T where T: Iterator { } From 29b68288dd443b65917646d9e327865de4cd79d3 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 13:52:20 -0500 Subject: [PATCH 015/633] Update src/lib.rs Co-authored-by: Jack Wrenn --- src/lib.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8edfbe0fa..17d755d6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1608,11 +1608,12 @@ pub trait Itertools : Iterator { None } - /// Check whether the iterator contains an item. + /// Returns `true` if the given item is present in this iterator. /// - /// If the iterator contains the item prior to its end, - /// this method will short-circuit and only partially - /// exhaust the iterator. + /// This method is short-circuiting. If the given item is present in this + /// iterator, the this method will consume the iterator up-to-and-including + /// the item. If the given item is not present in this iterator, the + /// iterator will be exhausted. /// /// ``` /// use itertools::Itertools; From 3c4d78475ebe8cb3b345251372f7c7363e9f38df Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 13:52:49 -0500 Subject: [PATCH 016/633] Update src/lib.rs Co-authored-by: Jack Wrenn --- src/lib.rs | 43 ++++++++++++++----------------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 17d755d6f..f8c8c58dd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1618,35 +1618,20 @@ pub trait Itertools : Iterator { /// ``` /// use itertools::Itertools; /// - /// let data = vec![ - /// "foo".to_owned(), - /// "bar".to_owned(), - /// "baz".to_owned(), - /// ]; - /// // this could get expensive: - /// assert!(data.contains(&"foo".to_owned())); - /// // this, less so: - /// assert!(data.iter().contains("foo")); - /// - /// // now the not-as-motivating tests involving Copy data: - /// - /// let data = vec![4usize, 5, 1, 3, 0, 2]; - /// assert!(data.iter().contains(&4)); - /// assert!(!data.iter().contains(&6)); - /// for x in (0..50) { - /// assert_eq!(data.contains(&x), data.iter().contains(&x)); - /// } - /// - /// let mut it = data.iter(); - /// assert!(!it.contains(&6)); - /// assert_eq!(it.next(), None); - /// - /// let mut it = data.iter(); - /// assert!(it.contains(&3)); - /// assert_eq!(it.next(), Some(&0)); - /// - /// let data : Option = None; - /// assert!(!data.into_iter().contains(0)); + /// #[derive(PartialEq, Debug)] + /// enum Enum { A, B, C, D, E, } + /// + /// let mut iter = vec![Enum::A, Enum::B, Enum::C, Enum::D].into_iter(); + /// + /// // search `iter` for `B` + /// assert_eq!(iter.contains(Enum::B), true); + /// // `B` was found, so the iterator now rests at the item after `B` (i.e, `C`). + /// assert_eq!(iter.next(), Some(Enum::C)); + /// + /// // search `iter` for `E` + /// assert_eq!(iter.contains(Enum::E), false); + /// // `E` wasn't found, so `iter` is now exhausted + /// assert_eq!(iter.next(), None); /// ``` fn contains(&mut self, query: Q) -> bool where From d4a2937237d04266cef7b890fbc16be0d37b4715 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 13:57:35 -0500 Subject: [PATCH 017/633] Update src/lib.rs Co-authored-by: Jack Wrenn --- src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f8c8c58dd..f1716f4aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1616,8 +1616,6 @@ pub trait Itertools : Iterator { /// iterator will be exhausted. /// /// ``` - /// use itertools::Itertools; - /// /// #[derive(PartialEq, Debug)] /// enum Enum { A, B, C, D, E, } /// @@ -1633,12 +1631,13 @@ pub trait Itertools : Iterator { /// // `E` wasn't found, so `iter` is now exhausted /// assert_eq!(iter.next(), None); /// ``` - fn contains(&mut self, query: Q) -> bool + fn contains(&mut self, query: &Q) -> bool where Self: Sized, - Self::Item: PartialEq, + Self::Item: Borrow, + Q: PartialEq, { - self.any(|x| x == query) + self.any(|x| x.borrow() == query) } /// Check whether all elements compare equal. From 43911579d995ebab2ea5f384b29c4c400161c47c Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 13:59:04 -0500 Subject: [PATCH 018/633] Update src/lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index f1716f4aa..42ec95f5e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1611,7 +1611,7 @@ pub trait Itertools : Iterator { /// Returns `true` if the given item is present in this iterator. /// /// This method is short-circuiting. If the given item is present in this - /// iterator, the this method will consume the iterator up-to-and-including + /// iterator, this method will consume the iterator up-to-and-including /// the item. If the given item is not present in this iterator, the /// iterator will be exhausted. /// From eb967a1a0b5765fd76891fb285ad18a3cbe7f3e2 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 14:11:13 -0500 Subject: [PATCH 019/633] Fix missing import --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 42ec95f5e..74e165500 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,6 +59,8 @@ use alloc::{ pub use either::Either; +#[cfg(feature = "use_std")] +use std::borrow::Borrow; #[cfg(feature = "use_std")] use std::collections::HashMap; use std::iter::{IntoIterator, once}; From 2541ca928802492714ac27ac812390a3b6215df3 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 14:21:56 -0500 Subject: [PATCH 020/633] Make the doctests pass --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74e165500..ae2683e5d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1618,18 +1618,20 @@ pub trait Itertools : Iterator { /// iterator will be exhausted. /// /// ``` + /// use itertools::Itertools; + /// /// #[derive(PartialEq, Debug)] /// enum Enum { A, B, C, D, E, } /// /// let mut iter = vec![Enum::A, Enum::B, Enum::C, Enum::D].into_iter(); /// /// // search `iter` for `B` - /// assert_eq!(iter.contains(Enum::B), true); + /// assert_eq!(iter.contains(&Enum::B), true); /// // `B` was found, so the iterator now rests at the item after `B` (i.e, `C`). /// assert_eq!(iter.next(), Some(Enum::C)); /// /// // search `iter` for `E` - /// assert_eq!(iter.contains(Enum::E), false); + /// assert_eq!(iter.contains(&Enum::E), false); /// // `E` wasn't found, so `iter` is now exhausted /// assert_eq!(iter.next(), None); /// ``` From a15e7d9d54455750bf70a909119dfb42f42260c7 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 14:33:28 -0500 Subject: [PATCH 021/633] Update src/lib.rs Co-authored-by: Jack Wrenn --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ae2683e5d..a9916ad72 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,8 +59,7 @@ use alloc::{ pub use either::Either; -#[cfg(feature = "use_std")] -use std::borrow::Borrow; +use core::borrow:Borrow; #[cfg(feature = "use_std")] use std::collections::HashMap; use std::iter::{IntoIterator, once}; From 6a8a6c16b2e1dd013bc0ec9409b753a20f5852b7 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 14:35:55 -0500 Subject: [PATCH 022/633] Fix typo --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index a9916ad72..fee1e6108 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -59,7 +59,7 @@ use alloc::{ pub use either::Either; -use core::borrow:Borrow; +use core::borrow::Borrow; #[cfg(feature = "use_std")] use std::collections::HashMap; use std::iter::{IntoIterator, once}; From 7cf4af605a14ab25deed24c42fa232803f95d5e9 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Sun, 10 Jan 2021 14:42:57 -0500 Subject: [PATCH 023/633] Improve brevity+elegance at the cost of greater reliance on compiler optimizations. --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7e3402a6d..d1f5daeca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3074,9 +3074,7 @@ pub trait Itertools : Iterator { K: Eq + Hash, F: FnMut(Self::Item) -> K, { - let mut counts = HashMap::new(); - self.for_each(|item| *counts.entry(f(item)).or_default() += 1); - counts + self.map(f).counts() } } From 8d313d54a366f60919061f4d908b5eedf9c6f123 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Tue, 12 Jan 2021 07:55:53 -0500 Subject: [PATCH 024/633] Update src/lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index d1f5daeca..3dd39ee53 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3068,7 +3068,7 @@ pub trait Itertools : Iterator { /// assert_eq!(counts.get(&0), None); /// ``` #[cfg(feature = "use_std")] - fn counts_by(self, mut f: F) -> HashMap + fn counts_by(self, f: F) -> HashMap where Self: Sized, K: Eq + Hash, From eb165f415aa1e3cfd156fddab748d1b733291ff7 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Wed, 13 Jan 2021 21:04:46 -0500 Subject: [PATCH 025/633] Update src/lib.rs Co-authored-by: Jack Wrenn --- src/lib.rs | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3dd39ee53..b9cc0dbd4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3054,18 +3054,31 @@ pub trait Itertools : Iterator { /// of times it appears, /// determining identity using a keying function. /// - /// # Examples /// ``` - /// # use itertools::Itertools; - /// # use std::collections::HashMap; - /// let counts: HashMap = vec![ - /// (1, "foo"), (1, "bar"), (1, "baz"), - /// (3, "spam"), (3, "eggs"), (5, "foo") - /// ].into_iter().counts_by(|(fst,snd)| fst); - /// assert_eq!(counts[&1], 3); - /// assert_eq!(counts[&3], 2); - /// assert_eq!(counts[&5], 1); - /// assert_eq!(counts.get(&0), None); + /// struct Character { + /// first_name: &'static str, + /// last_name: &'static str, + /// } + /// + /// let characters = + /// vec![ + /// Character { first_name: "Amy", last_name: "Pond" }, + /// Character { first_name: "Amy", last_name: "Wong" }, + /// Character { first_name: "Amy", last_name: "Santiago" }, + /// Character { first_name: "James", last_name: "Bond" }, + /// Character { first_name: "James", last_name: "Sullivan" }, + /// Character { first_name: "James", last_name: "Norington" }, + /// Character { first_name: "James", last_name: "Kirk" }, + /// ]; + /// + /// let first_name_frequency = + /// characters + /// .into_iter() + /// .counts_by(|c| c.first_name); + /// + /// assert_eq!(first_name_frequency["Amy"], 3); + /// assert_eq!(first_name_frequency["James"], 4); + /// assert_eq!(first_name_frequency.contains_key("Asha"), false); /// ``` #[cfg(feature = "use_std")] fn counts_by(self, f: F) -> HashMap From 738cfd5e0fd23b5548092d1efd0470eac1af9147 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Wed, 13 Jan 2021 21:10:05 -0500 Subject: [PATCH 026/633] Update src/lib.rs --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index b9cc0dbd4..92495881e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3055,6 +3055,7 @@ pub trait Itertools : Iterator { /// determining identity using a keying function. /// /// ``` + # use itertools::Itertools; /// struct Character { /// first_name: &'static str, /// last_name: &'static str, From e0960958c722768a9a4d81a0ea3ed5ba47a40554 Mon Sep 17 00:00:00 2001 From: Milo Mirate Date: Wed, 13 Jan 2021 21:11:42 -0500 Subject: [PATCH 027/633] Update lib.rs --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 92495881e..c5a6151ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3055,7 +3055,7 @@ pub trait Itertools : Iterator { /// determining identity using a keying function. /// /// ``` - # use itertools::Itertools; + /// # use itertools::Itertools; /// struct Character { /// first_name: &'static str, /// last_name: &'static str, From d7b3fa4e76223b9d409faa4d241a83d716c122f0 Mon Sep 17 00:00:00 2001 From: Giles Cope Date: Sun, 17 Jan 2021 16:02:28 +0000 Subject: [PATCH 028/633] Switched numbers to chars so that its clearer which part of the tuple result is the count. --- src/lib.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 284a25d6b..7cd451d1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1110,12 +1110,13 @@ pub trait Itertools : Iterator { /// ``` /// use itertools::Itertools; /// - /// let data = vec![1., 1., 2., 3., 3., 2., 2.]; + /// let data = vec!['a', 'a', 'b', 'c', 'c', 'b', 'b']; /// itertools::assert_equal(data.into_iter().dedup_with_count(), - /// vec![(2, 1.), (1, 2.), (2, 3.), (2, 2.)]); + /// vec![(2, 'a'), (1, 'b'), (2, 'c'), (2, 'b')]); /// ``` fn dedup_with_count(self) -> DedupWithCount - where Self: Sized, + where + Self: Sized, { adaptors::dedup_with_count(self) } @@ -1132,13 +1133,14 @@ pub trait Itertools : Iterator { /// ``` /// use itertools::Itertools; /// - /// let data = vec![(0, 1.), (1, 1.), (0, 2.), (0, 3.), (1, 3.), (1, 2.), (2, 2.)]; + /// let data = vec![(0, 'a'), (1, 'a'), (0, 'b'), (0, 'c'), (1, 'c'), (1, 'b'), (2, 'b')]; /// itertools::assert_equal(data.into_iter().dedup_by_with_count(|x, y| x.1 == y.1), - /// vec![(2, (0, 1.)), (1, (0, 2.)), (2, (0, 3.)), (2, (1, 2.))]); + /// vec![(2, (0, 'a')), (1, (0, 'b')), (2, (0, 'c')), (2, (1, 'b'))]); /// ``` fn dedup_by_with_count(self, cmp: Cmp) -> DedupByWithCount - where Self: Sized, - Cmp: FnMut(&Self::Item, &Self::Item) -> bool, + where + Self: Sized, + Cmp: FnMut(&Self::Item, &Self::Item) -> bool, { adaptors::dedup_by_with_count(self, cmp) } From ddeb9f1945d07f7be7b0bdeec5bea3fea2af59a9 Mon Sep 17 00:00:00 2001 From: Samuel Tardieu Date: Mon, 4 Dec 2017 13:59:57 +0100 Subject: [PATCH 029/633] FEAT: implement .all_unique() --- src/lib.rs | 26 ++++++++++++++++++++++++++ tests/test_std.rs | 7 +++++++ 2 files changed, 33 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7cd451d1f..6c60213ea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,6 +66,8 @@ use std::iter::{IntoIterator, once}; use std::cmp::Ordering; use std::fmt; #[cfg(feature = "use_std")] +use std::collections::HashSet; +#[cfg(feature = "use_std")] use std::hash::Hash; #[cfg(feature = "use_alloc")] use std::fmt::Write; @@ -1671,6 +1673,30 @@ pub trait Itertools : Iterator { } } + /// Check whether all elements are unique (non equal). + /// + /// Empty iterators are considered to have unique elements: + /// + /// ``` + /// use itertools::Itertools; + /// + /// let data = vec![1, 2, 3, 4, 1, 5]; + /// assert!(!data.iter().all_unique()); + /// assert!(data[0..4].iter().all_unique()); + /// assert!(data[1..6].iter().all_unique()); + /// + /// let data : Option = None; + /// assert!(data.into_iter().all_unique()); + /// ``` + #[cfg(feature = "use_std")] + fn all_unique(&mut self) -> bool + where Self: Sized, + Self::Item: Eq + Hash + { + let mut used = HashSet::new(); + self.all(move |elt| used.insert(elt)) + } + /// Consume the first `n` elements from the iterator eagerly, /// and return the same iterator again. /// diff --git a/tests/test_std.rs b/tests/test_std.rs index d1ff815da..4f99b8258 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -189,6 +189,13 @@ fn all_equal() { } } +#[test] +fn all_unique() { + assert!("ABCDEFGH".chars().all_unique()); + assert!(!"ABCDEFGA".chars().all_unique()); + assert!(::std::iter::empty::().all_unique()); +} + #[test] fn test_put_back_n() { let xs = [0, 1, 1, 1, 2, 1, 3, 3]; From 2c9315c70a8d3324cde281252595b65fb4307512 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Wed, 20 Jan 2021 14:18:34 +0100 Subject: [PATCH 030/633] Use internal iteration in GroupingMap --- src/grouping_map.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 5232f5d6d..cc89efe13 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -102,12 +102,12 @@ impl GroupingMap { let mut destination_map = HashMap::new(); - for (key, val) in self.iter { + self.iter.for_each(|(key, val)| { let acc = destination_map.remove(&key); if let Some(op_res) = operation(acc, &key, val) { destination_map.insert(key, op_res); } - } + }); destination_map } @@ -208,9 +208,9 @@ impl GroupingMap { let mut destination_map = HashMap::new(); - for (key, val) in self.iter { + self.iter.for_each(|(key, val)| { destination_map.entry(key).or_insert_with(C::default).extend(Some(val)); - } + }); destination_map } From 1b8192106031913f505f85c6eb2a4bf655754db2 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Wed, 20 Jan 2021 14:20:13 +0100 Subject: [PATCH 031/633] Use internal iteration in k_smallest --- src/k_smallest.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index d58ec70d0..acaea5941 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -6,7 +6,7 @@ pub(crate) fn k_smallest>(mut iter: I, k: usize) - let mut heap = iter.by_ref().take(k).collect::>(); - for i in iter { + iter.for_each(|i| { debug_assert_eq!(heap.len(), k); // Equivalent to heap.push(min(i, heap.pop())) but more efficient. // This should be done with a single `.peek_mut().unwrap()` but @@ -14,7 +14,7 @@ pub(crate) fn k_smallest>(mut iter: I, k: usize) - if *heap.peek().unwrap() > i { *heap.peek_mut().unwrap() = i; } - } + }); heap } From 80cd4e90dbb25d2024bac63e08139f26fbfde3b6 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Wed, 20 Jan 2021 14:33:07 +0100 Subject: [PATCH 032/633] Use internal iteration in coalesce --- src/adaptors/coalesce.rs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index bf8dd05b3..1afbee58f 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -40,20 +40,21 @@ where fn next(&mut self) -> Option { // this fuses the iterator - let mut last = match self.last.take() { - None => return None, - Some(x) => x, - }; - for next in &mut self.iter { - match self.f.coalesce_pair(last, next) { - Ok(joined) => last = joined, - Err((last_, next_)) => { - self.last = Some(next_); - return Some(last_); - } - } - } - Some(last) + let last = self.last.take()?; + + let self_last = &mut self.last; + let self_f = &mut self.f; + Some( + self.iter + .try_fold(last, |last, next| match self_f.coalesce_pair(last, next) { + Ok(joined) => Ok(joined), + Err((last_, next_)) => { + *self_last = Some(next_); + Err(last_) + } + }) + .unwrap_or_else(|x| x), + ) } fn size_hint(&self) -> (usize, Option) { From 2cb789cef76681f43de604bda14a872689fdf8f5 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Wed, 20 Jan 2021 17:02:56 +0100 Subject: [PATCH 033/633] Remove unpredictable branch from kmerge::sift_down --- src/kmerge_impl.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index a1dcec2a1..36bfc5a8a 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -74,14 +74,13 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) debug_assert!(index <= heap.len()); let mut pos = index; let mut child = 2 * pos + 1; - // the `pos` conditional is to avoid a bounds check - while pos < heap.len() && child < heap.len() { - let right = child + 1; - + // Require the right child to be present + // This allows to find the index of the smallest child without a branch + // that wouldn't be predicted if present + while child + 1 < heap.len() { // pick the smaller of the two children - if right < heap.len() && less_than(&heap[right], &heap[child]) { - child = right; - } + // use aritmethic to avoid an unpredictable branch + child += less_than(&heap[child+1], &heap[child]) as usize; // sift down is done if we are already in order if !less_than(&heap[child], &heap[pos]) { @@ -91,6 +90,11 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) pos = child; child = 2 * pos + 1; } + // Check if the last (left) child was an only child + // if it is then it has to be compared with the parent + if child + 1 == heap.len() && less_than(&heap[child], &heap[pos]) { + heap.swap(pos, child); + } } /// An iterator adaptor that merges an abitrary number of base iterators in ascending order. From e1090aa0902943d2f6cc89a5878193d0ff950b29 Mon Sep 17 00:00:00 2001 From: Petros Angelatos Date: Thu, 3 Dec 2020 18:33:43 +0100 Subject: [PATCH 034/633] add .duplicates() and .duplicates_by(..) operations Uses a HashMap to detect duplicates in an iterator and emits them only once. Items are never cloned. Signed-off-by: Petros Angelatos --- src/duplicates_impl.rs | 206 +++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 52 +++++++++++ tests/quick.rs | 6 ++ tests/test_std.rs | 26 ++++++ 4 files changed, 290 insertions(+) create mode 100644 src/duplicates_impl.rs diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs new file mode 100644 index 000000000..a581cc04d --- /dev/null +++ b/src/duplicates_impl.rs @@ -0,0 +1,206 @@ +use std::hash::Hash; + +mod private { + use std::collections::HashMap; + use std::hash::Hash; + use std::fmt; + + #[derive(Clone)] + #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] + pub struct DuplicatesBy { + pub(crate) iter: I, + pub(crate) meta: Meta, + } + + impl fmt::Debug for DuplicatesBy + where + I: Iterator + fmt::Debug, + V: fmt::Debug + Hash + Eq, + { + debug_fmt_fields!(DuplicatesBy, iter, meta.used); + } + + impl DuplicatesBy { + pub(crate) fn new(iter: I, key_method: F) -> Self { + DuplicatesBy { + iter, + meta: Meta { + used: HashMap::new(), + pending: 0, + key_method, + }, + } + } + } + + #[derive(Clone)] + pub struct Meta { + used: HashMap, + pending: usize, + key_method: F, + } + + impl Meta + where + Key: Eq + Hash, + { + /// Takes an item and returns it back to the caller if it's the second time we see it. + /// Otherwise the item is consumed and None is returned + #[inline(always)] + fn filter(&mut self, item: I) -> Option + where + F: KeyMethod, + { + let kv = self.key_method.make(item); + match self.used.get_mut(kv.key_ref()) { + None => { + self.used.insert(kv.key(), false); + self.pending += 1; + None + } + Some(true) => None, + Some(produced) => { + *produced = true; + self.pending -= 1; + Some(kv.value()) + } + } + } + } + + impl Iterator for DuplicatesBy + where + I: Iterator, + Key: Eq + Hash, + F: KeyMethod, + { + type Item = I::Item; + + fn next(&mut self) -> Option { + let DuplicatesBy { iter, meta } = self; + iter.find_map(|v| meta.filter(v)) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let (_, hi) = self.iter.size_hint(); + // There are `hi` number of items left in the base iterator. In the best case scenario, + // these items are exactly the same as the ones pending (i.e items seen exactly once so + // far), plus (hi - pending) / 2 pairs of never seen before items. + let hi = hi.map(|hi| { + let max_pending = std::cmp::min(self.meta.pending, hi); + let max_new = std::cmp::max(hi - self.meta.pending, 0) / 2; + max_pending + max_new + }); + // The lower bound is always 0 since we might only get unique items from now on + (0, hi) + } + } + + impl DoubleEndedIterator for DuplicatesBy + where + I: DoubleEndedIterator, + Key: Eq + Hash, + F: KeyMethod, + { + fn next_back(&mut self) -> Option { + let DuplicatesBy { iter, meta } = self; + iter.rev().find_map(|v| meta.filter(v)) + } + } + + /// A keying method for use with `DuplicatesBy` + pub trait KeyMethod { + type Container: KeyXorValue; + + fn make(&mut self, value: V) -> Self::Container; + } + + /// Apply the identity function to elements before checking them for equality. + pub struct ById; + impl KeyMethod for ById { + type Container = JustValue; + + fn make(&mut self, v: V) -> Self::Container { + JustValue(v) + } + } + + /// Apply a user-supplied function to elements before checking them for equality. + pub struct ByFn(pub(crate) F); + impl KeyMethod for ByFn + where + F: FnMut(&V) -> K, + { + type Container = KeyValue; + + fn make(&mut self, v: V) -> Self::Container { + KeyValue((self.0)(&v), v) + } + } + + // Implementors of this trait can hold onto a key and a value but only give access to one of them + // at a time. This allows the key and the value to be the same value internally + pub trait KeyXorValue { + fn key_ref(&self) -> &K; + fn key(self) -> K; + fn value(self) -> V; + } + + pub struct KeyValue(K, V); + impl KeyXorValue for KeyValue { + fn key_ref(&self) -> &K { + &self.0 + } + fn key(self) -> K { + self.0 + } + fn value(self) -> V { + self.1 + } + } + + pub struct JustValue(V); + impl KeyXorValue for JustValue { + fn key_ref(&self) -> &V { + &self.0 + } + fn key(self) -> V { + self.0 + } + fn value(self) -> V { + self.0 + } + } +} + +/// An iterator adapter to filter for duplicate elements. +/// +/// See [`.duplicates_by()`](../trait.Itertools.html#method.duplicates_by) for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub type DuplicatesBy = private::DuplicatesBy>; + +/// Create a new `DuplicatesBy` iterator. +pub fn duplicates_by(iter: I, f: F) -> DuplicatesBy +where + Key: Eq + Hash, + F: FnMut(&I::Item) -> Key, + I: Iterator, +{ + DuplicatesBy::new(iter, private::ByFn(f)) +} + +/// An iterator adapter to filter out duplicate elements. +/// +/// See [`.duplicates()`](../trait.Itertools.html#method.duplicates) for more information. +pub type Duplicates = private::DuplicatesBy::Item, private::ById>; + +/// Create a new `Duplicates` iterator. +pub fn duplicates(iter: I) -> Duplicates +where + I: Iterator, + I::Item: Eq + Hash, +{ + Duplicates::new(iter, private::ById) +} + diff --git a/src/lib.rs b/src/lib.rs index 7cd451d1f..26237b454 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,6 +147,8 @@ pub mod structs { pub use crate::tee::Tee; pub use crate::tuple_impl::{TupleBuffer, TupleWindows, CircularTupleWindows, Tuples}; #[cfg(feature = "use_std")] + pub use crate::duplicates_impl::{Duplicates, DuplicatesBy}; + #[cfg(feature = "use_std")] pub use crate::unique_impl::{Unique, UniqueBy}; pub use crate::with_position::WithPosition; pub use crate::zip_eq_impl::ZipEq; @@ -228,6 +230,8 @@ mod sources; mod tee; mod tuple_impl; #[cfg(feature = "use_std")] +mod duplicates_impl; +#[cfg(feature = "use_std")] mod unique_impl; mod with_position; mod zip_eq_impl; @@ -1145,6 +1149,54 @@ pub trait Itertools : Iterator { adaptors::dedup_by_with_count(self, cmp) } + /// Return an iterator adaptor that produces elements that appear more than once during the + /// iteration. Duplicates are detected using hash and equality. + /// + /// The iterator is stable, returning the duplicate items in the order in which they occur in + /// the adapted iterator. Each duplicate item is returned exactly once. If an item appears more + /// than twice, the second item is the item retained and the rest are discarded. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let data = vec![10, 20, 30, 20, 40, 10, 50]; + /// itertools::assert_equal(data.into_iter().duplicates(), + /// vec![20, 10]); + /// ``` + #[cfg(feature = "use_std")] + fn duplicates(self) -> Duplicates + where Self: Sized, + Self::Item: Eq + Hash + { + duplicates_impl::duplicates(self) + } + + /// Return an iterator adaptor that produces elements that appear more than once during the + /// iteration. Duplicates are detected using hash and equality. + /// + /// Duplicates are detected by comparing the key they map to with the keying function `f` by + /// hash and equality. The keys are stored in a hash map in the iterator. + /// + /// The iterator is stable, returning the duplicate items in the order in which they occur in + /// the adapted iterator. Each duplicate item is returned exactly once. If an item appears more + /// than twice, the second item is the item retained and the rest are discarded. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let data = vec!["a", "bb", "aa", "c", "ccc"]; + /// itertools::assert_equal(data.into_iter().duplicates_by(|s| s.len()), + /// vec!["aa", "c"]); + /// ``` + #[cfg(feature = "use_std")] + fn duplicates_by(self, f: F) -> DuplicatesBy + where Self: Sized, + V: Eq + Hash, + F: FnMut(&Self::Item) -> V + { + duplicates_impl::duplicates_by(self, f) + } + /// Return an iterator adaptor that filters out elements that have /// already been produced once during the iteration. Duplicates /// are detected using hash and equality. diff --git a/tests/quick.rs b/tests/quick.rs index e5bee1713..b4ae576af 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -915,6 +915,12 @@ quickcheck! { } } +quickcheck! { + fn size_duplicates(it: Iter) -> bool { + correct_size_hint(it.duplicates()) + } +} + quickcheck! { fn size_unique(it: Iter) -> bool { correct_size_hint(it.unique()) diff --git a/tests/test_std.rs b/tests/test_std.rs index d1ff815da..86b50b6de 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -59,6 +59,32 @@ fn interleave_shortest() { assert_eq!(it.size_hint(), (6, Some(6))); } +#[test] +fn duplicates_by() { + let xs = ["aaa", "bbbbb", "aa", "ccc", "bbbb", "aaaaa", "cccc"]; + let ys = ["aa", "bbbb", "cccc"]; + it::assert_equal(ys.iter(), xs.iter().duplicates_by(|x| x[..2].to_string())); + it::assert_equal(ys.iter(), xs.iter().rev().duplicates_by(|x| x[..2].to_string()).rev()); + let ys_rev = ["ccc", "aa", "bbbbb"]; + it::assert_equal(ys_rev.iter(), xs.iter().duplicates_by(|x| x[..2].to_string()).rev()); +} + +#[test] +fn duplicates() { + let xs = [0, 1, 2, 3, 2, 1, 3]; + let ys = [2, 1, 3]; + it::assert_equal(ys.iter(), xs.iter().duplicates()); + it::assert_equal(ys.iter(), xs.iter().rev().duplicates().rev()); + let ys_rev = [3, 2, 1]; + it::assert_equal(ys_rev.iter(), xs.iter().duplicates().rev()); + + let xs = [0, 1, 0, 1]; + let ys = [0, 1]; + it::assert_equal(ys.iter(), xs.iter().duplicates()); + it::assert_equal(ys.iter(), xs.iter().rev().duplicates().rev()); + let ys_rev = [1, 0]; + it::assert_equal(ys_rev.iter(), xs.iter().duplicates().rev()); +} #[test] fn unique_by() { From 4f24ced2bb5525837698b9a5f001e3e62afb108a Mon Sep 17 00:00:00 2001 From: David Sanders Date: Sat, 30 Jan 2021 14:43:07 -0800 Subject: [PATCH 035/633] Added at_most_one --- src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ tests/quick.rs | 11 +++++++++++ tests/test_core.rs | 8 ++++++++ 3 files changed, 55 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4db8f5d40..c066a8852 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3145,6 +3145,42 @@ pub trait Itertools : Iterator { } } + /// If the iterator yields no elements, Ok(None) will be returned. If the iterator yields + /// exactly one element, that element will be returned, otherwise an error will be returned + /// containing an iterator that has the same output as the input iterator. + /// + /// This provides an additional layer of validation over just calling `Iterator::next()`. + /// If your assumption that there should be at most one element yielded is false this provides + /// the opportunity to detect and handle that, preventing errors at a distance. + /// + /// # Examples + /// ``` + /// use itertools::Itertools; + /// + /// assert_eq!((0..10).filter(|&x| x == 2).at_most_one().unwrap(), Some(2)); + /// assert!((0..10).filter(|&x| x > 1 && x < 4).at_most_one().unwrap_err().eq(2..4)); + /// assert!((0..10).filter(|&x| x > 1 && x < 5).at_most_one().unwrap_err().eq(2..5)); + /// assert_eq!((0..10).filter(|&_| false).at_most_one().unwrap(), None); + /// ``` + fn at_most_one(mut self) -> Result, ExactlyOneError> + where + Self: Sized, + { + match self.next() { + Some(first) => { + match self.next() { + Some(second) => { + Err(ExactlyOneError::new(Some(Either::Left([first, second])), self)) + } + None => { + Ok(Some(first)) + } + } + } + None => Ok(None), + } + } + /// An iterator adaptor that allows the user to peek at multiple `.next()` /// values without advancing the base iterator. /// diff --git a/tests/quick.rs b/tests/quick.rs index b4ae576af..7769cb432 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1203,6 +1203,17 @@ quickcheck! { } } +quickcheck! { + fn at_most_one_i32(a: Vec) -> TestResult { + let ret = a.iter().cloned().at_most_one(); + match a.len() { + 0 => TestResult::from_bool(ret.unwrap() == None), + 1 => TestResult::from_bool(ret.unwrap() == Some(a[0])), + _ => TestResult::from_bool(ret.unwrap_err().eq(a.iter().cloned())), + } + } +} + quickcheck! { fn consistent_grouping_map_with_by(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo }; // Avoid `% 0` diff --git a/tests/test_core.rs b/tests/test_core.rs index 5861653da..16cf06f25 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -253,6 +253,14 @@ fn exactly_one() { assert!((0..10).filter(|&_| false).exactly_one().unwrap_err().eq(0..0)); } +#[test] +fn at_most_one() { + assert_eq!((0..10).filter(|&x| x == 2).at_most_one().unwrap(), Some(2)); + assert!((0..10).filter(|&x| x > 1 && x < 4).at_most_one().unwrap_err().eq(2..4)); + assert!((0..10).filter(|&x| x > 1 && x < 5).at_most_one().unwrap_err().eq(2..5)); + assert_eq!((0..10).filter(|&_| false).at_most_one().unwrap(), None); +} + #[test] fn sum1() { let v: &[i32] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; From 66832632bb7eabf70d5af0f9e02e5ebb3f5ffff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Cassiers?= Date: Thu, 4 Feb 2021 18:28:28 +0100 Subject: [PATCH 036/633] Add a chain! macro. The chain! macro creates an iterator running multiple iterators sequentially. --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ tests/test_core.rs | 23 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 4db8f5d40..1cb21d7a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -346,6 +346,43 @@ macro_rules! izip { }; } +#[macro_export] +/// Create an iterator running multiple iterators sequentially. +/// +/// This is a version of the standard ``.chain()`` that's supporting more than +/// two iterators. `chain!` takes `IntoIterator` arguments. +/// Alternatively, this is an alternative to the standard ``.flatten()`` for a +/// fixed number of iterators of distinct sizes. +/// +/// **Note:** The result of this macro is in the general case an iterator +/// composed of repeated `.chain()`. +/// The special case of one arguments produce $a.into_iter(). +/// +/// +/// ``` +/// # use itertools::chain; +/// # +/// # fn main() { +/// +/// // chain three sequences +/// let chained: Vec = chain!(0..=3, 4..6, vec![6, 7]).collect(); +/// assert_eq!(chained, (0..=7).collect::>()); +/// # } +/// ``` +macro_rules! chain { + () => { + core::iter::empty() + }; + ( $first:expr $(, $rest:expr )* $(,)*) => { + core::iter::IntoIterator::into_iter($first) + $( + .chain( + core::iter::IntoIterator::into_iter($rest) + ) + )* + }; +} + /// An [`Iterator`] blanket implementation that provides extra adaptors and /// methods. /// diff --git a/tests/test_core.rs b/tests/test_core.rs index 5861653da..3d0295825 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -13,6 +13,7 @@ use crate::it::multizip; use crate::it::free::put_back; use crate::it::iproduct; use crate::it::izip; +use crate::it::chain; #[test] fn product2() { @@ -87,6 +88,28 @@ fn multizip3() { } } +#[test] +fn chain_macro() { + let mut chain = chain!(2..3); + assert!(chain.next() == Some(2)); + assert!(chain.next().is_none()); + + let mut chain = chain!(0..2, 2..3, 3..5i8); + for i in 0..5i8 { + assert_eq!(Some(i), chain.next()); + } + assert!(chain.next().is_none()); + + let mut chain = chain!(); + assert_eq!(chain.next(), Option::<()>::None); +} + +#[test] +fn chain2() { + let _ = chain!(1.., 2..); + let _ = chain!(1.., 2.., ); +} + #[test] fn write_to() { let xs = [7, 9, 8]; From 80046c69f62ec214c2d2d4d408bb4ab7ba27f6c4 Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 12:36:19 -0500 Subject: [PATCH 037/633] add .flatten_ok() --- src/flatten_ok.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 26 ++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/flatten_ok.rs diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs new file mode 100644 index 000000000..6298ba9e1 --- /dev/null +++ b/src/flatten_ok.rs @@ -0,0 +1,61 @@ +pub fn flatten_ok(iter: I) -> FlattenOk +where + I: Iterator>, + T: IntoIterator, +{ + FlattenOk { iter, inner: None } +} + +/// An iterator adaptor that flattens `Result::Ok` values and +/// allows `Result::Err` values through unchanged. +/// +/// See [`.flatten_ok()`](crate::Itertools::flatten_ok) for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct FlattenOk +where + I: Iterator>, + T: IntoIterator, +{ + iter: I, + inner: Option, +} + +impl Iterator for FlattenOk +where + I: Iterator>, + T: IntoIterator, +{ + type Item = Result; + + fn next(&mut self) -> Option { + loop { + if let Some(inner) = &mut self.inner { + if let Some(item) = inner.next() { + return Some(Ok(item)); + } else { + self.inner = None; + } + } else { + match self.iter.next() { + Some(Ok(ok)) => self.inner = Some(ok.into_iter()), + Some(Err(e)) => return Some(Err(e)), + None => return None, + } + } + } + } +} + +impl Clone for FlattenOk +where + I: Iterator> + Clone, + T: IntoIterator, + T::IntoIter: Clone, +{ + fn clone(&self) -> Self { + Self { + iter: self.iter.clone(), + inner: self.inner.clone(), + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 4db8f5d40..cdf45137a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -119,6 +119,7 @@ pub mod structs { pub use crate::cons_tuples_impl::ConsTuples; pub use crate::exactly_one_err::ExactlyOneError; pub use crate::format::{Format, FormatWith}; + pub use crate::flatten_ok::FlattenOk; #[cfg(feature = "use_std")] pub use crate::grouping_map::{GroupingMap, GroupingMapBy}; #[cfg(feature = "use_alloc")] @@ -194,6 +195,7 @@ mod combinations; mod combinations_with_replacement; mod exactly_one_err; mod diff; +mod flatten_ok; mod format; #[cfg(feature = "use_std")] mod grouping_map; @@ -833,6 +835,30 @@ pub trait Itertools : Iterator { adaptors::filter_map_ok(self, f) } + /// Return an iterator adaptor that flattens every `Result::Ok` value into + /// a series of `Result::Ok` values. `Result::Err` values are unchanged. + /// + /// This is useful when you have some common error type for your crate and + /// need to propogate it upwards, but the `Result::Ok` case needs to be flattened. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let input = vec![Ok(0..2), Err(false), Ok(2..4)]; + /// let it = input.iter().cloned().flatten_ok(); + /// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]); + /// + /// // This can also be used to propogate errors when collecting. + /// let output_result: Result, bool> = it.collect(); + /// assert_eq!(output_result, Err(false)); + /// ``` + fn flatten_ok(self) -> FlattenOk + where Self: Iterator> + Sized, + T: IntoIterator + { + flatten_ok::flatten_ok(self) + } + /// Return an iterator adaptor that merges the two base iterators in /// ascending order. If both base iterators are sorted (ascending), the /// result is sorted. From 8421d27745db7ceafaec79b668399abc1997bef5 Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 17:17:38 -0500 Subject: [PATCH 038/633] address some flatten_ok() PR comments --- src/flatten_ok.rs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 6298ba9e1..d69feeb2e 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -1,3 +1,5 @@ +use std::fmt; + pub fn flatten_ok(iter: I) -> FlattenOk where I: Iterator>, @@ -35,12 +37,12 @@ where } else { self.inner = None; } - } else { - match self.iter.next() { - Some(Ok(ok)) => self.inner = Some(ok.into_iter()), - Some(Err(e)) => return Some(Err(e)), - None => return None, - } + } + + match self.iter.next() { + Some(Ok(ok)) => self.inner = Some(ok.into_iter()), + Some(Err(e)) => return Some(Err(e)), + None => return None, } } } @@ -52,10 +54,20 @@ where T: IntoIterator, T::IntoIter: Clone, { - fn clone(&self) -> Self { - Self { - iter: self.iter.clone(), - inner: self.inner.clone(), - } + #[inline] + clone_fields!(iter, inner); +} + +impl fmt::Debug for FlattenOk +where + I: Iterator> + fmt::Debug, + T: IntoIterator, + T::IntoIter: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlattenOk") + .field("iter", &self.iter) + .field("inner", &self.inner) + .finish() } } From 2fb1784abbe7ef81cb05d6b55279756b6e8f3def Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 17:21:38 -0500 Subject: [PATCH 039/633] add FusedIterator impl for FlattenOk --- src/flatten_ok.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index d69feeb2e..09bc74b31 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{fmt, iter::FusedIterator}; pub fn flatten_ok(iter: I) -> FlattenOk where @@ -35,6 +35,8 @@ where if let Some(item) = inner.next() { return Some(Ok(item)); } else { + // This is necessary for the iterator to implement `FusedIterator` + // with only the orginal iterator being fused. self.inner = None; } } @@ -71,3 +73,11 @@ where .finish() } } + +/// Only the iterator being flattened needs to implement [`FusedIterator`]. +impl FusedIterator for FlattenOk +where + I: FusedIterator>, + T: IntoIterator, +{ +} From 4c09d1324b204db58adca0f6430ea33dfe65a1b4 Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 17:33:39 -0500 Subject: [PATCH 040/633] add size_hint impl for FlattenOk --- src/flatten_ok.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 09bc74b31..9e207fb17 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -48,6 +48,29 @@ where } } } + + fn size_hint(&self) -> (usize, Option) { + if let Some(inner) = &self.inner { + // If we have an inner iterator, then its lower bound is our lower bound, + // but we still don't know the upper bound. + let (inner_lower, inner_higher) = inner.size_hint(); + let (_, outer_higher) = self.iter.size_hint(); + if outer_higher == Some(0) { + // If there is nothing remaining in the outer iterator, we know the upper bound. + (inner_lower, inner_higher) + } else { + // However, if the outer iterator could have more items in it, we don't + // know the upper bound. + (inner_lower, None) + } + } else if self.iter.size_hint() == (0, Some(0)) { + // If the outer iterator is empty, we have no items. + (0, Some(0)) + } else { + // Otherwise we do not know anything about the number of items. + (0, None) + } + } } impl Clone for FlattenOk From 38805c6a882a9f00615078250ccc8c070c3a214d Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 17:59:33 -0500 Subject: [PATCH 041/633] add inner_front,inner_back to FlattenOk and update methods --- src/flatten_ok.rs | 72 +++++++++++++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 9e207fb17..319e2c28f 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -1,3 +1,4 @@ +use crate::size_hint; use std::{fmt, iter::FusedIterator}; pub fn flatten_ok(iter: I) -> FlattenOk @@ -5,7 +6,11 @@ where I: Iterator>, T: IntoIterator, { - FlattenOk { iter, inner: None } + FlattenOk { + iter, + inner_front: None, + inner_back: None, + } } /// An iterator adaptor that flattens `Result::Ok` values and @@ -19,7 +24,8 @@ where T: IntoIterator, { iter: I, - inner: Option, + inner_front: Option, + inner_back: Option, } impl Iterator for FlattenOk @@ -31,45 +37,54 @@ where fn next(&mut self) -> Option { loop { - if let Some(inner) = &mut self.inner { + // Handle the front inner iterator. + if let Some(inner) = &mut self.inner_front { if let Some(item) = inner.next() { return Some(Ok(item)); } else { // This is necessary for the iterator to implement `FusedIterator` // with only the orginal iterator being fused. - self.inner = None; + self.inner_front = None; } } match self.iter.next() { - Some(Ok(ok)) => self.inner = Some(ok.into_iter()), + Some(Ok(ok)) => self.inner_front = Some(ok.into_iter()), Some(Err(e)) => return Some(Err(e)), - None => return None, + None => { + // Handle the back inner iterator. + if let Some(inner) = &mut self.inner_back { + if let Some(item) = inner.next() { + return Some(Ok(item)); + } else { + // This is necessary for the iterator to implement `FusedIterator` + // with only the orginal iterator being fused. + self.inner_back = None; + } + } else { + return None; + } + } } } } fn size_hint(&self) -> (usize, Option) { - if let Some(inner) = &self.inner { - // If we have an inner iterator, then its lower bound is our lower bound, - // but we still don't know the upper bound. - let (inner_lower, inner_higher) = inner.size_hint(); - let (_, outer_higher) = self.iter.size_hint(); - if outer_higher == Some(0) { - // If there is nothing remaining in the outer iterator, we know the upper bound. - (inner_lower, inner_higher) - } else { - // However, if the outer iterator could have more items in it, we don't - // know the upper bound. - (inner_lower, None) - } - } else if self.iter.size_hint() == (0, Some(0)) { - // If the outer iterator is empty, we have no items. - (0, Some(0)) - } else { - // Otherwise we do not know anything about the number of items. - (0, None) - } + let inner_hint = |inner: &Option| { + inner + .as_ref() + .map(Iterator::size_hint) + .unwrap_or((0, Some(0))) + }; + let inner_front = inner_hint(&self.inner_front); + let inner_back = inner_hint(&self.inner_back); + // The outer iterator `Ok` case could be (0, None) as we don't know its size_hint yet. + let outer = match self.iter.size_hint() { + (0, Some(0)) => (0, Some(0)), + _ => (0, None), + }; + + size_hint::add(size_hint::add(inner_front, inner_back), outer) } } @@ -80,7 +95,7 @@ where T::IntoIter: Clone, { #[inline] - clone_fields!(iter, inner); + clone_fields!(iter, inner_front, inner_back); } impl fmt::Debug for FlattenOk @@ -92,7 +107,8 @@ where fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("FlattenOk") .field("iter", &self.iter) - .field("inner", &self.inner) + .field("inner_front", &self.inner_front) + .field("inner_back", &self.inner_back) .finish() } } From 238578b88766d07cc6bf8f22b951c78c3535b481 Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 18:04:44 -0500 Subject: [PATCH 042/633] add impl of DoubleEndedIterator to FlattenOk --- src/flatten_ok.rs | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 319e2c28f..d46bbde4e 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -1,5 +1,8 @@ use crate::size_hint; -use std::{fmt, iter::FusedIterator}; +use std::{ + fmt, + iter::{DoubleEndedIterator, FusedIterator}, +}; pub fn flatten_ok(iter: I) -> FlattenOk where @@ -88,6 +91,47 @@ where } } +impl DoubleEndedIterator for FlattenOk +where + I: DoubleEndedIterator>, + T: IntoIterator, + T::IntoIter: DoubleEndedIterator, +{ + fn next_back(&mut self) -> Option { + loop { + // Handle the back inner iterator. + if let Some(inner) = &mut self.inner_back { + if let Some(item) = inner.next_back() { + return Some(Ok(item)); + } else { + // This is necessary for the iterator to implement `FusedIterator` + // with only the orginal iterator being fused. + self.inner_back = None; + } + } + + match self.iter.next_back() { + Some(Ok(ok)) => self.inner_back = Some(ok.into_iter()), + Some(Err(e)) => return Some(Err(e)), + None => { + // Handle the front inner iterator. + if let Some(inner) = &mut self.inner_front { + if let Some(item) = inner.next_back() { + return Some(Ok(item)); + } else { + // This is necessary for the iterator to implement `FusedIterator` + // with only the orginal iterator being fused. + self.inner_front = None; + } + } else { + return None; + } + } + } + } + } +} + impl Clone for FlattenOk where I: Iterator> + Clone, From fedb59ed6bed69e1c19022a32168a8dd179afd2e Mon Sep 17 00:00:00 2001 From: Geordon Worley Date: Sun, 7 Feb 2021 18:21:10 -0500 Subject: [PATCH 043/633] added basic integration tests for .flatten_ok() without size_hint tests --- tests/flatten_ok.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 tests/flatten_ok.rs diff --git a/tests/flatten_ok.rs b/tests/flatten_ok.rs new file mode 100644 index 000000000..bf835b5d7 --- /dev/null +++ b/tests/flatten_ok.rs @@ -0,0 +1,76 @@ +use itertools::{assert_equal, Itertools}; +use std::{ops::Range, vec::IntoIter}; + +fn mix_data() -> IntoIter, bool>> { + vec![Ok(0..2), Err(false), Ok(2..4), Err(true), Ok(4..6)].into_iter() +} + +fn ok_data() -> IntoIter, bool>> { + vec![Ok(0..2), Ok(2..4), Ok(4..6)].into_iter() +} + +#[test] +fn flatten_ok_mixed_expected_forward() { + assert_equal( + mix_data().flatten_ok(), + vec![ + Ok(0), + Ok(1), + Err(false), + Ok(2), + Ok(3), + Err(true), + Ok(4), + Ok(5), + ], + ); +} + +#[test] +fn flatten_ok_mixed_expected_reverse() { + assert_equal( + mix_data().flatten_ok().rev(), + vec![ + Ok(5), + Ok(4), + Err(true), + Ok(3), + Ok(2), + Err(false), + Ok(1), + Ok(0), + ], + ); +} + +#[test] +fn flatten_ok_collect_mixed_forward() { + assert_eq!( + mix_data().flatten_ok().collect::, _>>(), + Err(false) + ); +} + +#[test] +fn flatten_ok_collect_mixed_reverse() { + assert_eq!( + mix_data().flatten_ok().rev().collect::, _>>(), + Err(true) + ); +} + +#[test] +fn flatten_ok_collect_ok_forward() { + assert_eq!( + ok_data().flatten_ok().collect::, _>>(), + Ok((0..6).collect()) + ); +} + +#[test] +fn flatten_ok_collect_ok_reverse() { + assert_eq!( + ok_data().flatten_ok().rev().collect::, _>>(), + Ok((0..6).rev().collect()) + ); +} From c75c4071a287ee2c5429495350236981337ba833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ga=C3=ABtan=20Cassiers?= Date: Mon, 1 Mar 2021 22:52:30 +0100 Subject: [PATCH 044/633] Apply suggestions from code review Co-authored-by: Jack Wrenn --- src/lib.rs | 69 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1cb21d7a2..22649148f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -347,39 +347,68 @@ macro_rules! izip { } #[macro_export] -/// Create an iterator running multiple iterators sequentially. +/// [Chain][`chain`] zero or more iterators together into one sequence. /// -/// This is a version of the standard ``.chain()`` that's supporting more than -/// two iterators. `chain!` takes `IntoIterator` arguments. -/// Alternatively, this is an alternative to the standard ``.flatten()`` for a -/// fixed number of iterators of distinct sizes. +/// The comma-separated arguments must implement [`IntoIterator`]. +/// The final argument may be followed by a trailing comma. /// -/// **Note:** The result of this macro is in the general case an iterator -/// composed of repeated `.chain()`. -/// The special case of one arguments produce $a.into_iter(). +/// [`chain`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain +/// [`IntoIterator`]: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html +/// +/// # Examples /// +/// [`iter::empty`]: https://doc.rust-lang.org/std/iter/fn.empty.html /// +/// Empty invocations of `chain!` expand to an invocation of [`iter::empty`]: /// ``` -/// # use itertools::chain; -/// # -/// # fn main() { +/// # use std::iter; +/// use itertools::chain; /// -/// // chain three sequences -/// let chained: Vec = chain!(0..=3, 4..6, vec![6, 7]).collect(); -/// assert_eq!(chained, (0..=7).collect::>()); -/// # } +/// let _: iter::Empty<()> = chain!(); +/// let _: iter::Empty = chain!(); +/// ``` +/// +/// Invocations of `chain!` with one argument expand to [`arg.into_iter()`][`IntoIterator`]: +/// ``` +/// use std::{ops::Range, slice}; +/// use itertools::chain; +/// let _: as IntoIterator>::IntoIter = chain!((2..6),); // trailing comma optional! +/// let _: <&[_] as IntoIterator>::IntoIter = chain!(&[2, 3, 4]); +/// ``` +/// +/// Invocations of `chain!` with multiple arguments [`.into_iter()`][`IntoIterator`] each +/// argument, and then [`chain`] them together: +/// ``` +/// use std::{iter::*, ops::Range, slice}; +/// use itertools::{assert_equal, chain}; +/// +/// // e.g., this: +/// let with_macro: Chain, Take>>, slice::Iter<_>> = +/// chain![once(&0), repeat(&1).take(2), &[2, 3, 5],]; +/// +/// // ...is equivalant to this: +/// let with_method: Chain, Take>>, slice::Iter<_>> = +/// once(&0) +/// .chain(repeat(&1).take(2)) +/// .chain(&[2, 3, 5]); +/// +/// assert_equal(with_macro, with_method); /// ``` macro_rules! chain { () => { core::iter::empty() }; - ( $first:expr $(, $rest:expr )* $(,)*) => { - core::iter::IntoIterator::into_iter($first) + ($first:expr $(, $rest:expr )* $(,)?) => { + { + let iter = core::iter::IntoIterator::into_iter($first); $( - .chain( - core::iter::IntoIterator::into_iter($rest) - ) + let iter = + core::iter::Iterator::chain( + iter, + core::iter::IntoIterator::into_iter($rest)); )* + iter + } }; } From 8998202659150f21bd4af9294c6e88224e7441fc Mon Sep 17 00:00:00 2001 From: b05902132 <32428261+b05902132@users.noreply.github.com> Date: Thu, 8 Apr 2021 20:43:53 +0800 Subject: [PATCH 045/633] Clarify how `next()` interacts with `peek()` in` MultiPeek`. While intuitively this is the most reasonable behavior, it's nice to document it. --- src/multipeek_impl.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 93b02279f..5917681fc 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -38,6 +38,7 @@ impl MultiPeek { /// Works exactly like `.next()` with the only difference that it doesn't /// advance itself. `.peek()` can be called multiple times, to peek /// further ahead. + /// When `.next()` is called, reset the peeking “cursor”. pub fn peek(&mut self) -> Option<&I::Item> { let ret = if self.index < self.buf.len() { Some(&self.buf[self.index]) From c767441fa889b7bfc5ae96b4f3be980d69aa1778 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 16:07:01 +0200 Subject: [PATCH 046/633] Add find_or_last method --- src/lib.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 22649148f..65bf62689 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![warn(missing_docs)] #![crate_name="itertools"] #![cfg_attr(not(feature = "use_std"), no_std)] +#![feature(control_flow_enum)] //! Extra iterator adaptors, functions and macros. //! @@ -75,6 +76,7 @@ use std::fmt::Write; type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] use std::iter::FromIterator; +use std::ops::ControlFlow; #[macro_use] mod impl_macros; @@ -1730,7 +1732,32 @@ pub trait Itertools : Iterator { } None } + /// Find the value of the first element satisfying a predicate or return the last element, if any. + /// + /// The iterator is not advanced past the first element found. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let numbers = [1, 2, 3, 4]; + /// assert_eq!(numbers.iter().find_or_last(|x| x > 5), Some(4)); + /// ``` + fn find_or_last

(mut self, predicate: P) -> Option + where Self: Sized, + P: FnMut(&Self::Item) -> bool, + { + #[inline] + fn check(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(Option, T) -> ControlFlow> { + move |_, x| { + if predicate(&x) { ControlFlow::Break(x) } else { ControlFlow::Continue(Some(x)) } + } + } + match self.try_fold(None, check(predicate)) { + ControlFlow::Continue(x) => x, + ControlFlow::Break(x) => Some(x), + } + } /// Returns `true` if the given item is present in this iterator. /// /// This method is short-circuiting. If the given item is present in this From b773be3d3da6ad55d1c8f93b87fa62cc20d7f981 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 16:15:03 +0200 Subject: [PATCH 047/633] Use Result instead of ControlFlow --- src/lib.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 65bf62689..1a7b979b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #![warn(missing_docs)] #![crate_name="itertools"] #![cfg_attr(not(feature = "use_std"), no_std)] -#![feature(control_flow_enum)] //! Extra iterator adaptors, functions and macros. //! @@ -76,7 +75,6 @@ use std::fmt::Write; type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] use std::iter::FromIterator; -use std::ops::ControlFlow; #[macro_use] mod impl_macros; @@ -1747,16 +1745,13 @@ pub trait Itertools : Iterator { P: FnMut(&Self::Item) -> bool, { #[inline] - fn check(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(Option, T) -> ControlFlow> { + fn check(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(Option, T) -> Result, T> { move |_, x| { - if predicate(&x) { ControlFlow::Break(x) } else { ControlFlow::Continue(Some(x)) } + if predicate(&x) { Result::Err(x) } else { Result::Ok(Some(x)) } } } - match self.try_fold(None, check(predicate)) { - ControlFlow::Continue(x) => x, - ControlFlow::Break(x) => Some(x), - } + self.try_fold(None, check(predicate)).unwrap_or_else(Some) } /// Returns `true` if the given item is present in this iterator. /// From 7bd13fc37df69313bc6e605535c02b2c8bf55c69 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 16:25:45 +0200 Subject: [PATCH 048/633] Fix doc test --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 1a7b979b1..a8537f653 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1738,7 +1738,7 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let numbers = [1, 2, 3, 4]; - /// assert_eq!(numbers.iter().find_or_last(|x| x > 5), Some(4)); + /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 5), Some(&4)); /// ``` fn find_or_last

(mut self, predicate: P) -> Option where Self: Sized, From b5c124213406a9e449968ac39a315d2bc6df59d4 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 16:29:25 +0200 Subject: [PATCH 049/633] Add more tests to doc test --- src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a8537f653..30a8dcd75 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1739,6 +1739,8 @@ pub trait Itertools : Iterator { /// /// let numbers = [1, 2, 3, 4]; /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 5), Some(&4)); + /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 2), Some(&3)); + /// assert_eq!(std::iter::empty::().find_or_last(|&x| x > 5), None); /// ``` fn find_or_last

(mut self, predicate: P) -> Option where Self: Sized, From f02cfd93931f7fa40cb751722c1ed608b68723e8 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 20:40:06 +0200 Subject: [PATCH 050/633] Fix indentation --- src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 30a8dcd75..bd241ee1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1742,11 +1742,11 @@ pub trait Itertools : Iterator { /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 2), Some(&3)); /// assert_eq!(std::iter::empty::().find_or_last(|&x| x > 5), None); /// ``` - fn find_or_last

(mut self, predicate: P) -> Option - where Self: Sized, - P: FnMut(&Self::Item) -> bool, - { - #[inline] + fn find_or_last

(mut self, predicate: P) -> Option + where Self: Sized, + P: FnMut(&Self::Item) -> bool, + { + #[inline] fn check(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(Option, T) -> Result, T> { move |_, x| { if predicate(&x) { Result::Err(x) } else { Result::Ok(Some(x)) } @@ -1754,7 +1754,7 @@ pub trait Itertools : Iterator { } self.try_fold(None, check(predicate)).unwrap_or_else(Some) - } + } /// Returns `true` if the given item is present in this iterator. /// /// This method is short-circuiting. If the given item is present in this From 4a52f3cca7ab5bdf60eb5c24cc4aebe506a0cf74 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 21:16:37 +0200 Subject: [PATCH 051/633] Implement alternative implementation of find_or_last using accumulator variable and find_map --- src/lib.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bd241ee1f..bc3eb0f12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1742,18 +1742,13 @@ pub trait Itertools : Iterator { /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 2), Some(&3)); /// assert_eq!(std::iter::empty::().find_or_last(|&x| x > 5), None); /// ``` - fn find_or_last

(mut self, predicate: P) -> Option + fn find_or_last

(mut self, mut predicate: P) -> Option where Self: Sized, P: FnMut(&Self::Item) -> bool, { - #[inline] - fn check(mut predicate: impl FnMut(&T) -> bool) -> impl FnMut(Option, T) -> Result, T> { - move |_, x| { - if predicate(&x) { Result::Err(x) } else { Result::Ok(Some(x)) } - } - } - - self.try_fold(None, check(predicate)).unwrap_or_else(Some) + let mut prev = None; + self.find_map(|x| if predicate(&x) { Some(x) } else { prev = Some(x); None }) + .or(prev) } /// Returns `true` if the given item is present in this iterator. /// From 086b7d2f29ec325104afa9a26f7de37f3551a7d1 Mon Sep 17 00:00:00 2001 From: Linus Behrbohm Date: Sat, 10 Apr 2021 21:26:40 +0200 Subject: [PATCH 052/633] Add find_or_first method to Itertools trait --- src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index bc3eb0f12..bc2d8c2f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1750,6 +1750,29 @@ pub trait Itertools : Iterator { self.find_map(|x| if predicate(&x) { Some(x) } else { prev = Some(x); None }) .or(prev) } + /// Find the value of the first element satisfying a predicate or return the first element, if any. + /// + /// The iterator is not advanced past the first element found. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let numbers = [1, 2, 3, 4]; + /// assert_eq!(numbers.iter().find_or_first(|&&x| x > 5), Some(&1)); + /// assert_eq!(numbers.iter().find_or_first(|&&x| x > 2), Some(&3)); + /// assert_eq!(std::iter::empty::().find_or_first(|&x| x > 5), None); + /// ``` + fn find_or_first

(mut self, mut predicate: P) -> Option + where Self: Sized, + P: FnMut(&Self::Item) -> bool, + { + let first = self.next()?; + Some(if predicate(&first) { + first + } else { + self.find(|x| predicate(&x)).unwrap_or(first) + }) + } /// Returns `true` if the given item is present in this iterator. /// /// This method is short-circuiting. If the given item is present in this From 609c08962a98d5d89958e60089185a214c707dee Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Tue, 13 Apr 2021 11:31:17 +0200 Subject: [PATCH 053/633] Improve docs for into_group_map_by * Fix a typo * Fix formatting of example * Use more inline code markup --- src/lib.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 22649148f..a20734d57 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2724,18 +2724,18 @@ pub trait Itertools : Iterator { group_map::into_group_map(self) } - /// Return an `Iterator` on a HahMap. Keys mapped to `Vec`s of values. The key is specified in + /// Return an `Iterator` on a `HashMap`. Keys mapped to `Vec`s of values. The key is specified /// in the closure. - /// Different of into_group_map_by because the key is still present. It is also more general. - /// you can also fold the group_map. + /// Different to `into_group_map_by` because the key is still present. It is also more general. + /// You can also fold the `group_map`. /// /// ``` /// use itertools::Itertools; /// use std::collections::HashMap; /// /// let data = vec![(0, 10), (2, 12), (3, 13), (0, 20), (3, 33), (2, 42)]; - /// let lookup: HashMap> = data.clone().into_iter().into_group_map_by(|a| - /// a.0); + /// let lookup: HashMap> = + /// data.clone().into_iter().into_group_map_by(|a| a.0); /// /// assert_eq!(lookup[&0], vec![(0,10),(0,20)]); /// assert_eq!(lookup.get(&1), None); @@ -2744,10 +2744,12 @@ pub trait Itertools : Iterator { /// /// assert_eq!( /// data.into_iter() - /// .into_group_map_by(|x| x.0) - /// .into_iter() - /// .map(|(key, values)| (key, values.into_iter().fold(0,|acc, (_,v)| acc + v ))) - /// .collect::>()[&0], 30) + /// .into_group_map_by(|x| x.0) + /// .into_iter() + /// .map(|(key, values)| (key, values.into_iter().fold(0,|acc, (_,v)| acc + v ))) + /// .collect::>()[&0], + /// 30, + /// ); /// ``` #[cfg(feature = "use_std")] fn into_group_map_by(self, f: F) -> HashMap> From ad1a7f02ca4123627663242dcddcba5002c21730 Mon Sep 17 00:00:00 2001 From: Alex Brassel Date: Thu, 29 Apr 2021 00:34:32 -0400 Subject: [PATCH 054/633] Added the `or_default` method to `EitherOrBoth` See #537 for context. --- src/either_or_both.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 3598f49f3..3779ca133 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -188,3 +188,16 @@ impl Into>> for EitherOrBoth { } } } + +impl EitherOrBoth { + /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. + /// Otherwise, returns the wrapped value for the present element, and the [`default`](Default::default) + /// for the other. + pub fn or_default(self) -> (A, B) { + match self { + EitherOrBoth::Left(l) => (l, B::default()), + EitherOrBoth::Right(r) => (A::default(), r), + EitherOrBoth::Both(l, r) => (l, r), + } + } +} \ No newline at end of file From 1ee058beb3cd06341e124f069d2264b0564e299b Mon Sep 17 00:00:00 2001 From: Alex Brassel Date: Thu, 29 Apr 2021 10:37:21 -0400 Subject: [PATCH 055/633] Moved into the impl --- src/either_or_both.rs | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 3779ca133..982b85189 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -163,6 +163,21 @@ impl EitherOrBoth { Right(b) | Both(_, b) => f(b), } } + + /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. + /// Otherwise, returns the wrapped value for the present element, and the [`default`](Default::default) + /// for the other. + pub fn or_default(self) -> (A, B) + where + A: Default, + B: Default, + { + match self { + EitherOrBoth::Left(l) => (l, B::default()), + EitherOrBoth::Right(r) => (A::default(), r), + EitherOrBoth::Both(l, r) => (l, r), + } + } } impl EitherOrBoth { @@ -188,16 +203,3 @@ impl Into>> for EitherOrBoth { } } } - -impl EitherOrBoth { - /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. - /// Otherwise, returns the wrapped value for the present element, and the [`default`](Default::default) - /// for the other. - pub fn or_default(self) -> (A, B) { - match self { - EitherOrBoth::Left(l) => (l, B::default()), - EitherOrBoth::Right(r) => (A::default(), r), - EitherOrBoth::Both(l, r) => (l, r), - } - } -} \ No newline at end of file From 31c9f729c2dcad7889a7859aca0b6a8def369f49 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 2 May 2021 16:08:40 +0200 Subject: [PATCH 056/633] Fix warning --- src/tuple_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index c73d8822d..82ecd8632 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -314,7 +314,7 @@ macro_rules! impl_tuple_collect { let &mut ($(ref mut $Y),*,) = self; macro_rules! replace_item{($i:ident) => { item = replace($i, item); - }}; + }} rev_for_each_ident!(replace_item, $($Y,)*); drop(item); } From 08036afa06227e84c324d9ae4eb14af5df42afbb Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 18:24:37 +0200 Subject: [PATCH 057/633] Link to common traits, structs, etc. --- src/adaptors/mod.rs | 4 ++-- src/concat_impl.rs | 4 ++-- src/diff.rs | 2 +- src/free.rs | 24 ++++++++++++------------ src/groupbylazy.rs | 4 ++-- src/lib.rs | 12 ++++++------ src/peek_nth.rs | 2 +- src/sources.rs | 2 +- src/zip_eq_impl.rs | 2 +- src/ziptuple.rs | 2 +- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index dfc68978f..0c01eafb8 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -35,7 +35,7 @@ pub struct Interleave { /// Create an iterator that interleaves elements in `i` and `j`. /// -/// `IntoIterator` enabled version of `i.interleave(j)`. +/// [`IntoIterator`] enabled version of `i.interleave(j)`. /// /// See [`.interleave()`](crate::Itertools::interleave) for more information. pub fn interleave(i: I, j: J) -> Interleave<::IntoIter, ::IntoIter> @@ -481,7 +481,7 @@ pub type Merge = MergeBy; /// Create an iterator that merges elements in `i` and `j`. /// -/// `IntoIterator` enabled version of `i.merge(j)`. +/// [`IntoIterator`] enabled version of `i.merge(j)`. /// /// ``` /// use itertools::merge; diff --git a/src/concat_impl.rs b/src/concat_impl.rs index 0233d01f6..422fecd9e 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -1,8 +1,8 @@ use crate::Itertools; -/// Combine all an iterator's elements into one element by using `Extend`. +/// Combine all an iterator's elements into one element by using [`Extend`]. /// -/// `IntoIterator`-enabled version of `.concat()` +/// [`IntoIterator`]-enabled version of `.concat()` /// /// This combinator will extend the first item with each of the rest of the /// items of the iterator. If the iterator is empty, the default value of diff --git a/src/diff.rs b/src/diff.rs index b8d26cdd3..1731f0639 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -26,7 +26,7 @@ pub enum Diff } /// Compares every element yielded by both `i` and `j` with the given function in lock-step and -/// returns a `Diff` which describes how `j` differs from `i`. +/// returns a [`Diff`] which describes how `j` differs from `i`. /// /// If the number of elements yielded by `j` is less than the number of elements yielded by `i`, /// the number of `j` elements yielded will be returned along with `i`'s remaining elements as diff --git a/src/free.rs b/src/free.rs index 0520eb5e6..d77a6dd3f 100644 --- a/src/free.rs +++ b/src/free.rs @@ -1,6 +1,6 @@ //! Free functions that create iterator adaptors or call iterator methods. //! -//! The benefit of free functions is that they accept any `IntoIterator` as +//! The benefit of free functions is that they accept any [`IntoIterator`] as //! argument, so the resulting code may be easier to read. #[cfg(feature = "use_alloc")] @@ -37,7 +37,7 @@ pub use crate::rciter_impl::rciter; /// Iterate `iterable` with a running index. /// -/// `IntoIterator` enabled version of `.enumerate()`. +/// [`IntoIterator`] enabled version of `.enumerate()`. /// /// ``` /// use itertools::enumerate; @@ -54,7 +54,7 @@ pub fn enumerate(iterable: I) -> iter::Enumerate /// Iterate `iterable` in reverse. /// -/// `IntoIterator` enabled version of `.rev()`. +/// [`IntoIterator`] enabled version of `.rev()`. /// /// ``` /// use itertools::rev; @@ -72,7 +72,7 @@ pub fn rev(iterable: I) -> iter::Rev /// Iterate `i` and `j` in lock step. /// -/// `IntoIterator` enabled version of `i.zip(j)`. +/// [`IntoIterator`] enabled version of `i.zip(j)`. /// /// ``` /// use itertools::zip; @@ -109,7 +109,7 @@ pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, (iterable: I) -> iter::Cloned /// Perform a fold operation over the iterable. /// -/// `IntoIterator` enabled version of `i.fold(init, f)` +/// [`IntoIterator`] enabled version of `i.fold(init, f)` /// /// ``` /// use itertools::fold; @@ -141,7 +141,7 @@ pub fn fold(iterable: I, init: B, f: F) -> B /// Test whether the predicate holds for all elements in the iterable. /// -/// `IntoIterator` enabled version of `i.all(f)` +/// [`IntoIterator`] enabled version of `i.all(f)` /// /// ``` /// use itertools::all; @@ -157,7 +157,7 @@ pub fn all(iterable: I, f: F) -> bool /// Test whether the predicate holds for any elements in the iterable. /// -/// `IntoIterator` enabled version of `i.any(f)` +/// [`IntoIterator`] enabled version of `i.any(f)` /// /// ``` /// use itertools::any; @@ -173,7 +173,7 @@ pub fn any(iterable: I, f: F) -> bool /// Return the maximum value of the iterable. /// -/// `IntoIterator` enabled version of `i.max()`. +/// [`IntoIterator`] enabled version of `i.max()`. /// /// ``` /// use itertools::max; @@ -189,7 +189,7 @@ pub fn max(iterable: I) -> Option /// Return the minimum value of the iterable. /// -/// `IntoIterator` enabled version of `i.min()`. +/// [`IntoIterator`] enabled version of `i.min()`. /// /// ``` /// use itertools::min; @@ -206,7 +206,7 @@ pub fn min(iterable: I) -> Option /// Combine all iterator elements into one String, seperated by `sep`. /// -/// `IntoIterator` enabled version of `iterable.join(sep)`. +/// [`IntoIterator`] enabled version of `iterable.join(sep)`. /// /// ``` /// use itertools::join; @@ -223,7 +223,7 @@ pub fn join(iterable: I, sep: &str) -> String /// Sort all iterator elements into a new iterator in ascending order. /// -/// `IntoIterator` enabled version of [`iterable.sorted()`][1]. +/// [`IntoIterator`] enabled version of [`iterable.sorted()`][1]. /// /// [1]: crate::Itertools::sorted /// diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index b57d4046b..91c52ea59 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -279,7 +279,7 @@ impl GroupInner /// no allocations. It needs allocations only if several group iterators /// are alive at the same time. /// -/// This type implements `IntoIterator` (it is **not** an iterator +/// This type implements [`IntoIterator`] (it is **not** an iterator /// itself), because the group iterators need to borrow from this /// value. It should be stored in a local variable or temporary and /// iterated. @@ -453,7 +453,7 @@ pub fn new_chunks(iter: J, size: usize) -> IntoChunks /// `IntoChunks` behaves just like `GroupBy`: it is iterable, and /// it only buffers if several chunk iterators are alive at the same time. /// -/// This type implements `IntoIterator` (it is **not** an iterator +/// This type implements [`IntoIterator`] (it is **not** an iterator /// itself), because the chunk iterators need to borrow from this /// value. It should be stored in a local variable or temporary and /// iterated. diff --git a/src/lib.rs b/src/lib.rs index 4d6dd93ac..812bbe84b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -595,7 +595,7 @@ pub trait Itertools : Iterator { /// allocations. It needs allocations only if several group iterators /// are alive at the same time. /// - /// This type implements `IntoIterator` (it is **not** an iterator + /// This type implements [`IntoIterator`] (it is **not** an iterator /// itself), because the group iterators need to borrow from this /// value. It should be stored in a local variable or temporary and /// iterated. @@ -824,7 +824,7 @@ pub trait Itertools : Iterator { adaptors::step(self, n) } - /// Convert each item of the iterator using the `Into` trait. + /// Convert each item of the iterator using the [`Into`] trait. /// /// ```rust /// use itertools::Itertools; @@ -1915,7 +1915,7 @@ pub trait Itertools : Iterator { self.for_each(f) } - /// Combine all an iterator's elements into one element by using `Extend`. + /// Combine all an iterator's elements into one element by using [`Extend`]. /// /// This combinator will extend the first item with each of the rest of the /// items of the iterator. If the iterator is empty, the default value of @@ -2865,7 +2865,7 @@ pub trait Itertools : Iterator { /// Return the minimum and maximum element of an iterator, as determined by /// the specified function. /// - /// The return value is a variant of `MinMaxResult` like for `minmax()`. + /// The return value is a variant of [`MinMaxResult`] like for `minmax()`. /// /// For the minimum, the first minimal element is returned. For the maximum, /// the last maximal element wins. This matches the behavior of the standard @@ -2882,7 +2882,7 @@ pub trait Itertools : Iterator { /// Return the minimum and maximum element of an iterator, as determined by /// the specified comparison function. /// - /// The return value is a variant of `MinMaxResult` like for `minmax()`. + /// The return value is a variant of [`MinMaxResult`] like for `minmax()`. /// /// For the minimum, the first minimal element is returned. For the maximum, /// the last maximal element wins. This matches the behavior of the standard @@ -3370,7 +3370,7 @@ impl Itertools for T where T: Iterator { } /// (elements pairwise equal and sequences of the same length), /// `false` otherwise. /// -/// This is an `IntoIterator` enabled function that is similar to the standard +/// This is an [`IntoIterator`] enabled function that is similar to the standard /// library method `Iterator::eq`. /// /// ``` diff --git a/src/peek_nth.rs b/src/peek_nth.rs index 48b44bb70..bcca45838 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -13,7 +13,7 @@ where buf: VecDeque, } -/// A drop-in replacement for `std::iter::Peekable` which adds a `peek_nth` +/// A drop-in replacement for [`std::iter::Peekable`] which adds a `peek_nth` /// method allowing the user to `peek` at a value several iterations forward /// without advancing the base iterator. /// diff --git a/src/sources.rs b/src/sources.rs index 5bb6afe4a..b4a7bf707 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -67,7 +67,7 @@ impl Iterator for RepeatCall /// `unfold` is a general iterator builder: it has a mutable state value, /// and a closure with access to the state that produces the next value. /// -/// This more or less equivalent to a regular struct with an `Iterator` +/// This more or less equivalent to a regular struct with an [`Iterator`] /// implementation, and is useful for one-off iterators. /// /// ``` diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 864930bc0..552afea73 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -14,7 +14,7 @@ pub struct ZipEq { /// /// **Panics** if the iterators are not of the same length. /// -/// `IntoIterator` enabled version of `i.zip_eq(j)`. +/// [`IntoIterator`] enabled version of `i.zip_eq(j)`. /// /// ``` /// use itertools::zip_eq; diff --git a/src/ziptuple.rs b/src/ziptuple.rs index fe9659ee5..b7902ae53 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -10,7 +10,7 @@ pub struct Zip { /// An iterator that generalizes *.zip()* and allows running multiple iterators in lockstep. /// /// The iterator `Zip<(I, J, ..., M)>` is formed from a tuple of iterators (or values that -/// implement `IntoIterator`) and yields elements +/// implement [`IntoIterator`]) and yields elements /// until any of the subiterators yields `None`. /// /// The iterator element type is a tuple like like `(A, B, ..., E)` where `A` to `E` are the From 9933ea130b5859f68ea9870bbc6481eebf52b188 Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 18:50:16 +0200 Subject: [PATCH 058/633] Add method links in docs of free functions --- src/adaptors/mod.rs | 2 +- src/concat_impl.rs | 2 +- src/free.rs | 22 +++++++++++----------- src/lib.rs | 4 ++-- src/zip_eq_impl.rs | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 0c01eafb8..fce4e6ca1 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -481,7 +481,7 @@ pub type Merge = MergeBy; /// Create an iterator that merges elements in `i` and `j`. /// -/// [`IntoIterator`] enabled version of `i.merge(j)`. +/// [`IntoIterator`] enabled version of [`Itertools::merge`](crate::Itertools::merge). /// /// ``` /// use itertools::merge; diff --git a/src/concat_impl.rs b/src/concat_impl.rs index 422fecd9e..450f7fce1 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -2,7 +2,7 @@ use crate::Itertools; /// Combine all an iterator's elements into one element by using [`Extend`]. /// -/// [`IntoIterator`]-enabled version of `.concat()` +/// [`IntoIterator`]-enabled version of [`Itertools::concat`]. /// /// This combinator will extend the first item with each of the rest of the /// items of the iterator. If the iterator is empty, the default value of diff --git a/src/free.rs b/src/free.rs index d77a6dd3f..2c74dab5d 100644 --- a/src/free.rs +++ b/src/free.rs @@ -37,7 +37,7 @@ pub use crate::rciter_impl::rciter; /// Iterate `iterable` with a running index. /// -/// [`IntoIterator`] enabled version of `.enumerate()`. +/// [`IntoIterator`] enabled version of [`Iterator::enumerate`]. /// /// ``` /// use itertools::enumerate; @@ -54,7 +54,7 @@ pub fn enumerate(iterable: I) -> iter::Enumerate /// Iterate `iterable` in reverse. /// -/// [`IntoIterator`] enabled version of `.rev()`. +/// [`IntoIterator`] enabled version of [`Iterator::rev`]. /// /// ``` /// use itertools::rev; @@ -72,7 +72,7 @@ pub fn rev(iterable: I) -> iter::Rev /// Iterate `i` and `j` in lock step. /// -/// [`IntoIterator`] enabled version of `i.zip(j)`. +/// [`IntoIterator`] enabled version of [`Iterator::zip`]. /// /// ``` /// use itertools::zip; @@ -91,7 +91,7 @@ pub fn zip(i: I, j: J) -> Zip /// Create an iterator that first iterates `i` and then `j`. /// -/// `IntoIterator` enabled version of `i.chain(j)`. +/// [`IntoIterator`] enabled version of [`Iterator::chain`]. /// /// ``` /// use itertools::chain; @@ -109,7 +109,7 @@ pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, (iterable: I) -> iter::Cloned /// Perform a fold operation over the iterable. /// -/// [`IntoIterator`] enabled version of `i.fold(init, f)` +/// [`IntoIterator`] enabled version of [`Iterator::fold`]. /// /// ``` /// use itertools::fold; @@ -141,7 +141,7 @@ pub fn fold(iterable: I, init: B, f: F) -> B /// Test whether the predicate holds for all elements in the iterable. /// -/// [`IntoIterator`] enabled version of `i.all(f)` +/// [`IntoIterator`] enabled version of [`Iterator::all`]. /// /// ``` /// use itertools::all; @@ -157,7 +157,7 @@ pub fn all(iterable: I, f: F) -> bool /// Test whether the predicate holds for any elements in the iterable. /// -/// [`IntoIterator`] enabled version of `i.any(f)` +/// [`IntoIterator`] enabled version of [`Iterator::any`]. /// /// ``` /// use itertools::any; @@ -173,7 +173,7 @@ pub fn any(iterable: I, f: F) -> bool /// Return the maximum value of the iterable. /// -/// [`IntoIterator`] enabled version of `i.max()`. +/// [`IntoIterator`] enabled version of [`Iterator::max`]. /// /// ``` /// use itertools::max; @@ -189,7 +189,7 @@ pub fn max(iterable: I) -> Option /// Return the minimum value of the iterable. /// -/// [`IntoIterator`] enabled version of `i.min()`. +/// [`IntoIterator`] enabled version of [`Iterator::min`]. /// /// ``` /// use itertools::min; @@ -206,7 +206,7 @@ pub fn min(iterable: I) -> Option /// Combine all iterator elements into one String, seperated by `sep`. /// -/// [`IntoIterator`] enabled version of `iterable.join(sep)`. +/// [`IntoIterator`] enabled version of [`Itertools::join`]. /// /// ``` /// use itertools::join; diff --git a/src/lib.rs b/src/lib.rs index 812bbe84b..eb26c70b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3371,7 +3371,7 @@ impl Itertools for T where T: Iterator { } /// `false` otherwise. /// /// This is an [`IntoIterator`] enabled function that is similar to the standard -/// library method `Iterator::eq`. +/// library method [`Iterator::eq`]. /// /// ``` /// assert!(itertools::equal(vec![1, 2, 3], 1..4)); @@ -3396,7 +3396,7 @@ pub fn equal(a: I, b: J) -> bool } /// Assert that two iterables produce equal sequences, with the same -/// semantics as *equal(a, b)*. +/// semantics as [`equal(a, b)`](equal). /// /// **Panics** on assertion failure with a message that shows the /// two iteration elements. diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 552afea73..a079b326a 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -14,7 +14,7 @@ pub struct ZipEq { /// /// **Panics** if the iterators are not of the same length. /// -/// [`IntoIterator`] enabled version of `i.zip_eq(j)`. +/// [`IntoIterator`] enabled version of [`Itertools::zip_eq`](crate::Itertools::zip_eq). /// /// ``` /// use itertools::zip_eq; From 0366c2d0ff1d3bc46d0348cb2574ce56edb72f3f Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 19:11:13 +0200 Subject: [PATCH 059/633] Simplify link paths --- src/combinations.rs | 4 +--- src/duplicates_impl.rs | 4 ++-- src/either_or_both.rs | 4 ++-- src/free.rs | 4 +--- src/grouping_map.rs | 6 +++--- src/lib.rs | 20 ++++++++++---------- src/powerset.rs | 2 +- src/process_results_impl.rs | 2 +- src/repeatn.rs | 2 +- src/sources.rs | 9 ++++----- 10 files changed, 26 insertions(+), 31 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 1ed04087b..05f649d51 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -47,9 +47,7 @@ impl Combinations { pub fn k(&self) -> usize { self.indices.len() } /// Returns the (current) length of the pool from which combination elements are - /// selected. This value can change between invocations of [`next`]. - /// - /// [`next`]: #method.next + /// selected. This value can change between invocations of [`next`](Combinations::next). #[inline] pub fn n(&self) -> usize { self.pool.len() } diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index a581cc04d..42049df35 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -176,7 +176,7 @@ mod private { /// An iterator adapter to filter for duplicate elements. /// -/// See [`.duplicates_by()`](../trait.Itertools.html#method.duplicates_by) for more information. +/// See [`.duplicates_by()`](crate::Itertools::duplicates_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DuplicatesBy = private::DuplicatesBy>; @@ -192,7 +192,7 @@ where /// An iterator adapter to filter out duplicate elements. /// -/// See [`.duplicates()`](../trait.Itertools.html#method.duplicates) for more information. +/// See [`.duplicates()`](crate::Itertools::duplicates) for more information. pub type Duplicates = private::DuplicatesBy::Item, private::ById>; /// Create a new `Duplicates` iterator. diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 982b85189..31c18ef2f 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -25,7 +25,7 @@ impl EitherOrBoth { } /// If Left, return true otherwise, return false. - /// Exclusive version of [`has_left`](Self::has_left). + /// Exclusive version of [`has_left`](EitherOrBoth::has_left). pub fn is_left(&self) -> bool { match *self { Left(_) => true, @@ -34,7 +34,7 @@ impl EitherOrBoth { } /// If Right, return true otherwise, return false. - /// Exclusive version of [`has_right`](Self::has_right). + /// Exclusive version of [`has_right`](EitherOrBoth::has_right). pub fn is_right(&self) -> bool { match *self { Right(_) => true, diff --git a/src/free.rs b/src/free.rs index 2c74dab5d..c78dc1d02 100644 --- a/src/free.rs +++ b/src/free.rs @@ -223,9 +223,7 @@ pub fn join(iterable: I, sep: &str) -> String /// Sort all iterator elements into a new iterator in ascending order. /// -/// [`IntoIterator`] enabled version of [`iterable.sorted()`][1]. -/// -/// [1]: crate::Itertools::sorted +/// [`IntoIterator`] enabled version of [`Itertools::sorted`]. /// /// ``` /// use itertools::sorted; diff --git a/src/grouping_map.rs b/src/grouping_map.rs index cc89efe13..1212be866 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -7,7 +7,7 @@ use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; -/// A wrapper to allow for an easy [`into_grouping_map_by`](../trait.Itertools.html#method.into_grouping_map_by) +/// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by) #[derive(Clone, Debug)] pub struct MapForGrouping(I, F); @@ -38,7 +38,7 @@ pub fn new(iter: I) -> GroupingMap /// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations. /// -/// See [`GroupingMap`](./struct.GroupingMap.html) for more informations. +/// See [`GroupingMap`] for more informations. #[must_use = "GroupingMapBy is lazy and do nothing unless consumed"] pub type GroupingMapBy = GroupingMap>; @@ -377,7 +377,7 @@ impl GroupingMap /// If several elements are equally maximum, the last element is picked. /// If several elements are equally minimum, the first element is picked. /// - /// See [.minmax()](../trait.Itertools.html#method.minmax) for the non-grouping version. + /// See [.minmax()](crate::Itertools::minmax) for the non-grouping version. /// /// Differences from the non grouping version: /// - It never produces a `MinMaxResult::NoElements` diff --git a/src/lib.rs b/src/lib.rs index eb26c70b1..2eba67049 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -421,11 +421,11 @@ macro_rules! chain { /// /// * *Adaptors* take an iterator and parameter as input, and return /// a new iterator value. These are listed first in the trait. An example -/// of an adaptor is [`.interleave()`](#method.interleave) +/// of an adaptor is [`.interleave()`](Itertools::interleave) /// /// * *Regular methods* are those that don't return iterators and instead /// return a regular value of some other kind. -/// [`.next_tuple()`](#method.next_tuple) is an example and the first regular +/// [`.next_tuple()`](Itertools::next_tuple) is an example and the first regular /// method in the list. pub trait Itertools : Iterator { // adaptors @@ -740,7 +740,7 @@ pub trait Itertools : Iterator { /// Return an iterator that groups the items in tuples of a specific size /// (up to 4). /// - /// See also the method [`.next_tuple()`](#method.next_tuple). + /// See also the method [`.next_tuple()`](Itertools::next_tuple). /// /// ``` /// use itertools::Itertools; @@ -838,7 +838,7 @@ pub trait Itertools : Iterator { adaptors::map_into(self) } - /// See [`.map_ok()`](#method.map_ok). + /// See [`.map_ok()`](Itertools::map_ok). #[deprecated(note="Use .map_ok() instead", since="0.10.0")] fn map_results(self, f: F) -> MapOk where Self: Iterator> + Sized, @@ -1354,7 +1354,7 @@ pub trait Itertools : Iterator { /// `peeking_take_while` is done. /// /// - /// See also [`.take_while_ref()`](#method.take_while_ref) + /// See also [`.take_while_ref()`](Itertools::take_while_ref) /// which is a similar adaptor. fn peeking_take_while(&mut self, accept: F) -> PeekingTakeWhile where Self: Sized + PeekingNext, @@ -2094,7 +2094,7 @@ pub trait Itertools : Iterator { format::new_format(self, sep, format) } - /// See [`.fold_ok()`](#method.fold_ok). + /// See [`.fold_ok()`](Itertools::fold_ok). #[deprecated(note="Use .fold_ok() instead", since="0.10.0")] fn fold_results(&mut self, start: B, f: F) -> Result where Self: Iterator>, @@ -2794,7 +2794,7 @@ pub trait Itertools : Iterator { /// value of type `K` will be used as key to identify the groups and the /// value of type `V` as value for the folding operation. /// - /// See [`GroupingMap`](./structs/struct.GroupingMap.html) for more informations + /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] fn into_grouping_map(self) -> GroupingMap @@ -2810,7 +2810,7 @@ pub trait Itertools : Iterator { /// The values from this iterator will be used as values for the folding operation /// while the keys will be obtained from the values by calling `key_mapper`. /// - /// See [`GroupingMap`](./structs/struct.GroupingMap.html) for more informations + /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] fn into_grouping_map_by(self, key_mapper: F) -> GroupingMapBy @@ -3471,9 +3471,9 @@ pub fn partition<'a, A: 'a, I, F>(iter: I, mut pred: F) -> usize split_index } -/// An enum used for controlling the execution of `.fold_while()`. +/// An enum used for controlling the execution of `fold_while`. /// -/// See [`.fold_while()`](crate::Itertools::fold_while) for more information. +/// See [`.fold_while()`](Itertools::fold_while) for more information. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum FoldWhile { /// Continue folding with this value diff --git a/src/powerset.rs b/src/powerset.rs index ef17752b3..465e1bfa7 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -7,7 +7,7 @@ use super::size_hint; /// An iterator to iterate through the powerset of the elements from an iterator. /// -/// See [`.powerset()`](../trait.Itertools.html#method.powerset) for more +/// See [`.powerset()`](crate::Itertools::powerset) for more /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Powerset { diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index d74925ade..9da108b15 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -2,7 +2,7 @@ /// An iterator that produces only the `T` values as long as the /// inner iterator produces `Ok(T)`. /// -/// Used by [`process_results`](../fn.process_results.html), see its docs +/// Used by [`process_results`](crate::process_results), see its docs /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug)] diff --git a/src/repeatn.rs b/src/repeatn.rs index 8bc485083..176431adb 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -1,7 +1,7 @@ /// An iterator that produces *n* repetitions of an element. /// -/// See [`repeat_n()`](../fn.repeat_n.html) for more information. +/// See [`repeat_n()`](crate::repeat_n) for more information. #[must_use = "iterators are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct RepeatN { diff --git a/src/sources.rs b/src/sources.rs index b4a7bf707..3877ce3c8 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -5,7 +5,7 @@ use std::fmt; use std::mem; -/// See [`repeat_call`](../fn.repeat_call.html) for more information. +/// See [`repeat_call`](crate::repeat_call) for more information. #[derive(Clone)] #[deprecated(note="Use std repeat_with() instead", since="0.8.0")] pub struct RepeatCall { @@ -112,7 +112,7 @@ impl fmt::Debug for Unfold debug_fmt_fields!(Unfold, state); } -/// See [`unfold`](../fn.unfold.html) for more information. +/// See [`unfold`](crate::unfold) for more information. #[derive(Clone)] #[must_use = "iterators are lazy and do nothing unless consumed"] pub struct Unfold { @@ -134,9 +134,8 @@ impl Iterator for Unfold /// An iterator that infinitely applies function to value and yields results. /// -/// This `struct` is created by the [`iterate()`] function. See its documentation for more. -/// -/// [`iterate()`]: ../fn.iterate.html +/// This `struct` is created by the [`iterate()`](crate::iterate) function. +/// See its documentation for more. #[derive(Clone)] #[must_use = "iterators are lazy and do nothing unless consumed"] pub struct Iterate { From ba7d4e2d3f3a59e0182ef919ec5110718efa7c6a Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 19:15:48 +0200 Subject: [PATCH 060/633] Add missing links --- src/lib.rs | 32 ++++++++++++++++---------------- src/minmax.rs | 5 +++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2eba67049..4a088c514 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -948,7 +948,7 @@ pub trait Itertools : Iterator { } /// Return an iterator adaptor that merges the two base iterators in order. - /// This is much like `.merge()` but allows for a custom ordering. + /// This is much like [`.merge()`](Itertools::merge) but allows for a custom ordering. /// /// This can be especially useful for sequences of tuples. /// @@ -1935,7 +1935,7 @@ pub trait Itertools : Iterator { concat(self) } - /// `.collect_vec()` is simply a type specialization of `.collect()`, + /// `.collect_vec()` is simply a type specialization of [`Iterator::collect`], /// for convenience. #[cfg(feature = "use_alloc")] fn collect_vec(self) -> Vec @@ -2057,7 +2057,7 @@ pub trait Itertools : Iterator { /// Format all iterator elements, separated by `sep`. /// - /// This is a customizable version of `.format()`. + /// This is a customizable version of [`.format()`](Itertools::format). /// /// The supplied closure `format` is called once per iterator element, /// with two arguments: the element and a callback that takes a @@ -2164,7 +2164,7 @@ pub trait Itertools : Iterator { /// value is returned inside `Some`. Otherwise, the operation terminates /// and returns `None`. No iterator elements are consumed after the `None`. /// - /// This is the `Option` equivalent to `fold_ok`. + /// This is the `Option` equivalent to [`fold_ok`](Itertools::fold_ok). /// /// ``` /// use std::ops::Add; @@ -2318,7 +2318,7 @@ pub trait Itertools : Iterator { /// An iterator method that applies a function, producing a single, final value. /// - /// `fold_while()` is basically equivalent to `fold()` but with additional support for + /// `fold_while()` is basically equivalent to [`Iterator::fold`] but with additional support for /// early exit via short-circuiting. /// /// ``` @@ -2437,7 +2437,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort_unstable()` method and returns the result as a new + /// [`slice::sort_unstable`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2466,7 +2466,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort_unstable_by()` method and returns the result as a new + /// [`slice::sort_unstable_by`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2499,7 +2499,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort_unstable_by_key()` method and returns the result as a new + /// [`slice::sort_unstable_by_key`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2533,7 +2533,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort()` method and returns the result as a new + /// [`slice::sort`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2562,7 +2562,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort_by()` method and returns the result as a new + /// [`slice::sort_by`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2595,7 +2595,7 @@ pub trait Itertools : Iterator { /// Sort all iterator elements into a new iterator in ascending order. /// /// **Note:** This consumes the entire iterator, uses the - /// `slice::sort_by_key()` method and returns the result as a new + /// [`slice::sort_by_key`] method and returns the result as a new /// iterator that owns its elements. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2664,7 +2664,7 @@ pub trait Itertools : Iterator { } /// Collect all iterator elements into one of two - /// partitions. Unlike `Iterator::partition`, each partition may + /// partitions. Unlike [`Iterator::partition`], each partition may /// have a distinct type. /// /// ``` @@ -2865,11 +2865,11 @@ pub trait Itertools : Iterator { /// Return the minimum and maximum element of an iterator, as determined by /// the specified function. /// - /// The return value is a variant of [`MinMaxResult`] like for `minmax()`. + /// The return value is a variant of [`MinMaxResult`] like for [`.minmax()`](Itertools::minmax). /// /// For the minimum, the first minimal element is returned. For the maximum, /// the last maximal element wins. This matches the behavior of the standard - /// `Iterator::min()` and `Iterator::max()` methods. + /// [`Iterator::min`] and [`Iterator::max`] methods. /// /// The keys can be floats but no particular result is guaranteed /// if a key is NaN. @@ -2882,11 +2882,11 @@ pub trait Itertools : Iterator { /// Return the minimum and maximum element of an iterator, as determined by /// the specified comparison function. /// - /// The return value is a variant of [`MinMaxResult`] like for `minmax()`. + /// The return value is a variant of [`MinMaxResult`] like for [`.minmax()`](Itertools::minmax). /// /// For the minimum, the first minimal element is returned. For the maximum, /// the last maximal element wins. This matches the behavior of the standard - /// `Iterator::min()` and `Iterator::max()` methods. + /// [`Iterator::min`] and [`Iterator::max`] methods. fn minmax_by(self, mut compare: F) -> MinMaxResult where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering { diff --git a/src/minmax.rs b/src/minmax.rs index 38180ef6d..52b2f115d 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -1,6 +1,7 @@ -/// `MinMaxResult` is an enum returned by `minmax`. See `Itertools::minmax()` for -/// more detail. +/// `MinMaxResult` is an enum returned by `minmax`. +/// +/// See [`.minmax()`](crate::Itertools::minmax) for more detail. #[derive(Copy, Clone, PartialEq, Debug)] pub enum MinMaxResult { /// Empty iterator From dcb6f778e8859495e7c79e72493d009afa3861ef Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 19:17:08 +0200 Subject: [PATCH 061/633] Refine links in docs for the chain! macro --- src/lib.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4a088c514..8d4705a60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -354,23 +354,20 @@ macro_rules! izip { /// The comma-separated arguments must implement [`IntoIterator`]. /// The final argument may be followed by a trailing comma. /// -/// [`chain`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.chain -/// [`IntoIterator`]: https://doc.rust-lang.org/std/iter/trait.IntoIterator.html +/// [`chain`]: Iterator::chain /// /// # Examples /// -/// [`iter::empty`]: https://doc.rust-lang.org/std/iter/fn.empty.html -/// -/// Empty invocations of `chain!` expand to an invocation of [`iter::empty`]: +/// Empty invocations of `chain!` expand to an invocation of [`std::iter::empty`]: /// ``` -/// # use std::iter; +/// use std::iter; /// use itertools::chain; /// /// let _: iter::Empty<()> = chain!(); /// let _: iter::Empty = chain!(); /// ``` /// -/// Invocations of `chain!` with one argument expand to [`arg.into_iter()`][`IntoIterator`]: +/// Invocations of `chain!` with one argument expand to [`arg.into_iter()`](IntoIterator): /// ``` /// use std::{ops::Range, slice}; /// use itertools::chain; @@ -378,7 +375,7 @@ macro_rules! izip { /// let _: <&[_] as IntoIterator>::IntoIter = chain!(&[2, 3, 4]); /// ``` /// -/// Invocations of `chain!` with multiple arguments [`.into_iter()`][`IntoIterator`] each +/// Invocations of `chain!` with multiple arguments [`.into_iter()`](IntoIterator) each /// argument, and then [`chain`] them together: /// ``` /// use std::{iter::*, ops::Range, slice}; From b883c2c7ac7b98bf693506dd5c512fd0822b1292 Mon Sep 17 00:00:00 2001 From: Marcin Puc Date: Sun, 2 May 2021 19:18:06 +0200 Subject: [PATCH 062/633] Reformat link to the Itertools trait in main doc section --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8d4705a60..e4fc044c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,7 +5,7 @@ //! Extra iterator adaptors, functions and macros. //! //! To extend [`Iterator`] with methods in this crate, import -//! the [`Itertools` trait](Itertools): +//! the [`Itertools`] trait: //! //! ``` //! use itertools::Itertools; From 9cfca9dda18eabfdc91cc11a261f9ac221dd035c Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Wed, 5 May 2021 09:32:08 -0700 Subject: [PATCH 063/633] Update some links and some spelling/capitalization fixes --- CHANGELOG.md | 8 ++++---- Cargo.toml | 6 +++--- README.rst | 8 ++++---- src/adaptors/mod.rs | 4 ++-- tests/test_core.rs | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e1682b1a..5e1d191c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -192,7 +192,7 @@ ## 0.5.1 - Workaround module/function name clash that made racer crash on completing itertools. Only internal changes needed. ## 0.5.0 - - [Release announcement](http://bluss.github.io/rust/2016/09/26/itertools-0.5.0/) + - [Release announcement](https://bluss.github.io/rust/2016/09/26/itertools-0.5.0/) - Renamed: - `combinations` is now `tuple_combinations` - `combinations_n` to `combinations` @@ -246,7 +246,7 @@ ## 0.4.15 - Fixup on top of the workaround in 0.4.14. A function in `itertools::free` was removed by mistake and now it is added back again. ## 0.4.14 - - Workaround an upstream regression in a rust nightly build that broke compilation of of `itertools::free::{interleave, merge}` + - Workaround an upstream regression in a Rust nightly build that broke compilation of of `itertools::free::{interleave, merge}` ## 0.4.13 - Add `.minmax()` and `.minmax_by_key()`, iterator methods for finding both minimum and maximum in one scan. - Add `.format_default()`, a simpler version of `.format()` (lazy formatting for iterators). @@ -312,9 +312,9 @@ ## 0.3.19 - Added `.group_by_lazy()`, a possibly nonallocating group by - Added `.format()`, a nonallocating formatting helper for iterators - - Remove uses of `RandomAccessIterator` since it has been deprecated in rust. + - Remove uses of `RandomAccessIterator` since it has been deprecated in Rust. ## 0.3.17 - - Added (adopted) `Unfold` from rust + - Added (adopted) `Unfold` from Rust ## 0.3.16 - Added adaptors `.unique()`, `.unique_by()` ## 0.3.15 diff --git a/Cargo.toml b/Cargo.toml index 46b209417..47eb755f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "itertools" version = "0.10.0" license = "MIT/Apache-2.0" -repository = "https://github.com/bluss/rust-itertools" +repository = "https://github.com/rust-itertools/itertools" documentation = "https://docs.rs/itertools/" authors = ["bluss"] @@ -27,8 +27,8 @@ either = { version = "1.0", default-features = false } [dev-dependencies] rand = "0.7" -criterion = "=0" # TODO how could this work with our minimum supported rust version? -paste = "1.0.0" # Used in test_std to instanciate generic tests +criterion = "=0" # TODO how could this work with our minimum supported Rust version? +paste = "1.0.0" # Used in test_std to instantiate generic tests [dev-dependencies.quickcheck] version = "0.9" diff --git a/README.rst b/README.rst index b65c127c6..aa37f6bb7 100644 --- a/README.rst +++ b/README.rst @@ -13,7 +13,7 @@ __ https://docs.rs/itertools/ .. |build_status| image:: https://travis-ci.org/rust-itertools/itertools.svg?branch=master .. _build_status: https://travis-ci.org/rust-itertools/itertools -.. |crates| image:: http://meritbadge.herokuapp.com/itertools +.. |crates| image:: https://meritbadge.herokuapp.com/itertools .. _crates: https://crates.io/crates/itertools How to use with cargo: @@ -36,7 +36,7 @@ How to contribute - Include tests for your new feature, preferably a quickcheck test - Make a Pull Request -For new features, please first consider filing a PR to `rust-lang/rust `_, +For new features, please first consider filing a PR to `rust-lang/rust `_, adding your new feature to the `Iterator` trait of the standard library, if you believe it is reasonable. If it isn't accepted there, proposing it for inclusion in ``itertools`` is a good idea. The reason for doing is this is so that we avoid future breakage as with ``.flatten()``. @@ -49,7 +49,7 @@ License Dual-licensed to be compatible with the Rust project. Licensed under the Apache License, Version 2.0 -http://www.apache.org/licenses/LICENSE-2.0 or the MIT license -http://opensource.org/licenses/MIT, at your +https://www.apache.org/licenses/LICENSE-2.0 or the MIT license +https://opensource.org/licenses/MIT, at your option. This file may not be copied, modified, or distributed except according to those terms. diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index dfc68978f..cfb63ba37 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1,6 +1,6 @@ //! Licensed under the Apache License, Version 2.0 -//! or the MIT license -//! , at your +//! or the MIT license +//! , at your //! option. This file may not be copied, modified, or distributed //! except according to those terms. diff --git a/tests/test_core.rs b/tests/test_core.rs index 548ffb366..bcdca0ecd 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -1,6 +1,6 @@ //! Licensed under the Apache License, Version 2.0 -//! http://www.apache.org/licenses/LICENSE-2.0 or the MIT license -//! http://opensource.org/licenses/MIT, at your +//! https://www.apache.org/licenses/LICENSE-2.0 or the MIT license +//! https://opensource.org/licenses/MIT, at your //! option. This file may not be copied, modified, or distributed //! except according to those terms. #![no_std] From bfe4b8af7f38be55adfafde955677959dada8ae3 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Sun, 2 May 2021 09:38:50 -0400 Subject: [PATCH 064/633] either_or_both: Fix Left/Right confusion in docstrings I stared at this for a bit, confused before deciding the docs must be wrong. --- src/either_or_both.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 3598f49f3..6697a08a2 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -140,7 +140,7 @@ impl EitherOrBoth { } } - /// Apply the function `f` on the value `b` in `Right(b)` or `Both(a, _)` variants if it is + /// Apply the function `f` on the value `a` in `Left(a)` or `Both(a, _)` variants if it is /// present. pub fn left_and_then(self, f: F) -> EitherOrBoth where @@ -152,8 +152,8 @@ impl EitherOrBoth { } } - /// Apply the function `f` on the value `a` - /// in `Left(a)` or `Both(a, _)` variants if it is present. + /// Apply the function `f` on the value `b` + /// in `Right(b)` or `Both(_, b)` variants if it is present. pub fn right_and_then(self, f: F) -> EitherOrBoth where F: FnOnce(B) -> EitherOrBoth, From 4f4230f876eb842da4d96851d837c0acd52d7b26 Mon Sep 17 00:00:00 2001 From: aobatact Date: Wed, 26 May 2021 01:41:56 +0900 Subject: [PATCH 065/633] Add FusedIterator for which it is commented fused. Interleave, IntersperseWith, and ZipLongest are said to be fused, but it wasn't marked as FusedIterator. --- src/adaptors/mod.rs | 7 ++++++- src/intersperse.rs | 7 ++++++- src/zip_longest.rs | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index dfc68978f..1d067dcc7 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -15,7 +15,7 @@ pub use self::map::MapResults; pub use self::multi_product::*; use std::fmt; -use std::iter::{Fuse, Peekable, FromIterator}; +use std::iter::{Fuse, Peekable, FromIterator, FusedIterator}; use std::marker::PhantomData; use crate::size_hint; @@ -75,6 +75,11 @@ impl Iterator for Interleave } } +impl FusedIterator for Interleave + where I: Iterator, + J: Iterator +{} + /// An iterator adaptor that alternates elements from the two iterators until /// one of them runs out. /// diff --git a/src/intersperse.rs b/src/intersperse.rs index 687726f9d..2c660d492 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -1,4 +1,4 @@ -use std::iter::Fuse; +use std::iter::{Fuse, FusedIterator}; use super::size_hint; pub trait IntersperseElement { @@ -112,3 +112,8 @@ impl Iterator for IntersperseWith }) } } + +impl FusedIterator for IntersperseWith + where I: Iterator, + ElemF: IntersperseElement +{} diff --git a/src/zip_longest.rs b/src/zip_longest.rs index 553fe3b08..cb9a7bacb 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering::{Equal, Greater, Less}; use super::size_hint; -use std::iter::Fuse; +use std::iter::{Fuse, FusedIterator}; use crate::either_or_both::EitherOrBoth; @@ -76,3 +76,8 @@ impl ExactSizeIterator for ZipLongest where T: ExactSizeIterator, U: ExactSizeIterator {} + +impl FusedIterator for ZipLongest + where T: Iterator, + U: Iterator +{} From ccc5081c3267130c7b736711bd917edd38eb2d8b Mon Sep 17 00:00:00 2001 From: aobatact Date: Wed, 26 May 2021 13:10:46 +0900 Subject: [PATCH 066/633] Add more FusedIterator Some Iterator is fused if the underlying Iterator is fused. (UniqueBy, Unique, InterleaveShortest, Product, MergeBy, FilterOk, FilterMapOk, Positions, Update) Combinations is fused. --- src/adaptors/mod.rs | 40 ++++++++++++++++++++++++- src/combinations.rs | 6 ++++ src/unique_impl.rs | 12 ++++++++ tests/quick.rs | 73 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index dfc68978f..23a41f0cb 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -15,7 +15,7 @@ pub use self::map::MapResults; pub use self::multi_product::*; use std::fmt; -use std::iter::{Fuse, Peekable, FromIterator}; +use std::iter::{Fuse, Peekable, FromIterator, FusedIterator}; use std::marker::PhantomData; use crate::size_hint; @@ -157,6 +157,11 @@ impl Iterator for InterleaveShortest } } +impl FusedIterator for InterleaveShortest + where I: FusedIterator, + J: FusedIterator +{} + #[derive(Clone, Debug)] /// An iterator adaptor that allows putting back a single /// item to the front of the iterator. @@ -361,6 +366,12 @@ impl Iterator for Product } } +impl FusedIterator for Product + where I: FusedIterator, + J: Clone + FusedIterator, + I::Item: Clone +{} + /// A “meta iterator adaptor”. Its closure receives a reference to the iterator /// and may pick off as many elements as it likes, to produce the next iterator element. /// @@ -588,6 +599,12 @@ impl Iterator for MergeBy } } +impl FusedIterator for MergeBy + where I: FusedIterator, + J: FusedIterator, + F: MergePredicate +{} + /// An iterator adaptor that borrows from a `Clone`-able iterator /// to only pick off elements while the predicate returns `true`. /// @@ -876,6 +893,11 @@ impl Iterator for FilterOk } } +impl FusedIterator for FilterOk + where I: FusedIterator>, + F: FnMut(&T) -> bool, +{} + /// An iterator adapter to filter and apply a transformation on values within a nested `Result::Ok`. /// /// See [`.filter_map_ok()`](crate::Itertools::filter_map_ok) for more information. @@ -947,6 +969,11 @@ impl Iterator for FilterMapOk } } +impl FusedIterator for FilterMapOk + where I: FusedIterator>, + F: FnMut(T) -> Option, +{} + /// An iterator adapter to get the positions of each element that matches a predicate. /// /// See [`.positions()`](crate::Itertools::positions) for more information. @@ -1006,6 +1033,11 @@ impl DoubleEndedIterator for Positions } } +impl FusedIterator for Positions + where I: FusedIterator, + F: FnMut(I::Item) -> bool, +{} + /// An iterator adapter to apply a mutating function to each element before yielding it. /// /// See [`.update()`](crate::Itertools::update) for more information. @@ -1081,3 +1113,9 @@ where } } } + +impl FusedIterator for Update +where + I: FusedIterator, + F: FnMut(&mut I::Item), +{} diff --git a/src/combinations.rs b/src/combinations.rs index 1ed04087b..a9a4041ce 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; use alloc::vec::Vec; @@ -122,3 +123,8 @@ impl Iterator for Combinations Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) } } + +impl FusedIterator for Combinations + where I: Iterator, + I::Item: Clone +{} diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 7073e8e2c..2240f36ed 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use std::collections::hash_map::{Entry}; use std::hash::Hash; use std::fmt; +use std::iter::FusedIterator; /// An iterator adapter to filter out duplicate elements. /// @@ -92,6 +93,12 @@ impl DoubleEndedIterator for UniqueBy } } +impl FusedIterator for UniqueBy + where I: FusedIterator, + V: Eq + Hash, + F: FnMut(&I::Item) -> V +{} + impl Iterator for Unique where I: Iterator, I::Item: Eq + Hash + Clone @@ -136,6 +143,11 @@ impl DoubleEndedIterator for Unique } } +impl FusedIterator for Unique + where I: FusedIterator, + I::Item: Eq + Hash + Clone +{} + /// An iterator adapter to filter out duplicate elements. /// /// See [`.unique()`](crate::Itertools::unique) for more information. diff --git a/tests/quick.rs b/tests/quick.rs index 7769cb432..0ea51ece1 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1598,3 +1598,76 @@ quickcheck! { TestResult::from_bool(itertools::equal(x, y)) } } + + +fn is_fused(mut it: I) -> bool +{ + while let Some(_) = it.next() {} + for _ in 0..10{ + if it.next().is_some(){ + return false; + } + } + true +} + +quickcheck! { + fn fused_combination(a: Iter) -> bool + { + is_fused(a.clone().combinations(1)) && + is_fused(a.combinations(3)) + } + + fn fused_unique(a: Iter) -> bool + { + is_fused(a.fuse().unique()) + } + + fn fused_unique_by(a: Iter) -> bool + { + is_fused(a.fuse().unique_by(|x| x % 100)) + } + + fn fused_interleave_shortest(a: Iter, b: Iter) -> bool + { + !is_fused(a.clone().interleave_shortest(b.clone())) && + is_fused(a.fuse().interleave_shortest(b.fuse())) + } + + fn fused_product(a: Iter, b: Iter) -> bool + { + is_fused(a.fuse().cartesian_product(b.fuse())) + } + + fn fused_merge(a: Iter, b: Iter) -> bool + { + is_fused(a.fuse().merge(b.fuse())) + } + + fn fused_filter_ok(a: Iter) -> bool + { + is_fused(a.map(|x| if x % 2 == 0 {Ok(x)} else {Err(x)} ) + .filter_ok(|x| x % 3 == 0) + .fuse()) + } + + fn fused_filter_map_ok(a: Iter) -> bool + { + is_fused(a.map(|x| if x % 2 == 0 {Ok(x)} else {Err(x)} ) + .filter_map_ok(|x| if x % 3 == 0 {Some(x / 3)} else {None}) + .fuse()) + } + + fn fused_positions(a: Iter) -> bool + { + !is_fused(a.clone().positions(|x|x%2==0)) && + is_fused(a.fuse().positions(|x|x%2==0)) + } + + fn fused_update(a: Iter) -> bool + { + !is_fused(a.clone().update(|x|*x+=1)) && + is_fused(a.fuse().update(|x|*x+=1)) + } +} + From f9ccc345d1001d0c47515dec3067206e096f7f33 Mon Sep 17 00:00:00 2001 From: aobatact Date: Wed, 26 May 2021 15:38:02 +0900 Subject: [PATCH 067/633] Add more FusedIterator Mark FusedIterator to WhileSome, RcIter, PadUsing, TupleWindows, TupleCombinations, RepeatN, Powerset, CombinationsWithReplacement, WithPosition, KMergeBy --- src/adaptors/mod.rs | 5 +++++ src/combinations_with_replacement.rs | 7 +++++++ src/kmerge_impl.rs | 6 ++++++ src/pad_tail.rs | 8 +++++++- src/powerset.rs | 7 +++++++ src/rciter_impl.rs | 7 ++++++- src/repeatn.rs | 5 +++++ src/tuple_impl.rs | 7 +++++++ src/with_position.rs | 5 ++++- tests/quick.rs | 22 ++++++++++++++++++++++ 10 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 23a41f0cb..c3edc6487 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -728,6 +728,11 @@ impl Iterator for TupleCombinations } } +impl FusedIterator for TupleCombinations + where I: FusedIterator, + T: HasCombination, +{} + #[derive(Clone, Debug)] pub struct Tuple1Combination { iter: I, diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 1f1bcf5b2..81b13f130 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,5 +1,6 @@ use alloc::vec::Vec; use std::fmt; +use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; @@ -100,3 +101,9 @@ where } } } + +impl FusedIterator for CombinationsWithReplacement +where + I: Iterator, + I::Item: Clone, +{} diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 36bfc5a8a..dce5b782c 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -2,6 +2,7 @@ use crate::size_hint; use crate::Itertools; use alloc::vec::Vec; +use std::iter::FusedIterator; use std::mem::replace; use std::fmt; @@ -219,3 +220,8 @@ impl Iterator for KMergeBy .unwrap_or((0, Some(0))) } } + +impl FusedIterator for KMergeBy + where I: Iterator, + F: KMergePredicate +{} diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 18e666bad..03867cbf0 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -1,4 +1,4 @@ -use std::iter::Fuse; +use std::iter::{Fuse, FusedIterator}; use crate::size_hint; /// An iterator adaptor that pads a sequence to a minimum length by filling @@ -81,3 +81,9 @@ impl ExactSizeIterator for PadUsing where I: ExactSizeIterator, F: FnMut(usize) -> I::Item {} + + +impl FusedIterator for PadUsing + where I: FusedIterator, + F: FnMut(usize) -> I::Item +{} diff --git a/src/powerset.rs b/src/powerset.rs index ef17752b3..f50d860a2 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -1,4 +1,5 @@ use std::fmt; +use std::iter::FusedIterator; use std::usize; use alloc::vec::Vec; @@ -81,3 +82,9 @@ impl Iterator for Powerset } } } + +impl FusedIterator for Powerset + where + I: Iterator, + I::Item: Clone, +{} diff --git a/src/rciter_impl.rs b/src/rciter_impl.rs index 9122dadc9..782908e28 100644 --- a/src/rciter_impl.rs +++ b/src/rciter_impl.rs @@ -1,5 +1,5 @@ -use std::iter::IntoIterator; +use std::iter::{FusedIterator, IntoIterator}; use alloc::rc::Rc; use std::cell::RefCell; @@ -93,3 +93,8 @@ impl<'a, I> IntoIterator for &'a RcIter self.clone() } } + + +impl FusedIterator for RcIter + where I: FusedIterator +{} diff --git a/src/repeatn.rs b/src/repeatn.rs index 8bc485083..94a02651b 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -1,3 +1,4 @@ +use std::iter::FusedIterator; /// An iterator that produces *n* repetitions of an element. /// @@ -52,3 +53,7 @@ impl DoubleEndedIterator for RepeatN impl ExactSizeIterator for RepeatN where A: Clone {} + +impl FusedIterator for RepeatN + where A: Clone +{} diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 82ecd8632..ca8b97c6a 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -1,6 +1,7 @@ //! Some iterator that produces tuples use std::iter::Fuse; +use std::iter::FusedIterator; use std::iter::Take; use std::iter::Cycle; use std::marker::PhantomData; @@ -187,6 +188,12 @@ impl Iterator for TupleWindows } } +impl FusedIterator for TupleWindows + where I: FusedIterator, + T: HomogeneousTuple + Clone, + T::Item: Clone +{} + /// 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. diff --git a/src/with_position.rs b/src/with_position.rs index c53b652c6..1388503d1 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -1,4 +1,4 @@ -use std::iter::{Fuse,Peekable}; +use std::iter::{Fuse,Peekable, FusedIterator}; /// An iterator adaptor that wraps each element in an [`Position`]. /// @@ -95,3 +95,6 @@ impl Iterator for WithPosition { impl ExactSizeIterator for WithPosition where I: ExactSizeIterator, { } + +impl FusedIterator for WithPosition +{} diff --git a/tests/quick.rs b/tests/quick.rs index 0ea51ece1..7e222a641 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1618,6 +1618,18 @@ quickcheck! { is_fused(a.combinations(3)) } + fn fused_combination_with_replacement(a: Iter) -> bool + { + is_fused(a.clone().combinations_with_replacement(1)) && + is_fused(a.combinations_with_replacement(3)) + } + + fn fused_tuple_combination(a: Iter) -> bool + { + is_fused(a.clone().fuse().tuple_combinations::<(_,)>()) && + is_fused(a.fuse().tuple_combinations::<(_,_,_)>()) + } + fn fused_unique(a: Iter) -> bool { is_fused(a.fuse().unique()) @@ -1669,5 +1681,15 @@ quickcheck! { !is_fused(a.clone().update(|x|*x+=1)) && is_fused(a.fuse().update(|x|*x+=1)) } + + fn fused_tuple_windows(a: Iter) -> bool + { + is_fused(a.fuse().tuple_windows::<(_,_)>()) + } + + fn fused_pad_using(a: Iter) -> bool + { + is_fused(a.fuse().pad_using(100,|_|0)) + } } From 9ed31f0a6e944d058a78d5fcf8227b37daf0af6b Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 9 Jun 2021 12:41:06 -0400 Subject: [PATCH 068/633] Fix dead link to GroupingMap::fold --- src/grouping_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 1212be866..be22ec849 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -160,7 +160,7 @@ impl GroupingMap /// /// Return a `HashMap` associating the key of each group with the result of folding that group's elements. /// - /// [`fold`]: #tymethod.fold + /// [`fold`]: GroupingMap::fold /// /// ``` /// use itertools::Itertools; From 30e6cb50516646e92acad5b0e36e196fac5bdb72 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 9 Jun 2021 12:42:29 -0400 Subject: [PATCH 069/633] Update CHANGELOG.md for 0.10.1 (#542) --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e1d191c8..41c4ab037 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## 0.10.1 + - Add `Itertools::contains` (#514) + - Add `Itertools::counts_by` (#515) + - Add `Itertools::partition_result` (#511) + - Add `Itertools::all_unique` (#241) + - Add `Itertools::duplicates` and `Itertools::duplicates_by` (#502) + - Add `chain!` (#525) + - Add `Itertools::at_most_one` (#523) + - Add `Itertools::flatten_ok` (#527) + - Add `EitherOrBoth::or_default` (#583) + - Add `Itertools::find_or_last` and `Itertools::find_or_first` (#535) + - Implement `FusedIterator` for `FilterOk`, `FilterMapOk`, `InterleaveShortest`, `KMergeBy`, `MergeBy`, `PadUsing`, `Positions`, `Product` , `RcIter`, `TupleWindows`, `Unique`, `UniqueBy`, `Update`, `WhileSome`, `Combinations`, `CombinationsWithReplacement`, `Powerset`, `RepeatN`, and `WithPosition` (#550) + - Implement `FusedIterator` for `Interleave`, `IntersperseWith`, and `ZipLongest` (#548) + ## 0.10.0 - **Increase minimum supported Rust version to 1.32.0** - Improve macro hygiene (#507) From 20c09bd4fed037fa3df6f2a6a1e717b01be89f11 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 9 Jun 2021 12:45:32 -0400 Subject: [PATCH 070/633] (cargo-release) version 0.10.1 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 47eb755f1..d7f6b231f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.0" +version = "0.10.1" license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" From fab46e712372c9ba9c6043d2e8eda6b0d81b10c8 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 23 Jul 2021 16:23:49 +0200 Subject: [PATCH 071/633] README.rst -> README.md --- README.rst => README.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename README.rst => README.md (100%) diff --git a/README.rst b/README.md similarity index 100% rename from README.rst rename to README.md From 59fda6b26e0f9b2b5bb76a5c1db1df24990d9784 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 23 Jul 2021 16:23:49 +0200 Subject: [PATCH 072/633] README.rst -> README.md (2) --- README.md | 39 ++++++++++++++------------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index aa37f6bb7..0a27d6b71 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,39 @@ - -Itertools -========= +# Itertools Extra iterator adaptors, functions and macros. -Please read the `API documentation here`__ - -__ https://docs.rs/itertools/ - -|build_status|_ |crates|_ +Please read the [API documentation here](https://docs.rs/itertools/) -.. |build_status| image:: https://travis-ci.org/rust-itertools/itertools.svg?branch=master -.. _build_status: https://travis-ci.org/rust-itertools/itertools - -.. |crates| image:: https://meritbadge.herokuapp.com/itertools -.. _crates: https://crates.io/crates/itertools +[![build_status](https://travis-ci.org/rust-itertools/itertools.svg?branch=master)](https://travis-ci.org/rust-itertools/itertools) +[crates.io](https://crates.io/crates/itertools) How to use with cargo: -.. code:: toml - - [dependencies] - itertools = "0.10.0" +``` +[dependencies] +itertools = "0.10.0" +``` How to use in your crate: -.. code:: rust - - use itertools::Itertools; +``` +use itertools::Itertools; +``` -How to contribute ------------------ +## How to contribute - Fix a bug or implement a new thing - Include tests for your new feature, preferably a quickcheck test - Make a Pull Request -For new features, please first consider filing a PR to `rust-lang/rust `_, +For new features, please first consider filing a PR to [rust-lang/rust](https://github.com/rust-lang/rust), adding your new feature to the `Iterator` trait of the standard library, if you believe it is reasonable. If it isn't accepted there, proposing it for inclusion in ``itertools`` is a good idea. The reason for doing is this is so that we avoid future breakage as with ``.flatten()``. However, if your feature involves heap allocation, such as storing elements in a ``Vec``, then it can't be accepted into ``libcore``, and you should propose it for ``itertools`` directly instead. -License -------- +## License Dual-licensed to be compatible with the Rust project. From d17535f4fa5c532ac4143c3af243f0a5190337ed Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 23 Jul 2021 16:36:17 +0200 Subject: [PATCH 073/633] Add README.md to Cargo.toml --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index d7f6b231f..247d639ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" documentation = "https://docs.rs/itertools/" authors = ["bluss"] +readme = "README.md" description = "Extra iterator adaptors, iterator methods, free functions, and macros." From 7e68f5bf67068a87f2a23fceab278ecc000a540d Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Fri, 23 Jul 2021 09:41:39 -0700 Subject: [PATCH 074/633] Fix crates.io badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a27d6b71..2236a957e 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Extra iterator adaptors, functions and macros. Please read the [API documentation here](https://docs.rs/itertools/) [![build_status](https://travis-ci.org/rust-itertools/itertools.svg?branch=master)](https://travis-ci.org/rust-itertools/itertools) -[crates.io](https://crates.io/crates/itertools) +[![crates.io](https://img.shields.io/crates/v/itertools.svg)](https://crates.io/crates/itertools) How to use with cargo: From aad12603a9a08217c06fb622e92db0bce0ee7758 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 24 Jul 2021 18:19:32 +0200 Subject: [PATCH 075/633] Implement Itertools::multiunzip --- src/lib.rs | 27 ++++++++++++++++++++++ src/unziptuple.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 6 +++++ 3 files changed, 91 insertions(+) create mode 100644 src/unziptuple.rs diff --git a/src/lib.rs b/src/lib.rs index 7dd624135..9f84937c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,7 @@ pub use crate::repeatn::repeat_n; #[allow(deprecated)] pub use crate::sources::{repeat_call, unfold, iterate}; pub use crate::with_position::Position; +pub use crate::unziptuple::{multiunzip, MultiUnzip}; pub use crate::ziptuple::multizip; mod adaptors; mod either_or_both; @@ -237,6 +238,7 @@ mod tuple_impl; mod duplicates_impl; #[cfg(feature = "use_std")] mod unique_impl; +mod unziptuple; mod with_position; mod zip_eq_impl; mod zip_longest; @@ -3401,6 +3403,31 @@ pub trait Itertools : Iterator { { self.map(f).counts() } + + /// Unzips an iterator over tuples into a tuple of containers. + /// + /// The first element of each tuple will be put into the first container, the second element into + /// the second, .... + /// + /// It can be thought of as the reverse operation to [`Itertools::multiunzip`]. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let inputs = vec![(1, 2, 3), (4, 5, 6), (7, 8, 9)]; + /// + /// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = inputs + /// .into_iter() + /// .multiunzip(); + /// + /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); + /// ``` + fn multiunzip(self) -> FromI + where + Self: Sized + MultiUnzip, + { + MultiUnzip::multiunzip(self) + } } impl Itertools for T where T: Iterator { } diff --git a/src/unziptuple.rs b/src/unziptuple.rs new file mode 100644 index 000000000..699e39a62 --- /dev/null +++ b/src/unziptuple.rs @@ -0,0 +1,58 @@ +/// Unzips an iterator over tuples into a tuple of containers. +/// +/// ``` +/// use itertools::multiunzip; +/// +/// let inputs = vec![(1, 2, 3), (4, 5, 6), (7, 8, 9)]; +/// +/// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(inputs); +/// +/// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); +/// ``` +pub fn multiunzip(i: I) -> FromI +where + I: IntoIterator, + I::IntoIter: MultiUnzip, +{ + i.into_iter().multiunzip() +} + +/// An iterator that can be unzipped into multiple collections. +/// +/// See [`.multiunzip()`](crate::Itertools::multiunzip) for more information. +pub trait MultiUnzip: Iterator { + /// Unzip this iterator into multiple collections. + fn multiunzip(self) -> FromI; +} + +macro_rules! impl_unzip_iter { + ($($T:ident => $FromT:ident),*) => ( + impl_unzip_iter!(@rec $($T => $FromT,)*); + ); + (@rec) => (); + (@rec $__:ident => $___:ident, $($T:ident => $FromT:ident,)*) => ( + #[allow(non_snake_case)] + impl, $($T, $FromT: Default + Extend<$T>),* > MultiUnzip<($($FromT,)*)> for IT { + fn multiunzip(self) -> ($($FromT,)*) { + let mut res = ($($FromT::default(),)*); + let ($($FromT,)*) = &mut res; + + // Still unstable #72631 + // let (lower_bound, _) = self.size_hint(); + // if lower_bound > 0 { + // $($FromT.extend_reserve(lower_bound);)* + // } + + self.fold((), |(), ($($T,)*)| { + // Still unstable #72631 + // $( $FromT.extend_one($T); )* + $( $FromT.extend(std::iter::once($T)); )* + }); + res + } + } + impl_unzip_iter!(@rec $($T => $FromT,)*); + ); +} + +impl_unzip_iter!(L => FromL, K => FromK, J => FromJ, I => FromI, H => FromH, G => FromG, F => FromF, E => FromE, D => FromD, C => FromC, B => FromB, A => FromA); diff --git a/tests/test_std.rs b/tests/test_std.rs index 7cda9b5e5..65e224276 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1080,3 +1080,9 @@ fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); + assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); +} \ No newline at end of file From b93e11d44989adf76963abd9ca5b53b1d8122a2d Mon Sep 17 00:00:00 2001 From: Frank Steffahn Date: Thu, 29 Jul 2021 14:37:26 +0200 Subject: [PATCH 076/633] Fix documentation of `HomogeneousTuple` Compare the docs of `Itertools::collect_tuple` that already mention the correct upper bound on the number of tuple elements. --- src/tuple_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index ca8b97c6a..e225f3d34 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -11,7 +11,7 @@ use std::marker::PhantomData; // hiding the implementation details of `TupleCollect`. // See https://github.com/rust-itertools/itertools/issues/387 -/// Implemented for homogeneous tuples of size up to 4. +/// Implemented for homogeneous tuples of size up to 12. pub trait HomogeneousTuple : TupleCollect {} From 50722b29ad414457bd6e1ae9df818f18b3e0c3b0 Mon Sep 17 00:00:00 2001 From: philipp Date: Thu, 29 Jul 2021 16:48:28 +0200 Subject: [PATCH 077/633] Fix documentation of `HomogeneousTuple` (2) et al --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7dd624135..c45d93058 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -660,7 +660,7 @@ pub trait Itertools : Iterator { } /// Return an iterator over all contiguous windows producing tuples of - /// a specific size (up to 4). + /// a specific size (up to 12). /// /// `tuple_windows` clones the iterator elements so that they can be /// part of successive windows, this makes it most suited for iterators @@ -702,7 +702,7 @@ pub trait Itertools : Iterator { /// 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). + /// iterator, producing tuples of a specific size (up to 12). /// /// `circular_tuple_windows` clones the iterator elements so that they can be /// part of successive windows, this makes it most suited for iterators @@ -735,7 +735,7 @@ pub trait Itertools : Iterator { tuple_impl::circular_tuple_windows(self) } /// Return an iterator that groups the items in tuples of a specific size - /// (up to 4). + /// (up to 12). /// /// See also the method [`.next_tuple()`](Itertools::next_tuple). /// From 7d1a7396650d1310b957bd476368d17fe34b2dd4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 31 Jul 2021 20:22:38 +0200 Subject: [PATCH 078/633] Add more multiunzip tests, fix up multiunzip doc comments --- src/lib.rs | 8 ++++---- src/unziptuple.rs | 34 +++++++++++++++++++++++++++------- tests/test_std.rs | 3 +++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9f84937c7..5d7acca8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3404,12 +3404,12 @@ pub trait Itertools : Iterator { self.map(f).counts() } - /// Unzips an iterator over tuples into a tuple of containers. + /// Converts an iterator of tuples into a tuple of containers. /// - /// The first element of each tuple will be put into the first container, the second element into - /// the second, .... + /// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each + /// column. /// - /// It can be thought of as the reverse operation to [`Itertools::multiunzip`]. + /// This function is, in some sense, the opposite of [`multizip`]. /// /// ``` /// use itertools::Itertools; diff --git a/src/unziptuple.rs b/src/unziptuple.rs index 699e39a62..b1d198e58 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -1,4 +1,9 @@ -/// Unzips an iterator over tuples into a tuple of containers. +/// Converts an iterator of tuples into a tuple of containers. +/// +/// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each +/// column. +/// +/// This function is, in some sense, the opposite of [`multizip`]. /// /// ``` /// use itertools::multiunzip; @@ -9,6 +14,8 @@ /// /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); /// ``` +/// +/// [`multizip`]: crate::multizip pub fn multiunzip(i: I) -> FromI where I: IntoIterator, @@ -27,13 +34,15 @@ pub trait MultiUnzip: Iterator { macro_rules! impl_unzip_iter { ($($T:ident => $FromT:ident),*) => ( - impl_unzip_iter!(@rec $($T => $FromT,)*); - ); - (@rec) => (); - (@rec $__:ident => $___:ident, $($T:ident => $FromT:ident,)*) => ( #[allow(non_snake_case)] impl, $($T, $FromT: Default + Extend<$T>),* > MultiUnzip<($($FromT,)*)> for IT { fn multiunzip(self) -> ($($FromT,)*) { + // This implementation mirrors the logic of Iterator::unzip as close as possible. + // Unfortunately a lot of the used api there is still unstable represented by + // the commented out parts that follow. + // + // https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#2816-2844 + let mut res = ($($FromT::default(),)*); let ($($FromT,)*) = &mut res; @@ -51,8 +60,19 @@ macro_rules! impl_unzip_iter { res } } - impl_unzip_iter!(@rec $($T => $FromT,)*); ); } -impl_unzip_iter!(L => FromL, K => FromK, J => FromJ, I => FromI, H => FromH, G => FromG, F => FromF, E => FromE, D => FromD, C => FromC, B => FromB, A => FromA); +impl_unzip_iter!(); +impl_unzip_iter!(A => FromA); +impl_unzip_iter!(A => FromA, B => FromB); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ, K => FromK); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ, K => FromK, L => FromL); diff --git a/tests/test_std.rs b/tests/test_std.rs index 65e224276..1cac30dc8 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1085,4 +1085,7 @@ fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); + let (): () = [(), (), ()].iter().cloned().multiunzip(); + let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); + assert_eq!(t, (vec![0], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6], vec![7], vec![8], vec![9], vec![10], vec![11])); } \ No newline at end of file From f39d142630b13b082b3e8e97ae72c66a5ae4300f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 31 Jul 2021 20:26:42 +0200 Subject: [PATCH 079/633] Adjust multiunzip doc examples --- src/lib.rs | 4 +++- src/unziptuple.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5d7acca8e..96600f55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3420,7 +3420,9 @@ pub trait Itertools : Iterator { /// .into_iter() /// .multiunzip(); /// - /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); + /// assert_eq!(a, vec![1, 4, 7]); + /// assert_eq!(b, vec![2, 5, 8]); + /// assert_eq!(c, vec![3, 6, 9]); /// ``` fn multiunzip(self) -> FromI where diff --git a/src/unziptuple.rs b/src/unziptuple.rs index b1d198e58..f468f05e0 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -12,7 +12,9 @@ /// /// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(inputs); /// -/// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); +/// assert_eq!(a, vec![1, 4, 7]); +/// assert_eq!(b, vec![2, 5, 8]); +/// assert_eq!(c, vec![3, 6, 9]); /// ``` /// /// [`multizip`]: crate::multizip From bf06457ef8609bad304cb35810394b09b3d1d470 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Sat, 31 Jul 2021 18:05:32 -0700 Subject: [PATCH 080/633] Readme update --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2236a957e..eaedd0980 100644 --- a/README.md +++ b/README.md @@ -2,28 +2,28 @@ Extra iterator adaptors, functions and macros. -Please read the [API documentation here](https://docs.rs/itertools/) +Please read the [API documentation here](https://docs.rs/itertools/). -[![build_status](https://travis-ci.org/rust-itertools/itertools.svg?branch=master)](https://travis-ci.org/rust-itertools/itertools) +[![build_status](https://github.com/rust-itertools/itertools/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-itertools/itertools/actions) [![crates.io](https://img.shields.io/crates/v/itertools.svg)](https://crates.io/crates/itertools) -How to use with cargo: +How to use with Cargo: -``` +```toml [dependencies] itertools = "0.10.0" ``` How to use in your crate: -``` +```rust use itertools::Itertools; ``` ## How to contribute - Fix a bug or implement a new thing -- Include tests for your new feature, preferably a quickcheck test +- Include tests for your new feature, preferably a QuickCheck test - Make a Pull Request For new features, please first consider filing a PR to [rust-lang/rust](https://github.com/rust-lang/rust), From 599ae8cced7ede91c85a0e507808e8dcbf1acd27 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 15 Aug 2021 21:19:10 +0200 Subject: [PATCH 081/633] Update documentation for into_group_map[_by] --- src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c45d93058..e9b2c7831 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2770,6 +2770,8 @@ pub trait Itertools : Iterator { /// Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values /// are taken from `(Key, Value)` tuple pairs yielded by the input iterator. /// + /// Essentially a shorthand for `.into_grouping_map().collect::>()`. + /// /// ``` /// use itertools::Itertools; /// @@ -2791,8 +2793,8 @@ pub trait Itertools : Iterator { /// Return an `Iterator` on a `HashMap`. Keys mapped to `Vec`s of values. The key is specified /// in the closure. - /// Different to `into_group_map_by` because the key is still present. It is also more general. - /// You can also fold the `group_map`. + /// + /// Essentially a shorthand for `.into_grouping_map_by(f).collect::>()`. /// /// ``` /// use itertools::Itertools; From 514af56db1a3364082a78b130a4aecb7066bc04d Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Thu, 17 Jun 2021 23:37:17 +0530 Subject: [PATCH 082/633] Fix subtraction overflow in DuplicatesBy size_hint --- src/duplicates_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 42049df35..44689a616 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -89,7 +89,7 @@ mod private { // far), plus (hi - pending) / 2 pairs of never seen before items. let hi = hi.map(|hi| { let max_pending = std::cmp::min(self.meta.pending, hi); - let max_new = std::cmp::max(hi - self.meta.pending, 0) / 2; + let max_new = hi.checked_sub(self.meta.pending).unwrap_or(0) / 2; max_pending + max_new }); // The lower bound is always 0 since we might only get unique items from now on From 5f0e1bb92a48fa95094d3f41a97588ffca00d785 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Fri, 18 Jun 2021 16:16:52 +0530 Subject: [PATCH 083/633] Use saturating_sub as per clippy warning --- src/duplicates_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 44689a616..a56fe5a72 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -89,7 +89,7 @@ mod private { // far), plus (hi - pending) / 2 pairs of never seen before items. let hi = hi.map(|hi| { let max_pending = std::cmp::min(self.meta.pending, hi); - let max_new = hi.checked_sub(self.meta.pending).unwrap_or(0) / 2; + let max_new = hi.saturating_sub(self.meta.pending) / 2; max_pending + max_new }); // The lower bound is always 0 since we might only get unique items from now on From 9e2531cd9ee33d0da9c4b839f76ccf7a2f7e77f6 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Fri, 18 Jun 2021 16:18:06 +0530 Subject: [PATCH 084/633] Add test for subtraction overflow in DuplicatesBy --- tests/test_std.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 1cac30dc8..819995861 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -84,6 +84,13 @@ fn duplicates() { it::assert_equal(ys.iter(), xs.iter().rev().duplicates().rev()); let ys_rev = [1, 0]; it::assert_equal(ys_rev.iter(), xs.iter().duplicates().rev()); + + let xs = vec![0, 1, 2, 1, 2]; + let ys = vec![1, 2]; + assert_eq!(ys, xs.iter().duplicates().cloned().collect_vec()); + assert_eq!(ys, xs.iter().rev().duplicates().rev().cloned().collect_vec()); + let ys_rev = vec![2, 1]; + assert_eq!(ys_rev, xs.iter().duplicates().rev().cloned().collect_vec()); } #[test] From 4a14bf764530422aba38a77e2fd25e7f84edbca9 Mon Sep 17 00:00:00 2001 From: Arpan Kapoor Date: Mon, 16 Aug 2021 17:07:43 +0530 Subject: [PATCH 085/633] Make hi calculation clearer in DuplicatesBy size_hint --- src/duplicates_impl.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index a56fe5a72..d2a0d2b3f 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -84,13 +84,18 @@ mod private { #[inline] fn size_hint(&self) -> (usize, Option) { let (_, hi) = self.iter.size_hint(); - // There are `hi` number of items left in the base iterator. In the best case scenario, - // these items are exactly the same as the ones pending (i.e items seen exactly once so - // far), plus (hi - pending) / 2 pairs of never seen before items. let hi = hi.map(|hi| { - let max_pending = std::cmp::min(self.meta.pending, hi); - let max_new = hi.saturating_sub(self.meta.pending) / 2; - max_pending + max_new + if hi <= self.meta.pending { + // fewer or equally many iter-remaining elements than pending elements + // => at most, each iter-remaining element is matched + hi + } else { + // fewer pending elements than iter-remaining elements + // => at most: + // * each pending element is matched + // * the other iter-remaining elements come in pairs + self.meta.pending + (hi - self.meta.pending) / 2 + } }); // The lower bound is always 0 since we might only get unique items from now on (0, hi) From 015759183ac9c0205936b472bc1bc8bbedde0262 Mon Sep 17 00:00:00 2001 From: Andres Suarez Date: Tue, 22 Jun 2021 12:25:08 -0400 Subject: [PATCH 086/633] Add intersperse and intersperse_with free functions --- src/free.rs | 37 ++++++++++++++++++++++++++++++++++++- tests/test_core.rs | 19 +++++++++++++++++++ 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/free.rs b/src/free.rs index c78dc1d02..667403040 100644 --- a/src/free.rs +++ b/src/free.rs @@ -14,8 +14,8 @@ use alloc::{ string::String, }; -#[cfg(feature = "use_alloc")] use crate::Itertools; +use crate::intersperse::{Intersperse, IntersperseWith}; pub use crate::adaptors::{ interleave, @@ -35,6 +35,41 @@ pub use crate::merge_join::merge_join_by; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::rciter; +/// Iterate `iterable` with a particular value inserted between each element. +/// +/// [`IntoIterator`] enabled version of [`Iterator::intersperse`]. +/// +/// ``` +/// use itertools::intersperse; +/// +/// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]); +/// ``` +pub fn intersperse(iterable: I, element: I::Item) -> Intersperse + where I: IntoIterator, + ::Item: Clone +{ + Itertools::intersperse(iterable.into_iter(), element) +} + +/// Iterate `iterable` with a particular value created by a function inserted +/// between each element. +/// +/// [`IntoIterator`] enabled version of [`Iterator::intersperse_with`]. +/// +/// ``` +/// use itertools::intersperse_with; +/// +/// let mut i = 10; +/// itertools::assert_equal(intersperse_with((0..3), || { i -= 1; i }), vec![0, 9, 1, 8, 2]); +/// assert_eq!(i, 8); +/// ``` +pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith + where I: IntoIterator, + F: FnMut() -> I::Item +{ + Itertools::intersperse_with(iterable.into_iter(), element) +} + /// Iterate `iterable` with a running index. /// /// [`IntoIterator`] enabled version of [`Iterator::enumerate`]. diff --git a/tests/test_core.rs b/tests/test_core.rs index bcdca0ecd..a7b7449d3 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -9,6 +9,8 @@ use core::iter; use itertools as it; use crate::it::Itertools; use crate::it::interleave; +use crate::it::intersperse; +use crate::it::intersperse_with; use crate::it::multizip; use crate::it::free::put_back; use crate::it::iproduct; @@ -136,6 +138,23 @@ fn test_interleave() { it::assert_equal(it, rs.iter()); } +#[test] +fn test_intersperse() { + let xs = [1u8, 2, 3]; + let ys = [1u8, 0, 2, 0, 3]; + let it = intersperse(&xs, &0); + it::assert_equal(it, ys.iter()); +} + +#[test] +fn test_intersperse_with() { + let xs = [1u8, 2, 3]; + let ys = [1u8, 10, 2, 10, 3]; + let i = 10; + let it = intersperse_with(&xs, || &i); + it::assert_equal(it, ys.iter()); +} + #[allow(deprecated)] #[test] fn foreach() { From 041610897cd99493ed9f810112248cdcddc89e36 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 20 Aug 2021 15:39:27 +0200 Subject: [PATCH 087/633] impl `Debug` (1) derive where possible and sensible --- src/adaptors/coalesce.rs | 4 ++-- src/adaptors/map.rs | 4 ++-- src/adaptors/mod.rs | 2 +- src/duplicates_impl.rs | 3 +++ src/kmerge_impl.rs | 2 +- src/tuple_impl.rs | 2 +- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index 1afbee58f..f0cddd20e 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -137,7 +137,7 @@ where } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DedupEq; impl DedupPredicate for DedupEq { @@ -186,7 +186,7 @@ where pub type DedupByWithCount = CoalesceBy, (usize, ::Item)>; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct DedupPredWithCount2CoalescePred(DP); impl CoalescePredicate for DedupPredWithCount2CoalescePred diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index eee5cc35f..00ff51629 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -1,7 +1,7 @@ use std::iter::FromIterator; use std::marker::PhantomData; -#[derive(Clone)] +#[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MapSpecialCase { iter: I, @@ -108,7 +108,7 @@ impl, U> MapSpecialCaseFn for MapSpecialCaseFnInto { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MapSpecialCaseFnInto(PhantomData); /// Create a new [`MapInto`] iterator. diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index bd0d93a5f..9bacf8d5c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -477,7 +477,7 @@ pub trait MergePredicate { fn merge_pred(&mut self, a: &T, b: &T) -> bool; } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct MergeLte; impl MergePredicate for MergeLte { diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index d2a0d2b3f..75676aefb 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -122,6 +122,7 @@ mod private { } /// Apply the identity function to elements before checking them for equality. + #[derive(Debug)] pub struct ById; impl KeyMethod for ById { type Container = JustValue; @@ -152,6 +153,7 @@ mod private { fn value(self) -> V; } + #[derive(Debug)] pub struct KeyValue(K, V); impl KeyXorValue for KeyValue { fn key_ref(&self) -> &K { @@ -165,6 +167,7 @@ mod private { } } + #[derive(Debug)] pub struct JustValue(V); impl KeyXorValue for JustValue { fn key_ref(&self) -> &V { diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index dce5b782c..bd56b0317 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -111,7 +111,7 @@ pub trait KMergePredicate { fn kmerge_pred(&mut self, a: &T, b: &T) -> bool; } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct KMergeByLt; impl KMergePredicate for KMergeByLt { diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index e225f3d34..d914e0323 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -77,7 +77,7 @@ impl ExactSizeIterator for TupleBuffer /// An iterator that groups the items in tuples of a specific size. /// /// See [`.tuples()`](crate::Itertools::tuples) for more information. -#[derive(Clone)] +#[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Tuples where I: Iterator, From b055dc94ba172c5e6f33d00a4b5ad1be5b9f4958 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 20 Aug 2021 15:39:27 +0200 Subject: [PATCH 088/633] impl `Debug` (2) some debug_fmt_fields --- src/adaptors/coalesce.rs | 4 ++++ src/adaptors/map.rs | 4 ++++ src/adaptors/mod.rs | 28 ++++++++++++++++++++++++++++ src/duplicates_impl.rs | 3 +++ src/pad_tail.rs | 7 +++++++ src/peeking_take_while.rs | 7 +++++++ 6 files changed, 53 insertions(+) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index f0cddd20e..b1aff6e27 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -119,6 +119,10 @@ pub type DedupBy = CoalesceBy, (DP); +impl fmt::Debug for DedupPred2CoalescePred { + debug_fmt_fields!(DedupPred2CoalescePred,); +} + pub trait DedupPredicate { // TODO replace by Fn(&T, &T)->bool once Rust supports it fn dedup_pair(&mut self, a: &T, b: &T) -> bool; diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index 00ff51629..cf5e5a00d 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -84,6 +84,10 @@ where #[derive(Clone)] pub struct MapSpecialCaseFnOk(F); +impl std::fmt::Debug for MapSpecialCaseFnOk { + debug_fmt_fields!(MapSpecialCaseFnOk,); +} + /// Create a new `MapOk` iterator. pub fn map_ok(iter: I, f: F) -> MapOk where diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 9bacf8d5c..2010f535b 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -849,6 +849,13 @@ pub struct FilterOk { f: F } +impl fmt::Debug for FilterOk +where + I: fmt::Debug, +{ + debug_fmt_fields!(FilterOk, iter); +} + /// Create a new `FilterOk` iterator. pub fn filter_ok(iter: I, f: F) -> FilterOk where I: Iterator>, @@ -917,6 +924,13 @@ pub struct FilterMapOk { f: F } +impl fmt::Debug for FilterMapOk +where + I: fmt::Debug, +{ + debug_fmt_fields!(FilterMapOk, iter); +} + fn transpose_result(result: Result, E>) -> Option> { match result { Ok(Some(v)) => Some(Ok(v)), @@ -995,6 +1009,13 @@ pub struct Positions { count: usize, } +impl fmt::Debug for Positions +where + I: fmt::Debug, +{ + debug_fmt_fields!(Positions, iter, count); +} + /// Create a new `Positions` iterator. pub fn positions(iter: I, f: F) -> Positions where I: Iterator, @@ -1058,6 +1079,13 @@ pub struct Update { f: F, } +impl fmt::Debug for Update +where + I: fmt::Debug, +{ + debug_fmt_fields!(Update, iter); +} + /// Create a new `Update` iterator. pub fn update(iter: I, f: F) -> Update where diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 75676aefb..640d4818c 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -134,6 +134,9 @@ mod private { /// Apply a user-supplied function to elements before checking them for equality. pub struct ByFn(pub(crate) F); + impl fmt::Debug for ByFn { + debug_fmt_fields!(ByFn,); + } impl KeyMethod for ByFn where F: FnMut(&V) -> K, diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 03867cbf0..de57ee416 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -16,6 +16,13 @@ pub struct PadUsing { filler: F, } +impl std::fmt::Debug for PadUsing +where + I: std::fmt::Debug, +{ + debug_fmt_fields!(PadUsing, iter, min, pos); +} + /// Create a new **PadUsing** iterator. pub fn pad_using(iter: I, min: usize, filler: F) -> PadUsing where I: Iterator, diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index f9f213421..cd0945a52 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -83,6 +83,13 @@ pub struct PeekingTakeWhile<'a, I: 'a, F> f: F, } +impl<'a, I: 'a, F> std::fmt::Debug for PeekingTakeWhile<'a, I, F> +where + I: Iterator + std::fmt::Debug, +{ + debug_fmt_fields!(PeekingTakeWhile, iter); +} + /// Create a PeekingTakeWhile pub fn peeking_take_while(iter: &mut I, f: F) -> PeekingTakeWhile where I: Iterator, From a5ac115c02cd5fb55503f11105535102567e1be6 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 20 Aug 2021 15:39:27 +0200 Subject: [PATCH 089/633] impl `Debug` (3) MultiProduct This requires that debug_fmt_fields can also cope with tuple elements. --- src/adaptors/multi_product.rs | 8 ++++++++ src/impl_macros.rs | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 9708ef4bd..30650eda6 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -18,6 +18,14 @@ pub struct MultiProduct(Vec>) where I: Iterator + Clone, I::Item: Clone; +impl std::fmt::Debug for MultiProduct +where + I: Iterator + Clone + std::fmt::Debug, + I::Item: Clone + std::fmt::Debug, +{ + debug_fmt_fields!(CoalesceBy, 0); +} + /// Create a new cartesian product iterator over an arbitrary number /// of iterators of the same type. /// diff --git a/src/impl_macros.rs b/src/impl_macros.rs index 3da8c632f..5772baeb6 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -2,7 +2,7 @@ //! Implementation's internal macros macro_rules! debug_fmt_fields { - ($tyname:ident, $($($field:ident).+),*) => { + ($tyname:ident, $($($field:tt/*TODO ideally we would accept ident or tuple element here*/).+),*) => { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { f.debug_struct(stringify!($tyname)) $( From 0410bca334bc9e80e4e45a1d9b9e602422aef3a1 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Tue, 20 Jul 2021 20:53:43 +0200 Subject: [PATCH 090/633] Specialize ProcessResults::fold --- src/process_results_impl.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index 9da108b15..44308f378 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -30,6 +30,23 @@ impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> fn size_hint(&self) -> (usize, Option) { (0, self.iter.size_hint().1) } + + fn fold(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + let error = self.error; + self.iter + .try_fold(init, |acc, opt| match opt { + Ok(x) => Ok(f(acc, x)), + Err(e) => { + *error = Err(e); + Err(acc) + } + }) + .unwrap_or_else(|e| e) + } } /// “Lift” a function of the values of an iterator so that it can process From bc5ab46c8d86e7800e7e9e4b99c50bcc46233fbb Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Fri, 20 Aug 2021 18:30:00 +0200 Subject: [PATCH 091/633] Add ProcessResults specializations test --- tests/specializations.rs | 47 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index bc337c28e..6a1d0cffb 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -98,3 +98,50 @@ quickcheck! { test_specializations(&v.into_iter().map_ok(|u| u.checked_add(1))); } } + +quickcheck! { + fn process_results(v: Vec>) -> () { + helper(v.iter().copied()); + helper(v.iter().copied().filter(Result::is_ok)); + + fn helper(it: impl Iterator> + Clone) { + macro_rules! check_results_specialized { + ($src:expr, |$it:pat| $closure:expr) => { + assert_eq!( + itertools::process_results($src.clone(), |$it| $closure), + itertools::process_results($src.clone(), |i| { + let $it = Unspecialized(i); + $closure + }), + ) + } + } + + check_results_specialized!(it, |i| i.count()); + check_results_specialized!(it, |i| i.last()); + check_results_specialized!(it, |i| i.collect::>()); + check_results_specialized!(it, |i| { + let mut parameters_from_fold = vec![]; + let fold_result = i.fold(vec![], |mut acc, v| { + parameters_from_fold.push((acc.clone(), v.clone())); + acc.push(v); + acc + }); + (parameters_from_fold, fold_result) + }); + check_results_specialized!(it, |mut i| { + let mut parameters_from_all = vec![]; + let first = i.next(); + let all_result = i.all(|x| { + parameters_from_all.push(x.clone()); + Some(x)==first + }); + (parameters_from_all, all_result) + }); + let size = it.clone().count(); + for n in 0..size + 2 { + check_results_specialized!(it, |mut i| i.nth(n)); + } + } + } +} From 949610dbb8c116e8f5b6b8e0c7c41f367bacd7da Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Fri, 20 Aug 2021 18:39:14 +0200 Subject: [PATCH 092/633] Move intersperse fold_specialization test to the specializations quicktests --- tests/fold_specialization.rs | 13 ------------- tests/specializations.rs | 6 ++++++ 2 files changed, 6 insertions(+), 13 deletions(-) delete mode 100644 tests/fold_specialization.rs diff --git a/tests/fold_specialization.rs b/tests/fold_specialization.rs deleted file mode 100644 index a984b40b8..000000000 --- a/tests/fold_specialization.rs +++ /dev/null @@ -1,13 +0,0 @@ -use itertools::Itertools; - -#[test] -fn specialization_intersperse() { - let mut iter = (1..2).intersperse(0); - iter.clone().for_each(|x| assert_eq!(Some(x), iter.next())); - - let mut iter = (1..3).intersperse(0); - iter.clone().for_each(|x| assert_eq!(Some(x), iter.next())); - - let mut iter = (1..4).intersperse(0); - iter.clone().for_each(|x| assert_eq!(Some(x), iter.next())); -} diff --git a/tests/specializations.rs b/tests/specializations.rs index bc337c28e..8f3805c34 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -72,6 +72,12 @@ fn test_specializations( } } +quickcheck! { + fn intersperse(v: Vec) -> () { + test_specializations(&v.into_iter().intersperse(0)); + } +} + quickcheck! { fn put_back_qc(test_vec: Vec) -> () { test_specializations(&itertools::put_back(test_vec.iter())); From bf4ca16746d93d32cd0cc754bd2d2cad92e924a5 Mon Sep 17 00:00:00 2001 From: Giacomo Stevanato Date: Fri, 20 Aug 2021 18:39:54 +0200 Subject: [PATCH 093/633] Make specializations tests actually test specialized methods --- tests/specializations.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 8f3805c34..dc3ff6a8a 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -15,16 +15,16 @@ where } } -fn check_specialized<'a, V, IterItem, Iter, F>(iterator: &Iter, mapper: F) -where - V: Eq + Debug, - Iter: Iterator + Clone + 'a, - F: Fn(Box + 'a>) -> V, -{ - assert_eq!( - mapper(Box::new(Unspecialized(iterator.clone()))), - mapper(Box::new(iterator.clone())) - ) +macro_rules! check_specialized { + ($src:expr, |$it:pat| $closure:expr) => { + let $it = $src.clone(); + let v1 = $closure; + + let $it = Unspecialized($src.clone()); + let v2 = $closure; + + assert_eq!(v1, v2); + } } fn test_specializations( @@ -33,10 +33,10 @@ fn test_specializations( IterItem: Eq + Debug + Clone, Iter: Iterator + Clone, { - check_specialized(it, |i| i.count()); - check_specialized(it, |i| i.last()); - check_specialized(it, |i| i.collect::>()); - check_specialized(it, |i| { + check_specialized!(it, |i| i.count()); + check_specialized!(it, |i| i.last()); + check_specialized!(it, |i| i.collect::>()); + check_specialized!(it, |i| { let mut parameters_from_fold = vec![]; let fold_result = i.fold(vec![], |mut acc, v: IterItem| { parameters_from_fold.push((acc.clone(), v.clone())); @@ -45,7 +45,7 @@ fn test_specializations( }); (parameters_from_fold, fold_result) }); - check_specialized(it, |mut i| { + check_specialized!(it, |mut i| { let mut parameters_from_all = vec![]; let first = i.next(); let all_result = i.all(|x| { @@ -56,7 +56,7 @@ fn test_specializations( }); let size = it.clone().count(); for n in 0..size + 2 { - check_specialized(it, |mut i| i.nth(n)); + check_specialized!(it, |mut i| i.nth(n)); } // size_hint is a bit harder to check let mut it_sh = it.clone(); From d856ee74b1b6e8427f9ee09f581850cc0bcfd4a2 Mon Sep 17 00:00:00 2001 From: Justin Prieto Date: Mon, 6 Sep 2021 17:49:37 -0400 Subject: [PATCH 094/633] Add `sorted_by_cached_key` to `Itertools` trait Fixes #424. This function is a wrapper around `slice::sort_by_cached_key`. --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 25 +++++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 55c895844..37d2d52ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2667,6 +2667,43 @@ pub trait Itertools : Iterator { v.into_iter() } + /// Sort all iterator elements into a new iterator in ascending order. The key function is + /// called exactly once per key. + /// + /// **Note:** This consumes the entire iterator, uses the + /// [`slice::sort_by_cached_key`] method and returns the result as a new + /// iterator that owns its elements. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // sort people in descending order by age + /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 27)]; + /// + /// let oldest_people_first = people + /// .into_iter() + /// .sorted_by_cached_key(|x| -x.1) + /// .map(|(person, _age)| person); + /// + /// itertools::assert_equal(oldest_people_first, + /// vec!["Jill", "Jack", "Jane", "John"]); + /// ``` + /// ``` + #[cfg(feature = "use_alloc")] + fn sorted_by_cached_key(self, f: F) -> VecIntoIter + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, + { + let mut v = Vec::from_iter(self); + v.sort_by_cached_key(f); + v.into_iter() + } + /// Sort the k smallest elements into a new iterator, in ascending order. /// /// **Note:** This consumes the entire iterator, and returns the result diff --git a/tests/test_std.rs b/tests/test_std.rs index 819995861..0dc772a29 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -510,6 +510,31 @@ fn sorted_by_key() { it::assert_equal(v, vec![4, 3, 2, 1, 0]); } +#[test] +fn sorted_by_cached_key() { + // Track calls to key function + let mut ncalls = 0; + + let sorted = [3, 4, 1, 2].iter().cloned().sorted_by_cached_key(|&x| { + ncalls += 1; + x.to_string() + }); + it::assert_equal(sorted, vec![1, 2, 3, 4]); + // Check key function called once per element + assert_eq!(ncalls, 4); + + let mut ncalls = 0; + + let sorted = (0..5).sorted_by_cached_key(|&x| { + ncalls += 1; + -x + }); + it::assert_equal(sorted, vec![4, 3, 2, 1, 0]); + // Check key function called once per element + assert_eq!(ncalls, 5); +} + + #[test] fn test_multipeek() { let nums = vec![1u8,2,3,4,5]; From a1510e74053b36a44d1e9179e067d614987c91b1 Mon Sep 17 00:00:00 2001 From: Justin Prieto Date: Mon, 6 Sep 2021 19:47:15 -0400 Subject: [PATCH 095/633] remove extra space --- tests/test_std.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 0dc772a29..2049d1597 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -534,7 +534,6 @@ fn sorted_by_cached_key() { assert_eq!(ncalls, 5); } - #[test] fn test_multipeek() { let nums = vec![1u8,2,3,4,5]; From 42033adcbb850693cb6928216a13b0d18ef984b3 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 11 Oct 2021 20:58:14 +0300 Subject: [PATCH 096/633] Deprecate `fold1` in faviour of `reduce` --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index 55c895844..e876231bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2243,6 +2243,7 @@ pub trait Itertools : Iterator { /// assert_eq!((0..10).fold1(|x, y| x + y).unwrap_or(0), 45); /// assert_eq!((0..0).fold1(|x, y| x * y), None); /// ``` + #[deprecated(since = "0.10.2", note = "Use `Iterator::reduce` instead")] fn fold1(mut self, f: F) -> Option where F: FnMut(Self::Item, Self::Item) -> Self::Item, Self: Sized, From 72f300dc95037d5f5b42977af8ca5f3396f193a3 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 5 Dec 2021 21:29:11 +0100 Subject: [PATCH 097/633] Prepare 0.10.2 (1) changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41c4ab037..5e2032c4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.10.2 + - Add `Itertools::multiunzip` (#362, #565) + - Add `intersperse` and `intersperse_with` free functions (#555) + - Add `Itertools::sorted_by_cached_key` (#424, #575) + - Specialize `ProcessResults::fold` (#563) + - Fix subtraction overflow in `DuplicatesBy::size_hint` (#552) + - Fix specialization tests (#574) + - More `Debug` impls (#573) + - Deprecate `fold1` (use `reduce` instead) (#580) + - Documentation fixes (`HomogenousTuple`, `into_group_map`, `into_group_map_by`, `MultiPeek::peek`) (#543 et al.) + ## 0.10.1 - Add `Itertools::contains` (#514) - Add `Itertools::counts_by` (#515) From 2234b7f12ba7a55a830604bf9ca07a4153508ef0 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 5 Dec 2021 21:29:11 +0100 Subject: [PATCH 098/633] Prepare 0.10.2 (2) Cargo.toml --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 247d639ce..312eaff53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.1" +version = "0.10.2" license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" From defab962d5b9aceb46f13f1caf140d2e4492570f Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 5 Dec 2021 21:29:11 +0100 Subject: [PATCH 099/633] Prepare 0.10.2 (3) README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eaedd0980..4cc3f8fd3 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.10.0" +itertools = "0.10.2" ``` How to use in your crate: From 3003c2a968f144cbccb63c768d2fec2e83a69ca4 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 6 Dec 2021 16:09:00 -0500 Subject: [PATCH 100/633] (cargo-release) version 0.10.3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 312eaff53..22a08a8d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.2" +version = "0.10.3" license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" From 2357d1ad66235af85856b63e43ecf42727f8ce18 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 18 Dec 2021 22:24:34 +0100 Subject: [PATCH 101/633] Update comments for multiunzip --- src/unziptuple.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/unziptuple.rs b/src/unziptuple.rs index f468f05e0..7af29ec4a 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -39,11 +39,11 @@ macro_rules! impl_unzip_iter { #[allow(non_snake_case)] impl, $($T, $FromT: Default + Extend<$T>),* > MultiUnzip<($($FromT,)*)> for IT { fn multiunzip(self) -> ($($FromT,)*) { - // This implementation mirrors the logic of Iterator::unzip as close as possible. - // Unfortunately a lot of the used api there is still unstable represented by - // the commented out parts that follow. + // This implementation mirrors the logic of Iterator::unzip resp. Extend for (A, B) as close as possible. + // Unfortunately a lot of the used api there is still unstable (https://github.com/rust-lang/rust/issues/72631). // - // https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#2816-2844 + // Iterator::unzip: https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#2825-2865 + // Extend for (A, B): https://doc.rust-lang.org/src/core/iter/traits/collect.rs.html#370-411 let mut res = ($($FromT::default(),)*); let ($($FromT,)*) = &mut res; From ac654709697daea8ffda25e27dc8ab9c74d069b1 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 102/633] Canonicalize some free functions (1) equal --- src/lib.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index df95e19ba..4dc48824a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3490,17 +3490,7 @@ pub fn equal(a: I, b: J) -> bool J: IntoIterator, I::Item: PartialEq { - let mut ia = a.into_iter(); - let mut ib = b.into_iter(); - loop { - match ia.next() { - Some(x) => match ib.next() { - Some(y) => if x != y { return false; }, - None => return false, - }, - None => return ib.next().is_none() - } - } + a.into_iter().eq(b) } /// Assert that two iterables produce equal sequences, with the same From f1e9a73c9171a5df5b860897080ead89df002a9f Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 103/633] Canonicalize some free functions (1.2) equal: documentation --- src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4dc48824a..f2ecd3ec7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3478,8 +3478,7 @@ impl Itertools for T where T: Iterator { } /// (elements pairwise equal and sequences of the same length), /// `false` otherwise. /// -/// This is an [`IntoIterator`] enabled function that is similar to the standard -/// library method [`Iterator::eq`]. +/// [`IntoIterator`] enabled version of [`Iterator::eq`]. /// /// ``` /// assert!(itertools::equal(vec![1, 2, 3], 1..4)); From aff1c816a417e32b0362c90752763789a2d0a612 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 104/633] Canonicalize some free functions (2) interleave: documentation --- src/adaptors/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 2010f535b..d324e4a68 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -35,9 +35,7 @@ pub struct Interleave { /// Create an iterator that interleaves elements in `i` and `j`. /// -/// [`IntoIterator`] enabled version of `i.interleave(j)`. -/// -/// See [`.interleave()`](crate::Itertools::interleave) for more information. +/// [`IntoIterator`] enabled version of `[Itertools::interleave]`. pub fn interleave(i: I, j: J) -> Interleave<::IntoIter, ::IntoIter> where I: IntoIterator, J: IntoIterator From 4d7f793ec54c77cbe0842d2f4bdee6bc10e28dd7 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 105/633] Canonicalize some free functions (3) kmerge: documentation --- src/kmerge_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index bd56b0317..cadd136a3 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -129,7 +129,7 @@ implbool> KMergePredicate for F { /// Create an iterator that merges elements of the contained iterators using /// the ordering function. /// -/// Equivalent to `iterable.into_iter().kmerge()`. +/// [`IntoIterator`] enabled version of [`Itertools::kmerge`]. /// /// ``` /// use itertools::kmerge; From caed277e5a37f56e66bb3a10da3d1f46d366e3ac Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 106/633] Canonicalize some free functions (3.2) kmerge_by: documentation --- src/kmerge_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index cadd136a3..f51ed23e6 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -170,7 +170,7 @@ impl fmt::Debug for KMergeBy /// Create an iterator that merges elements of the contained iterators. /// -/// Equivalent to `iterable.into_iter().kmerge_by(less_than)`. +/// [`IntoIterator`] enabled version of [`Itertools::kmerge_by`]. pub fn kmerge_by(iterable: I, mut less_than: F) -> KMergeBy<::IntoIter, F> where I: IntoIterator, From 08ea4c46b581fefba0382b591cd15bedda20c371 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 107/633] Canonicalize some free functions (4) merge_join_by: documentation --- src/merge_join.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 4c0048f68..f2fbdea2c 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -4,10 +4,12 @@ use std::fmt; use super::adaptors::{PutBack, put_back}; use crate::either_or_both::EitherOrBoth; +#[cfg(doc)] +use crate::Itertools; /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// -/// See [`.merge_join_by()`](crate::Itertools::merge_join_by) for more information. +/// [`IntoIterator`] enabled version of [`Itertools::merge_join_by`]. pub fn merge_join_by(left: I, right: J, cmp_fn: F) -> MergeJoinBy where I: IntoIterator, From 8e73e5d2b217b78cb54f7255fb84853b77952765 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Dec 2021 22:06:32 +0100 Subject: [PATCH 108/633] Canonicalize some free functions (5) multipeek: documentation --- src/multipeek_impl.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 5917681fc..0ac9380b6 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -2,6 +2,8 @@ use std::iter::Fuse; use alloc::collections::VecDeque; use crate::size_hint; use crate::PeekingNext; +#[cfg(doc)] +use crate::Itertools; /// See [`multipeek()`] for more information. #[derive(Clone, Debug)] @@ -15,6 +17,8 @@ pub struct MultiPeek /// An iterator adaptor that allows the user to peek at multiple `.next()` /// values without advancing the base iterator. +/// +/// [`IntoIterator`] enabled version of [`Itertools::multipeek`]. pub fn multipeek(iterable: I) -> MultiPeek where I: IntoIterator { From 1a666a7fe09408dec608728a68169d6eb030dee1 Mon Sep 17 00:00:00 2001 From: Carl Andersson Date: Wed, 29 Dec 2021 17:18:08 +0100 Subject: [PATCH 109/633] EitherOrBoth: Add or and or_else methods to simplify getting default values --- src/either_or_both.rs | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 28d1df757..ef3985f75 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -164,6 +164,33 @@ impl EitherOrBoth { } } + /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. + /// Otherwise, returns the wrapped value for the present element, and the supplied + /// value for the other. The first (`l`) argument is used for a missing `Left` + /// value. The second (`r`) argument is used for a missing `Right` value. + /// + /// Arguments passed to `or` are eagerly evaluated; if you are passing + /// the result of a function call, it is recommended to use [`or_else`], + /// which is lazily evaluated. + /// + /// [`or_else`]: EitherOrBoth::or_else + /// + /// # Examples + /// + /// ``` + /// # use itertools::EitherOrBoth; + /// assert_eq!(EitherOrBoth::Both("tree", 1).or("stone", 5), ("tree", 1)); + /// assert_eq!(EitherOrBoth::Left("tree").or("stone", 5), ("tree", 5)); + /// assert_eq!(EitherOrBoth::Right(1).or("stone", 5), ("stone", 1)); + /// ``` + pub fn or(self, l: A, r: B) -> (A, B) { + match self { + Left(inner_l) => (inner_l, r), + Right(inner_r) => (l, inner_r), + Both(inner_l, inner_r) => (inner_l, inner_r), + } + } + /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. /// Otherwise, returns the wrapped value for the present element, and the [`default`](Default::default) /// for the other. @@ -178,6 +205,28 @@ impl EitherOrBoth { EitherOrBoth::Both(l, r) => (l, r), } } + + /// Returns a tuple consisting of the `l` and `r` in `Both(l, r)`, if present. + /// Otherwise, returns the wrapped value for the present element, and computes the + /// missing value with the supplied closure. The first argument (`l`) is used for a + /// missing `Left` value. The second argument (`r`) is used for a missing `Right` value. + /// + /// # Examples + /// + /// ``` + /// # use itertools::EitherOrBoth; + /// let k = 10; + /// assert_eq!(EitherOrBoth::Both("tree", 1).or_else(|| "stone", || 2 * k), ("tree", 1)); + /// assert_eq!(EitherOrBoth::Left("tree").or_else(|| "stone", || 2 * k), ("tree", 20)); + /// assert_eq!(EitherOrBoth::Right(1).or_else(|| "stone", || 2 * k), ("stone", 1)); + /// ``` + pub fn or_else A, R: FnOnce() -> B>(self, l: L, r: R) -> (A, B) { + match self { + Left(inner_l) => (inner_l, r()), + Right(inner_r) => (l(), inner_r), + Both(inner_l, inner_r) => (inner_l, inner_r), + } + } } impl EitherOrBoth { From 22fa6f0fb80a01bdf1be14c97899b121f242bf14 Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 25 Mar 2022 15:21:29 +0100 Subject: [PATCH 110/633] Make Format/FormatWith use Cell Resolves #607 --- src/format.rs | 90 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/src/format.rs b/src/format.rs index d87cee950..2927af706 100644 --- a/src/format.rs +++ b/src/format.rs @@ -1,5 +1,5 @@ +use std::cell::Cell; use std::fmt; -use std::cell::RefCell; /// Format all iterator elements lazily, separated by `sep`. /// @@ -7,11 +7,10 @@ use std::cell::RefCell; /// exhausted. /// /// See [`.format_with()`](crate::Itertools::format_with) for more information. -#[derive(Clone)] pub struct FormatWith<'a, I, F> { sep: &'a str, /// FormatWith uses interior mutability because Display::fmt takes &self. - inner: RefCell>, + inner: Cell>, } /// Format all iterator elements lazily, separated by `sep`. @@ -21,38 +20,40 @@ pub struct FormatWith<'a, I, F> { /// /// See [`.format()`](crate::Itertools::format) /// for more information. -#[derive(Clone)] pub struct Format<'a, I> { sep: &'a str, /// Format uses interior mutability because Display::fmt takes &self. - inner: RefCell>, + inner: Cell>, } pub fn new_format(iter: I, separator: &str, f: F) -> FormatWith<'_, I, F> - where I: Iterator, - F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result +where + I: Iterator, + F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, { FormatWith { sep: separator, - inner: RefCell::new(Some((iter, f))), + inner: Cell::new(Some((iter, f))), } } pub fn new_format_default(iter: I, separator: &str) -> Format<'_, I> - where I: Iterator, +where + I: Iterator, { Format { sep: separator, - inner: RefCell::new(Some(iter)), + inner: Cell::new(Some(iter)), } } impl<'a, I, F> fmt::Display for FormatWith<'a, I, F> - where I: Iterator, - F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result +where + I: Iterator, + F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let (mut iter, mut format) = match self.inner.borrow_mut().take() { + let (mut iter, mut format) = match self.inner.take() { Some(t) => t, None => panic!("FormatWith: was already formatted once"), }; @@ -71,12 +72,14 @@ impl<'a, I, F> fmt::Display for FormatWith<'a, I, F> } impl<'a, I> Format<'a, I> - where I: Iterator, +where + I: Iterator, { fn format(&self, f: &mut fmt::Formatter, mut cb: F) -> fmt::Result - where F: FnMut(&I::Item, &mut fmt::Formatter) -> fmt::Result, + where + F: FnMut(&I::Item, &mut fmt::Formatter) -> fmt::Result, { - let mut iter = match self.inner.borrow_mut().take() { + let mut iter = match self.inner.take() { Some(t) => t, None => panic!("Format: was already formatted once"), }; @@ -109,5 +112,56 @@ macro_rules! impl_format { } } -impl_format!{Display Debug - UpperExp LowerExp UpperHex LowerHex Octal Binary Pointer} +impl_format! {Display Debug UpperExp LowerExp UpperHex LowerHex Octal Binary Pointer} + +impl<'a, I, F> Clone for FormatWith<'a, I, F> +where + (I, F): Clone, +{ + fn clone(&self) -> Self { + struct PutBackOnDrop<'r, 'a, I, F> { + into: &'r FormatWith<'a, I, F>, + inner: Option<(I, F)>, + } + // This ensures we preserve the state of the original `FormatWith` if `Clone` panics + impl<'r, 'a, I, F> Drop for PutBackOnDrop<'r, 'a, I, F> { + fn drop(&mut self) { + self.into.inner.set(self.inner.take()) + } + } + let pbod = PutBackOnDrop { + inner: self.inner.take(), + into: self, + }; + Self { + inner: Cell::new(pbod.inner.clone()), + sep: self.sep, + } + } +} + +impl<'a, I> Clone for Format<'a, I> +where + I: Clone, +{ + fn clone(&self) -> Self { + struct PutBackOnDrop<'r, 'a, I> { + into: &'r Format<'a, I>, + inner: Option, + } + // This ensures we preserve the state of the original `FormatWith` if `Clone` panics + impl<'r, 'a, I> Drop for PutBackOnDrop<'r, 'a, I> { + fn drop(&mut self) { + self.into.inner.set(self.inner.take()) + } + } + let pbod = PutBackOnDrop { + inner: self.inner.take(), + into: self, + }; + Self { + inner: Cell::new(pbod.inner.clone()), + sep: self.sep, + } + } +} From dce737881142787cb17cc5d8a06ff6ac006d630d Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 25 Mar 2022 16:00:33 +0100 Subject: [PATCH 111/633] remove unnecessary genericity --- src/format.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/format.rs b/src/format.rs index 2927af706..c4cb65dcb 100644 --- a/src/format.rs +++ b/src/format.rs @@ -75,10 +75,11 @@ impl<'a, I> Format<'a, I> where I: Iterator, { - fn format(&self, f: &mut fmt::Formatter, mut cb: F) -> fmt::Result - where - F: FnMut(&I::Item, &mut fmt::Formatter) -> fmt::Result, - { + fn format( + &self, + f: &mut fmt::Formatter, + cb: fn(&I::Item, &mut fmt::Formatter) -> fmt::Result, + ) -> fmt::Result { let mut iter = match self.inner.take() { Some(t) => t, None => panic!("Format: was already formatted once"), From f520157bf69707142fe287d2491f2c11dfe90615 Mon Sep 17 00:00:00 2001 From: cuishuang Date: Sun, 24 Apr 2022 17:32:47 +0800 Subject: [PATCH 112/633] fix some typos Signed-off-by: cuishuang --- benches/extra/zipslices.rs | 2 +- src/flatten_ok.rs | 8 ++++---- src/free.rs | 2 +- src/kmerge_impl.rs | 2 +- src/lib.rs | 8 ++++---- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/benches/extra/zipslices.rs b/benches/extra/zipslices.rs index 8bf3967f5..633be5906 100644 --- a/benches/extra/zipslices.rs +++ b/benches/extra/zipslices.rs @@ -3,7 +3,7 @@ use std::cmp; // Note: There are different ways to implement ZipSlices. // This version performed the best in benchmarks. // -// I also implemented a version with three pointes (tptr, tend, uptr), +// I also implemented a version with three pointers (tptr, tend, uptr), // that mimiced slice::Iter and only checked bounds by using tptr == tend, // but that was inferior to this solution. diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index d46bbde4e..91168406b 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -46,7 +46,7 @@ where return Some(Ok(item)); } else { // This is necessary for the iterator to implement `FusedIterator` - // with only the orginal iterator being fused. + // with only the original iterator being fused. self.inner_front = None; } } @@ -61,7 +61,7 @@ where return Some(Ok(item)); } else { // This is necessary for the iterator to implement `FusedIterator` - // with only the orginal iterator being fused. + // with only the original iterator being fused. self.inner_back = None; } } else { @@ -105,7 +105,7 @@ where return Some(Ok(item)); } else { // This is necessary for the iterator to implement `FusedIterator` - // with only the orginal iterator being fused. + // with only the original iterator being fused. self.inner_back = None; } } @@ -120,7 +120,7 @@ where return Some(Ok(item)); } else { // This is necessary for the iterator to implement `FusedIterator` - // with only the orginal iterator being fused. + // with only the original iterator being fused. self.inner_front = None; } } else { diff --git a/src/free.rs b/src/free.rs index 667403040..3886f5f1f 100644 --- a/src/free.rs +++ b/src/free.rs @@ -239,7 +239,7 @@ pub fn min(iterable: I) -> Option } -/// Combine all iterator elements into one String, seperated by `sep`. +/// Combine all iterator elements into one String, separated by `sep`. /// /// [`IntoIterator`] enabled version of [`Itertools::join`]. /// diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index f51ed23e6..f2280653f 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -80,7 +80,7 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) // that wouldn't be predicted if present while child + 1 < heap.len() { // pick the smaller of the two children - // use aritmethic to avoid an unpredictable branch + // use arithmetic to avoid an unpredictable branch child += less_than(&heap[child+1], &heap[child]) as usize; // sift down is done if we are already in order diff --git a/src/lib.rs b/src/lib.rs index f2ecd3ec7..ff0bf7640 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -387,7 +387,7 @@ macro_rules! izip { /// let with_macro: Chain, Take>>, slice::Iter<_>> = /// chain![once(&0), repeat(&1).take(2), &[2, 3, 5],]; /// -/// // ...is equivalant to this: +/// // ...is equivalent to this: /// let with_method: Chain, Take>>, slice::Iter<_>> = /// once(&0) /// .chain(repeat(&1).take(2)) @@ -904,7 +904,7 @@ pub trait Itertools : Iterator { /// a series of `Result::Ok` values. `Result::Err` values are unchanged. /// /// This is useful when you have some common error type for your crate and - /// need to propogate it upwards, but the `Result::Ok` case needs to be flattened. + /// need to propagate it upwards, but the `Result::Ok` case needs to be flattened. /// /// ``` /// use itertools::Itertools; @@ -913,7 +913,7 @@ pub trait Itertools : Iterator { /// let it = input.iter().cloned().flatten_ok(); /// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]); /// - /// // This can also be used to propogate errors when collecting. + /// // This can also be used to propagate errors when collecting. /// let output_result: Result, bool> = it.collect(); /// assert_eq!(output_result, Err(false)); /// ``` @@ -3157,7 +3157,7 @@ pub trait Itertools : Iterator { /// be equal to `ypos`. /// /// On an iterator of length `n`, `position_minmax` does `1.5 * n` - /// comparisons, and so is faster than calling `positon_min` and + /// comparisons, and so is faster than calling `position_min` and /// `position_max` separately which does `2 * n` comparisons. /// /// For the minimum, if several elements are equally minimum, the From e8c67ef406a95e27ec030facad93da9d97c656e0 Mon Sep 17 00:00:00 2001 From: Mikael Zayenz Lagerkvist Date: Tue, 11 Dec 2018 16:28:55 +0100 Subject: [PATCH 113/633] FEAT: Add {min,max}_set(_by{_key)?)? functions The function min_set returns a Vec of all the minimum values. All variants for max and with key extraction and comparison functions are added also. Since the functions need to return an unknown number of values and the values are not known until the iterator is finished, the function needs to allocate memory. Therefore Vec is used for returning the values. --- src/extrema_set.rs | 42 ++++++++++ src/lib.rs | 190 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 48 ++++++++++++ 3 files changed, 280 insertions(+) create mode 100644 src/extrema_set.rs diff --git a/src/extrema_set.rs b/src/extrema_set.rs new file mode 100644 index 000000000..e6d5eec0b --- /dev/null +++ b/src/extrema_set.rs @@ -0,0 +1,42 @@ +/// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`. +pub fn min_set_impl(mut it: I, + mut key_for: F, + mut lt: L) -> Option> + where I: Iterator, + F: FnMut(&I::Item) -> K, + L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, +{ + let (mut result, mut current_key) = match it.next() { + None => return None, + Some(element) => { + let key = key_for(&element); + (vec![element], key) + } + }; + + for element in it { + let key = key_for(&element); + if lt(&element, &result[0], &key, ¤t_key) { + result.clear(); + result.push(element); + current_key = key; + } else if !lt(&result[0], &element, ¤t_key, &key) { + result.push(element); + } + } + + Some(result) +} + +/// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`. +pub fn max_set_impl(it: I, + key_for: F, + mut lt: L) -> Option> + where I: Iterator, + F: FnMut(&I::Item) -> K, + L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, +{ + min_set_impl(it, key_for, |it1, it2, key1, key2| lt(it2, it1, key2, key1)) +} + + diff --git a/src/lib.rs b/src/lib.rs index f2ecd3ec7..29408ba02 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -197,6 +197,8 @@ mod combinations_with_replacement; mod exactly_one_err; mod diff; mod flatten_ok; +#[cfg(feature = "use_std")] +mod extrema_set; mod format; #[cfg(feature = "use_std")] mod grouping_map; @@ -2902,6 +2904,194 @@ pub trait Itertools : Iterator { grouping_map::new(grouping_map::MapForGrouping::new(self, key_mapper)) } + /// Return all minimum elements of an iterator. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let a: [i32; 0] = []; + /// assert_eq!(a.iter().min_set(), None); + /// + /// let a = [1]; + /// assert_eq!(a.iter().min_set(), Some(vec![&1])); + /// + /// let a = [1, 2, 3, 4, 5]; + /// assert_eq!(a.iter().min_set(), Some(vec![&1])); + /// + /// let a = [1, 1, 1, 1]; + /// assert_eq!(a.iter().min_set(), Some(vec![&1, &1, &1, &1])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn min_set(self) -> Option> + where Self: Sized, Self::Item: PartialOrd + { + extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x < y) + } + + /// Return all minimum elements of an iterator, as determined by + /// the specified function. + /// + /// # Examples + /// + /// ``` + /// # use std::cmp::Ordering; + /// use itertools::Itertools; + /// + /// let a: [(i32, i32); 0] = []; + /// assert_eq!(a.iter().min_set_by(|_, _| Ordering::Equal), None); + /// + /// let a = [(1, 2)]; + /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2)])); + /// + /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; + /// assert_eq!(a.iter().min_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), Some(vec![&(1, 2), &(2, 2)])); + /// + /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; + /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn min_set_by(self, mut compare: F) -> Option> + where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + { + extrema_set::min_set_impl( + self, + |_| (), + |x, y, _, _| Ordering::Less == compare(x, y) + ) + } + + /// Return all minimum elements of an iterator, as determined by + /// the specified function. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let a: [(i32, i32); 0] = []; + /// assert_eq!(a.iter().min_set_by_key(|_| ()), None); + /// + /// let a = [(1, 2)]; + /// assert_eq!(a.iter().min_set_by_key(|&&(k,_)| k), Some(vec![&(1, 2)])); + /// + /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; + /// assert_eq!(a.iter().min_set_by_key(|&&(_, k)| k), Some(vec![&(1, 2), &(2, 2)])); + /// + /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; + /// assert_eq!(a.iter().min_set_by_key(|&&(k, _)| k), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn min_set_by_key(self, key: F) -> Option> + where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + { + extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx < ky) + } + + /// Return all maximum elements of an iterator. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let a: [i32; 0] = []; + /// assert_eq!(a.iter().max_set(), None); + /// + /// let a = [1]; + /// assert_eq!(a.iter().max_set(), Some(vec![&1])); + /// + /// let a = [1, 2, 3, 4, 5]; + /// assert_eq!(a.iter().max_set(), Some(vec![&5])); + /// + /// let a = [1, 1, 1, 1]; + /// assert_eq!(a.iter().max_set(), Some(vec![&1, &1, &1, &1])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn max_set(self) -> Option> + where Self: Sized, Self::Item: PartialOrd + { + extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x < y) + } + + /// Return all maximum elements of an iterator, as determined by + /// the specified function. + /// + /// # Examples + /// + /// ``` + /// # use std::cmp::Ordering; + /// use itertools::Itertools; + /// + /// let a: [(i32, i32); 0] = []; + /// assert_eq!(a.iter().max_set_by(|_, _| Ordering::Equal), None); + /// + /// let a = [(1, 2)]; + /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2)])); + /// + /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; + /// assert_eq!(a.iter().max_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), Some(vec![&(3, 9), &(5, 9)])); + /// + /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; + /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn max_set_by(self, mut compare: F) -> Option> + where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + { + extrema_set::max_set_impl( + self, + |_| (), + |x, y, _, _| Ordering::Less == compare(x, y) + ) + } + + /// Return all minimum elements of an iterator, as determined by + /// the specified function. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let a: [(i32, i32); 0] = []; + /// assert_eq!(a.iter().max_set_by_key(|_| ()), None); + /// + /// let a = [(1, 2)]; + /// assert_eq!(a.iter().max_set_by_key(|&&(k,_)| k), Some(vec![&(1, 2)])); + /// + /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; + /// assert_eq!(a.iter().max_set_by_key(|&&(_, k)| k), Some(vec![&(3, 9), &(5, 9)])); + /// + /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; + /// assert_eq!(a.iter().max_set_by_key(|&&(k, _)| k), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// ``` + /// + /// The elements can be floats but no particular result is guaranteed + /// if an element is NaN. + #[cfg(feature = "use_std")] + fn max_set_by_key(self, key: F) -> Option> + where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + { + extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx < ky) + } + /// Return the minimum and maximum elements in the iterator. /// /// The return type `MinMaxResult` is an enum of three variants: diff --git a/tests/test_std.rs b/tests/test_std.rs index 2049d1597..cf41b8843 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -992,6 +992,54 @@ fn diff_shorter() { }); } +#[test] +fn extrema_set() { + use std::cmp::Ordering; + + // A peculiar type: Equality compares both tuple items, but ordering only the + // first item. Used to distinguish equal elements. + #[derive(Clone, Debug, PartialEq, Eq)] + struct Val(u32, u32); + + impl PartialOrd for Val { + fn partial_cmp(&self, other: &Val) -> Option { + self.0.partial_cmp(&other.0) + } + } + + impl Ord for Val { + fn cmp(&self, other: &Val) -> Ordering { + self.0.cmp(&other.0) + } + } + + assert_eq!(None::>.iter().min_set(), None); + assert_eq!(None::>.iter().max_set(), None); + + assert_eq!(Some(1u32).iter().min_set(), Some(vec![&1])); + assert_eq!(Some(1u32).iter().max_set(), Some(vec![&1])); + + let data = vec![Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; + + let min_set = data.iter().min_set().unwrap(); + assert_eq!(min_set, vec![&Val(0, 1), &Val(0, 2)]); + + let min_set_by_key = data.iter().min_set_by_key(|v| v.1).unwrap(); + assert_eq!(min_set_by_key, vec![&Val(2, 0), &Val(1, 0)]); + + let min_set_by = data.iter().min_set_by(|x, y| x.1.cmp(&y.1)).unwrap(); + assert_eq!(min_set_by, vec![&Val(2, 0), &Val(1, 0)]); + + let max_set = data.iter().max_set().unwrap(); + assert_eq!(max_set, vec![&Val(2, 0), &Val(2, 1)]); + + let max_set_by_key = data.iter().max_set_by_key(|v| v.1).unwrap(); + assert_eq!(max_set_by_key, vec![&Val(0, 2)]); + + let max_set_by = data.iter().max_set_by(|x, y| x.1.cmp(&y.1)).unwrap(); + assert_eq!(max_set_by, vec![&Val(0, 2)]); +} + #[test] fn minmax() { use std::cmp::Ordering; From bb8b41a88105e9f74af45eaa1d92d1f60cc2c247 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 114/633] Add {min,max}_set(_by{_key)?)? functions (2) return Vec instead Option --- src/extrema_set.rs | 8 +++---- src/lib.rs | 60 +++++++++++++++++++++++----------------------- tests/test_std.rs | 22 ++++++++--------- 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index e6d5eec0b..60f8979a9 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -1,13 +1,13 @@ /// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`. pub fn min_set_impl(mut it: I, mut key_for: F, - mut lt: L) -> Option> + mut lt: L) -> Vec where I: Iterator, F: FnMut(&I::Item) -> K, L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, { let (mut result, mut current_key) = match it.next() { - None => return None, + None => return Vec::new(), Some(element) => { let key = key_for(&element); (vec![element], key) @@ -25,13 +25,13 @@ pub fn min_set_impl(mut it: I, } } - Some(result) + result } /// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`. pub fn max_set_impl(it: I, key_for: F, - mut lt: L) -> Option> + mut lt: L) -> Vec where I: Iterator, F: FnMut(&I::Item) -> K, L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, diff --git a/src/lib.rs b/src/lib.rs index 29408ba02..791de1c71 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2912,22 +2912,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [i32; 0] = []; - /// assert_eq!(a.iter().min_set(), None); + /// assert_eq!(a.iter().min_set(), Vec::<&i32>::new()); /// /// let a = [1]; - /// assert_eq!(a.iter().min_set(), Some(vec![&1])); + /// assert_eq!(a.iter().min_set(), vec![&1]); /// /// let a = [1, 2, 3, 4, 5]; - /// assert_eq!(a.iter().min_set(), Some(vec![&1])); + /// assert_eq!(a.iter().min_set(), vec![&1]); /// /// let a = [1, 1, 1, 1]; - /// assert_eq!(a.iter().min_set(), Some(vec![&1, &1, &1, &1])); + /// assert_eq!(a.iter().min_set(), vec![&1, &1, &1, &1]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn min_set(self) -> Option> + fn min_set(self) -> Vec where Self: Sized, Self::Item: PartialOrd { extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x < y) @@ -2943,22 +2943,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [(i32, i32); 0] = []; - /// assert_eq!(a.iter().min_set_by(|_, _| Ordering::Equal), None); + /// assert_eq!(a.iter().min_set_by(|_, _| Ordering::Equal), Vec::<&(i32, i32)>::new()); /// /// let a = [(1, 2)]; - /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2)])); + /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2)]); /// /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; - /// assert_eq!(a.iter().min_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), Some(vec![&(1, 2), &(2, 2)])); + /// assert_eq!(a.iter().min_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), vec![&(1, 2), &(2, 2)]); /// /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; - /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn min_set_by(self, mut compare: F) -> Option> + fn min_set_by(self, mut compare: F) -> Vec where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering { extrema_set::min_set_impl( @@ -2977,22 +2977,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [(i32, i32); 0] = []; - /// assert_eq!(a.iter().min_set_by_key(|_| ()), None); + /// assert_eq!(a.iter().min_set_by_key(|_| ()), Vec::<&(i32, i32)>::new()); /// /// let a = [(1, 2)]; - /// assert_eq!(a.iter().min_set_by_key(|&&(k,_)| k), Some(vec![&(1, 2)])); + /// assert_eq!(a.iter().min_set_by_key(|&&(k,_)| k), vec![&(1, 2)]); /// /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; - /// assert_eq!(a.iter().min_set_by_key(|&&(_, k)| k), Some(vec![&(1, 2), &(2, 2)])); + /// assert_eq!(a.iter().min_set_by_key(|&&(_, k)| k), vec![&(1, 2), &(2, 2)]); /// /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; - /// assert_eq!(a.iter().min_set_by_key(|&&(k, _)| k), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// assert_eq!(a.iter().min_set_by_key(|&&(k, _)| k), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn min_set_by_key(self, key: F) -> Option> + fn min_set_by_key(self, key: F) -> Vec where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K { extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx < ky) @@ -3006,22 +3006,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [i32; 0] = []; - /// assert_eq!(a.iter().max_set(), None); + /// assert_eq!(a.iter().max_set(), Vec::<&i32>::new()); /// /// let a = [1]; - /// assert_eq!(a.iter().max_set(), Some(vec![&1])); + /// assert_eq!(a.iter().max_set(), vec![&1]); /// /// let a = [1, 2, 3, 4, 5]; - /// assert_eq!(a.iter().max_set(), Some(vec![&5])); + /// assert_eq!(a.iter().max_set(), vec![&5]); /// /// let a = [1, 1, 1, 1]; - /// assert_eq!(a.iter().max_set(), Some(vec![&1, &1, &1, &1])); + /// assert_eq!(a.iter().max_set(), vec![&1, &1, &1, &1]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn max_set(self) -> Option> + fn max_set(self) -> Vec where Self: Sized, Self::Item: PartialOrd { extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x < y) @@ -3037,22 +3037,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [(i32, i32); 0] = []; - /// assert_eq!(a.iter().max_set_by(|_, _| Ordering::Equal), None); + /// assert_eq!(a.iter().max_set_by(|_, _| Ordering::Equal), Vec::<&(i32, i32)>::new()); /// /// let a = [(1, 2)]; - /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2)])); + /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2)]); /// /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; - /// assert_eq!(a.iter().max_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), Some(vec![&(3, 9), &(5, 9)])); + /// assert_eq!(a.iter().max_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), vec![&(3, 9), &(5, 9)]); /// /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; - /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn max_set_by(self, mut compare: F) -> Option> + fn max_set_by(self, mut compare: F) -> Vec where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering { extrema_set::max_set_impl( @@ -3071,22 +3071,22 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let a: [(i32, i32); 0] = []; - /// assert_eq!(a.iter().max_set_by_key(|_| ()), None); + /// assert_eq!(a.iter().max_set_by_key(|_| ()), Vec::<&(i32, i32)>::new()); /// /// let a = [(1, 2)]; - /// assert_eq!(a.iter().max_set_by_key(|&&(k,_)| k), Some(vec![&(1, 2)])); + /// assert_eq!(a.iter().max_set_by_key(|&&(k,_)| k), vec![&(1, 2)]); /// /// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)]; - /// assert_eq!(a.iter().max_set_by_key(|&&(_, k)| k), Some(vec![&(3, 9), &(5, 9)])); + /// assert_eq!(a.iter().max_set_by_key(|&&(_, k)| k), vec![&(3, 9), &(5, 9)]); /// /// let a = [(1, 2), (1, 3), (1, 4), (1, 5)]; - /// assert_eq!(a.iter().max_set_by_key(|&&(k, _)| k), Some(vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)])); + /// assert_eq!(a.iter().max_set_by_key(|&&(k, _)| k), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]); /// ``` /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. #[cfg(feature = "use_std")] - fn max_set_by_key(self, key: F) -> Option> + fn max_set_by_key(self, key: F) -> Vec where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K { extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx < ky) diff --git a/tests/test_std.rs b/tests/test_std.rs index cf41b8843..4463d912f 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1013,30 +1013,30 @@ fn extrema_set() { } } - assert_eq!(None::>.iter().min_set(), None); - assert_eq!(None::>.iter().max_set(), None); + assert_eq!(None::.iter().min_set(), Vec::<&u32>::new()); + assert_eq!(None::.iter().max_set(), Vec::<&u32>::new()); - assert_eq!(Some(1u32).iter().min_set(), Some(vec![&1])); - assert_eq!(Some(1u32).iter().max_set(), Some(vec![&1])); + assert_eq!(Some(1u32).iter().min_set(), vec![&1]); + assert_eq!(Some(1u32).iter().max_set(), vec![&1]); let data = vec![Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; - let min_set = data.iter().min_set().unwrap(); + let min_set = data.iter().min_set(); assert_eq!(min_set, vec![&Val(0, 1), &Val(0, 2)]); - let min_set_by_key = data.iter().min_set_by_key(|v| v.1).unwrap(); + let min_set_by_key = data.iter().min_set_by_key(|v| v.1); assert_eq!(min_set_by_key, vec![&Val(2, 0), &Val(1, 0)]); - let min_set_by = data.iter().min_set_by(|x, y| x.1.cmp(&y.1)).unwrap(); + let min_set_by = data.iter().min_set_by(|x, y| x.1.cmp(&y.1)); assert_eq!(min_set_by, vec![&Val(2, 0), &Val(1, 0)]); - let max_set = data.iter().max_set().unwrap(); + let max_set = data.iter().max_set(); assert_eq!(max_set, vec![&Val(2, 0), &Val(2, 1)]); - let max_set_by_key = data.iter().max_set_by_key(|v| v.1).unwrap(); + let max_set_by_key = data.iter().max_set_by_key(|v| v.1); assert_eq!(max_set_by_key, vec![&Val(0, 2)]); - let max_set_by = data.iter().max_set_by(|x, y| x.1.cmp(&y.1)).unwrap(); + let max_set_by = data.iter().max_set_by(|x, y| x.1.cmp(&y.1)); assert_eq!(max_set_by, vec![&Val(0, 2)]); } @@ -1167,4 +1167,4 @@ fn multiunzip() { let (): () = [(), (), ()].iter().cloned().multiunzip(); let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); assert_eq!(t, (vec![0], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6], vec![7], vec![8], vec![9], vec![10], vec![11])); -} \ No newline at end of file +} From 491b34a37faa11ea6ce103e43dcd5fea7758810d Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 115/633] Add {min,max}_set(_by{_key)?)? functions (3) use Ord instead of PartialOrd We may relax this bound at some point, but I'd go with this until we have evidence that it bothers users, as there have been cases where I actually was happy to be informed about Ord vs PartialOrd. --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 791de1c71..3f058b1f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2928,7 +2928,7 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn min_set(self) -> Vec - where Self: Sized, Self::Item: PartialOrd + where Self: Sized, Self::Item: Ord { extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x < y) } @@ -2993,7 +2993,7 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn min_set_by_key(self, key: F) -> Vec - where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K { extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx < ky) } @@ -3022,7 +3022,7 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn max_set(self) -> Vec - where Self: Sized, Self::Item: PartialOrd + where Self: Sized, Self::Item: Ord { extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x < y) } @@ -3087,7 +3087,7 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn max_set_by_key(self, key: F) -> Vec - where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K { extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx < ky) } From 834bcc5b9b293ef438ee57451b9fb555fc5b4e15 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 116/633] Add {min,max}_set(_by{_key)?)? functions (4) use internal iteration We may relax this bound at some point, but I'd go with this until we have evidence that it bothers users, as there have been cases where I actually was happy to be informed about Ord vs PartialOrd. --- src/extrema_set.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index 60f8979a9..3c0794808 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -14,7 +14,7 @@ pub fn min_set_impl(mut it: I, } }; - for element in it { + it.for_each(|element| { let key = key_for(&element); if lt(&element, &result[0], &key, ¤t_key) { result.clear(); @@ -23,7 +23,7 @@ pub fn min_set_impl(mut it: I, } else if !lt(&result[0], &element, ¤t_key, &key) { result.push(element); } - } + }); result } From 6f96e0142e93ee07b986317eadae54e8f7e554bf Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 117/633] Add {min,max}_set(_by{_key)?)? functions (5) comparator uses Ordering instead of bool More canonical. --- src/extrema_set.rs | 33 ++++++++++++++++++++------------- src/lib.rs | 12 ++++++------ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index 3c0794808..2c7cef681 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -1,10 +1,12 @@ +use std::cmp::Ordering; + /// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`. -pub fn min_set_impl(mut it: I, +pub fn min_set_impl(mut it: I, mut key_for: F, - mut lt: L) -> Vec + mut compare: Compare) -> Vec where I: Iterator, F: FnMut(&I::Item) -> K, - L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, + Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, { let (mut result, mut current_key) = match it.next() { None => return Vec::new(), @@ -16,12 +18,17 @@ pub fn min_set_impl(mut it: I, it.for_each(|element| { let key = key_for(&element); - if lt(&element, &result[0], &key, ¤t_key) { - result.clear(); - result.push(element); - current_key = key; - } else if !lt(&result[0], &element, ¤t_key, &key) { - result.push(element); + match compare(&element, &result[0], &key, ¤t_key) { + Ordering::Less => { + result.clear(); + result.push(element); + current_key = key; + }, + Ordering::Equal => { + result.push(element); + }, + Ordering::Greater => { + }, } }); @@ -29,14 +36,14 @@ pub fn min_set_impl(mut it: I, } /// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`. -pub fn max_set_impl(it: I, +pub fn max_set_impl(it: I, key_for: F, - mut lt: L) -> Vec + mut compare: Compare) -> Vec where I: Iterator, F: FnMut(&I::Item) -> K, - L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, + Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, { - min_set_impl(it, key_for, |it1, it2, key1, key2| lt(it2, it1, key2, key1)) + min_set_impl(it, key_for, |it1, it2, key1, key2| compare(it2, it1, key2, key1)) } diff --git a/src/lib.rs b/src/lib.rs index 3f058b1f8..c2c1750be 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2930,7 +2930,7 @@ pub trait Itertools : Iterator { fn min_set(self) -> Vec where Self: Sized, Self::Item: Ord { - extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x < y) + extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x.cmp(y)) } /// Return all minimum elements of an iterator, as determined by @@ -2964,7 +2964,7 @@ pub trait Itertools : Iterator { extrema_set::min_set_impl( self, |_| (), - |x, y, _, _| Ordering::Less == compare(x, y) + |x, y, _, _| compare(x, y) ) } @@ -2995,7 +2995,7 @@ pub trait Itertools : Iterator { fn min_set_by_key(self, key: F) -> Vec where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K { - extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx < ky) + extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky)) } /// Return all maximum elements of an iterator. @@ -3024,7 +3024,7 @@ pub trait Itertools : Iterator { fn max_set(self) -> Vec where Self: Sized, Self::Item: Ord { - extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x < y) + extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x.cmp(y)) } /// Return all maximum elements of an iterator, as determined by @@ -3058,7 +3058,7 @@ pub trait Itertools : Iterator { extrema_set::max_set_impl( self, |_| (), - |x, y, _, _| Ordering::Less == compare(x, y) + |x, y, _, _| compare(x, y) ) } @@ -3089,7 +3089,7 @@ pub trait Itertools : Iterator { fn max_set_by_key(self, key: F) -> Vec where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K { - extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx < ky) + extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky)) } /// Return the minimum and maximum elements in the iterator. From fb57fc62a88ab745ab2f3206b1f5c8a4004614ab Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 118/633] Add {min,max}_set(_by{_key)?)? functions (6) inline bulk into match arm Slightly simplifies control flow as it avoids mutable variables that may not even be used in case of early return. --- src/extrema_set.rs | 43 +++++++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index 2c7cef681..88db0ed94 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -8,31 +8,30 @@ pub fn min_set_impl(mut it: I, F: FnMut(&I::Item) -> K, Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, { - let (mut result, mut current_key) = match it.next() { - None => return Vec::new(), + match it.next() { + None => Vec::new(), Some(element) => { - let key = key_for(&element); - (vec![element], key) + let mut current_key = key_for(&element); + let mut result = vec![element]; + it.for_each(|element| { + let key = key_for(&element); + match compare(&element, &result[0], &key, ¤t_key) { + Ordering::Less => { + result.clear(); + result.push(element); + current_key = key; + }, + Ordering::Equal => { + result.push(element); + }, + Ordering::Greater => { + }, + } + }); + result } - }; + } - it.for_each(|element| { - let key = key_for(&element); - match compare(&element, &result[0], &key, ¤t_key) { - Ordering::Less => { - result.clear(); - result.push(element); - current_key = key; - }, - Ordering::Equal => { - result.push(element); - }, - Ordering::Greater => { - }, - } - }); - - result } /// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`. From 08a10da17a2c7f2cb4b38bac02077a26a97ecc67 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 119/633] Add {min,max}_set(_by{_key)?)? functions (7) cargo fmt extrema_set.rs --- src/extrema_set.rs | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index 88db0ed94..ae128364c 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -1,12 +1,15 @@ use std::cmp::Ordering; /// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`. -pub fn min_set_impl(mut it: I, - mut key_for: F, - mut compare: Compare) -> Vec - where I: Iterator, - F: FnMut(&I::Item) -> K, - Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, +pub fn min_set_impl( + mut it: I, + mut key_for: F, + mut compare: Compare, +) -> Vec +where + I: Iterator, + F: FnMut(&I::Item) -> K, + Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, { match it.next() { None => Vec::new(), @@ -20,29 +23,26 @@ pub fn min_set_impl(mut it: I, result.clear(); result.push(element); current_key = key; - }, + } Ordering::Equal => { result.push(element); - }, - Ordering::Greater => { - }, + } + Ordering::Greater => {} } }); result } } - } /// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`. -pub fn max_set_impl(it: I, - key_for: F, - mut compare: Compare) -> Vec - where I: Iterator, - F: FnMut(&I::Item) -> K, - Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, +pub fn max_set_impl(it: I, key_for: F, mut compare: Compare) -> Vec +where + I: Iterator, + F: FnMut(&I::Item) -> K, + Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering, { - min_set_impl(it, key_for, |it1, it2, key1, key2| compare(it2, it1, key2, key1)) + min_set_impl(it, key_for, |it1, it2, key1, key2| { + compare(it2, it1, key2, key1) + }) } - - From 846219fe89d07f62224f0c16f8f0e65bde98b4c4 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 4 May 2022 22:09:14 +0200 Subject: [PATCH 120/633] Add {min,max}_set(_by{_key)?)? functions (8) add quickcheck tests the {min,max}_set results must contain the result of the corresponding {min,max} variant. --- tests/quick.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 7e222a641..5829d1c1d 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1693,3 +1693,62 @@ quickcheck! { } } +quickcheck! { + fn min_set_contains_min(a: Vec<(usize, char)>) -> bool { + let result_set = a.iter().min_set(); + if let Some(result_element) = a.iter().min() { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } + + fn min_set_by_contains_min(a: Vec<(usize, char)>) -> bool { + let compare = |x: &&(usize, char), y: &&(usize, char)| x.1.cmp(&y.1); + let result_set = a.iter().min_set_by(compare); + if let Some(result_element) = a.iter().min_by(compare) { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } + + fn min_set_by_key_contains_min(a: Vec<(usize, char)>) -> bool { + let key = |x: &&(usize, char)| x.1; + let result_set = a.iter().min_set_by_key(&key); + if let Some(result_element) = a.iter().min_by_key(&key) { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } + + fn max_set_contains_max(a: Vec<(usize, char)>) -> bool { + let result_set = a.iter().max_set(); + if let Some(result_element) = a.iter().max() { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } + + fn max_set_by_contains_max(a: Vec<(usize, char)>) -> bool { + let compare = |x: &&(usize, char), y: &&(usize, char)| x.1.cmp(&y.1); + let result_set = a.iter().max_set_by(compare); + if let Some(result_element) = a.iter().max_by(compare) { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } + + fn max_set_by_key_contains_max(a: Vec<(usize, char)>) -> bool { + let key = |x: &&(usize, char)| x.1; + let result_set = a.iter().max_set_by_key(&key); + if let Some(result_element) = a.iter().max_by_key(&key) { + result_set.contains(&result_element) + } else { + result_set.is_empty() + } + } +} From 1f73b567eb436368c32e365daf2d102a1ffaa540 Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 23 May 2022 21:24:38 +0200 Subject: [PATCH 121/633] Pull #[inline] into clone_fields As the warning tells, it has no effect otherwise. It's unclear whether it's really meaningful, but for now keep in within the macro. --- src/flatten_ok.rs | 1 - src/impl_macros.rs | 1 + src/rciter_impl.rs | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 91168406b..cee77c9a6 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -138,7 +138,6 @@ where T: IntoIterator, T::IntoIter: Clone, { - #[inline] clone_fields!(iter, inner_front, inner_back); } diff --git a/src/impl_macros.rs b/src/impl_macros.rs index 5772baeb6..a029843b0 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -15,6 +15,7 @@ macro_rules! debug_fmt_fields { macro_rules! clone_fields { ($($field:ident),*) => { + #[inline] // TODO is this sensible? fn clone(&self) -> Self { Self { $($field: self.$field.clone(),)* diff --git a/src/rciter_impl.rs b/src/rciter_impl.rs index 782908e28..7298350a8 100644 --- a/src/rciter_impl.rs +++ b/src/rciter_impl.rs @@ -51,7 +51,6 @@ pub fn rciter(iterable: I) -> RcIter } impl Clone for RcIter { - #[inline] clone_fields!(rciter); } From ca28006326d0fc9bfcccd807f2bb53738ed68d0f Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 23 May 2022 21:29:32 +0200 Subject: [PATCH 122/633] Do not put #[must_use] on type alias As warned by the compiler, it has no effect. --- src/adaptors/coalesce.rs | 4 +--- src/adaptors/mod.rs | 1 - src/duplicates_impl.rs | 1 - src/grouping_map.rs | 1 - src/kmerge_impl.rs | 1 - 5 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index b1aff6e27..3df7cc582 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -3,6 +3,7 @@ use std::iter::FusedIterator; use crate::size_hint; +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct CoalesceBy where I: Iterator, @@ -86,7 +87,6 @@ impl, T> FusedIterator for Coalesc /// An iterator adaptor that may join together adjacent elements. /// /// See [`.coalesce()`](crate::Itertools::coalesce) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type Coalesce = CoalesceBy::Item>; impl CoalescePredicate for F @@ -113,7 +113,6 @@ where /// An iterator adaptor that removes repeated duplicates, determining equality using a comparison function. /// /// See [`.dedup_by()`](crate::Itertools::dedup_by) or [`.dedup()`](crate::Itertools::dedup) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DedupBy = CoalesceBy, ::Item>; #[derive(Clone)] @@ -186,7 +185,6 @@ where /// /// See [`.dedup_by_with_count()`](crate::Itertools::dedup_by_with_count) or /// [`.dedup_with_count()`](crate::Itertools::dedup_with_count) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DedupByWithCount = CoalesceBy, (usize, ::Item)>; diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index d324e4a68..19008ccf2 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -490,7 +490,6 @@ impl MergePredicate for MergeLte { /// Iterator element type is `I::Item`. /// /// See [`.merge()`](crate::Itertools::merge_by) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type Merge = MergeBy; /// Create an iterator that merges elements in `i` and `j`. diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 640d4818c..28eda44a9 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -188,7 +188,6 @@ mod private { /// An iterator adapter to filter for duplicate elements. /// /// See [`.duplicates_by()`](crate::Itertools::duplicates_by) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type DuplicatesBy = private::DuplicatesBy>; /// Create a new `DuplicatesBy` iterator. diff --git a/src/grouping_map.rs b/src/grouping_map.rs index be22ec849..82ba1192b 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -39,7 +39,6 @@ pub fn new(iter: I) -> GroupingMap /// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations. /// /// See [`GroupingMap`] for more informations. -#[must_use = "GroupingMapBy is lazy and do nothing unless consumed"] pub type GroupingMapBy = GroupingMap>; /// `GroupingMap` is an intermediate struct for efficient group-and-fold operations. diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index f2280653f..5642b36c9 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -104,7 +104,6 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) /// Iterator element type is `I::Item`. /// /// See [`.kmerge()`](crate::Itertools::kmerge) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub type KMerge = KMergeBy; pub trait KMergePredicate { From 11ca7914824850459164b195774d2b56521104ba Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Mon, 23 May 2022 15:43:38 -0600 Subject: [PATCH 123/633] added take_until adaptor and doctests --- src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++---------- src/take_until.rs | 54 +++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 src/take_until.rs diff --git a/src/lib.rs b/src/lib.rs index 6e86ab789..364409ae0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,7 @@ pub mod structs { pub use crate::repeatn::RepeatN; #[allow(deprecated)] pub use crate::sources::{RepeatCall, Unfold, Iterate}; + pub use crate::take_until::TakeUntil; #[cfg(feature = "use_alloc")] pub use crate::tee::Tee; pub use crate::tuple_impl::{TupleBuffer, TupleWindows, CircularTupleWindows, Tuples}; @@ -233,6 +234,7 @@ mod rciter_impl; mod repeatn; mod size_hint; mod sources; +mod take_until; #[cfg(feature = "use_alloc")] mod tee; mod tuple_impl; @@ -904,7 +906,7 @@ pub trait Itertools : Iterator { /// Return an iterator adaptor that flattens every `Result::Ok` value into /// a series of `Result::Ok` values. `Result::Err` values are unchanged. - /// + /// /// This is useful when you have some common error type for your crate and /// need to propagate it upwards, but the `Result::Ok` case needs to be flattened. /// @@ -914,7 +916,7 @@ pub trait Itertools : Iterator { /// let input = vec![Ok(0..2), Err(false), Ok(2..4)]; /// let it = input.iter().cloned().flatten_ok(); /// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]); - /// + /// /// // This can also be used to propagate errors when collecting. /// let output_result: Result, bool> = it.collect(); /// assert_eq!(output_result, Err(false)); @@ -1389,6 +1391,53 @@ pub trait Itertools : Iterator { adaptors::take_while_ref(self, accept) } + /// An iterator adaptor that consumes elements while the given predicate is + /// true, including the element for which the predicate first returned + /// false. + /// + /// The [`.take_while()`][std::iter::Iterator::take_while] adaptor is useful + /// when you want items satisfying a predicate, but to know when to stop + /// taking elements, we have to consume that last element that doesn't + /// satisfy the predicate. This adaptor simply includest that element where + /// [`.take_while()`][std::iter::Iterator::take_while] would drop it. + /// + /// The [`.take_while_ref()`][crate::Itertools::take_while_ref] adaptor + /// serves a similar purpose, but this adaptor doesn't require cloning the + /// underlying elements. + /// + /// # Examples + /// + /// ```rust + /// use itertools::Itertools; + /// + /// let items = vec![1, 2, 3, 4, 5]; + /// let filtered: Vec = items.into_iter().take_until(|&n| n % 3 != 0).collect(); + /// assert_eq!(filtered, vec![1, 2, 3]); + /// ``` + /// + /// ```rust + /// use itertools::Itertools; + /// #[derive(Debug, PartialEq)] + /// struct NoCloneImpl(i32); + /// + /// let non_clonable_items: Vec<_> = vec![1, 2, 3, 4, 5] + /// .into_iter() + /// .map(NoCloneImpl) + /// .collect(); + /// let filtered: Vec<_> = non_clonable_items + /// .into_iter() + /// .take_until(|n| n.0 % 3 != 0) + /// .collect(); + /// let expected: Vec<_> = vec![1, 2, 3].into_iter().map(NoCloneImpl).collect(); + /// assert_eq!(filtered, expected); + fn take_until(&mut self, accept: F) -> TakeUntil + where + Self: Sized, + F: FnMut(&Self::Item) -> bool, + { + take_until::TakeUntil::new(self, accept) + } + /// Return an iterator adaptor that filters `Option` iterator elements /// and produces `A`. Stops on the first `None` encountered. /// @@ -1812,14 +1861,14 @@ pub trait Itertools : Iterator { /// /// #[derive(PartialEq, Debug)] /// enum Enum { A, B, C, D, E, } - /// + /// /// let mut iter = vec![Enum::A, Enum::B, Enum::C, Enum::D].into_iter(); - /// + /// /// // search `iter` for `B` /// assert_eq!(iter.contains(&Enum::B), true); /// // `B` was found, so the iterator now rests at the item after `B` (i.e, `C`). /// assert_eq!(iter.next(), Some(Enum::C)); - /// + /// /// // search `iter` for `E` /// assert_eq!(iter.contains(&Enum::E), false); /// // `E` wasn't found, so `iter` is now exhausted @@ -2870,13 +2919,13 @@ pub trait Itertools : Iterator { group_map::into_group_map_by(self, f) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The input iterator must yield item in the form of `(K, V)` where the /// value of type `K` will be used as key to identify the groups and the /// value of type `V` as value for the folding operation. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -2887,12 +2936,12 @@ pub trait Itertools : Iterator { grouping_map::new(self) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The values from this iterator will be used as values for the folding operation /// while the keys will be obtained from the values by calling `key_mapper`. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -3603,7 +3652,7 @@ pub trait Itertools : Iterator { /// first_name: &'static str, /// last_name: &'static str, /// } - /// + /// /// let characters = /// vec![ /// Character { first_name: "Amy", last_name: "Pond" }, @@ -3614,12 +3663,12 @@ pub trait Itertools : Iterator { /// Character { first_name: "James", last_name: "Norington" }, /// Character { first_name: "James", last_name: "Kirk" }, /// ]; - /// - /// let first_name_frequency = + /// + /// let first_name_frequency = /// characters /// .into_iter() /// .counts_by(|c| c.first_name); - /// + /// /// assert_eq!(first_name_frequency["Amy"], 3); /// assert_eq!(first_name_frequency["James"], 4); /// assert_eq!(first_name_frequency.contains_key("Asha"), false); @@ -3640,7 +3689,7 @@ pub trait Itertools : Iterator { /// column. /// /// This function is, in some sense, the opposite of [`multizip`]. - /// + /// /// ``` /// use itertools::Itertools; /// diff --git a/src/take_until.rs b/src/take_until.rs new file mode 100644 index 000000000..a33ae44ce --- /dev/null +++ b/src/take_until.rs @@ -0,0 +1,54 @@ +use std::fmt; + +/// An iterator adaptor that consumes elements while the given predicate is true, including the +/// element for which the predicate first returned false. +/// +/// See [`.take_until()`](crate::Itertools::take_until) for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct TakeUntil<'a, I: 'a, F> { + iter: &'a mut I, + f: F, + done: bool, +} + +impl<'a, I, F> TakeUntil<'a, I, F> +where + I: Iterator, + F: FnMut(&I::Item) -> bool, +{ + /// Create a new [`TakeUntil`] from an iterator and a predicate. + pub fn new(iter: &'a mut I, f: F) -> Self { + Self { iter, f, done: false} + } +} + +impl<'a, I, F> fmt::Debug for TakeUntil<'a, I, F> + where I: Iterator + fmt::Debug, +{ + debug_fmt_fields!(TakeUntil, iter); +} + +impl<'a, I, F> Iterator for TakeUntil<'a, I, F> +where + I: Iterator, + F: FnMut(&I::Item) -> bool +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + if self.done { + None + } else { + self.iter.next().map(|item| { + if !(self.f)(&item) { + self.done = true; + } + item + }) + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, self.iter.size_hint().1) + } +} \ No newline at end of file From 60d5c7cce3c620adf0b9416e825d9a553037f1cc Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Mon, 23 May 2022 15:47:16 -0600 Subject: [PATCH 124/633] added test showing difference between take_until and take_while --- src/lib.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 364409ae0..602bdc857 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1411,12 +1411,31 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// let items = vec![1, 2, 3, 4, 5]; - /// let filtered: Vec = items.into_iter().take_until(|&n| n % 3 != 0).collect(); + /// let filtered: Vec<_> = items.into_iter().take_until(|&n| n % 3 != 0).collect(); + /// /// assert_eq!(filtered, vec![1, 2, 3]); /// ``` /// /// ```rust /// use itertools::Itertools; + /// let items = vec![1, 2, 3, 4, 5]; + /// + /// let take_until_result: Vec<_> = items + /// .clone() + /// .into_iter() + /// .take_until(|&n| n % 3 != 0) + /// .collect(); + /// let take_while_result: Vec<_> = items + /// .into_iter() + /// .take_while(|&n| n % 3 != 0) + /// .collect(); + /// + /// assert_eq!(take_until_result, vec![1, 2, 3]); + /// assert_eq!(take_while_result, vec![1, 2]); + /// ``` + /// + /// ```rust + /// use itertools::Itertools; /// #[derive(Debug, PartialEq)] /// struct NoCloneImpl(i32); /// From 37cd39134ad394612bf5cb9ddf2a3cce36b3a8d9 Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Mon, 23 May 2022 15:51:03 -0600 Subject: [PATCH 125/633] changed docs formatting to fit in with existing code --- src/lib.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 602bdc857..ca8bba7d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1391,24 +1391,22 @@ pub trait Itertools : Iterator { adaptors::take_while_ref(self, accept) } - /// An iterator adaptor that consumes elements while the given predicate is - /// true, including the element for which the predicate first returned - /// false. + /// Returns an iterator adaptor that consumes elements while the given + /// predicate is `true`, *including* the element for which the predicate + /// first returned `false`. /// /// The [`.take_while()`][std::iter::Iterator::take_while] adaptor is useful /// when you want items satisfying a predicate, but to know when to stop /// taking elements, we have to consume that last element that doesn't - /// satisfy the predicate. This adaptor simply includest that element where + /// satisfy the predicate. This adaptor simply includes that element where /// [`.take_while()`][std::iter::Iterator::take_while] would drop it. /// /// The [`.take_while_ref()`][crate::Itertools::take_while_ref] adaptor - /// serves a similar purpose, but this adaptor doesn't require cloning the - /// underlying elements. - /// - /// # Examples + /// serves a similar purpose, but this adaptor doesn't require [`Clone`]ing + /// the underlying elements. /// /// ```rust - /// use itertools::Itertools; + /// # use itertools::Itertools; /// /// let items = vec![1, 2, 3, 4, 5]; /// let filtered: Vec<_> = items.into_iter().take_until(|&n| n % 3 != 0).collect(); @@ -1417,7 +1415,7 @@ pub trait Itertools : Iterator { /// ``` /// /// ```rust - /// use itertools::Itertools; + /// # use itertools::Itertools; /// let items = vec![1, 2, 3, 4, 5]; /// /// let take_until_result: Vec<_> = items @@ -1435,7 +1433,7 @@ pub trait Itertools : Iterator { /// ``` /// /// ```rust - /// use itertools::Itertools; + /// # use itertools::Itertools; /// #[derive(Debug, PartialEq)] /// struct NoCloneImpl(i32); /// From d67e86f16d11827779d49f321884bb363f603352 Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Wed, 25 May 2022 12:02:04 -0600 Subject: [PATCH 126/633] added fusediterator impl and improved size hint, inspired by hdevalke/take-until --- src/take_until.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/take_until.rs b/src/take_until.rs index a33ae44ce..ae166d260 100644 --- a/src/take_until.rs +++ b/src/take_until.rs @@ -1,3 +1,4 @@ +use core::iter::FusedIterator; use std::fmt; /// An iterator adaptor that consumes elements while the given predicate is true, including the @@ -49,6 +50,17 @@ where } fn size_hint(&self) -> (usize, Option) { - (0, self.iter.size_hint().1) + if self.done { + (0, Some(0)) + } else { + (0, self.iter.size_hint().1) + } } +} + +impl FusedIterator for TakeUntil<'_, I, F> +where + I: Iterator, + F: FnMut(&I::Item) -> bool +{ } \ No newline at end of file From 681ed7a038a9e8e8abcd4978199ba834038641d2 Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Wed, 25 May 2022 12:10:28 -0600 Subject: [PATCH 127/633] reversed semantics of predicate to read more naturally (takes elements until the predicate returns true) --- src/lib.rs | 17 +++++++++++------ src/take_until.rs | 6 +++--- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca8bba7d9..d433374bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1392,15 +1392,20 @@ pub trait Itertools : Iterator { } /// Returns an iterator adaptor that consumes elements while the given - /// predicate is `true`, *including* the element for which the predicate - /// first returned `false`. + /// predicate is `false`, *including* the element for which the predicate + /// first returned `true`. /// /// The [`.take_while()`][std::iter::Iterator::take_while] adaptor is useful /// when you want items satisfying a predicate, but to know when to stop /// taking elements, we have to consume that last element that doesn't - /// satisfy the predicate. This adaptor simply includes that element where + /// satisfy the predicate. This adaptor includes that element where /// [`.take_while()`][std::iter::Iterator::take_while] would drop it. /// + /// Note that the semantics of this predicate are reversed from + /// [`.take_while()`][std::iter::Iterator::take_while], i.e. this function's + /// predicate yields elements when it evaluates to `false` instead of when + /// it evaluates to `true`. + /// /// The [`.take_while_ref()`][crate::Itertools::take_while_ref] adaptor /// serves a similar purpose, but this adaptor doesn't require [`Clone`]ing /// the underlying elements. @@ -1409,7 +1414,7 @@ pub trait Itertools : Iterator { /// # use itertools::Itertools; /// /// let items = vec![1, 2, 3, 4, 5]; - /// let filtered: Vec<_> = items.into_iter().take_until(|&n| n % 3 != 0).collect(); + /// let filtered: Vec<_> = items.into_iter().take_until(|&n| n % 3 == 0).collect(); /// /// assert_eq!(filtered, vec![1, 2, 3]); /// ``` @@ -1421,7 +1426,7 @@ pub trait Itertools : Iterator { /// let take_until_result: Vec<_> = items /// .clone() /// .into_iter() - /// .take_until(|&n| n % 3 != 0) + /// .take_until(|&n| n % 3 == 0) /// .collect(); /// let take_while_result: Vec<_> = items /// .into_iter() @@ -1443,7 +1448,7 @@ pub trait Itertools : Iterator { /// .collect(); /// let filtered: Vec<_> = non_clonable_items /// .into_iter() - /// .take_until(|n| n.0 % 3 != 0) + /// .take_until(|n| n.0 % 3 == 0) /// .collect(); /// let expected: Vec<_> = vec![1, 2, 3].into_iter().map(NoCloneImpl).collect(); /// assert_eq!(filtered, expected); diff --git a/src/take_until.rs b/src/take_until.rs index ae166d260..40a590361 100644 --- a/src/take_until.rs +++ b/src/take_until.rs @@ -1,8 +1,8 @@ use core::iter::FusedIterator; use std::fmt; -/// An iterator adaptor that consumes elements while the given predicate is true, including the -/// element for which the predicate first returned false. +/// An iterator adaptor that consumes elements while the given predicate is false, including the +/// element for which the predicate first returned true. /// /// See [`.take_until()`](crate::Itertools::take_until) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -41,7 +41,7 @@ where None } else { self.iter.next().map(|item| { - if !(self.f)(&item) { + if (self.f)(&item) { self.done = true; } item From 0b49d8b4b2437f83037d458c933ac6f2ec619287 Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Wed, 25 May 2022 12:25:28 -0600 Subject: [PATCH 128/633] changed name to take_while_inclusive and reversed semantics back to match take_while --- src/lib.rs | 26 +++++++++---------- ...{take_until.rs => take_while_inclusive.rs} | 22 ++++++++-------- 2 files changed, 23 insertions(+), 25 deletions(-) rename src/{take_until.rs => take_while_inclusive.rs} (61%) diff --git a/src/lib.rs b/src/lib.rs index d433374bf..5e36df0ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,7 +146,7 @@ pub mod structs { pub use crate::repeatn::RepeatN; #[allow(deprecated)] pub use crate::sources::{RepeatCall, Unfold, Iterate}; - pub use crate::take_until::TakeUntil; + pub use crate::take_while_inclusive::TakeWhileInclusive; #[cfg(feature = "use_alloc")] pub use crate::tee::Tee; pub use crate::tuple_impl::{TupleBuffer, TupleWindows, CircularTupleWindows, Tuples}; @@ -234,7 +234,7 @@ mod rciter_impl; mod repeatn; mod size_hint; mod sources; -mod take_until; +mod take_while_inclusive; #[cfg(feature = "use_alloc")] mod tee; mod tuple_impl; @@ -1392,8 +1392,8 @@ pub trait Itertools : Iterator { } /// Returns an iterator adaptor that consumes elements while the given - /// predicate is `false`, *including* the element for which the predicate - /// first returned `true`. + /// predicate is `true`, *including* the element for which the predicate + /// first returned `false`. /// /// The [`.take_while()`][std::iter::Iterator::take_while] adaptor is useful /// when you want items satisfying a predicate, but to know when to stop @@ -1401,11 +1401,6 @@ pub trait Itertools : Iterator { /// satisfy the predicate. This adaptor includes that element where /// [`.take_while()`][std::iter::Iterator::take_while] would drop it. /// - /// Note that the semantics of this predicate are reversed from - /// [`.take_while()`][std::iter::Iterator::take_while], i.e. this function's - /// predicate yields elements when it evaluates to `false` instead of when - /// it evaluates to `true`. - /// /// The [`.take_while_ref()`][crate::Itertools::take_while_ref] adaptor /// serves a similar purpose, but this adaptor doesn't require [`Clone`]ing /// the underlying elements. @@ -1414,7 +1409,10 @@ pub trait Itertools : Iterator { /// # use itertools::Itertools; /// /// let items = vec![1, 2, 3, 4, 5]; - /// let filtered: Vec<_> = items.into_iter().take_until(|&n| n % 3 == 0).collect(); + /// let filtered: Vec<_> = items + /// .into_iter() + /// .take_while_inclusive(|&n| n % 3 != 0) + /// .collect(); /// /// assert_eq!(filtered, vec![1, 2, 3]); /// ``` @@ -1426,7 +1424,7 @@ pub trait Itertools : Iterator { /// let take_until_result: Vec<_> = items /// .clone() /// .into_iter() - /// .take_until(|&n| n % 3 == 0) + /// .take_while_inclusive(|&n| n % 3 != 0) /// .collect(); /// let take_while_result: Vec<_> = items /// .into_iter() @@ -1448,16 +1446,16 @@ pub trait Itertools : Iterator { /// .collect(); /// let filtered: Vec<_> = non_clonable_items /// .into_iter() - /// .take_until(|n| n.0 % 3 == 0) + /// .take_while_inclusive(|n| n.0 % 3 != 0) /// .collect(); /// let expected: Vec<_> = vec![1, 2, 3].into_iter().map(NoCloneImpl).collect(); /// assert_eq!(filtered, expected); - fn take_until(&mut self, accept: F) -> TakeUntil + fn take_while_inclusive(&mut self, accept: F) -> TakeWhileInclusive where Self: Sized, F: FnMut(&Self::Item) -> bool, { - take_until::TakeUntil::new(self, accept) + take_while_inclusive::TakeWhileInclusive::new(self, accept) } /// Return an iterator adaptor that filters `Option` iterator elements diff --git a/src/take_until.rs b/src/take_while_inclusive.rs similarity index 61% rename from src/take_until.rs rename to src/take_while_inclusive.rs index 40a590361..ed577ef56 100644 --- a/src/take_until.rs +++ b/src/take_while_inclusive.rs @@ -1,35 +1,35 @@ use core::iter::FusedIterator; use std::fmt; -/// An iterator adaptor that consumes elements while the given predicate is false, including the -/// element for which the predicate first returned true. +/// An iterator adaptor that consumes elements while the given predicate is `true`, including the +/// element for which the predicate first returned `false`. /// -/// See [`.take_until()`](crate::Itertools::take_until) for more information. +/// See [`.take_while_inclusive()`](crate::Itertools::take_while_inclusive) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct TakeUntil<'a, I: 'a, F> { +pub struct TakeWhileInclusive<'a, I: 'a, F> { iter: &'a mut I, f: F, done: bool, } -impl<'a, I, F> TakeUntil<'a, I, F> +impl<'a, I, F> TakeWhileInclusive<'a, I, F> where I: Iterator, F: FnMut(&I::Item) -> bool, { - /// Create a new [`TakeUntil`] from an iterator and a predicate. + /// Create a new [`TakeWhileInclusive`] from an iterator and a predicate. pub fn new(iter: &'a mut I, f: F) -> Self { Self { iter, f, done: false} } } -impl<'a, I, F> fmt::Debug for TakeUntil<'a, I, F> +impl<'a, I, F> fmt::Debug for TakeWhileInclusive<'a, I, F> where I: Iterator + fmt::Debug, { - debug_fmt_fields!(TakeUntil, iter); + debug_fmt_fields!(TakeWhileInclusive, iter); } -impl<'a, I, F> Iterator for TakeUntil<'a, I, F> +impl<'a, I, F> Iterator for TakeWhileInclusive<'a, I, F> where I: Iterator, F: FnMut(&I::Item) -> bool @@ -41,7 +41,7 @@ where None } else { self.iter.next().map(|item| { - if (self.f)(&item) { + if !(self.f)(&item) { self.done = true; } item @@ -58,7 +58,7 @@ where } } -impl FusedIterator for TakeUntil<'_, I, F> +impl FusedIterator for TakeWhileInclusive<'_, I, F> where I: Iterator, F: FnMut(&I::Item) -> bool From 91fe99ef8c0555702a4617407a94b919937ce4eb Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Wed, 25 May 2022 12:42:10 -0600 Subject: [PATCH 129/633] renamed some items and improved docs for clarity --- src/lib.rs | 14 +++++++------- src/take_while_inclusive.rs | 16 +++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5e36df0ba..057920aed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1397,7 +1397,7 @@ pub trait Itertools : Iterator { /// /// The [`.take_while()`][std::iter::Iterator::take_while] adaptor is useful /// when you want items satisfying a predicate, but to know when to stop - /// taking elements, we have to consume that last element that doesn't + /// taking elements, we have to consume that first element that doesn't /// satisfy the predicate. This adaptor includes that element where /// [`.take_while()`][std::iter::Iterator::take_while] would drop it. /// @@ -1407,7 +1407,6 @@ pub trait Itertools : Iterator { /// /// ```rust /// # use itertools::Itertools; - /// /// let items = vec![1, 2, 3, 4, 5]; /// let filtered: Vec<_> = items /// .into_iter() @@ -1421,9 +1420,9 @@ pub trait Itertools : Iterator { /// # use itertools::Itertools; /// let items = vec![1, 2, 3, 4, 5]; /// - /// let take_until_result: Vec<_> = items - /// .clone() - /// .into_iter() + /// let take_while_inclusive_result: Vec<_> = items + /// .iter() + /// .copied() /// .take_while_inclusive(|&n| n % 3 != 0) /// .collect(); /// let take_while_result: Vec<_> = items @@ -1431,8 +1430,10 @@ pub trait Itertools : Iterator { /// .take_while(|&n| n % 3 != 0) /// .collect(); /// - /// assert_eq!(take_until_result, vec![1, 2, 3]); + /// assert_eq!(take_while_inclusive_result, vec![1, 2, 3]); /// assert_eq!(take_while_result, vec![1, 2]); + /// // both iterators have the same items remaining at this point---the 3 + /// // is lost from the `take_while` vec /// ``` /// /// ```rust @@ -2763,7 +2764,6 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(oldest_people_first, /// vec!["Jill", "Jack", "Jane", "John"]); /// ``` - /// ``` #[cfg(feature = "use_alloc")] fn sorted_by_cached_key(self, f: F) -> VecIntoIter where diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index ed577ef56..e2a7479e0 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -1,14 +1,16 @@ use core::iter::FusedIterator; use std::fmt; -/// An iterator adaptor that consumes elements while the given predicate is `true`, including the -/// element for which the predicate first returned `false`. +/// An iterator adaptor that consumes elements while the given predicate is +/// `true`, including the element for which the predicate first returned +/// `false`. /// -/// See [`.take_while_inclusive()`](crate::Itertools::take_while_inclusive) for more information. +/// See [`.take_while_inclusive()`](crate::Itertools::take_while_inclusive) +/// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct TakeWhileInclusive<'a, I: 'a, F> { iter: &'a mut I, - f: F, + predicate: F, done: bool, } @@ -18,8 +20,8 @@ where F: FnMut(&I::Item) -> bool, { /// Create a new [`TakeWhileInclusive`] from an iterator and a predicate. - pub fn new(iter: &'a mut I, f: F) -> Self { - Self { iter, f, done: false} + pub fn new(iter: &'a mut I, predicate: F) -> Self { + Self { iter, predicate, done: false} } } @@ -41,7 +43,7 @@ where None } else { self.iter.next().map(|item| { - if !(self.f)(&item) { + if !(self.predicate)(&item) { self.done = true; } item From 12f76cc02d0c58a6d252f5f5a70ce75c5dc8f5f9 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:00:48 +0200 Subject: [PATCH 130/633] fix clippy::len_zero --- src/combinations_with_replacement.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 81b13f130..595faafa4 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -64,7 +64,7 @@ where // If this is the first iteration, return early if self.first { // In empty edge cases, stop iterating immediately - return if self.indices.len() != 0 && !self.pool.get_next() { + return if !(self.indices.is_empty() || self.pool.get_next()) { None // Otherwise, yield the initial state } else { From d5281fa0f2a9919de98d77c517dc70cda63d9ec1 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:20:21 +0200 Subject: [PATCH 131/633] fix clippy::redundant_clone in tests --- tests/quick.rs | 41 ++++++++++++++++++----------------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 1099e8e50..e5b686823 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -438,7 +438,7 @@ quickcheck! { } assert_eq!(answer, actual); - assert_eq!(answer.into_iter().last(), a.clone().multi_cartesian_product().last()); + assert_eq!(answer.into_iter().last(), a.multi_cartesian_product().last()); } #[allow(deprecated)] @@ -498,15 +498,13 @@ quickcheck! { exact_size(it) } - fn equal_merge(a: Vec, b: Vec) -> bool { - let mut sa = a.clone(); - let mut sb = b.clone(); - sa.sort(); - sb.sort(); - let mut merged = sa.clone(); - merged.extend(sb.iter().cloned()); + fn equal_merge(mut a: Vec, mut b: Vec) -> bool { + a.sort(); + b.sort(); + let mut merged = a.clone(); + merged.extend(b.iter().cloned()); merged.sort(); - itertools::equal(&merged, sa.iter().merge(&sb)) + itertools::equal(&merged, a.iter().merge(&b)) } fn size_merge(a: Iter, b: Iter) -> bool { correct_size_hint(a.merge(b)) @@ -517,7 +515,7 @@ quickcheck! { exact_size(multizip((a, b, c))) } fn size_zip_rc(a: Iter, b: Iter) -> bool { - let rc = rciter(a.clone()); + let rc = rciter(a); correct_size_hint(multizip((&rc, &rc, b))) } @@ -526,19 +524,16 @@ quickcheck! { correct_size_hint(izip!(filt, b.clone(), c.clone())) && exact_size(izip!(a, b, c)) } - fn equal_kmerge(a: Vec, b: Vec, c: Vec) -> bool { + fn equal_kmerge(mut a: Vec, mut b: Vec, mut c: Vec) -> bool { use itertools::free::kmerge; - let mut sa = a.clone(); - let mut sb = b.clone(); - let mut sc = c.clone(); - sa.sort(); - sb.sort(); - sc.sort(); - let mut merged = sa.clone(); - merged.extend(sb.iter().cloned()); - merged.extend(sc.iter().cloned()); + a.sort(); + b.sort(); + c.sort(); + let mut merged = a.clone(); + merged.extend(b.iter().cloned()); + merged.extend(c.iter().cloned()); merged.sort(); - itertools::equal(merged.into_iter(), kmerge(vec![sa, sb, sc])) + itertools::equal(merged.into_iter(), kmerge(vec![a, b, c])) } // Any number of input iterators @@ -610,7 +605,7 @@ quickcheck! { fn size_2_zip_longest(a: Iter, b: Iter) -> bool { let it = a.clone().zip_longest(b.clone()); let jt = a.clone().zip_longest(b.clone()); - itertools::equal(a.clone(), + itertools::equal(a, it.filter_map(|elt| match elt { EitherOrBoth::Both(x, _) => Some(x), EitherOrBoth::Left(x) => Some(x), @@ -618,7 +613,7 @@ quickcheck! { } )) && - itertools::equal(b.clone(), + itertools::equal(b, jt.filter_map(|elt| match elt { EitherOrBoth::Both(_, y) => Some(y), EitherOrBoth::Right(y) => Some(y), From 11309962393cc4b56a246e0abc0a16d4cc5f8098 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:38:23 +0200 Subject: [PATCH 132/633] fix clippy::manual_map in tests --- tests/test_core.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/tests/test_core.rs b/tests/test_core.rs index cff0ed6be..df94eb665 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -180,15 +180,10 @@ fn batching() { let ys = [(0, 1), (2, 1)]; // An iterator that gathers elements up in pairs - let pit = xs.iter().cloned().batching(|it| { - match it.next() { - None => None, - Some(x) => match it.next() { - None => None, - Some(y) => Some((x, y)), - } - } - }); + let pit = xs + .iter() + .cloned() + .batching(|it| it.next().and_then(|x| it.next().map(|y| (x, y)))); it::assert_equal(pit, ys.iter().cloned()); } From 2efabf919598a5b301991d3a26294c684f01aa35 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:39:53 +0200 Subject: [PATCH 133/633] fix clippy::single_component_path_imports in tests --- tests/test_std.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index c9dc03fb2..f59034234 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1,5 +1,3 @@ -use paste; -use permutohedron; use quickcheck as qc; use rand::{distributions::{Distribution, Standard}, Rng, SeedableRng, rngs::StdRng}; use rand::{seq::SliceRandom, thread_rng}; From 6ac06a86e8b5da2e93dc21293f42ffcc088329e3 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:33:27 +0200 Subject: [PATCH 134/633] fix clippy::manual_assert in tests --- tests/adaptors_no_collect.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/adaptors_no_collect.rs b/tests/adaptors_no_collect.rs index a47f906f9..103db23f1 100644 --- a/tests/adaptors_no_collect.rs +++ b/tests/adaptors_no_collect.rs @@ -11,12 +11,11 @@ impl Iterator for PanickingCounter { fn next(&mut self) -> Option { self.curr += 1; - if self.curr == self.max { - panic!( - "Input iterator reached maximum of {} suggesting collection by adaptor", - self.max - ); - } + assert_ne!( + self.curr, self.max, + "Input iterator reached maximum of {} suggesting collection by adaptor", + self.max + ); Some(()) } From ba098ad98f1c3a78367afcb13a00a22ebb3070d0 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 08:57:35 +0200 Subject: [PATCH 135/633] fix clippy::explicit_counter_loop --- src/lib.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ba4efaa28..ddb11d13c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1748,12 +1748,10 @@ pub trait Itertools : Iterator { fn find_position

(&mut self, mut pred: P) -> Option<(usize, Self::Item)> where P: FnMut(&Self::Item) -> bool { - let mut index = 0usize; - for elt in self { + for (index, elt) in self.enumerate() { if pred(&elt) { return Some((index, elt)); } - index += 1; } None } From 501783ed6dba20685a1e7a73db48fcc4b193859f Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:07:08 +0200 Subject: [PATCH 136/633] allow usage of fold1 --- src/concat_impl.rs | 1 + src/kmerge_impl.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/concat_impl.rs b/src/concat_impl.rs index 7d079e482..f022ec90a 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -18,5 +18,6 @@ pub fn concat(iterable: I) -> I::Item where I: IntoIterator, I::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default { + #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` iterable.into_iter().fold1(|mut a, b| { a.extend(b); a }).unwrap_or_default() } diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 5642b36c9..509d5fc6a 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -213,6 +213,7 @@ impl Iterator for KMergeBy } fn size_hint(&self) -> (usize, Option) { + #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` self.heap.iter() .map(|i| i.size_hint()) .fold1(size_hint::add) From a16c01c90781076e9028782ea42975218be37b14 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:04:18 +0200 Subject: [PATCH 137/633] fix clippy::manual_map --- src/adaptors/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 19008ccf2..3c052ad04 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -327,12 +327,7 @@ impl Iterator for Product } Some(x) => x }; - match self.a_cur { - None => None, - Some(ref a) => { - Some((a.clone(), elt_b)) - } - } + self.a_cur.as_ref().map(|a| (a.clone(), elt_b)) } fn size_hint(&self) -> (usize, Option) { From 2c74555c98d3bc0a317ab337069c803345aa9d36 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Tue, 7 Jun 2022 10:30:34 +0200 Subject: [PATCH 138/633] explain reason behind hashmap in unique_impl --- src/unique_impl.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 2240f36ed..4e81e78ec 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -1,6 +1,5 @@ - use std::collections::HashMap; -use std::collections::hash_map::{Entry}; +use std::collections::hash_map::Entry; use std::hash::Hash; use std::fmt; use std::iter::FusedIterator; @@ -12,7 +11,9 @@ use std::iter::FusedIterator; #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct UniqueBy { iter: I, - // Use a hashmap for the entry API + // Use a Hashmap for the Entry API in order to prevent hashing twice. + // This can maybe be replaced with a HashSet once `get_or_insert_with` + // or a proper Entry API for Hashset is stable and meets this msrv used: HashMap, f: F, } From 71055de5947ccae3c54459723c48b533ee00abb4 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 08:56:42 +0200 Subject: [PATCH 139/633] fix clippy::needless_borrow --- src/grouping_map.rs | 6 +++--- src/lib.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 82ba1192b..bb5b582c9 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -289,7 +289,7 @@ impl GroupingMap where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.max_by(|key, v1, v2| f(key, &v1).cmp(&f(key, &v2))) + self.max_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group. @@ -367,7 +367,7 @@ impl GroupingMap where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.min_by(|key, v1, v2| f(key, &v1).cmp(&f(key, &v2))) + self.min_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of @@ -480,7 +480,7 @@ impl GroupingMap where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.minmax_by(|key, v1, v2| f(key, &v1).cmp(&f(key, &v2))) + self.minmax_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } /// Groups elements from the `GroupingMap` source by key and sums them. diff --git a/src/lib.rs b/src/lib.rs index 6e86ab789..40d619665 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1797,7 +1797,7 @@ pub trait Itertools : Iterator { Some(if predicate(&first) { first } else { - self.find(|x| predicate(&x)).unwrap_or(first) + self.find(|x| predicate(x)).unwrap_or(first) }) } /// Returns `true` if the given item is present in this iterator. From 0a7f5d4a586fdf96a33a804dba174a1b6a501a73 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 08:59:14 +0200 Subject: [PATCH 140/633] fix clippy::let_and_return --- src/intersperse.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/intersperse.rs b/src/intersperse.rs index 2c660d492..9de313ee2 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -107,8 +107,7 @@ impl Iterator for IntersperseWith self.iter.fold(accum, |accum, x| { let accum = f(accum, element.generate()); - let accum = f(accum, x); - accum + f(accum, x) }) } } From 28e0b959e72b18635a2bc353044e0465cae18365 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 08:59:58 +0200 Subject: [PATCH 141/633] fix clippy::extra_unused_lifetimes --- src/groupbylazy.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 91c52ea59..37371f619 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -7,7 +7,7 @@ trait KeyFunction { fn call_mut(&mut self, arg: A) -> Self::Key; } -impl<'a, A, K, F: ?Sized> KeyFunction for F +impl KeyFunction for F where F: FnMut(A) -> K { type Key = K; @@ -37,7 +37,7 @@ impl ChunkIndex { } } -impl<'a, A> KeyFunction for ChunkIndex { +impl KeyFunction for ChunkIndex { type Key = usize; #[inline(always)] fn call_mut(&mut self, _arg: A) -> Self::Key { From 3010ae5357dfe33a03fecb9ad9a12def53586c04 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:01:24 +0200 Subject: [PATCH 142/633] fix clippy::unwrap_or_else_default --- src/concat_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/concat_impl.rs b/src/concat_impl.rs index 450f7fce1..7d079e482 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -18,5 +18,5 @@ pub fn concat(iterable: I) -> I::Item where I: IntoIterator, I::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default { - iterable.into_iter().fold1(|mut a, b| { a.extend(b); a }).unwrap_or_else(<_>::default) + iterable.into_iter().fold1(|mut a, b| { a.extend(b); a }).unwrap_or_default() } From e9a245a55ab88b9ed6dab224794c831234a11c0e Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:04:59 +0200 Subject: [PATCH 143/633] fix clippy::collapsible_else_if --- src/multipeek_impl.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 0ac9380b6..8b49c695e 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -71,10 +71,8 @@ impl PeekingNext for MultiPeek if let Some(r) = self.peek() { if !accept(r) { return None } } - } else { - if let Some(r) = self.buf.get(0) { - if !accept(r) { return None } - } + } else if let Some(r) = self.buf.get(0) { + if !accept(r) { return None } } self.next() } From c4c434bbfc9ceb10d25a3f597d559d555bab6a4a Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:12:42 +0200 Subject: [PATCH 144/633] fix clippy::clone_double_ref in tests --- tests/test_std.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 4463d912f..026c1572b 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -123,7 +123,7 @@ fn unique() { #[test] fn intersperse() { let xs = ["a", "", "b", "c"]; - let v: Vec<&str> = xs.iter().map(|x| x.clone()).intersperse(", ").collect(); + let v: Vec<&str> = xs.iter().cloned().intersperse(", ").collect(); let text: String = v.concat(); assert_eq!(text, "a, , b, c".to_string()); From cfcc195d260b415ce7033274454b7f23c5fe1a7c Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:13:05 +0200 Subject: [PATCH 145/633] fix clippy::approx_constant in tests --- tests/test_std.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 026c1572b..495f184d1 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1091,9 +1091,9 @@ fn format() { let t2 = format!("{:?}", data.iter().format("--")); assert_eq!(t2, ans2); - let dataf = [1.1, 2.71828, -22.]; + let dataf = [1.1, 5.71828, -22.]; let t3 = format!("{:.2e}", dataf.iter().format(", ")); - assert_eq!(t3, "1.10e0, 2.72e0, -2.20e1"); + assert_eq!(t3, "1.10e0, 5.72e0, -2.20e1"); } #[test] From 3a4a29a88b931ff80c76eae7a9cabe3930e4ffc5 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:14:28 +0200 Subject: [PATCH 146/633] fix clippy::while_let_on_iterator in tests --- tests/quick.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 5829d1c1d..076278f2c 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -721,7 +721,7 @@ quickcheck! { assert_eq!(expected_first, curr_perm); - while let Some(next_perm) = perms.next() { + for next_perm in perms { assert!( next_perm > curr_perm, "next perm isn't greater-than current; next_perm={:?} curr_perm={:?} n={}", @@ -1602,7 +1602,7 @@ quickcheck! { fn is_fused(mut it: I) -> bool { - while let Some(_) = it.next() {} + for _ in it.by_ref() {} for _ in 0..10{ if it.next().is_some(){ return false; From 95f238ba38bf2f802252b5e7fc2817d4f579dcb1 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:15:09 +0200 Subject: [PATCH 147/633] fix clippy::comparison_to_empty in tests --- tests/test_std.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 495f184d1..be476b65f 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1143,7 +1143,7 @@ fn tree_fold1() { "0 1 x 2 3 x x 4 5 x 6 7 x x x 8 9 x 10 11 x x 12 13 x 14 15 x x x x", ]; for (i, &s) in x.iter().enumerate() { - let expected = if s == "" { None } else { Some(s.to_string()) }; + let expected = if s.is_empty() { None } else { Some(s.to_string()) }; let num_strings = (0..i).map(|x| x.to_string()); let actual = num_strings.tree_fold1(|a, b| format!("{} {} x", a, b)); assert_eq!(actual, expected); From 758821e5a67eeabe42af45d3151fb1c456567b49 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:16:44 +0200 Subject: [PATCH 148/633] fix clippy::unnecessary_fold in tests --- tests/quick.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/quick.rs b/tests/quick.rs index 076278f2c..1810ff9d9 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1286,7 +1286,7 @@ quickcheck! { .map(|i| (i % modulo, i)) .into_group_map() .into_iter() - .map(|(key, vals)| (key, vals.into_iter().fold(0u64, |acc, val| acc + val))) + .map(|(key, vals)| (key, vals.into_iter().sum())) .collect::>(); assert_eq!(lookup, group_map_lookup); From 9819fda23037b83f81ba7a7659903fb2f41e9dda Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:17:25 +0200 Subject: [PATCH 149/633] fix clippy::clone_on_copy in tests --- tests/specializations.rs | 4 ++-- tests/test_std.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 199cf562a..057e11c9f 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -129,7 +129,7 @@ quickcheck! { check_results_specialized!(it, |i| { let mut parameters_from_fold = vec![]; let fold_result = i.fold(vec![], |mut acc, v| { - parameters_from_fold.push((acc.clone(), v.clone())); + parameters_from_fold.push((acc.clone(), v)); acc.push(v); acc }); @@ -139,7 +139,7 @@ quickcheck! { let mut parameters_from_all = vec![]; let first = i.next(); let all_result = i.all(|x| { - parameters_from_all.push(x.clone()); + parameters_from_all.push(x); Some(x)==first }); (parameters_from_all, all_result) diff --git a/tests/test_std.rs b/tests/test_std.rs index be476b65f..ca8d4f4af 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1110,7 +1110,7 @@ fn fold_while() { let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; let sum = vec.into_iter().fold_while(0, |acc, item| { iterations += 1; - let new_sum = acc.clone() + item; + let new_sum = acc + item; if new_sum <= 20 { FoldWhile::Continue(new_sum) } else { From 738382836e7fa13df88c8bf8328dad53806934a0 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:17:56 +0200 Subject: [PATCH 150/633] fix clippy::let_and_return in tests --- tests/quick.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 1810ff9d9..ffd219a32 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -943,8 +943,7 @@ quickcheck! { fn fuzz_group_by_lazy_1(it: Iter) -> bool { let jt = it.clone(); let groups = it.group_by(|k| *k); - let res = itertools::equal(jt, groups.into_iter().flat_map(|(_, x)| x)); - res + itertools::equal(jt, groups.into_iter().flat_map(|(_, x)| x)) } } From a989b3a5536531e164933b8fc3581c73f428a267 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:18:25 +0200 Subject: [PATCH 151/633] fix clippy::iter_nth_zero in tests --- tests/test_std.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index ca8d4f4af..79c54cce5 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -694,7 +694,7 @@ fn group_by() { } } - let toupper = |ch: &char| ch.to_uppercase().nth(0).unwrap(); + let toupper = |ch: &char| ch.to_uppercase().next().unwrap(); // try all possible orderings for indices in permutohedron::Heap::new(&mut [0, 1, 2, 3]) { From fe06b67094510e7cac7aac902130b3625593141c Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:22:24 +0200 Subject: [PATCH 152/633] fix clippy::map_clone in tests --- tests/test_core.rs | 2 +- tests/test_std.rs | 14 +++++++------- tests/zip.rs | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_core.rs b/tests/test_core.rs index a7b7449d3..cff0ed6be 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -116,7 +116,7 @@ fn chain2() { fn write_to() { let xs = [7, 9, 8]; let mut ys = [0; 5]; - let cnt = ys.iter_mut().set_from(xs.iter().map(|x| *x)); + let cnt = ys.iter_mut().set_from(xs.iter().copied()); assert!(cnt == xs.len()); assert!(ys == [7, 9, 8, 0, 0]); diff --git a/tests/test_std.rs b/tests/test_std.rs index 79c54cce5..b1d0f4e8e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -128,7 +128,7 @@ fn intersperse() { assert_eq!(text, "a, , b, c".to_string()); let ys = [0, 1, 2, 3]; - let mut it = ys[..0].iter().map(|x| *x).intersperse(1); + let mut it = ys[..0].iter().copied().intersperse(1); assert!(it.next() == None); } @@ -538,10 +538,10 @@ fn sorted_by_cached_key() { fn test_multipeek() { let nums = vec![1u8,2,3,4,5]; - let mp = multipeek(nums.iter().map(|&x| x)); + let mp = multipeek(nums.iter().copied()); assert_eq!(nums, mp.collect::>()); - let mut mp = multipeek(nums.iter().map(|&x| x)); + let mut mp = multipeek(nums.iter().copied()); assert_eq!(mp.peek(), Some(&1)); assert_eq!(mp.next(), Some(1)); assert_eq!(mp.peek(), Some(&2)); @@ -579,7 +579,7 @@ fn test_multipeek_peeking_next() { use crate::it::PeekingNext; let nums = vec![1u8,2,3,4,5,6,7]; - let mut mp = multipeek(nums.iter().map(|&x| x)); + let mut mp = multipeek(nums.iter().copied()); assert_eq!(mp.peeking_next(|&x| x != 0), Some(1)); assert_eq!(mp.next(), Some(2)); assert_eq!(mp.peek(), Some(&3)); @@ -604,10 +604,10 @@ fn test_multipeek_peeking_next() { fn test_peek_nth() { let nums = vec![1u8,2,3,4,5]; - let iter = peek_nth(nums.iter().map(|&x| x)); + let iter = peek_nth(nums.iter().copied()); assert_eq!(nums, iter.collect::>()); - let mut iter = peek_nth(nums.iter().map(|&x| x)); + let mut iter = peek_nth(nums.iter().copied()); assert_eq!(iter.peek_nth(0), Some(&1)); assert_eq!(iter.peek_nth(0), Some(&1)); @@ -638,7 +638,7 @@ fn test_peek_nth() { fn test_peek_nth_peeking_next() { use it::PeekingNext; let nums = vec![1u8,2,3,4,5,6,7]; - let mut iter = peek_nth(nums.iter().map(|&x| x)); + let mut iter = peek_nth(nums.iter().copied()); assert_eq!(iter.peeking_next(|&x| x != 0), Some(1)); assert_eq!(iter.next(), Some(2)); diff --git a/tests/zip.rs b/tests/zip.rs index 5801b4232..75157d34f 100644 --- a/tests/zip.rs +++ b/tests/zip.rs @@ -29,8 +29,8 @@ fn test_zip_longest_size_hint() { fn test_double_ended_zip_longest() { let xs = [1, 2, 3, 4, 5, 6]; let ys = [1, 2, 3, 7]; - let a = xs.iter().map(|&x| x); - let b = ys.iter().map(|&x| x); + let a = xs.iter().copied(); + let b = ys.iter().copied(); let mut it = a.zip_longest(b); assert_eq!(it.next(), Some(Both(1, 1))); assert_eq!(it.next(), Some(Both(2, 2))); @@ -45,8 +45,8 @@ fn test_double_ended_zip_longest() { fn test_double_ended_zip() { let xs = [1, 2, 3, 4, 5, 6]; let ys = [1, 2, 3, 7]; - let a = xs.iter().map(|&x| x); - let b = ys.iter().map(|&x| x); + let a = xs.iter().copied(); + let b = ys.iter().copied(); let mut it = multizip((a, b)); assert_eq!(it.next_back(), Some((4, 7))); assert_eq!(it.next_back(), Some((3, 3))); From a016b870db8179573ed2da101e3f5854f7afb048 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:26:30 +0200 Subject: [PATCH 153/633] fix clippy::redundant_pattern_matching in tests --- tests/quick.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index ffd219a32..c1b3819ce 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -258,12 +258,12 @@ where let mut it = get_it(); for _ in 0..(counts.len() - 1) { - if let None = it.next() { + if it.next().is_none() { panic!("Iterator shouldn't be finished, may not be deterministic"); } } - if let None = it.next() { + if it.next().is_none() { break 'outer; } From 2e6cf7fe5224397aafd95e8e3c5b24225511c7e7 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:38:57 +0200 Subject: [PATCH 154/633] fix duplicate_macro_attributes in tests the #[test] macro is automatically applied to all functions inside of the `quickcheck!` macro, so this one was redundant --- tests/quick.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/quick.rs b/tests/quick.rs index c1b3819ce..1099e8e50 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1550,7 +1550,6 @@ quickcheck! { } quickcheck! { - #[test] fn counts(nums: Vec) -> TestResult { let counts = nums.iter().counts(); for (&item, &count) in counts.iter() { From 5b3b648c926c7bfa733f178b90432b26a1dce2ad Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:40:18 +0200 Subject: [PATCH 155/633] fix clippy::unused_unit in tests --- tests/test_std.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index b1d0f4e8e..c9dc03fb2 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -474,7 +474,7 @@ impl qc::Arbitrary for Ran // Check that taking the k smallest is the same as // sorting then taking the k first elements -fn k_smallest_sort(i: I, k: u16) -> () +fn k_smallest_sort(i: I, k: u16) where I: Iterator + Clone, I::Item: Ord + Debug, From aac4268547990d1f376742555eb6331e0565ee2d Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 2 Jun 2022 13:51:49 +0200 Subject: [PATCH 156/633] fix rustdoc warnings --- src/lib.rs | 1 - src/ziptuple.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 40d619665..4b9d73e6f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2694,7 +2694,6 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(oldest_people_first, /// vec!["Jill", "Jack", "Jane", "John"]); /// ``` - /// ``` #[cfg(feature = "use_alloc")] fn sorted_by_cached_key(self, f: F) -> VecIntoIter where diff --git a/src/ziptuple.rs b/src/ziptuple.rs index b7902ae53..6d3a584c4 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -36,6 +36,7 @@ pub struct Zip { /// /// assert_eq!(results, [0 + 3, 10 + 7, 29, 36]); /// ``` +/// [`izip!()`]: crate::izip pub fn multizip(t: U) -> Zip where Zip: From, Zip: Iterator, From 7287e5798352a84f6a05e5a5668e1de632ab8dd4 Mon Sep 17 00:00:00 2001 From: wandalen Date: Mon, 13 Jun 2022 17:53:47 +0300 Subject: [PATCH 157/633] better description of function tee to avoid misunderstandings --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 4b9d73e6f..ba4efaa28 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -780,7 +780,7 @@ pub trait Itertools : Iterator { /// the original iterator. /// /// **Note:** If the iterator is clonable, prefer using that instead - /// of using this method. It is likely to be more efficient. + /// of using this method. Cloning is likely to be more efficient. /// /// Iterator element type is `Self::Item`. /// From 79c82e9ed8420b2c4adb8277e685ac98c2510886 Mon Sep 17 00:00:00 2001 From: Rain Date: Fri, 1 Jul 2022 18:21:11 -0700 Subject: [PATCH 158/633] if use_std is enabled, also enable either/use_std --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 22a08a8d9..15ddfa8f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,7 @@ version = "0.2" [features] default = ["use_std"] -use_std = ["use_alloc"] +use_std = ["use_alloc", "either/use_std"] use_alloc = [] [profile] From dcea15ba8e75f479fcd07f321fb0ee0fc27a9d60 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 18:14:50 -0400 Subject: [PATCH 159/633] improve grammar in either_or_both --- src/either_or_both.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index ef3985f75..7d50b4b0b 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -14,7 +14,7 @@ pub enum EitherOrBoth { } impl EitherOrBoth { - /// If `Left`, or `Both`, return true, otherwise, return false. + /// If `Left`, or `Both`, return true. Otherwise, return false. pub fn has_left(&self) -> bool { self.as_ref().left().is_some() } @@ -24,7 +24,7 @@ impl EitherOrBoth { self.as_ref().right().is_some() } - /// If Left, return true otherwise, return false. + /// If `Left`, return true. Otherwise, return false. /// Exclusive version of [`has_left`](EitherOrBoth::has_left). pub fn is_left(&self) -> bool { match *self { @@ -33,7 +33,7 @@ impl EitherOrBoth { } } - /// If Right, return true otherwise, return false. + /// If `Right`, return true. Otherwise, return false. /// Exclusive version of [`has_right`](EitherOrBoth::has_right). pub fn is_right(&self) -> bool { match *self { @@ -42,13 +42,13 @@ impl EitherOrBoth { } } - /// If Right, return true otherwise, return false. + /// If `Both`, return true. Otherwise, return false. /// Equivalent to `self.as_ref().both().is_some()`. pub fn is_both(&self) -> bool { self.as_ref().both().is_some() } - /// If `Left`, or `Both`, return `Some` with the left value, otherwise, return `None`. + /// If `Left`, or `Both`, return `Some` with the left value. Otherwise, return `None`. pub fn left(self) -> Option { match self { Left(left) | Both(left, _) => Some(left), @@ -56,7 +56,7 @@ impl EitherOrBoth { } } - /// If `Right`, or `Both`, return `Some` with the right value, otherwise, return `None`. + /// If `Right`, or `Both`, return `Some` with the right value. Otherwise, return `None`. pub fn right(self) -> Option { match self { Right(right) | Both(_, right) => Some(right), @@ -64,7 +64,7 @@ impl EitherOrBoth { } } - /// If Both, return `Some` tuple containing left and right. + /// If `Both`, return `Some` containing the left and right values. Otherwise, return `None`. pub fn both(self) -> Option<(A, B)> { match self { Both(a, b) => Some((a, b)), From f020a09bc742100f82110259bc03e3e9095f6b9b Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 18:26:39 -0400 Subject: [PATCH 160/633] add `just_{left,right}` methods to `EitherOrBoth` --- src/either_or_both.rs | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 7d50b4b0b..681ee2f0b 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -64,6 +64,56 @@ impl EitherOrBoth { } } + /// If `Left`, return `Some` with the left value. If `Right` or `Both`, return `None`. + /// + /// # Examples + /// + /// ``` + /// // On the `Left` variant. + /// # use itertools::{EitherOrBoth, EitherOrBoth::{Left, Right, Both}}; + /// let x: EitherOrBoth<_, ()> = Left("bonjour"); + /// assert_eq!(x.just_left(), Some("bonjour")); + /// + /// // On the `Right` variant. + /// let x: EitherOrBoth<(), _> = Right("hola"); + /// assert_eq!(x.just_left(), None); + /// + /// // On the `Both` variant. + /// let x = Both("bonjour", "hola"); + /// assert_eq!(x.just_left(), None); + /// ``` + pub fn just_left(self) -> Option { + match self { + Left(left) => Some(left), + _ => None, + } + } + + /// If `Right`, return `Some` with the right value. If `Left` or `Both`, return `None`. + /// + /// # Examples + /// + /// ``` + /// // On the `Left` variant. + /// # use itertools::{EitherOrBoth::{Left, Right, Both}, EitherOrBoth}; + /// let x: EitherOrBoth<_, ()> = Left("auf wiedersehen"); + /// assert_eq!(x.just_left(), Some("auf wiedersehen")); + /// + /// // On the `Right` variant. + /// let x: EitherOrBoth<(), _> = Right("adios"); + /// assert_eq!(x.just_left(), None); + /// + /// // On the `Both` variant. + /// let x = Both("auf wiedersehen", "adios"); + /// assert_eq!(x.just_left(), None); + /// ``` + pub fn just_right(self) -> Option { + match self { + Right(right) => Some(right), + _ => None, + } + } + /// If `Both`, return `Some` containing the left and right values. Otherwise, return `None`. pub fn both(self) -> Option<(A, B)> { match self { From 6dc42f59beb4dcebf5c26555d89180537c2b4e1c Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 18:32:52 -0400 Subject: [PATCH 161/633] add `into_{left,right}` methods to EitherOrBoth --- src/either_or_both.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 681ee2f0b..041993024 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -122,6 +122,28 @@ impl EitherOrBoth { } } + /// If `Left` or `Both`, return the left value. Otherwise, convert the right value and return it. + pub fn into_left(self) -> A + where + B: Into, + { + match self { + Left(a) | Both(a, _) => a, + Right(b) => b.into(), + } + } + + /// If `Right` or `Both`, return the right value. Otherwise, convert the left value and return it. + pub fn into_right(self) -> B + where + A: Into, + { + match self { + Right(b) | Both(_, b) => b, + Left(a) => a.into(), + } + } + /// Converts from `&EitherOrBoth` to `EitherOrBoth<&A, &B>`. pub fn as_ref(&self) -> EitherOrBoth<&A, &B> { match *self { From 65d0a93fe7412310c6b112c253cc1b25b6365f4b Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 19:09:20 -0400 Subject: [PATCH 162/633] add `insert_{left,right,both}` methods to `EitherOrBoth` --- src/either_or_both.rs | 97 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 041993024..35cf678fc 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -299,6 +299,103 @@ impl EitherOrBoth { Both(inner_l, inner_r) => (inner_l, inner_r), } } + + /// Sets the `left` value of this instance, and returns a mutable reference to it. + /// Does not affect the `right` value. + /// + /// # Examples + /// ``` + /// # use itertools::{EitherOrBoth, EitherOrBoth::{Left, Right, Both}}; + /// + /// // Overwriting a pre-existing value. + /// let mut either: EitherOrBoth<_, ()> = Left(0_u32); + /// assert_eq!(*either.insert_left(69), 69); + /// + /// // Inserting a second value. + /// let mut either = Right("no"); + /// assert_eq!(*either.insert_left("yes"), "yes"); + /// assert_eq!(either, Both("yes", "no")); + /// ``` + pub fn insert_left(&mut self, val: A) -> &mut A { + match self { + Left(left) | Both(left, _) => { + *left = val; + left + } + Right(right) => { + // This is like a map in place operation. We move out of the reference, + // change the value, and then move back into the reference. + unsafe { + // SAFETY: We know this pointer is valid for reading since we got it from a reference. + let right = std::ptr::read(right as *mut _); + // SAFETY: Again, we know the pointer is valid since we got it from a reference. + std::ptr::write(self as *mut _, Both(val, right)); + } + + if let Both(left, _) = self { + left + } else { + // SAFETY: The above pattern will always match, since we just + // set `self` equal to `Both`. + unsafe { std::hint::unreachable_unchecked() } + } + } + } + } + + /// Sets the `right` value of this instance, and returns a mutable reference to it. + /// Does not affect the `left` value. + /// + /// # Examples + /// ``` + /// # use itertools::{EitherOrBoth, EitherOrBoth::{Left, Both}}; + /// // Overwriting a pre-existing value. + /// let mut either: EitherOrBoth<_, ()> = Left(0_u32); + /// assert_eq!(*either.insert_left(69), 69); + /// + /// // Inserting a second value. + /// let mut either = Left("what's"); + /// assert_eq!(*either.insert_right(9 + 10), 21 - 2); + /// assert_eq!(either, Both("what's", 9+10)); + /// ``` + pub fn insert_right(&mut self, val: B) -> &mut B { + match self { + Right(right) | Both(_, right) => { + *right = val; + right + } + Left(left) => { + // This is like a map in place operation. We move out of the reference, + // change the value, and then move back into the reference. + unsafe { + // SAFETY: We know this pointer is valid for reading since we got it from a reference. + let left = std::ptr::read(left as *mut _); + // SAFETY: Again, we know the pointer is valid since we got it from a reference. + std::ptr::write(self as *mut _, Both(left, val)); + } + if let Both(_, right) = self { + right + } else { + // SAFETY: The above pattern will always match, since we just + // set `self` equal to `Both`. + unsafe { std::hint::unreachable_unchecked() } + } + } + } + } + + /// Set `self` to `Both(..)`, containing the specified left and right values, + /// and returns a mutable reference to those values. + pub fn insert_both(&mut self, left: A, right: B) -> (&mut A, &mut B) { + *self = Both(left, right); + if let Both(left, right) = self { + (left, right) + } else { + // SAFETY: The above pattern will always match, since we just + // set `self` equal to `Both`. + unsafe { std::hint::unreachable_unchecked() } + } + } } impl EitherOrBoth { From 259dcb0ed5554f6a2e49eb03e4071cc155d0a427 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 19:22:28 -0400 Subject: [PATCH 163/633] add `{left,right}or_insert{_with}` methods to `EitherOrBoth` --- src/either_or_both.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 35cf678fc..61873fe04 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -300,6 +300,42 @@ impl EitherOrBoth { } } + /// Returns a mutable reference to the left value. If the left value is not present, + /// it is replaced with `val`. + pub fn left_or_insert(&mut self, val: A) -> &mut A { + self.left_or_insert_with(|| val) + } + + /// Returns a mutable reference to the right value. If the right value is not present, + /// it is replaced with `val`. + pub fn right_or_insert(&mut self, val: B) -> &mut B { + self.right_or_insert_with(|| val) + } + + /// If the left value is not present, replace it the value computed by the closure `f`. + /// Returns a mutable reference to the now-present left value. + pub fn left_or_insert_with(&mut self, f: F) -> &mut A + where + F: FnOnce() -> A, + { + match self { + Left(left) | Both(left, _) => left, + Right(_) => self.insert_left(f()), + } + } + + /// If the right value is not present, replace it the value computed by the closure `f`. + /// Returns a mutable reference to the now-present right value. + pub fn right_or_insert_with(&mut self, f: F) -> &mut B + where + F: FnOnce() -> B, + { + match self { + Right(right) | Both(_, right) => right, + Left(_) => self.insert_right(f()), + } + } + /// Sets the `left` value of this instance, and returns a mutable reference to it. /// Does not affect the `right` value. /// From a9fc41295068c6361e9873afac18825e3b27ba69 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 19:22:57 -0400 Subject: [PATCH 164/633] remove some extraneous information from `EitherOrBoth::is_both` --- src/either_or_both.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 61873fe04..152dcde17 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -43,7 +43,6 @@ impl EitherOrBoth { } /// If `Both`, return true. Otherwise, return false. - /// Equivalent to `self.as_ref().both().is_some()`. pub fn is_both(&self) -> bool { self.as_ref().both().is_some() } From 4b15974b64ae042a9c70a87f4ac2b4d019002037 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 19:31:23 -0400 Subject: [PATCH 165/633] add `as_deref{_mut}` methods to `EitherOrBoth` --- src/either_or_both.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 152dcde17..06b1044aa 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -1,3 +1,5 @@ +use core::ops::{Deref, DerefMut}; + use crate::EitherOrBoth::*; use either::Either; @@ -161,6 +163,32 @@ impl EitherOrBoth { } } + /// Converts from `&EitherOrBoth` to `EitherOrBoth<&_, &_>` using the [`Deref`] trait. + pub fn as_deref(&self) -> EitherOrBoth<&A::Target, &B::Target> + where + A: Deref, + B: Deref, + { + match *self { + Left(ref left) => Left(left), + Right(ref right) => Right(right), + Both(ref left, ref right) => Both(left, right), + } + } + + /// Converts from `&mut EitherOrBoth` to `EitherOrBoth<&mut _, &mut _>` using the [`DerefMut`] trait. + pub fn as_deref_mut(&mut self) -> EitherOrBoth<&mut A::Target, &mut B::Target> + where + A: DerefMut, + B: DerefMut, + { + match *self { + Left(ref mut left) => Left(left), + Right(ref mut right) => Right(right), + Both(ref mut left, ref mut right) => Both(left, right), + } + } + /// Convert `EitherOrBoth` to `EitherOrBoth`. pub fn flip(self) -> EitherOrBoth { match self { From 700424b752d6690a54fba82a29eec185e049aa91 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:04:09 +0200 Subject: [PATCH 166/633] add clippy.toml --- clippy.toml | 1 + 1 file changed, 1 insertion(+) create mode 100644 clippy.toml diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 000000000..0a5485386 --- /dev/null +++ b/clippy.toml @@ -0,0 +1 @@ +msrv = "1.36.0" From c3027a9934cf9ed3d76e2029667d6da1cb11df41 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Fri, 3 Jun 2022 09:13:29 +0200 Subject: [PATCH 167/633] allow clippy::absurd_extreme_comparisons in tests --- tests/quick.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/quick.rs b/tests/quick.rs index e5b686823..5c73d20a9 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1548,6 +1548,7 @@ quickcheck! { fn counts(nums: Vec) -> TestResult { let counts = nums.iter().counts(); for (&item, &count) in counts.iter() { + #[allow(clippy::absurd_extreme_comparisons)] if count <= 0 { return TestResult::failed(); } From 9af1fcbd80d720b09b3968ae6925e97b2407d1cc Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:08:27 +0200 Subject: [PATCH 168/633] allow clippy::manual_assert in tests --- tests/quick.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/quick.rs b/tests/quick.rs index 5c73d20a9..0adcf1ad7 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -258,6 +258,7 @@ where let mut it = get_it(); for _ in 0..(counts.len() - 1) { + #[allow(clippy::manual_assert)] if it.next().is_none() { panic!("Iterator shouldn't be finished, may not be deterministic"); } From 1c50468ff7c2f3d9bb9fe30d453f3450c8165700 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:15:27 +0200 Subject: [PATCH 169/633] fix clippy::redundant_else --- src/flatten_ok.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index cee77c9a6..21ae1f722 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -44,11 +44,11 @@ where if let Some(inner) = &mut self.inner_front { if let Some(item) = inner.next() { return Some(Ok(item)); - } else { - // This is necessary for the iterator to implement `FusedIterator` - // with only the original iterator being fused. - self.inner_front = None; } + + // This is necessary for the iterator to implement `FusedIterator` + // with only the original iterator being fused. + self.inner_front = None; } match self.iter.next() { @@ -59,11 +59,11 @@ where if let Some(inner) = &mut self.inner_back { if let Some(item) = inner.next() { return Some(Ok(item)); - } else { - // This is necessary for the iterator to implement `FusedIterator` - // with only the original iterator being fused. - self.inner_back = None; } + + // This is necessary for the iterator to implement `FusedIterator` + // with only the original iterator being fused. + self.inner_back = None; } else { return None; } @@ -103,11 +103,11 @@ where if let Some(inner) = &mut self.inner_back { if let Some(item) = inner.next_back() { return Some(Ok(item)); - } else { - // This is necessary for the iterator to implement `FusedIterator` - // with only the original iterator being fused. - self.inner_back = None; } + + // This is necessary for the iterator to implement `FusedIterator` + // with only the original iterator being fused. + self.inner_back = None; } match self.iter.next_back() { @@ -118,11 +118,11 @@ where if let Some(inner) = &mut self.inner_front { if let Some(item) = inner.next_back() { return Some(Ok(item)); - } else { - // This is necessary for the iterator to implement `FusedIterator` - // with only the original iterator being fused. - self.inner_front = None; } + + // This is necessary for the iterator to implement `FusedIterator` + // with only the original iterator being fused. + self.inner_front = None; } else { return None; } From 6f3dbab0d1e39bd4949455454941ceb70f9bb33d Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:26:25 +0200 Subject: [PATCH 170/633] fix clippy::semicolon_if_nothing_returned --- src/adaptors/mod.rs | 2 +- src/combinations_with_replacement.rs | 2 +- src/groupbylazy.rs | 4 ++-- src/lib.rs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 3c052ad04..1695bbd65 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -208,7 +208,7 @@ impl PutBack /// If a value is already in the put back slot, it is overwritten. #[inline] pub fn put_back(&mut self, x: I::Item) { - self.top = Some(x) + self.top = Some(x); } } diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 595faafa4..0fec9671a 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -92,7 +92,7 @@ where // We need to update the rightmost non-max value // and all those to the right for indices_index in increment_from..self.indices.len() { - self.indices[indices_index] = increment_value + self.indices[indices_index] = increment_value; } Some(self.current()) } diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 37371f619..bceba95e1 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -330,7 +330,7 @@ impl GroupBy /// `client`: Index of group fn drop_group(&self, client: usize) { - self.inner.borrow_mut().drop_group(client) + self.inner.borrow_mut().drop_group(client); } } @@ -482,7 +482,7 @@ impl IntoChunks /// `client`: Index of chunk fn drop_group(&self, client: usize) { - self.inner.borrow_mut().drop_group(client) + self.inner.borrow_mut().drop_group(client); } } diff --git a/src/lib.rs b/src/lib.rs index ddb11d13c..2204e8419 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1953,7 +1953,7 @@ pub trait Itertools : Iterator { where F: FnMut(Self::Item), Self: Sized, { - self.for_each(f) + self.for_each(f); } /// Combine all an iterator's elements into one element by using [`Extend`]. From c88736c61c6f1c891e2d3403f8df3701f84b64b9 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:36:26 +0200 Subject: [PATCH 171/633] fix clippy::trait_duplication_in_bounds --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 2204e8419..5cca5d99f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1111,7 +1111,7 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn multi_cartesian_product(self) -> MultiProduct<::IntoIter> - where Self: Iterator + Sized, + where Self: Sized, Self::Item: IntoIterator, ::IntoIter: Clone, ::Item: Clone From ed6fbda086a913a787450a642acfd4d36dc07c3b Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:50:12 +0200 Subject: [PATCH 172/633] fix clippy::doc_markdown --- src/adaptors/multi_product.rs | 4 ++-- src/exactly_one_err.rs | 4 ++-- src/groupbylazy.rs | 4 ++-- src/intersperse.rs | 2 +- src/pad_tail.rs | 2 +- src/peeking_take_while.rs | 2 +- src/size_hint.rs | 16 ++++++++-------- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 30650eda6..0b3840698 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -40,7 +40,7 @@ pub fn multi_cartesian_product(iters: H) -> MultiProduct< where I: Iterator + Clone, I::Item: Clone @@ -50,7 +50,7 @@ struct MultiProductIter iter_orig: I, } -/// Holds the current state during an iteration of a MultiProduct. +/// Holds the current state during an iteration of a `MultiProduct`. #[derive(Debug)] enum MultiProductIterState { StartOfIter, diff --git a/src/exactly_one_err.rs b/src/exactly_one_err.rs index 63485c993..c54ae77ca 100644 --- a/src/exactly_one_err.rs +++ b/src/exactly_one_err.rs @@ -11,10 +11,10 @@ use crate::size_hint; /// Iterator returned for the error case of `IterTools::exactly_one()` /// This iterator yields exactly the same elements as the input iterator. /// -/// During the execution of exactly_one the iterator must be mutated. This wrapper +/// During the execution of `exactly_one` the iterator must be mutated. This wrapper /// effectively "restores" the state of the input iterator when it's handed back. /// -/// This is very similar to PutBackN except this iterator only supports 0-2 elements and does not +/// This is very similar to `PutBackN` except this iterator only supports 0-2 elements and does not /// use a `Vec`. #[derive(Clone)] pub struct ExactlyOneError diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index bceba95e1..a5a321df4 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -1,7 +1,7 @@ use std::cell::{Cell, RefCell}; use alloc::vec::{self, Vec}; -/// A trait to unify FnMut for GroupBy with the chunk key in IntoChunks +/// A trait to unify `FnMut` for `GroupBy` with the chunk key in `IntoChunks` trait KeyFunction { type Key; fn call_mut(&mut self, arg: A) -> Self::Key; @@ -18,7 +18,7 @@ impl KeyFunction for F } -/// ChunkIndex acts like the grouping key function for IntoChunks +/// `ChunkIndex` acts like the grouping key function for `IntoChunks` #[derive(Debug)] struct ChunkIndex { size: usize, diff --git a/src/intersperse.rs b/src/intersperse.rs index 9de313ee2..10a3a5389 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -55,7 +55,7 @@ pub struct IntersperseWith peek: Option, } -/// Create a new IntersperseWith iterator +/// Create a new `IntersperseWith` iterator pub fn intersperse_with(iter: I, elt: ElemF) -> IntersperseWith where I: Iterator, { diff --git a/src/pad_tail.rs b/src/pad_tail.rs index de57ee416..248a43243 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -23,7 +23,7 @@ where debug_fmt_fields!(PadUsing, iter, min, pos); } -/// Create a new **PadUsing** iterator. +/// Create a new `PadUsing` iterator. pub fn pad_using(iter: I, min: usize, filler: F) -> PadUsing where I: Iterator, F: FnMut(usize) -> I::Item diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index cd0945a52..b3a9c5ccb 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -90,7 +90,7 @@ where debug_fmt_fields!(PeekingTakeWhile, iter); } -/// Create a PeekingTakeWhile +/// Create a `PeekingTakeWhile` pub fn peeking_take_while(iter: &mut I, f: F) -> PeekingTakeWhile where I: Iterator, { diff --git a/src/size_hint.rs b/src/size_hint.rs index 1168ecaa3..71ea1412b 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -1,14 +1,14 @@ -//! Arithmetic on **Iterator** *.size_hint()* values. +//! Arithmetic on `Iterator.size_hint()` values. //! use std::usize; use std::cmp; use std::u32; -/// **SizeHint** is the return type of **Iterator::size_hint()**. +/// `SizeHint` is the return type of `Iterator::size_hint()`. pub type SizeHint = (usize, Option); -/// Add **SizeHint** correctly. +/// Add `SizeHint` correctly. #[inline] pub fn add(a: SizeHint, b: SizeHint) -> SizeHint { let min = a.0.saturating_add(b.0); @@ -20,7 +20,7 @@ pub fn add(a: SizeHint, b: SizeHint) -> SizeHint { (min, max) } -/// Add **x** correctly to a **SizeHint**. +/// Add `x` correctly to a `SizeHint`. #[inline] pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; @@ -29,7 +29,7 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint { (low, hi) } -/// Sbb **x** correctly to a **SizeHint**. +/// Subtract `x` correctly from a `SizeHint`. #[inline] #[allow(dead_code)] pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { @@ -40,7 +40,7 @@ pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { } -/// Multiply **SizeHint** correctly +/// Multiply `SizeHint` correctly /// /// ```ignore /// use std::usize; @@ -66,7 +66,7 @@ pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint { (low, hi) } -/// Multiply **x** correctly with a **SizeHint**. +/// Multiply `x` correctly with a `SizeHint`. #[inline] pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; @@ -75,7 +75,7 @@ pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint { (low, hi) } -/// Raise `base` correctly by a **`SizeHint`** exponent. +/// Raise `base` correctly by a `SizeHint` exponent. #[inline] pub fn pow_scalar_base(base: usize, exp: SizeHint) -> SizeHint { let exp_low = cmp::min(exp.0, u32::MAX as usize) as u32; From 412b75be00372758a9d47e8f59235b085e96c496 Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 11:10:34 +0200 Subject: [PATCH 173/633] fix some more clippy lints --- src/lazy_buffer.rs | 20 ++++++++------------ src/permutations.rs | 7 ++----- src/tuple_impl.rs | 2 +- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index fa514ec2d..ca24062aa 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -28,16 +28,12 @@ where if self.done { return false; } - let next_item = self.it.next(); - match next_item { - Some(x) => { - self.buffer.push(x); - true - } - None => { - self.done = true; - false - } + if let Some(x) = self.it.next() { + self.buffer.push(x); + true + } else { + self.done = true; + false } } @@ -61,7 +57,7 @@ where { type Output = as Index>::Output; - fn index(&self, _index: J) -> &Self::Output { - self.buffer.index(_index) + fn index(&self, index: J) -> &Self::Output { + self.buffer.index(index) } } diff --git a/src/permutations.rs b/src/permutations.rs index 3080f9d5c..d03b85262 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -113,19 +113,15 @@ where Some(indices.map(|i| vals[i].clone()).collect()) } - PermutationState::Complete(CompleteState::Start { .. }) => None, PermutationState::Complete(CompleteState::Ongoing { ref indices, ref cycles }) => { let k = cycles.len(); - Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) }, - PermutationState::Empty => None + PermutationState::Complete(CompleteState::Start { .. }) | PermutationState::Empty => None } } fn count(self) -> usize { - let Permutations { vals, state } = self; - fn from_complete(complete_state: CompleteState) -> usize { match complete_state.remaining() { CompleteStateRemaining::Known(count) => count, @@ -135,6 +131,7 @@ where } } + let Permutations { vals, state } = self; match state { PermutationState::StartUnknownLen { k } => { let n = vals.len() + vals.it.count(); diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index d914e0323..06b5c13cb 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -162,8 +162,8 @@ pub fn tuple_windows(mut iter: I) -> TupleWindows } TupleWindows { - last, iter, + last, } } From e75a656b48c10da6d58d298516f05b5518ec0b76 Mon Sep 17 00:00:00 2001 From: Joel Montes de Oca <6587811+JoelMon@users.noreply.github.com> Date: Fri, 8 Jul 2022 11:38:19 -0400 Subject: [PATCH 174/633] Added body to free::zip Added body to the code example in the docs for free::zip. The code body added uses two types to make it obvious how `zip` functions. --- src/free.rs | 106 +++++++++++++++++++++++++++++----------------------- 1 file changed, 60 insertions(+), 46 deletions(-) diff --git a/src/free.rs b/src/free.rs index 3886f5f1f..8889b5f62 100644 --- a/src/free.rs +++ b/src/free.rs @@ -10,30 +10,24 @@ use std::iter::{self, Zip}; type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] -use alloc::{ - string::String, -}; +use alloc::string::String; -use crate::Itertools; use crate::intersperse::{Intersperse, IntersperseWith}; +use crate::Itertools; -pub use crate::adaptors::{ - interleave, - merge, - put_back, -}; +pub use crate::adaptors::{interleave, merge, put_back}; #[cfg(feature = "use_alloc")] -pub use crate::put_back_n_impl::put_back_n; +pub use crate::kmerge_impl::kmerge; +pub use crate::merge_join::merge_join_by; #[cfg(feature = "use_alloc")] pub use crate::multipeek_impl::multipeek; #[cfg(feature = "use_alloc")] pub use crate::peek_nth::peek_nth; #[cfg(feature = "use_alloc")] -pub use crate::kmerge_impl::kmerge; -pub use crate::zip_eq_impl::zip_eq; -pub use crate::merge_join::merge_join_by; +pub use crate::put_back_n_impl::put_back_n; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::rciter; +pub use crate::zip_eq_impl::zip_eq; /// Iterate `iterable` with a particular value inserted between each element. /// @@ -45,8 +39,9 @@ pub use crate::rciter_impl::rciter; /// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]); /// ``` pub fn intersperse(iterable: I, element: I::Item) -> Intersperse - where I: IntoIterator, - ::Item: Clone +where + I: IntoIterator, + ::Item: Clone, { Itertools::intersperse(iterable.into_iter(), element) } @@ -64,8 +59,9 @@ pub fn intersperse(iterable: I, element: I::Item) -> Intersperse /// assert_eq!(i, 8); /// ``` pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith - where I: IntoIterator, - F: FnMut() -> I::Item +where + I: IntoIterator, + F: FnMut() -> I::Item, { Itertools::intersperse_with(iterable.into_iter(), element) } @@ -82,7 +78,8 @@ pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith(iterable: I) -> iter::Enumerate - where I: IntoIterator +where + I: IntoIterator, { iterable.into_iter().enumerate() } @@ -99,8 +96,9 @@ pub fn enumerate(iterable: I) -> iter::Enumerate /// } /// ``` pub fn rev(iterable: I) -> iter::Rev - where I: IntoIterator, - I::IntoIter: DoubleEndedIterator +where + I: IntoIterator, + I::IntoIter: DoubleEndedIterator, { iterable.into_iter().rev() } @@ -112,14 +110,20 @@ pub fn rev(iterable: I) -> iter::Rev /// ``` /// use itertools::zip; /// -/// let data = [1, 2, 3, 4, 5]; -/// for (a, b) in zip(&data, &data[1..]) { -/// /* loop body */ +/// let data_1 = [1, 2, 3, 4, 5]; +/// let data_2 = ['a', 'b', 'c']; +/// let mut result: Vec<(i32, char)> = Vec::new(); +/// +/// for (a, b) in zip(&data_1, &data_2) { +/// result.push((*a, *b)); /// } +/// assert_eq!(result, vec![(1, 'a'),(2, 'b'),(3, 'c')]); /// ``` +/// pub fn zip(i: I, j: J) -> Zip - where I: IntoIterator, - J: IntoIterator +where + I: IntoIterator, + J: IntoIterator, { i.into_iter().zip(j) } @@ -135,9 +139,13 @@ pub fn zip(i: I, j: J) -> Zip /// /* loop body */ /// } /// ``` -pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, ::IntoIter> - where I: IntoIterator, - J: IntoIterator +pub fn chain( + i: I, + j: J, +) -> iter::Chain<::IntoIter, ::IntoIter> +where + I: IntoIterator, + J: IntoIterator, { i.into_iter().chain(j) } @@ -152,8 +160,9 @@ pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, (iterable: I) -> iter::Cloned - where I: IntoIterator, - T: Clone, +where + I: IntoIterator, + T: Clone, { iterable.into_iter().cloned() } @@ -168,8 +177,9 @@ pub fn cloned<'a, I, T: 'a>(iterable: I) -> iter::Cloned /// assert_eq!(fold(&[1., 2., 3.], 0., |a, &b| f32::max(a, b)), 3.); /// ``` pub fn fold(iterable: I, init: B, f: F) -> B - where I: IntoIterator, - F: FnMut(B, I::Item) -> B +where + I: IntoIterator, + F: FnMut(B, I::Item) -> B, { iterable.into_iter().fold(init, f) } @@ -184,8 +194,9 @@ pub fn fold(iterable: I, init: B, f: F) -> B /// assert!(all(&[1, 2, 3], |elt| *elt > 0)); /// ``` pub fn all(iterable: I, f: F) -> bool - where I: IntoIterator, - F: FnMut(I::Item) -> bool +where + I: IntoIterator, + F: FnMut(I::Item) -> bool, { iterable.into_iter().all(f) } @@ -200,8 +211,9 @@ pub fn all(iterable: I, f: F) -> bool /// assert!(any(&[0, -1, 2], |elt| *elt > 0)); /// ``` pub fn any(iterable: I, f: F) -> bool - where I: IntoIterator, - F: FnMut(I::Item) -> bool +where + I: IntoIterator, + F: FnMut(I::Item) -> bool, { iterable.into_iter().any(f) } @@ -216,8 +228,9 @@ pub fn any(iterable: I, f: F) -> bool /// assert_eq!(max(0..10), Some(9)); /// ``` pub fn max(iterable: I) -> Option - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().max() } @@ -232,13 +245,13 @@ pub fn max(iterable: I) -> Option /// assert_eq!(min(0..10), Some(0)); /// ``` pub fn min(iterable: I) -> Option - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().min() } - /// Combine all iterator elements into one String, separated by `sep`. /// /// [`IntoIterator`] enabled version of [`Itertools::join`]. @@ -250,8 +263,9 @@ pub fn min(iterable: I) -> Option /// ``` #[cfg(feature = "use_alloc")] pub fn join(iterable: I, sep: &str) -> String - where I: IntoIterator, - I::Item: Display +where + I: IntoIterator, + I::Item: Display, { iterable.into_iter().join(sep) } @@ -268,9 +282,9 @@ pub fn join(iterable: I, sep: &str) -> String /// ``` #[cfg(feature = "use_alloc")] pub fn sorted(iterable: I) -> VecIntoIter - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().sorted() } - From 9150cab9988db6a78f6129de34d7cd4e030951a4 Mon Sep 17 00:00:00 2001 From: Joel Montes de Oca <6587811+JoelMon@users.noreply.github.com> Date: Sat, 9 Jul 2022 21:30:42 -0400 Subject: [PATCH 175/633] Reverted formatting changes --- src/free.rs | 96 ++++++++++++++++++++++++----------------------------- 1 file changed, 43 insertions(+), 53 deletions(-) diff --git a/src/free.rs b/src/free.rs index 8889b5f62..d93eb2bef 100644 --- a/src/free.rs +++ b/src/free.rs @@ -10,24 +10,30 @@ use std::iter::{self, Zip}; type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] -use alloc::string::String; +use alloc::{ + string::String, +}; -use crate::intersperse::{Intersperse, IntersperseWith}; use crate::Itertools; +use crate::intersperse::{Intersperse, IntersperseWith}; -pub use crate::adaptors::{interleave, merge, put_back}; +pub use crate::adaptors::{ + interleave, + merge, + put_back, +}; #[cfg(feature = "use_alloc")] -pub use crate::kmerge_impl::kmerge; -pub use crate::merge_join::merge_join_by; +pub use crate::put_back_n_impl::put_back_n; #[cfg(feature = "use_alloc")] pub use crate::multipeek_impl::multipeek; #[cfg(feature = "use_alloc")] pub use crate::peek_nth::peek_nth; #[cfg(feature = "use_alloc")] -pub use crate::put_back_n_impl::put_back_n; +pub use crate::kmerge_impl::kmerge; +pub use crate::zip_eq_impl::zip_eq; +pub use crate::merge_join::merge_join_by; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::rciter; -pub use crate::zip_eq_impl::zip_eq; /// Iterate `iterable` with a particular value inserted between each element. /// @@ -39,9 +45,8 @@ pub use crate::zip_eq_impl::zip_eq; /// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]); /// ``` pub fn intersperse(iterable: I, element: I::Item) -> Intersperse -where - I: IntoIterator, - ::Item: Clone, + where I: IntoIterator, + ::Item: Clone { Itertools::intersperse(iterable.into_iter(), element) } @@ -59,9 +64,8 @@ where /// assert_eq!(i, 8); /// ``` pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith -where - I: IntoIterator, - F: FnMut() -> I::Item, + where I: IntoIterator, + F: FnMut() -> I::Item { Itertools::intersperse_with(iterable.into_iter(), element) } @@ -78,8 +82,7 @@ where /// } /// ``` pub fn enumerate(iterable: I) -> iter::Enumerate -where - I: IntoIterator, + where I: IntoIterator { iterable.into_iter().enumerate() } @@ -96,9 +99,8 @@ where /// } /// ``` pub fn rev(iterable: I) -> iter::Rev -where - I: IntoIterator, - I::IntoIter: DoubleEndedIterator, + where I: IntoIterator, + I::IntoIter: DoubleEndedIterator { iterable.into_iter().rev() } @@ -119,11 +121,9 @@ where /// } /// assert_eq!(result, vec![(1, 'a'),(2, 'b'),(3, 'c')]); /// ``` -/// pub fn zip(i: I, j: J) -> Zip -where - I: IntoIterator, - J: IntoIterator, + where I: IntoIterator, + J: IntoIterator { i.into_iter().zip(j) } @@ -139,13 +139,9 @@ where /// /* loop body */ /// } /// ``` -pub fn chain( - i: I, - j: J, -) -> iter::Chain<::IntoIter, ::IntoIter> -where - I: IntoIterator, - J: IntoIterator, +pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, ::IntoIter> + where I: IntoIterator, + J: IntoIterator { i.into_iter().chain(j) } @@ -160,9 +156,8 @@ where /// assert_eq!(cloned(b"abc").next(), Some(b'a')); /// ``` pub fn cloned<'a, I, T: 'a>(iterable: I) -> iter::Cloned -where - I: IntoIterator, - T: Clone, + where I: IntoIterator, + T: Clone, { iterable.into_iter().cloned() } @@ -177,9 +172,8 @@ where /// assert_eq!(fold(&[1., 2., 3.], 0., |a, &b| f32::max(a, b)), 3.); /// ``` pub fn fold(iterable: I, init: B, f: F) -> B -where - I: IntoIterator, - F: FnMut(B, I::Item) -> B, + where I: IntoIterator, + F: FnMut(B, I::Item) -> B { iterable.into_iter().fold(init, f) } @@ -194,9 +188,8 @@ where /// assert!(all(&[1, 2, 3], |elt| *elt > 0)); /// ``` pub fn all(iterable: I, f: F) -> bool -where - I: IntoIterator, - F: FnMut(I::Item) -> bool, + where I: IntoIterator, + F: FnMut(I::Item) -> bool { iterable.into_iter().all(f) } @@ -211,9 +204,8 @@ where /// assert!(any(&[0, -1, 2], |elt| *elt > 0)); /// ``` pub fn any(iterable: I, f: F) -> bool -where - I: IntoIterator, - F: FnMut(I::Item) -> bool, + where I: IntoIterator, + F: FnMut(I::Item) -> bool { iterable.into_iter().any(f) } @@ -228,9 +220,8 @@ where /// assert_eq!(max(0..10), Some(9)); /// ``` pub fn max(iterable: I) -> Option -where - I: IntoIterator, - I::Item: Ord, + where I: IntoIterator, + I::Item: Ord { iterable.into_iter().max() } @@ -245,13 +236,13 @@ where /// assert_eq!(min(0..10), Some(0)); /// ``` pub fn min(iterable: I) -> Option -where - I: IntoIterator, - I::Item: Ord, + where I: IntoIterator, + I::Item: Ord { iterable.into_iter().min() } + /// Combine all iterator elements into one String, separated by `sep`. /// /// [`IntoIterator`] enabled version of [`Itertools::join`]. @@ -263,9 +254,8 @@ where /// ``` #[cfg(feature = "use_alloc")] pub fn join(iterable: I, sep: &str) -> String -where - I: IntoIterator, - I::Item: Display, + where I: IntoIterator, + I::Item: Display { iterable.into_iter().join(sep) } @@ -282,9 +272,9 @@ where /// ``` #[cfg(feature = "use_alloc")] pub fn sorted(iterable: I) -> VecIntoIter -where - I: IntoIterator, - I::Item: Ord, + where I: IntoIterator, + I::Item: Ord { iterable.into_iter().sorted() } + From 2b5e83f150fb4015f7bcee2f788fdde26ffbf178 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Fri, 12 Aug 2022 15:31:35 -0400 Subject: [PATCH 176/633] Chore: update tree_fold1 doc to remove reference to deprecated fold1 Not sure if this is needed, but I thought since it's deprecated it's best to move away from it. --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5cca5d99f..4984013e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2271,7 +2271,7 @@ pub trait Itertools : Iterator { /// ``` /// /// Which, for non-associative functions, will typically produce a different - /// result than the linear call tree used by `fold1`: + /// result than the linear call tree used by [`Iterator::reduce`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce): /// /// ```text /// 1 2 3 4 5 6 7 @@ -2279,7 +2279,7 @@ pub trait Itertools : Iterator { /// └─f─f─f─f─f─f /// ``` /// - /// If `f` is associative, prefer the normal `fold1` instead. + /// If `f` is associative, prefer the normal [`Iterator::reduce`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce) instead. /// /// ``` /// use itertools::Itertools; From 677900a0dd817638db718faa8e26b8df3b99cf07 Mon Sep 17 00:00:00 2001 From: Willem Wyndham Date: Mon, 15 Aug 2022 15:09:32 -0400 Subject: [PATCH 177/633] Apply suggestions from code review --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4984013e6..f91968870 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2271,7 +2271,7 @@ pub trait Itertools : Iterator { /// ``` /// /// Which, for non-associative functions, will typically produce a different - /// result than the linear call tree used by [`Iterator::reduce`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce): + /// result than the linear call tree used by [`Iterator::reduce`]: /// /// ```text /// 1 2 3 4 5 6 7 @@ -2279,7 +2279,7 @@ pub trait Itertools : Iterator { /// └─f─f─f─f─f─f /// ``` /// - /// If `f` is associative, prefer the normal [`Iterator::reduce`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce) instead. + /// If `f` is associative, prefer the normal [`Iterator::reduce`] instead. /// /// ``` /// use itertools::Itertools; From 520765c0b2740212b535bd81906384e500844a33 Mon Sep 17 00:00:00 2001 From: Joel Montes de Oca <6587811+JoelMon@users.noreply.github.com> Date: Thu, 25 Aug 2022 12:50:32 -0400 Subject: [PATCH 178/633] Improved chain docstrings --- src/free.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/free.rs b/src/free.rs index d93eb2bef..667a5fd20 100644 --- a/src/free.rs +++ b/src/free.rs @@ -128,16 +128,21 @@ pub fn zip(i: I, j: J) -> Zip i.into_iter().zip(j) } -/// Create an iterator that first iterates `i` and then `j`. + +/// Takes two iterables and creates a new iterator over both in sequence. /// /// [`IntoIterator`] enabled version of [`Iterator::chain`]. /// +/// ## Example /// ``` /// use itertools::chain; +/// +/// let mut result:Vec = Vec::new(); /// -/// for elt in chain(&[1, 2, 3], &[4]) { -/// /* loop body */ +/// for element in chain(&[1, 2, 3], &[4]) { +/// result.push(*element); /// } +/// assert_eq!(result, vec![1, 2, 3, 4]); /// ``` pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, ::IntoIter> where I: IntoIterator, From 42764765d2a6741ede9e87ce18635fb77a8985fd Mon Sep 17 00:00:00 2001 From: Joel Montes de Oca <6587811+JoelMon@users.noreply.github.com> Date: Mon, 11 Jul 2022 13:46:38 -0400 Subject: [PATCH 179/633] Docstring improvement: Added explanation of function Added example and link to std::iter::zip --- src/free.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/free.rs b/src/free.rs index d93eb2bef..4c657f25a 100644 --- a/src/free.rs +++ b/src/free.rs @@ -105,22 +105,23 @@ pub fn rev(iterable: I) -> iter::Rev iterable.into_iter().rev() } -/// Iterate `i` and `j` in lock step. +/// Converts the arguments to iterators and zips them. /// /// [`IntoIterator`] enabled version of [`Iterator::zip`]. +/// +/// ## Example /// /// ``` /// use itertools::zip; /// -/// let data_1 = [1, 2, 3, 4, 5]; -/// let data_2 = ['a', 'b', 'c']; /// let mut result: Vec<(i32, char)> = Vec::new(); /// -/// for (a, b) in zip(&data_1, &data_2) { +/// for (a, b) in zip(&[1, 2, 3, 4, 5], &['a', 'b', 'c']) { /// result.push((*a, *b)); /// } /// assert_eq!(result, vec![(1, 'a'),(2, 'b'),(3, 'c')]); /// ``` +#[deprecated(note="Use [std::iter::zip](https://doc.rust-lang.org/std/iter/fn.zip.html) instead", since="0.10.4")] pub fn zip(i: I, j: J) -> Zip where I: IntoIterator, J: IntoIterator From 8d362f233645e90de80e75224367b594dd66c1dc Mon Sep 17 00:00:00 2001 From: phimuemue Date: Mon, 12 Sep 2022 18:43:05 +0200 Subject: [PATCH 180/633] Prepare 0.10.4 --- CHANGELOG.md | 7 +++++++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e2032c4c..d2b40b5db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.10.4 + - Add `EitherOrBoth::or` and `EitherOrBoth::or_else` (#593) + - Add `min_set`, `max_set` et al. (#613, #323) + - Use `either/use_std` (#628) + - Documentation fixes (#612, #625, #632, #633, #634, #638) + - Code maintenance (#623, #624, #627, #630) + ## 0.10.2 - Add `Itertools::multiunzip` (#362, #565) - Add `intersperse` and `intersperse_with` free functions (#555) diff --git a/Cargo.toml b/Cargo.toml index 15ddfa8f3..53d4a6ff0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.3" +version = "0.10.4" license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" diff --git a/README.md b/README.md index 4cc3f8fd3..732b41b45 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.10.2" +itertools = "0.10.4" ``` How to use in your crate: From 590fdef8ca30c734729c7b57e08b66ea705842fa Mon Sep 17 00:00:00 2001 From: Sean Olson Date: Fri, 9 Sep 2022 15:24:57 -0700 Subject: [PATCH 181/633] Implement `PeekingNext` transitively over mutable references. This change applies patterns used for the standard `Iterator` trait to the `PeekingNext` trait. Generic methods require `Self: Sized` and `PeekingNext` is now transitively implemented over mutable references. This allows generic code to easily accept owned and mutably borrowed types that implement `PeekingNext`. This also makes `PeekingNext` object-safe (though this has little utility today). --- src/peeking_take_while.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index b3a9c5ccb..f8f03ccda 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -16,7 +16,18 @@ pub trait PeekingNext : Iterator { /// if `accept` returns true, return it as the next element, /// else None. fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool; + where Self: Sized, + F: FnOnce(&Self::Item) -> bool; +} + +impl<'a, I> PeekingNext for &'a mut I + where I: PeekingNext, +{ + fn peeking_next(&mut self, accept: F) -> Option + where F: FnOnce(&Self::Item) -> bool + { + (*self).peeking_next(accept) + } } impl PeekingNext for Peekable From a266a5b82dd7713c75787764c7aa7c18d1bc7375 Mon Sep 17 00:00:00 2001 From: Sean Olson Date: Fri, 9 Sep 2022 15:31:38 -0700 Subject: [PATCH 182/633] Implement `PeekingNext` for `PeekingTakeWhile`. This change implements `PeekingNext` for `PeekingTakeWhile` by composing its predicate with the predicate given to `PeekingNext::peeking_next`. This allows subsequent (and chained) calls to functions that require `PeekingNext` following `Itertools::peeking_take_while`. --- src/peeking_take_while.rs | 12 ++++++++++++ tests/peeking_take_while.rs | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index b3a9c5ccb..e808de988 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -115,6 +115,18 @@ impl<'a, I, F> Iterator for PeekingTakeWhile<'a, I, F> } } +impl<'a, I, F> PeekingNext for PeekingTakeWhile<'a, I, F> + where I: PeekingNext, + F: FnMut(&I::Item) -> bool, +{ + fn peeking_next(&mut self, g: G) -> Option + where G: FnOnce(&Self::Item) -> bool, + { + let f = &mut self.f; + self.iter.peeking_next(|r| f(r) && g(r)) + } +} + // Some iterators are so lightweight we can simply clone them to save their // state and use that for peeking. macro_rules! peeking_next_by_clone { diff --git a/tests/peeking_take_while.rs b/tests/peeking_take_while.rs index a1147027e..5be97271d 100644 --- a/tests/peeking_take_while.rs +++ b/tests/peeking_take_while.rs @@ -48,3 +48,22 @@ fn peeking_take_while_slice_iter_rev() { r.peeking_take_while(|_| true).count(); assert_eq!(r.next(), None); } + +#[test] +fn peeking_take_while_nested() { + let mut xs = (0..10).peekable(); + let ys: Vec<_> = xs + .peeking_take_while(|x| *x < 6) + .peeking_take_while(|x| *x != 3) + .collect(); + assert_eq!(ys, vec![0, 1, 2]); + assert_eq!(xs.next(), Some(3)); + + let mut xs = (4..10).peekable(); + let ys: Vec<_> = xs + .peeking_take_while(|x| *x != 3) + .peeking_take_while(|x| *x < 6) + .collect(); + assert_eq!(ys, vec![4, 5]); + assert_eq!(xs.next(), Some(6)); +} From d61d12e808c8691eb0a672d3f9b65560be970a44 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 18 Sep 2022 12:09:32 +0200 Subject: [PATCH 183/633] Prepare 0.10.5 --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 53d4a6ff0..afe2ed618 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.4" +version = "0.10.5" license = "MIT/Apache-2.0" repository = "https://github.com/rust-itertools/itertools" diff --git a/README.md b/README.md index 732b41b45..a911127f4 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.10.4" +itertools = "0.10.5" ``` How to use in your crate: From ce37330e662063f10750d634b41ee2664429c8f4 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Tue, 20 Sep 2022 15:03:48 -0700 Subject: [PATCH 184/633] Use SPDX license format --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index afe2ed618..f5908e865 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "itertools" version = "0.10.5" -license = "MIT/Apache-2.0" +license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" documentation = "https://docs.rs/itertools/" authors = ["bluss"] From de6c87e9fc1b86c764ea15fcae983ec0d8248301 Mon Sep 17 00:00:00 2001 From: hottea773 <61781404+hottea773@users.noreply.github.com> Date: Thu, 22 Sep 2022 16:08:01 +0100 Subject: [PATCH 185/633] Add all_equal_value method --- src/lib.rs | 59 ++++++++++++++++++++++++++++++++++------------- tests/test_std.rs | 12 ++++++++-- 2 files changed, 53 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f91968870..74a868d45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -904,7 +904,7 @@ pub trait Itertools : Iterator { /// Return an iterator adaptor that flattens every `Result::Ok` value into /// a series of `Result::Ok` values. `Result::Err` values are unchanged. - /// + /// /// This is useful when you have some common error type for your crate and /// need to propagate it upwards, but the `Result::Ok` case needs to be flattened. /// @@ -914,7 +914,7 @@ pub trait Itertools : Iterator { /// let input = vec![Ok(0..2), Err(false), Ok(2..4)]; /// let it = input.iter().cloned().flatten_ok(); /// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]); - /// + /// /// // This can also be used to propagate errors when collecting. /// let output_result: Result, bool> = it.collect(); /// assert_eq!(output_result, Err(false)); @@ -1810,14 +1810,14 @@ pub trait Itertools : Iterator { /// /// #[derive(PartialEq, Debug)] /// enum Enum { A, B, C, D, E, } - /// + /// /// let mut iter = vec![Enum::A, Enum::B, Enum::C, Enum::D].into_iter(); - /// + /// /// // search `iter` for `B` /// assert_eq!(iter.contains(&Enum::B), true); /// // `B` was found, so the iterator now rests at the item after `B` (i.e, `C`). /// assert_eq!(iter.next(), Some(Enum::C)); - /// + /// /// // search `iter` for `E` /// assert_eq!(iter.contains(&Enum::E), false); /// // `E` wasn't found, so `iter` is now exhausted @@ -1858,6 +1858,33 @@ pub trait Itertools : Iterator { } } + /// Returns the value of the first element if it exists and all elements are equal. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let data = vec![1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5]; + /// assert_eq!(data.iter().all_equal_value(), None); + /// assert_eq!(data[0..3].iter().all_equal_value(), Some(&1)); + /// assert_eq!(data[3..5].iter().all_equal_value(), Some(&2)); + /// assert_eq!(data[5..8].iter().all_equal_value(), Some(&3)); + /// + /// let data : Option = None; + /// assert_eq!(data.into_iter().all_equal_value(), None); + /// ``` + fn all_equal_value(&mut self) -> Option + where Self: Sized, + Self::Item: PartialEq, + { + let first = self.next()?; + + if self.all(|item| first == item) { + Some(first) + } else { + None + } + } + /// Check whether all elements are unique (non equal). /// /// Empty iterators are considered to have unique elements: @@ -2867,13 +2894,13 @@ pub trait Itertools : Iterator { group_map::into_group_map_by(self, f) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The input iterator must yield item in the form of `(K, V)` where the /// value of type `K` will be used as key to identify the groups and the /// value of type `V` as value for the folding operation. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -2884,12 +2911,12 @@ pub trait Itertools : Iterator { grouping_map::new(self) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The values from this iterator will be used as values for the folding operation /// while the keys will be obtained from the values by calling `key_mapper`. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -3600,7 +3627,7 @@ pub trait Itertools : Iterator { /// first_name: &'static str, /// last_name: &'static str, /// } - /// + /// /// let characters = /// vec![ /// Character { first_name: "Amy", last_name: "Pond" }, @@ -3611,12 +3638,12 @@ pub trait Itertools : Iterator { /// Character { first_name: "James", last_name: "Norington" }, /// Character { first_name: "James", last_name: "Kirk" }, /// ]; - /// - /// let first_name_frequency = + /// + /// let first_name_frequency = /// characters /// .into_iter() /// .counts_by(|c| c.first_name); - /// + /// /// assert_eq!(first_name_frequency["Amy"], 3); /// assert_eq!(first_name_frequency["James"], 4); /// assert_eq!(first_name_frequency.contains_key("Asha"), false); @@ -3637,7 +3664,7 @@ pub trait Itertools : Iterator { /// column. /// /// This function is, in some sense, the opposite of [`multizip`]. - /// + /// /// ``` /// use itertools::Itertools; /// diff --git a/tests/test_std.rs b/tests/test_std.rs index f59034234..e4b9dcad6 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -220,6 +220,14 @@ fn all_equal() { } } +#[test] +fn all_equal_value() { + assert!("".chars().all_equal_value().is_none()); + assert_eq!("A".chars().all_equal_value(), Some('A')); + assert!("AABBCCC".chars().all_equal_value().is_none()); + assert_eq!("AAAAAAA".chars().all_equal_value(), Some('A')); +} + #[test] fn all_unique() { assert!("ABCDEFGH".chars().all_unique()); @@ -1160,9 +1168,9 @@ fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); + let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); let (): () = [(), (), ()].iter().cloned().multiunzip(); - let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); + let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); assert_eq!(t, (vec![0], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6], vec![7], vec![8], vec![9], vec![10], vec![11])); } From 7046086dd7283a1a84a5f358884de494dc172467 Mon Sep 17 00:00:00 2001 From: hottea773 <61781404+hottea773@users.noreply.github.com> Date: Fri, 23 Sep 2022 16:29:19 +0100 Subject: [PATCH 186/633] Return all the information available from all_equal_value --- src/lib.rs | 32 ++++++++++++++++++-------------- tests/test_std.rs | 8 ++++---- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 74a868d45..42410e3ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1858,30 +1858,34 @@ pub trait Itertools : Iterator { } } - /// Returns the value of the first element if it exists and all elements are equal. + /// If there are elements and they are all equal, return a single copy of that element. + /// If there are no elements, return an Error containing None. + /// If there are elements and they are not all equal, return a tuple containing the first + /// two non-equal elements found. /// /// ``` /// use itertools::Itertools; /// /// let data = vec![1, 1, 1, 2, 2, 3, 3, 3, 4, 5, 5]; - /// assert_eq!(data.iter().all_equal_value(), None); - /// assert_eq!(data[0..3].iter().all_equal_value(), Some(&1)); - /// assert_eq!(data[3..5].iter().all_equal_value(), Some(&2)); - /// assert_eq!(data[5..8].iter().all_equal_value(), Some(&3)); + /// assert_eq!(data.iter().all_equal_value(), Err(Some((&1, &2)))); + /// assert_eq!(data[0..3].iter().all_equal_value(), Ok(&1)); + /// assert_eq!(data[3..5].iter().all_equal_value(), Ok(&2)); + /// assert_eq!(data[5..8].iter().all_equal_value(), Ok(&3)); /// /// let data : Option = None; - /// assert_eq!(data.into_iter().all_equal_value(), None); + /// assert_eq!(data.into_iter().all_equal_value(), Err(None)); /// ``` - fn all_equal_value(&mut self) -> Option - where Self: Sized, - Self::Item: PartialEq, + fn all_equal_value(&mut self) -> Result> + where + Self: Sized, + Self::Item: PartialEq { - let first = self.next()?; - - if self.all(|item| first == item) { - Some(first) + let first = self.next().ok_or(None)?; + let other = self.find(|x| !(x == &first)); + if let Some(other) = other { + Err(Some((first, other))) } else { - None + Ok(first) } } diff --git a/tests/test_std.rs b/tests/test_std.rs index e4b9dcad6..6300cab12 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -222,10 +222,10 @@ fn all_equal() { #[test] fn all_equal_value() { - assert!("".chars().all_equal_value().is_none()); - assert_eq!("A".chars().all_equal_value(), Some('A')); - assert!("AABBCCC".chars().all_equal_value().is_none()); - assert_eq!("AAAAAAA".chars().all_equal_value(), Some('A')); + assert_eq!("".chars().all_equal_value(), Err(None)); + assert_eq!("A".chars().all_equal_value(), Ok('A')); + assert_eq!("AABBCCC".chars().all_equal_value(), Err(Some(('A', 'B')))); + assert_eq!("AAAAAAA".chars().all_equal_value(), Ok('A')); } #[test] From 2106291a3b56a2951f7d0c459afddf7c28f7b1f4 Mon Sep 17 00:00:00 2001 From: phimuemue Date: Sun, 25 Sep 2022 15:17:16 +0200 Subject: [PATCH 187/633] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d2b40b5db..5376c3506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.10.5 + - Maintenance + ## 0.10.4 - Add `EitherOrBoth::or` and `EitherOrBoth::or_else` (#593) - Add `min_set`, `max_set` et al. (#613, #323) @@ -7,6 +10,9 @@ - Documentation fixes (#612, #625, #632, #633, #634, #638) - Code maintenance (#623, #624, #627, #630) +## 0.10.3 + - Maintenance + ## 0.10.2 - Add `Itertools::multiunzip` (#362, #565) - Add `intersperse` and `intersperse_with` free functions (#555) From 35e50104eece6cc4b4e0a02fb901a3fd871e10fe Mon Sep 17 00:00:00 2001 From: DidiBear Date: Fri, 7 Oct 2022 00:28:46 -0400 Subject: [PATCH 188/633] docs: use step_by instead of step in merge examples The Itertools::step method has been deprecated in favor to Iterator::step_by --- src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f91968870..8a6f0fa23 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -935,8 +935,8 @@ pub trait Itertools : Iterator { /// ``` /// use itertools::Itertools; /// - /// let a = (0..11).step(3); - /// let b = (0..11).step(5); + /// let a = (0..11).step_by(3); + /// let b = (0..11).step_by(5); /// let it = a.merge(b); /// itertools::assert_equal(it, vec![0, 0, 3, 5, 6, 9, 10]); /// ``` @@ -991,8 +991,8 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// use itertools::EitherOrBoth::{Left, Right, Both}; /// - /// let multiples_of_2 = (0..10).step(2); - /// let multiples_of_3 = (0..10).step(3); + /// let multiples_of_2 = (0..10).step_by(2); + /// let multiples_of_3 = (0..10).step_by(3); /// /// itertools::assert_equal( /// multiples_of_2.merge_join_by(multiples_of_3, |i, j| i.cmp(j)), @@ -1018,9 +1018,9 @@ pub trait Itertools : Iterator { /// ``` /// use itertools::Itertools; /// - /// let a = (0..6).step(3); - /// let b = (1..6).step(3); - /// let c = (2..6).step(3); + /// let a = (0..6).step_by(3); + /// let b = (1..6).step_by(3); + /// let c = (2..6).step_by(3); /// let it = vec![a, b, c].into_iter().kmerge(); /// itertools::assert_equal(it, vec![0, 1, 2, 3, 4, 5]); /// ``` From 7f6328ee240c874bf6c8a83816a129db4652e250 Mon Sep 17 00:00:00 2001 From: hottea773 <61781404+hottea773@users.noreply.github.com> Date: Thu, 22 Sep 2022 16:08:01 +0100 Subject: [PATCH 189/633] Cleanup: Remove some trailing whitespace --- src/lib.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8a6f0fa23..d6ee74c80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -904,7 +904,7 @@ pub trait Itertools : Iterator { /// Return an iterator adaptor that flattens every `Result::Ok` value into /// a series of `Result::Ok` values. `Result::Err` values are unchanged. - /// + /// /// This is useful when you have some common error type for your crate and /// need to propagate it upwards, but the `Result::Ok` case needs to be flattened. /// @@ -914,7 +914,7 @@ pub trait Itertools : Iterator { /// let input = vec![Ok(0..2), Err(false), Ok(2..4)]; /// let it = input.iter().cloned().flatten_ok(); /// itertools::assert_equal(it.clone(), vec![Ok(0), Ok(1), Err(false), Ok(2), Ok(3)]); - /// + /// /// // This can also be used to propagate errors when collecting. /// let output_result: Result, bool> = it.collect(); /// assert_eq!(output_result, Err(false)); @@ -1810,14 +1810,14 @@ pub trait Itertools : Iterator { /// /// #[derive(PartialEq, Debug)] /// enum Enum { A, B, C, D, E, } - /// + /// /// let mut iter = vec![Enum::A, Enum::B, Enum::C, Enum::D].into_iter(); - /// + /// /// // search `iter` for `B` /// assert_eq!(iter.contains(&Enum::B), true); /// // `B` was found, so the iterator now rests at the item after `B` (i.e, `C`). /// assert_eq!(iter.next(), Some(Enum::C)); - /// + /// /// // search `iter` for `E` /// assert_eq!(iter.contains(&Enum::E), false); /// // `E` wasn't found, so `iter` is now exhausted @@ -2867,13 +2867,13 @@ pub trait Itertools : Iterator { group_map::into_group_map_by(self, f) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The input iterator must yield item in the form of `(K, V)` where the /// value of type `K` will be used as key to identify the groups and the /// value of type `V` as value for the folding operation. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -2884,12 +2884,12 @@ pub trait Itertools : Iterator { grouping_map::new(self) } - /// Constructs a `GroupingMap` to be used later with one of the efficient + /// Constructs a `GroupingMap` to be used later with one of the efficient /// group-and-fold operations it allows to perform. - /// + /// /// The values from this iterator will be used as values for the folding operation /// while the keys will be obtained from the values by calling `key_mapper`. - /// + /// /// See [`GroupingMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] @@ -3600,7 +3600,7 @@ pub trait Itertools : Iterator { /// first_name: &'static str, /// last_name: &'static str, /// } - /// + /// /// let characters = /// vec![ /// Character { first_name: "Amy", last_name: "Pond" }, @@ -3611,12 +3611,12 @@ pub trait Itertools : Iterator { /// Character { first_name: "James", last_name: "Norington" }, /// Character { first_name: "James", last_name: "Kirk" }, /// ]; - /// - /// let first_name_frequency = + /// + /// let first_name_frequency = /// characters /// .into_iter() /// .counts_by(|c| c.first_name); - /// + /// /// assert_eq!(first_name_frequency["Amy"], 3); /// assert_eq!(first_name_frequency["James"], 4); /// assert_eq!(first_name_frequency.contains_key("Asha"), false); @@ -3637,7 +3637,7 @@ pub trait Itertools : Iterator { /// column. /// /// This function is, in some sense, the opposite of [`multizip`]. - /// + /// /// ``` /// use itertools::Itertools; /// From 274486e1a068e1159f9850a93001b4d33425f63d Mon Sep 17 00:00:00 2001 From: philipp Date: Thu, 13 Oct 2022 21:49:42 +0200 Subject: [PATCH 190/633] Use != instead of !(==) --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 8b6093ed3..f9cf0da7e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1881,7 +1881,7 @@ pub trait Itertools : Iterator { Self::Item: PartialEq { let first = self.next().ok_or(None)?; - let other = self.find(|x| !(x == &first)); + let other = self.find(|x| x != &first); if let Some(other) = other { Err(Some((first, other))) } else { From fd251d04e1d730198fb5b0c34f11c36ca7c96554 Mon Sep 17 00:00:00 2001 From: philipp Date: Thu, 13 Oct 2022 21:53:31 +0200 Subject: [PATCH 191/633] Add one test for all_equal_value --- tests/test_std.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 6300cab12..77207d87e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -226,6 +226,14 @@ fn all_equal_value() { assert_eq!("A".chars().all_equal_value(), Ok('A')); assert_eq!("AABBCCC".chars().all_equal_value(), Err(Some(('A', 'B')))); assert_eq!("AAAAAAA".chars().all_equal_value(), Ok('A')); + { + let mut it = [1,2,3].iter().copied(); + let result = it.all_equal_value(); + assert_eq!(result, Err(Some((1, 2)))); + let remaining = it.next(); + assert_eq!(remaining, Some(3)); + assert!(it.next().is_none()); + } } #[test] From e18f4ed8db9cb25b6181c11a1e961cbde24b9e88 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 14 Oct 2022 11:17:23 +0100 Subject: [PATCH 192/633] Added notes about stability to sort functions --- src/lib.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f9cf0da7e..ccaa1cd1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2512,6 +2512,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2541,6 +2543,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable_by`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2574,6 +2578,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable_by_key`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2608,6 +2614,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2637,6 +2645,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2670,6 +2680,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by_key`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. @@ -2705,6 +2717,8 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by_cached_key`] method and returns the result as a new /// iterator that owns its elements. + /// + /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted /// without any extra copying or allocation cost. From b1f5faa8c484831d4e95bf50c978bc7d16d42372 Mon Sep 17 00:00:00 2001 From: Mark Wainwright Date: Fri, 14 Oct 2022 11:22:17 +0100 Subject: [PATCH 193/633] Stable sorted_by doc tests now test stability --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ccaa1cd1e..6215ea82e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2655,7 +2655,7 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// // sort people in descending order by age - /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 27)]; + /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 30)]; /// /// let oldest_people_first = people /// .into_iter() @@ -2690,7 +2690,7 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// // sort people in descending order by age - /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 27)]; + /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 30)]; /// /// let oldest_people_first = people /// .into_iter() @@ -2727,7 +2727,7 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// /// // sort people in descending order by age - /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 27)]; + /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 30)]; /// /// let oldest_people_first = people /// .into_iter() From d3987a70f1629eb4362efd6d19ca8cb4a4ca976a Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Mon, 9 Jan 2023 16:05:22 -0600 Subject: [PATCH 194/633] fix typo (circular_tuple_windows) --- src/tuple_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 06b5c13cb..f4488dbf1 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -194,7 +194,7 @@ impl FusedIterator for TupleWindows T::Item: Clone {} -/// An iterator over all windows,wrapping back to the first elements when the +/// 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. /// From 6452c66e93629f39e5605bc8e0e437cd3d5ad13a Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 31 Jan 2023 07:45:18 +0000 Subject: [PATCH 195/633] move the msrv configuration from 'clippy.toml' to 'Cargo.toml' --- Cargo.toml | 2 ++ clippy.toml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 clippy.toml diff --git a/Cargo.toml b/Cargo.toml index f5908e865..00985cddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,8 @@ exclude = ["/bors.toml"] edition = "2018" +rust-version = "1.36.0" + [package.metadata.release] no-dev-version = true diff --git a/clippy.toml b/clippy.toml deleted file mode 100644 index 0a5485386..000000000 --- a/clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.36.0" From 0d803418beeaa36972ce963995135135594de30a Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 31 Jan 2023 08:29:28 +0000 Subject: [PATCH 196/633] tidy Cargo.toml --- Cargo.toml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5908e865..666e7fae0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,15 +28,10 @@ either = { version = "1.0", default-features = false } [dev-dependencies] rand = "0.7" -criterion = "=0" # TODO how could this work with our minimum supported Rust version? -paste = "1.0.0" # Used in test_std to instantiate generic tests - -[dev-dependencies.quickcheck] -version = "0.9" -default-features = false - -[dev-dependencies.permutohedron] -version = "0.2" +criterion = "0.4.0" +paste = "1.0.0" # Used in test_std to instantiate generic tests +permutohedron = "0.2" +quickcheck = { version = "0.9", default_features = false } [features] default = ["use_std"] From 0ee343e63dca78961bd736be18661907f71be410 Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 31 Jan 2023 10:50:37 +0000 Subject: [PATCH 197/633] add dependabot config --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..71607d0c3 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: +- package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily From 1aa1413f001eaca593b85d8b95cbd2b1f1dd2709 Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 31 Jan 2023 11:44:41 +0000 Subject: [PATCH 198/633] run tests in CI --- .github/workflows/ci.yml | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 143ac2b6d..76ab7f31e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,25 +8,38 @@ on: - trying jobs: - msrv: - name: Rust MSRV + + check: + name: check runs-on: ubuntu-latest + strategy: + matrix: + build: [msrv, stable] + features: ["", "--no-default-features", "--no-default-features --features use_alloc", "--all-targets --all-features"] + include: + - build: msrv + rust: 1.62.1 + - build: stable + rust: stable + exclude: + - build: msrv + # we only care about the MSRV with respect to the lib target + features: "--all-targets --all-features" steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@1.36.0 - - run: cargo check --no-default-features - - run: cargo check --no-default-features --features "use_alloc" - - run: cargo check + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.rust }} + - run: cargo check ${{ matrix.features }} - stable: - name: Rust Stable + test: + name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - - run: cargo check --no-default-features - - run: cargo check --no-default-features --features "use_alloc" - - run: cargo test + - run: cargo test --all-features + # https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 end-success: From 403c93a63b21f488f5a9a9941e9ca46134a4affa Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 31 Jan 2023 11:46:19 +0000 Subject: [PATCH 199/633] fixup --- .github/workflows/ci.yml | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 76ab7f31e..92122cca0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,14 +8,19 @@ on: - trying jobs: - check: name: check runs-on: ubuntu-latest strategy: matrix: build: [msrv, stable] - features: ["", "--no-default-features", "--no-default-features --features use_alloc", "--all-targets --all-features"] + features: + [ + "", + "--no-default-features", + "--no-default-features --features use_alloc", + "--all-targets --all-features", + ] include: - build: msrv rust: 1.62.1 @@ -40,13 +45,12 @@ jobs: - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features - # https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 end-success: name: bors build finished if: success() runs-on: ubuntu-latest - needs: [msrv,stable] + needs: [check, test] steps: - name: Mark the job as successful From c0353efd2f07527ca546ac82f87459ad5595d70a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 2 Mar 2023 18:20:47 +0100 Subject: [PATCH 200/633] Feature `process_results` as an `Itertools` method --- src/lib.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index f9cf0da7e..9f8b275c9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -926,6 +926,43 @@ pub trait Itertools : Iterator { flatten_ok::flatten_ok(self) } + /// “Lift” a function of the values of the current iterator so as to process + /// an iterator of `Result` values instead. + /// + /// `processor` is a closure that receives an adapted version of the iterator + /// as the only argument — the adapted iterator produces elements of type `T`, + /// as long as the original iterator produces `Ok` values. + /// + /// If the original iterable produces an error at any point, the adapted + /// iterator ends and it will return the error iself. + /// + /// Otherwise, the return value from the closure is returned wrapped + /// inside `Ok`. + /// + /// # Example + /// + /// ``` + /// use itertools::Itertools; + /// + /// type Item = Result; + /// + /// let first_values: Vec = vec![Ok(1), Ok(0), Ok(3)]; + /// let second_values: Vec = vec![Ok(2), Ok(1), Err("overflow")]; + /// + /// // “Lift” the iterator .max() method to work on the Ok-values. + /// let first_max = first_values.into_iter().process_results(|iter| iter.max().unwrap_or(0)); + /// let second_max = second_values.into_iter().process_results(|iter| iter.max().unwrap_or(0)); + /// + /// assert_eq!(first_max, Ok(3)); + /// assert!(second_max.is_err()); + /// ``` + fn process_results(self, processor: F) -> Result + where Self: Iterator> + Sized, + F: FnOnce(ProcessResults) -> R + { + process_results(self, processor) + } + /// Return an iterator adaptor that merges the two base iterators in /// ascending order. If both base iterators are sorted (ascending), the /// result is sorted. From 3dab85526024851a7ef9776cc9375d5a3311609b Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Tue, 21 Mar 2023 19:28:41 -0600 Subject: [PATCH 201/633] Added derive clone to Chunks. --- src/groupbylazy.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index a5a321df4..80c6f09f3 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -19,7 +19,7 @@ impl KeyFunction for F /// `ChunkIndex` acts like the grouping key function for `IntoChunks` -#[derive(Debug)] +#[derive(Debug, Clone)] struct ChunkIndex { size: usize, index: usize, @@ -50,7 +50,7 @@ impl KeyFunction for ChunkIndex { } } - +#[derive(Clone)] struct GroupInner where I: Iterator { @@ -471,6 +471,13 @@ pub struct IntoChunks index: Cell, } +impl Clone for IntoChunks + where I: Clone + Iterator, + I::Item: Clone, +{ + clone_fields!(inner, index); +} + impl IntoChunks where I: Iterator, @@ -507,6 +514,7 @@ impl<'a, I> IntoIterator for &'a IntoChunks /// /// See [`.chunks()`](crate::Itertools::chunks) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[derive(Clone)] pub struct Chunks<'a, I: 'a> where I: Iterator, I::Item: 'a, From e01a74b55dfa7ca129c17ee430c97818fd4aa646 Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Tue, 21 Mar 2023 19:46:30 -0600 Subject: [PATCH 202/633] Added test that `Chunks` clone works correctly. --- tests/test_std.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 77207d87e..0ea01408b 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -842,6 +842,7 @@ fn group_by_lazy_3() { fn chunks() { let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().chunks(3); + let grouper_clone = grouper.clone(); for (i, chunk) in grouper.into_iter().enumerate() { match i { 0 => it::assert_equal(chunk, &[0, 0, 0]), @@ -851,6 +852,15 @@ fn chunks() { _ => unreachable!(), } } + for (i, chunk) in grouper_clone.into_iter().enumerate() { + match i { + 0 => it::assert_equal(chunk, &[0, 0, 0]), + 1 => it::assert_equal(chunk, &[1, 1, 0]), + 2 => it::assert_equal(chunk, &[0, 2, 2]), + 3 => it::assert_equal(chunk, &[3, 3]), + _ => unreachable!(), + } + } } #[test] From d7cf9ac7bf8b842695c2b5890242b4ed33603e2d Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 22 Mar 2023 11:07:38 +0100 Subject: [PATCH 203/633] Canonicalize documentation of `itertools::process_results` --- src/process_results_impl.rs | 35 +++-------------------------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index 44308f378..713db4551 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -1,3 +1,5 @@ +#[cfg(doc)] +use crate::Itertools; /// An iterator that produces only the `T` values as long as the /// inner iterator produces `Ok(T)`. @@ -52,38 +54,7 @@ impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> /// “Lift” a function of the values of an iterator so that it can process /// an iterator of `Result` values instead. /// -/// `iterable` is an iterator or iterable with `Result` elements, where -/// `T` is the value type and `E` the error type. -/// -/// `processor` is a closure that receives an adapted version of the iterable -/// as the only argument — the adapted iterator produces elements of type `T`, -/// as long as the original iterator produces `Ok` values. -/// -/// If the original iterable produces an error at any point, the adapted -/// iterator ends and the `process_results` function will return the -/// error iself. -/// -/// Otherwise, the return value from the closure is returned wrapped -/// inside `Ok`. -/// -/// # Example -/// -/// ``` -/// use itertools::process_results; -/// -/// type R = Result; -/// -/// let first_values: Vec = vec![Ok(1), Ok(0), Ok(3)]; -/// let second_values: Vec = vec![Ok(2), Ok(1), Err("overflow")]; -/// -/// // “Lift” the iterator .max() method to work on the values in Results using process_results -/// -/// let first_max = process_results(first_values, |iter| iter.max().unwrap_or(0)); -/// let second_max = process_results(second_values, |iter| iter.max().unwrap_or(0)); -/// -/// assert_eq!(first_max, Ok(3)); -/// assert!(second_max.is_err()); -/// ``` +/// [`IntoIterator`] enabled version of [`Itertools::process_results`]. pub fn process_results(iterable: I, processor: F) -> Result where I: IntoIterator>, F: FnOnce(ProcessResults) -> R From 39db89bad0a8ea079ed1844240d89906e41259de Mon Sep 17 00:00:00 2001 From: Zachary S Date: Wed, 22 Mar 2023 21:32:39 -0500 Subject: [PATCH 204/633] Added `derive(Clone)` to CircularTupleWindows. --- src/tuple_impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index f4488dbf1..fdf086585 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -201,7 +201,7 @@ impl FusedIterator for TupleWindows /// See [`.circular_tuple_windows()`](crate::Itertools::circular_tuple_windows) for more /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CircularTupleWindows where I: Iterator + Clone, T: TupleCollect + Clone From d6082831e7f2f80723bf0caf04703a62c5b0faae Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Thu, 23 Mar 2023 14:42:34 -0600 Subject: [PATCH 205/633] Changed chunk clone test to quicktest. --- tests/quick.rs | 11 +++++++++++ tests/test_std.rs | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 0adcf1ad7..7af0ef602 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -993,6 +993,17 @@ quickcheck! { } } +quickcheck! { + fn chunk_clone_equal(a: Vec, size: u8) -> () { + let mut size = size; + if size == 0 { + size += 1; + } + let it = a.chunks(size as usize); + itertools::assert_equal(it.clone(), it); + } +} + quickcheck! { fn equal_chunks_lazy(a: Vec, size: u8) -> bool { let mut size = size; diff --git a/tests/test_std.rs b/tests/test_std.rs index 0ea01408b..77207d87e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -842,7 +842,6 @@ fn group_by_lazy_3() { fn chunks() { let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().chunks(3); - let grouper_clone = grouper.clone(); for (i, chunk) in grouper.into_iter().enumerate() { match i { 0 => it::assert_equal(chunk, &[0, 0, 0]), @@ -852,15 +851,6 @@ fn chunks() { _ => unreachable!(), } } - for (i, chunk) in grouper_clone.into_iter().enumerate() { - match i { - 0 => it::assert_equal(chunk, &[0, 0, 0]), - 1 => it::assert_equal(chunk, &[1, 1, 0]), - 2 => it::assert_equal(chunk, &[0, 2, 2]), - 3 => it::assert_equal(chunk, &[3, 3]), - _ => unreachable!(), - } - } } #[test] From 447370d31248a61284ee6e5563d03c7e55cb1bea Mon Sep 17 00:00:00 2001 From: Zachary S Date: Fri, 24 Mar 2023 12:58:42 -0500 Subject: [PATCH 206/633] Added tests that `CircularTupleWindows` and its `Clone` impl work correctly. --- tests/quick.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 0adcf1ad7..94111f584 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1010,7 +1010,71 @@ quickcheck! { } } +// tuple iterators quickcheck! { + fn equal_circular_tuple_windows_1(a: Vec) -> bool { + let x = a.iter().map(|e| (e,) ); + let y = a.iter().circular_tuple_windows::<(_,)>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_tuple_windows_2(a: Vec) -> bool { + let x = (0..a.len()).map(|start_idx| ( + &a[start_idx], + &a[(start_idx + 1) % a.len()], + )); + let y = a.iter().circular_tuple_windows::<(_, _)>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_tuple_windows_3(a: Vec) -> bool { + let x = (0..a.len()).map(|start_idx| ( + &a[start_idx], + &a[(start_idx + 1) % a.len()], + &a[(start_idx + 2) % a.len()], + )); + let y = a.iter().circular_tuple_windows::<(_, _, _)>(); + itertools::assert_equal(x,y); + true + } + + fn equal_circular_tuple_windows_4(a: Vec) -> bool { + let x = (0..a.len()).map(|start_idx| ( + &a[start_idx], + &a[(start_idx + 1) % a.len()], + &a[(start_idx + 2) % a.len()], + &a[(start_idx + 3) % a.len()], + )); + let y = a.iter().circular_tuple_windows::<(_, _, _, _)>(); + itertools::assert_equal(x,y); + true + } + + fn equal_cloned_circular_tuple_windows(a: Vec) -> bool { + let x = a.iter().circular_tuple_windows::<(_, _, _, _)>(); + let y = x.clone(); + itertools::assert_equal(x,y); + true + } + + fn equal_cloned_circular_tuple_windows_noninitial(a: Vec) -> bool { + let mut x = a.iter().circular_tuple_windows::<(_, _, _, _)>(); + let _ = x.next(); + let y = x.clone(); + itertools::assert_equal(x,y); + true + } + + fn equal_cloned_circular_tuple_windows_complete(a: Vec) -> bool { + let mut x = a.iter().circular_tuple_windows::<(_, _, _, _)>(); + for _ in x.by_ref() {} + let y = x.clone(); + itertools::assert_equal(x,y); + true + } + fn equal_tuple_windows_1(a: Vec) -> bool { let x = a.windows(1).map(|s| (&s[0], )); let y = a.iter().tuple_windows::<(_,)>(); From 0cef72dcedf9e1de44b9ebb4b293ea604c087d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Koritar?= Date: Thu, 13 Apr 2023 17:30:12 -0300 Subject: [PATCH 207/633] Fix max_set_by_key documentation --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 9f8b275c9..daf935f73 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3127,7 +3127,7 @@ pub trait Itertools : Iterator { ) } - /// Return all minimum elements of an iterator, as determined by + /// Return all maximum elements of an iterator, as determined by /// the specified function. /// /// # Examples From ed55b186ba9123ed87b8513e64679fdc38dcb744 Mon Sep 17 00:00:00 2001 From: 2ndDerivative Date: Fri, 14 Apr 2023 19:11:29 +0200 Subject: [PATCH 208/633] Add example of EitherOrBoth::reduce Also changed "product" to avoid confusion with multiplication --- src/either_or_both.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index ef3985f75..c63bc460b 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -230,7 +230,16 @@ impl EitherOrBoth { } impl EitherOrBoth { - /// Return either value of left, right, or the product of `f` applied where `Both` are present. + /// Return either value of left, right, or apply a function `f` to both values if both are present. + /// The input function has to return the same type as both Right and Left carry. + /// + /// # Examples + /// ``` + /// # use itertools::EitherOrBoth; + /// assert_eq!(EitherOrBoth::Both(3, 7).reduce(u32::max), 7); + /// assert_eq!(EitherOrBoth::Left(3).reduce(u32::max), 3); + /// assert_eq!(EitherOrBoth::Right(7).reduce(u32::max), 7); + /// ``` pub fn reduce(self, f: F) -> T where F: FnOnce(T, T) -> T, From a08956f6622024bb4a4ac2b4989eb99edad48092 Mon Sep 17 00:00:00 2001 From: 2ndDerivative Date: Fri, 14 Apr 2023 19:11:29 +0200 Subject: [PATCH 209/633] Add example of EitherOrBoth::reduce Also changed "product" to avoid confusion with multiplication --- src/either_or_both.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index ef3985f75..c63bc460b 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -230,7 +230,16 @@ impl EitherOrBoth { } impl EitherOrBoth { - /// Return either value of left, right, or the product of `f` applied where `Both` are present. + /// Return either value of left, right, or apply a function `f` to both values if both are present. + /// The input function has to return the same type as both Right and Left carry. + /// + /// # Examples + /// ``` + /// # use itertools::EitherOrBoth; + /// assert_eq!(EitherOrBoth::Both(3, 7).reduce(u32::max), 7); + /// assert_eq!(EitherOrBoth::Left(3).reduce(u32::max), 3); + /// assert_eq!(EitherOrBoth::Right(7).reduce(u32::max), 7); + /// ``` pub fn reduce(self, f: F) -> T where F: FnOnce(T, T) -> T, From 2b04a4fb812e189d5e133071d00de6a720ff3ea6 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 10 May 2023 12:04:06 +0200 Subject: [PATCH 210/633] Position -> (Position, Item) Note: This is a breaking change. --- src/lib.rs | 14 +++++++------- src/with_position.rs | 36 ++++++++++++------------------------ 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index daf935f73..a3266d6a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1657,24 +1657,24 @@ pub trait Itertools : Iterator { pad_tail::pad_using(self, min, f) } - /// Return an iterator adaptor that wraps each element in a `Position` to + /// Return an iterator adaptor that combines each element with a `Position` to /// ease special-case handling of the first or last elements. /// /// Iterator element type is - /// [`Position`](Position) + /// [`(Position, Self::Item)`](Position) /// /// ``` /// use itertools::{Itertools, Position}; /// /// let it = (0..4).with_position(); /// itertools::assert_equal(it, - /// vec![Position::First(0), - /// Position::Middle(1), - /// Position::Middle(2), - /// Position::Last(3)]); + /// vec![(Position::First, 0), + /// (Position::Middle, 1), + /// (Position::Middle, 2), + /// (Position::Last, 3)]); /// /// let it = (0..1).with_position(); - /// itertools::assert_equal(it, vec![Position::Only(0)]); + /// itertools::assert_equal(it, vec![(Position::Only, 0)]); /// ``` fn with_position(self) -> WithPosition where Self: Sized, diff --git a/src/with_position.rs b/src/with_position.rs index 1388503d1..dda9b25dc 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -2,7 +2,7 @@ use std::iter::{Fuse,Peekable, FusedIterator}; /// An iterator adaptor that wraps each element in an [`Position`]. /// -/// Iterator element type is `Position`. +/// Iterator element type is `(Position, I::Item)`. /// /// See [`.with_position()`](crate::Itertools::with_position) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -30,36 +30,24 @@ pub fn with_position(iter: I) -> WithPosition } } -/// A value yielded by `WithPosition`. +/// The first component of the value yielded by `WithPosition`. /// Indicates the position of this element in the iterator results. /// /// See [`.with_position()`](crate::Itertools::with_position) for more information. #[derive(Copy, Clone, Debug, PartialEq)] -pub enum Position { +pub enum Position { /// This is the first element. - First(T), + First, /// This is neither the first nor the last element. - Middle(T), + Middle, /// This is the last element. - Last(T), + Last, /// This is the only element. - Only(T), -} - -impl Position { - /// Return the inner value. - pub fn into_inner(self) -> T { - match self { - Position::First(x) | - Position::Middle(x) | - Position::Last(x) | - Position::Only(x) => x, - } - } + Only, } impl Iterator for WithPosition { - type Item = Position; + type Item = (Position, I::Item); fn next(&mut self) -> Option { match self.peekable.next() { @@ -70,15 +58,15 @@ impl Iterator for WithPosition { // Peek to see if this is also the last item, // in which case tag it as `Only`. match self.peekable.peek() { - Some(_) => Some(Position::First(item)), - None => Some(Position::Only(item)), + Some(_) => Some((Position::First, item)), + None => Some((Position::Only, item)), } } else { // Have seen the first item, and there's something left. // Peek to see if this is the last item. match self.peekable.peek() { - Some(_) => Some(Position::Middle(item)), - None => Some(Position::Last(item)), + Some(_) => Some((Position::Middle, item)), + None => Some((Position::Last, item)), } } } From ae31559af533208e25e277aa01950d2ebf04eecb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 11 Jun 2023 22:03:56 +0200 Subject: [PATCH 211/633] `MergeJoinBy` also accept functions returning `bool` Done with `trait OrderingOrBool`. Now, `merge_join_by` needs an extra type parameter `T` so this is a breaking change, because any [unlikely] invocations that explicitly provided these parameters are now one parameter short. Documentation updated, two quickcheck. --- src/lib.rs | 45 +++++++++++-- src/merge_join.rs | 163 ++++++++++++++++++++++++++++++---------------- tests/quick.rs | 25 +++++++ 3 files changed, 170 insertions(+), 63 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index daf935f73..340a04120 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1012,7 +1012,10 @@ pub trait Itertools : Iterator { /// Create an iterator that merges items from both this and the specified /// iterator in ascending order. /// - /// It chooses whether to pair elements based on the `Ordering` returned by the + /// The function can either return an `Ordering` variant or a boolean. + /// + /// If `cmp_fn` returns `Ordering`, + /// it chooses whether to pair elements based on the `Ordering` returned by the /// specified compare function. At any point, inspecting the tip of the /// iterators `I` and `J` as items `i` of type `I::Item` and `j` of type /// `J::Item` respectively, the resulting iterator will: @@ -1028,18 +1031,46 @@ pub trait Itertools : Iterator { /// use itertools::Itertools; /// use itertools::EitherOrBoth::{Left, Right, Both}; /// - /// let multiples_of_2 = (0..10).step_by(2); - /// let multiples_of_3 = (0..10).step_by(3); + /// let a = vec![0, 2, 4, 6, 1].into_iter(); + /// let b = (0..10).step_by(3); + /// + /// itertools::assert_equal( + /// a.merge_join_by(b, |i, j| i.cmp(j)), + /// vec![Both(0, 0), Left(2), Right(3), Left(4), Both(6, 6), Left(1), Right(9)] + /// ); + /// ``` + /// + /// If `cmp_fn` returns `bool`, + /// it chooses whether to pair elements based on the boolean returned by the + /// specified function. At any point, inspecting the tip of the + /// iterators `I` and `J` as items `i` of type `I::Item` and `j` of type + /// `J::Item` respectively, the resulting iterator will: + /// + /// - Emit `Either::Left(i)` when `true`, + /// and remove `i` from its source iterator + /// - Emit `Either::Right(j)` when `false`, + /// and remove `j` from its source iterator + /// + /// It is similar to the `Ordering` case if the first argument is considered + /// "less" than the second argument. + /// + /// ``` + /// use itertools::Itertools; + /// use itertools::Either::{Left, Right}; + /// + /// let a = vec![0, 2, 4, 6, 1].into_iter(); + /// let b = (0..10).step_by(3); /// /// itertools::assert_equal( - /// multiples_of_2.merge_join_by(multiples_of_3, |i, j| i.cmp(j)), - /// vec![Both(0, 0), Left(2), Right(3), Left(4), Both(6, 6), Left(8), Right(9)] + /// a.merge_join_by(b, |i, j| i <= j), + /// vec![Left(0), Right(0), Left(2), Right(3), Left(4), Left(6), Left(1), Right(6), Right(9)] /// ); /// ``` #[inline] - fn merge_join_by(self, other: J, cmp_fn: F) -> MergeJoinBy + fn merge_join_by(self, other: J, cmp_fn: F) -> MergeJoinBy where J: IntoIterator, - F: FnMut(&Self::Item, &J::Item) -> std::cmp::Ordering, + F: FnMut(&Self::Item, &J::Item) -> T, + T: merge_join::OrderingOrBool, Self: Sized { merge_join_by(self, other, cmp_fn) diff --git a/src/merge_join.rs b/src/merge_join.rs index f2fbdea2c..84f7d0333 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -2,19 +2,23 @@ use std::cmp::Ordering; use std::iter::Fuse; use std::fmt; +use either::Either; + use super::adaptors::{PutBack, put_back}; use crate::either_or_both::EitherOrBoth; +use crate::size_hint::{self, SizeHint}; #[cfg(doc)] use crate::Itertools; /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// [`IntoIterator`] enabled version of [`Itertools::merge_join_by`]. -pub fn merge_join_by(left: I, right: J, cmp_fn: F) +pub fn merge_join_by(left: I, right: J, cmp_fn: F) -> MergeJoinBy where I: IntoIterator, J: IntoIterator, - F: FnMut(&I::Item, &J::Item) -> Ordering + F: FnMut(&I::Item, &J::Item) -> T, + T: OrderingOrBool, { MergeJoinBy { left: put_back(left.into_iter().fuse()), @@ -30,7 +34,66 @@ pub fn merge_join_by(left: I, right: J, cmp_fn: F) pub struct MergeJoinBy { left: PutBack>, right: PutBack>, - cmp_fn: F + cmp_fn: F, +} + +pub trait OrderingOrBool { + type MergeResult; + fn left(left: L) -> Self::MergeResult; + fn right(right: R) -> Self::MergeResult; + // "merge" never returns (Some(...), Some(...), ...) so Option> + // is appealing but it is always followed by two put_backs, so we think the compiler is + // smart enough to optimize it. Or we could move put_backs into "merge". + fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult); + fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint; +} + +impl OrderingOrBool for Ordering { + type MergeResult = EitherOrBoth; + fn left(left: L) -> Self::MergeResult { + EitherOrBoth::Left(left) + } + fn right(right: R) -> Self::MergeResult { + EitherOrBoth::Right(right) + } + fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + match self { + Ordering::Equal => (None, None, EitherOrBoth::Both(left, right)), + Ordering::Less => (None, Some(right), EitherOrBoth::Left(left)), + Ordering::Greater => (Some(left), None, EitherOrBoth::Right(right)), + } + } + fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { + let (a_lower, a_upper) = left; + let (b_lower, b_upper) = right; + let lower = ::std::cmp::max(a_lower, b_lower); + let upper = match (a_upper, b_upper) { + (Some(x), Some(y)) => x.checked_add(y), + _ => None, + }; + (lower, upper) + } +} + +impl OrderingOrBool for bool { + type MergeResult = Either; + fn left(left: L) -> Self::MergeResult { + Either::Left(left) + } + fn right(right: R) -> Self::MergeResult { + Either::Right(right) + } + fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + if self { + (None, Some(right), Either::Left(left)) + } else { + (Some(left), None, Either::Right(right)) + } + } + fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { + // Not ExactSizeIterator because size may be larger than usize + size_hint::add(left, right) + } } impl Clone for MergeJoinBy @@ -52,49 +115,34 @@ impl fmt::Debug for MergeJoinBy debug_fmt_fields!(MergeJoinBy, left, right); } -impl Iterator for MergeJoinBy +impl Iterator for MergeJoinBy where I: Iterator, J: Iterator, - F: FnMut(&I::Item, &J::Item) -> Ordering + F: FnMut(&I::Item, &J::Item) -> T, + T: OrderingOrBool, { - type Item = EitherOrBoth; + type Item = T::MergeResult; fn next(&mut self) -> Option { match (self.left.next(), self.right.next()) { (None, None) => None, - (Some(left), None) => - Some(EitherOrBoth::Left(left)), - (None, Some(right)) => - Some(EitherOrBoth::Right(right)), + (Some(left), None) => Some(T::left(left)), + (None, Some(right)) => Some(T::right(right)), (Some(left), Some(right)) => { - match (self.cmp_fn)(&left, &right) { - Ordering::Equal => - Some(EitherOrBoth::Both(left, right)), - Ordering::Less => { - self.right.put_back(right); - Some(EitherOrBoth::Left(left)) - }, - Ordering::Greater => { - self.left.put_back(left); - Some(EitherOrBoth::Right(right)) - } + let (left, right, next) = (self.cmp_fn)(&left, &right).merge(left, right); + if let Some(left) = left { + self.left.put_back(left); + } + if let Some(right) = right { + self.right.put_back(right); } + Some(next) } } } - fn size_hint(&self) -> (usize, Option) { - let (a_lower, a_upper) = self.left.size_hint(); - let (b_lower, b_upper) = self.right.size_hint(); - - let lower = ::std::cmp::max(a_lower, b_lower); - - let upper = match (a_upper, b_upper) { - (Some(x), Some(y)) => x.checked_add(y), - _ => None, - }; - - (lower, upper) + fn size_hint(&self) -> SizeHint { + T::size_hint(self.left.size_hint(), self.right.size_hint()) } fn count(mut self) -> usize { @@ -106,10 +154,12 @@ impl Iterator for MergeJoinBy (None, Some(_right)) => break count + 1 + self.right.into_parts().1.count(), (Some(left), Some(right)) => { count += 1; - match (self.cmp_fn)(&left, &right) { - Ordering::Equal => {} - Ordering::Less => self.right.put_back(right), - Ordering::Greater => self.left.put_back(left), + let (left, right, _) = (self.cmp_fn)(&left, &right).merge(left, right); + if let Some(left) = left { + self.left.put_back(left); + } + if let Some(right) = right { + self.right.put_back(right); } } } @@ -122,27 +172,24 @@ impl Iterator for MergeJoinBy match (self.left.next(), self.right.next()) { (None, None) => break previous_element, (Some(left), None) => { - break Some(EitherOrBoth::Left( + break Some(T::left( self.left.into_parts().1.last().unwrap_or(left), )) } (None, Some(right)) => { - break Some(EitherOrBoth::Right( + break Some(T::right( self.right.into_parts().1.last().unwrap_or(right), )) } (Some(left), Some(right)) => { - previous_element = match (self.cmp_fn)(&left, &right) { - Ordering::Equal => Some(EitherOrBoth::Both(left, right)), - Ordering::Less => { - self.right.put_back(right); - Some(EitherOrBoth::Left(left)) - } - Ordering::Greater => { - self.left.put_back(left); - Some(EitherOrBoth::Right(right)) - } + let (left, right, elem) = (self.cmp_fn)(&left, &right).merge(left, right); + if let Some(left) = left { + self.left.put_back(left); + } + if let Some(right) = right { + self.right.put_back(right); } + previous_element = Some(elem); } } } @@ -156,13 +203,17 @@ impl Iterator for MergeJoinBy n -= 1; match (self.left.next(), self.right.next()) { (None, None) => break None, - (Some(_left), None) => break self.left.nth(n).map(EitherOrBoth::Left), - (None, Some(_right)) => break self.right.nth(n).map(EitherOrBoth::Right), - (Some(left), Some(right)) => match (self.cmp_fn)(&left, &right) { - Ordering::Equal => {} - Ordering::Less => self.right.put_back(right), - Ordering::Greater => self.left.put_back(left), - }, + (Some(_left), None) => break self.left.nth(n).map(T::left), + (None, Some(_right)) => break self.right.nth(n).map(T::right), + (Some(left), Some(right)) => { + let (left, right, _) = (self.cmp_fn)(&left, &right).merge(left, right); + if let Some(left) = left { + self.left.put_back(left); + } + if let Some(right) = right { + self.right.put_back(right); + } + } } } } diff --git a/tests/quick.rs b/tests/quick.rs index 0adcf1ad7..914960a5f 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -829,6 +829,31 @@ quickcheck! { } } +quickcheck! { + fn merge_join_by_ordering_vs_bool(a: Vec, b: Vec) -> bool { + use either::Either; + use itertools::free::merge_join_by; + let mut has_equal = false; + let it_ord = merge_join_by(a.clone(), b.clone(), Ord::cmp).flat_map(|v| match v { + EitherOrBoth::Both(l, r) => { + has_equal = true; + vec![Either::Left(l), Either::Right(r)] + } + EitherOrBoth::Left(l) => vec![Either::Left(l)], + EitherOrBoth::Right(r) => vec![Either::Right(r)], + }); + let it_bool = merge_join_by(a, b, PartialOrd::le); + itertools::equal(it_ord, it_bool) || has_equal + } + fn merge_join_by_bool_unwrapped_is_merge_by(a: Vec, b: Vec) -> bool { + use either::Either; + use itertools::free::merge_join_by; + let it = a.clone().into_iter().merge_by(b.clone(), PartialOrd::ge); + let it_join = merge_join_by(a, b, PartialOrd::ge).map(Either::into_inner); + itertools::equal(it, it_join) + } +} + quickcheck! { fn size_tee(a: Vec) -> bool { let (mut t1, mut t2) = a.iter().tee(); From 0ef6b7eaad173bd115f513c333958f72f709c497 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Wed, 21 Jun 2023 17:20:26 +0000 Subject: [PATCH 212/633] prepare v0.11.0 release --- CHANGELOG.md | 23 +++++++++++++++++++++++ README.md | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5376c3506..8d7404e75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,28 @@ # Changelog +## 0.11.0 + +### Breaking +- Make `Itertools::merge_join_by` also accept functions returning bool (#704) +- Implement `PeekingNext` transitively over mutable references (#643) +- Change `with_position` to yield `(Position, Item)` instead of `Position` (#699) + +### Added +- Add `Itertools::take_while_inclusive` (#616) +- Implement `PeekingNext` for `PeekingTakeWhile` (#644) +- Add `EitherOrBoth::{just_left, just_right, into_left, into_right, as_deref, as_deref_mut, left_or_insert, right_or_insert, left_or_insert_with, right_or_insert_with, insert_left, insert_right, insert_both}` (#629) +- Implement `Clone` for `CircularTupleWindows` (#686) +- Implement `Clone` for `Chunks` (#683) +- Add `Itertools::process_results` (#680) + +### Changed +- Use `Cell` instead of `RefCell` in `Format` and `FormatWith` (#608) +- CI tweaks (#674, #675) +- Document and test the difference between stable and unstable sorts (#653) +- Fix documentation error on `Itertools::max_set_by_key` (#692) +- Move MSRV metadata to `Cargo.toml` (#672) +- Implement `equal` with `Iterator::eq` (#591) + ## 0.10.5 - Maintenance diff --git a/README.md b/README.md index a911127f4..626d10d0d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.10.5" +itertools = "0.11.0" ``` How to use in your crate: From 8f17227193e19661d5587ca066e15f0bd08a2b7b Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 22 Jun 2023 12:08:41 +0000 Subject: [PATCH 213/633] remove `no-dev-version` Cargo release directive --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a67c6de76..d86b546cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,6 @@ edition = "2018" rust-version = "1.36.0" -[package.metadata.release] -no-dev-version = true - [lib] bench = false test = false From 62a6401afd6d45e1c2aea94c05cb5c70076b2ca4 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Thu, 22 Jun 2023 12:09:00 +0000 Subject: [PATCH 214/633] chore: Release itertools version 0.11.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index d86b546cc..a6dfefbf6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.10.5" +version = "0.11.0" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" From ba0ddf281bad63cc01a07a4a25e44f75c2330e4d Mon Sep 17 00:00:00 2001 From: Abhik Jain Date: Sat, 1 Jul 2023 15:17:36 +0530 Subject: [PATCH 215/633] fix: take_while_inclusive takes iterator by value this helps in case TakeWhileInclusive is returned from a function, as otherwise we would be returning a struct which takes a reference from a locally created iterator. --- src/lib.rs | 16 ++++++++-------- src/take_while_inclusive.rs | 16 ++++++++-------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c23a65db5..c9bad16eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1519,7 +1519,7 @@ pub trait Itertools : Iterator { /// .collect(); /// let expected: Vec<_> = vec![1, 2, 3].into_iter().map(NoCloneImpl).collect(); /// assert_eq!(filtered, expected); - fn take_while_inclusive(&mut self, accept: F) -> TakeWhileInclusive + fn take_while_inclusive(self, accept: F) -> TakeWhileInclusive where Self: Sized, F: FnMut(&Self::Item) -> bool, @@ -2650,7 +2650,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2681,7 +2681,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable_by`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2716,7 +2716,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_unstable_by_key`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is unstable (i.e., may reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2752,7 +2752,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2783,7 +2783,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2818,7 +2818,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by_key`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -2855,7 +2855,7 @@ pub trait Itertools : Iterator { /// **Note:** This consumes the entire iterator, uses the /// [`slice::sort_by_cached_key`] method and returns the result as a new /// iterator that owns its elements. - /// + /// /// This sort is stable (i.e., does not reorder equal elements). /// /// The sorted iterator, if directly collected to a `Vec`, is converted diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index e2a7479e0..5ef1953d2 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -8,30 +8,30 @@ use std::fmt; /// See [`.take_while_inclusive()`](crate::Itertools::take_while_inclusive) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct TakeWhileInclusive<'a, I: 'a, F> { - iter: &'a mut I, +pub struct TakeWhileInclusive { + iter: I, predicate: F, done: bool, } -impl<'a, I, F> TakeWhileInclusive<'a, I, F> +impl TakeWhileInclusive where I: Iterator, F: FnMut(&I::Item) -> bool, { /// Create a new [`TakeWhileInclusive`] from an iterator and a predicate. - pub fn new(iter: &'a mut I, predicate: F) -> Self { + pub fn new(iter: I, predicate: F) -> Self { Self { iter, predicate, done: false} } } -impl<'a, I, F> fmt::Debug for TakeWhileInclusive<'a, I, F> +impl fmt::Debug for TakeWhileInclusive where I: Iterator + fmt::Debug, { debug_fmt_fields!(TakeWhileInclusive, iter); } -impl<'a, I, F> Iterator for TakeWhileInclusive<'a, I, F> +impl Iterator for TakeWhileInclusive where I: Iterator, F: FnMut(&I::Item) -> bool @@ -60,9 +60,9 @@ where } } -impl FusedIterator for TakeWhileInclusive<'_, I, F> +impl FusedIterator for TakeWhileInclusive where I: Iterator, F: FnMut(&I::Item) -> bool { -} \ No newline at end of file +} From 83f45a310e01846db0775ae80c9cba703bf430d0 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Thu, 13 Jul 2023 11:21:54 +0200 Subject: [PATCH 216/633] Add conversion into (Option,Option) to EitherOrBoth Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index cf65fe788..ba2428eda 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -464,7 +464,7 @@ impl EitherOrBoth { impl EitherOrBoth { /// Return either value of left, right, or apply a function `f` to both values if both are present. /// The input function has to return the same type as both Right and Left carry. - /// + /// /// # Examples /// ``` /// # use itertools::EitherOrBoth; @@ -493,3 +493,13 @@ impl Into>> for EitherOrBoth { } } } + +impl Into<(Option, Option)> for EitherOrBoth { + fn into(self) -> (Option, Option) { + match self.map_any(Some, Some) { + EitherOrBoth::Left(l) => (l, None), + EitherOrBoth::Right(r) => (None, r), + EitherOrBoth::Both(l, r) => (l, r), + } + } +} From ca23089e0baa4f1701d33d897c25ac5112a2d699 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Thu, 13 Jul 2023 11:31:48 +0200 Subject: [PATCH 217/633] Change to use From instead of Into Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index ba2428eda..b6bda9da0 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -494,9 +494,9 @@ impl Into>> for EitherOrBoth { } } -impl Into<(Option, Option)> for EitherOrBoth { - fn into(self) -> (Option, Option) { - match self.map_any(Some, Some) { +impl From> for (Option, Option) { + fn from(val: EitherOrBoth) -> Self { + match val.map_any(Some, Some) { EitherOrBoth::Left(l) => (l, None), EitherOrBoth::Right(r) => (None, r), EitherOrBoth::Both(l, r) => (l, r), From d171ceea2e5544126ab9e5308df69c82b65a79ac Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Sat, 15 Jul 2023 13:48:36 +0200 Subject: [PATCH 218/633] Add left_or_right() method to EitherOrBoth, delegate From impl to it. Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index b6bda9da0..481f1cd1e 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -65,6 +65,18 @@ impl EitherOrBoth { } } + /// Return tupel of options corresponding to the left and right value respectively + /// + /// If `Left` return `(Some(..), None)`, if `Right` return `(None,Some(..))`, else return + /// `(Some(..),Some(..))` + pub fn left_and_right(self) -> (Option, Option) { + match self.map_any(Some, Some) { + EitherOrBoth::Left(l) => (l, None), + EitherOrBoth::Right(r) => (None, r), + EitherOrBoth::Both(l, r) => (l, r), + } + } + /// If `Left`, return `Some` with the left value. If `Right` or `Both`, return `None`. /// /// # Examples @@ -496,10 +508,6 @@ impl Into>> for EitherOrBoth { impl From> for (Option, Option) { fn from(val: EitherOrBoth) -> Self { - match val.map_any(Some, Some) { - EitherOrBoth::Left(l) => (l, None), - EitherOrBoth::Right(r) => (None, r), - EitherOrBoth::Both(l, r) => (l, r), - } + val.left_and_right() } } From 44f5f7b0324e5abf5d785e6b093681a071ba6fd4 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Sat, 15 Jul 2023 13:50:53 +0200 Subject: [PATCH 219/633] Correct spelling of tuple Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 481f1cd1e..410f0563f 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -65,7 +65,7 @@ impl EitherOrBoth { } } - /// Return tupel of options corresponding to the left and right value respectively + /// Return tuple of options corresponding to the left and right value respectively /// /// If `Left` return `(Some(..), None)`, if `Right` return `(None,Some(..))`, else return /// `(Some(..),Some(..))` From 5fe48e5037701c7dc3fcd55b4d6e1d62233d508c Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Sat, 15 Jul 2023 13:52:06 +0200 Subject: [PATCH 220/633] Add inline(always) Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 410f0563f..43a24545c 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -507,6 +507,7 @@ impl Into>> for EitherOrBoth { } impl From> for (Option, Option) { + #[inline(always)] fn from(val: EitherOrBoth) -> Self { val.left_and_right() } From 10c00138e8afa4a7ab9ba5f74c19676236f2321d Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Wed, 26 Jul 2023 07:27:41 +0900 Subject: [PATCH 221/633] Add conversion from Either to EitherOrBoth --- src/either_or_both.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index cf65fe788..baf540c34 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -493,3 +493,12 @@ impl Into>> for EitherOrBoth { } } } + +impl From> for EitherOrBoth { + fn from(either: Either) -> Self { + match either { + Either::Left(l) => EitherOrBoth::Left(l), + Either::Right(l) => EitherOrBoth::Right(l), + } + } +} From 1b36b4fe50e38ee9a9f6f8960783392610a09a7d Mon Sep 17 00:00:00 2001 From: Chris Morin Date: Wed, 26 Jul 2023 18:52:10 -0700 Subject: [PATCH 222/633] Fix multiunzip documentation --- src/unziptuple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/unziptuple.rs b/src/unziptuple.rs index 7af29ec4a..2c79c2d84 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -1,6 +1,6 @@ /// Converts an iterator of tuples into a tuple of containers. /// -/// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each +/// `multiunzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each /// column. /// /// This function is, in some sense, the opposite of [`multizip`]. From d7a539f8e35ae8df58ec3d414b13e559ac0b58a8 Mon Sep 17 00:00:00 2001 From: Justus Fluegel Date: Thu, 27 Jul 2023 17:51:45 +0200 Subject: [PATCH 223/633] Remove From impl, change left_and_right impl to use .or_default() Signed-off-by: Justus Fluegel --- src/either_or_both.rs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 43a24545c..64e1c871c 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -70,11 +70,7 @@ impl EitherOrBoth { /// If `Left` return `(Some(..), None)`, if `Right` return `(None,Some(..))`, else return /// `(Some(..),Some(..))` pub fn left_and_right(self) -> (Option, Option) { - match self.map_any(Some, Some) { - EitherOrBoth::Left(l) => (l, None), - EitherOrBoth::Right(r) => (None, r), - EitherOrBoth::Both(l, r) => (l, r), - } + self.map_any(Some, Some).or_default() } /// If `Left`, return `Some` with the left value. If `Right` or `Both`, return `None`. @@ -505,10 +501,3 @@ impl Into>> for EitherOrBoth { } } } - -impl From> for (Option, Option) { - #[inline(always)] - fn from(val: EitherOrBoth) -> Self { - val.left_and_right() - } -} From 5721f3774002bd5e4823d1e46e75a701dd607727 Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Sun, 6 Aug 2023 18:49:51 -0600 Subject: [PATCH 224/633] `try_len` method --- src/lib.rs | 16 ++++++++++++++++ src/size_hint.rs | 9 +++++++++ 2 files changed, 25 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c9bad16eb..2c9abba00 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3840,6 +3840,22 @@ pub trait Itertools : Iterator { { MultiUnzip::multiunzip(self) } + + /// Returns the length of the iterator if one exists. + /// + /// ``` + /// use itertools::Itertools; + /// + /// assert_eq!([0; 10].into_iter().try_len(), Some(10)); + /// assert_eq!((10..15).try_len(), Some(5)); + /// assert_eq!((15..10).try_len(), Some(0)); + /// assert_eq!((10..).try_len(), None); + /// ``` + fn try_len(self) -> Option + where Self: Sized + { + size_hint::try_len(self) + } } impl Itertools for T where T: Iterator { } diff --git a/src/size_hint.rs b/src/size_hint.rs index 71ea1412b..e329165c0 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -117,3 +117,12 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { }; (lower, upper) } + +/// Returns the length of the iterator if one exists. +#[inline] +pub fn try_len(it: impl Iterator) -> Option { + match it.size_hint() { + (lo, Some(hi)) if lo == hi => Some(lo), + _ => None + } +} \ No newline at end of file From 6e54b0fa8b4615a799418dd50e0efc5ef1ff2c27 Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Sun, 6 Aug 2023 19:02:01 -0600 Subject: [PATCH 225/633] take by ref --- src/lib.rs | 2 +- src/size_hint.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2c9abba00..52a8f3e8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3851,7 +3851,7 @@ pub trait Itertools : Iterator { /// assert_eq!((15..10).try_len(), Some(0)); /// assert_eq!((10..).try_len(), None); /// ``` - fn try_len(self) -> Option + fn try_len(&self) -> Option where Self: Sized { size_hint::try_len(self) diff --git a/src/size_hint.rs b/src/size_hint.rs index e329165c0..b221583f7 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -120,7 +120,7 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { /// Returns the length of the iterator if one exists. #[inline] -pub fn try_len(it: impl Iterator) -> Option { +pub fn try_len(it: &impl Iterator) -> Option { match it.size_hint() { (lo, Some(hi)) if lo == hi => Some(lo), _ => None From 952d8c4c87826a0413f0ae7ed8a5aee257d1c0a9 Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Mon, 7 Aug 2023 20:48:54 -0600 Subject: [PATCH 226/633] match other `sizehint` function conventions --- src/lib.rs | 3 ++- src/size_hint.rs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 52a8f3e8a..1244ab9a2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3842,6 +3842,7 @@ pub trait Itertools : Iterator { } /// Returns the length of the iterator if one exists. + /// Relies on the [`size_hint`] of the iterator being correct. /// /// ``` /// use itertools::Itertools; @@ -3854,7 +3855,7 @@ pub trait Itertools : Iterator { fn try_len(&self) -> Option where Self: Sized { - size_hint::try_len(self) + size_hint::try_len(self.size_hint()) } } diff --git a/src/size_hint.rs b/src/size_hint.rs index b221583f7..dfe69d1d9 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -120,8 +120,8 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { /// Returns the length of the iterator if one exists. #[inline] -pub fn try_len(it: &impl Iterator) -> Option { - match it.size_hint() { +pub fn try_len(sh: SizeHint) -> Option { + match sh { (lo, Some(hi)) if lo == hi => Some(lo), _ => None } From 052423a8c971e1ef46890840cd662ab7ddb2f809 Mon Sep 17 00:00:00 2001 From: philipp Date: Tue, 8 Aug 2023 21:49:55 +0200 Subject: [PATCH 227/633] Improve documentation for EitherOrBoth::reduce --- src/either_or_both.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 8bd03dfdb..681f3ed7c 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -473,12 +473,20 @@ impl EitherOrBoth { /// Return either value of left, right, or apply a function `f` to both values if both are present. /// The input function has to return the same type as both Right and Left carry. /// + /// This function can be used to preferrably extract the left resp. right value, + /// but fall back to the other (i.e. right resp. left) if the preferred one is not present. + /// /// # Examples /// ``` /// # use itertools::EitherOrBoth; /// assert_eq!(EitherOrBoth::Both(3, 7).reduce(u32::max), 7); /// assert_eq!(EitherOrBoth::Left(3).reduce(u32::max), 3); /// assert_eq!(EitherOrBoth::Right(7).reduce(u32::max), 7); + /// + /// // Extract the left value if present, fall back to the right otherwise. + /// assert_eq!(EitherOrBoth::Left("left").reduce(|l, _r| l), "left"); + /// assert_eq!(EitherOrBoth::Right("right").reduce(|l, _r| l), "right"); + /// assert_eq!(EitherOrBoth::Both("left", "right").reduce(|l, _r| l), "left"); /// ``` pub fn reduce(self, f: F) -> T where From 87ad574a88b93e01b7ccbd8e62c85cf7f1cc43cf Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Tue, 8 Aug 2023 19:54:26 -0600 Subject: [PATCH 228/633] Result not Option, inline, no Sized bound, improve docs --- src/lib.rs | 19 ++++++++++++------- src/size_hint.rs | 6 +++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1244ab9a2..08bc2e059 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3842,18 +3842,23 @@ pub trait Itertools : Iterator { } /// Returns the length of the iterator if one exists. - /// Relies on the [`size_hint`] of the iterator being correct. + /// Otherwise return `self.size_hint()`. + /// + /// Fallible [`ExactSizeIterator::len`]. + /// + /// Inherits guarantees and restrictions from [`Iterator::size_hint`]. /// /// ``` /// use itertools::Itertools; /// - /// assert_eq!([0; 10].into_iter().try_len(), Some(10)); - /// assert_eq!((10..15).try_len(), Some(5)); - /// assert_eq!((15..10).try_len(), Some(0)); - /// assert_eq!((10..).try_len(), None); + /// assert_eq!([0; 10].iter().try_len(), Ok(10)); + /// assert_eq!((10..15).try_len(), Ok(5)); + /// assert_eq!((15..10).try_len(), Ok(0)); + /// assert_eq!((10..).try_len(), Err((usize::MAX, None))); + /// assert_eq!((10..15).filter(|x| x % 2 == 0).try_len(), Err((0, Some(5)))); /// ``` - fn try_len(&self) -> Option - where Self: Sized + #[inline] + fn try_len(&self) -> Result { size_hint::try_len(self.size_hint()) } diff --git a/src/size_hint.rs b/src/size_hint.rs index dfe69d1d9..dbd5624e6 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -120,9 +120,9 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { /// Returns the length of the iterator if one exists. #[inline] -pub fn try_len(sh: SizeHint) -> Option { +pub fn try_len(sh: SizeHint) -> Result { match sh { - (lo, Some(hi)) if lo == hi => Some(lo), - _ => None + (lo, Some(hi)) if lo == hi => Ok(lo), + _ => Err(sh) } } \ No newline at end of file From 068de35d95d345ea6bf112ecdf8de504cdb3b334 Mon Sep 17 00:00:00 2001 From: Easyoakland <97992568+Easyoakland@users.noreply.github.com> Date: Tue, 8 Aug 2023 23:21:03 -0600 Subject: [PATCH 229/633] inline function source code and remove annotation --- src/lib.rs | 10 ++++++---- src/size_hint.rs | 9 --------- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 08bc2e059..41bedfcab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3857,10 +3857,12 @@ pub trait Itertools : Iterator { /// assert_eq!((10..).try_len(), Err((usize::MAX, None))); /// assert_eq!((10..15).filter(|x| x % 2 == 0).try_len(), Err((0, Some(5)))); /// ``` - #[inline] - fn try_len(&self) -> Result - { - size_hint::try_len(self.size_hint()) + fn try_len(&self) -> Result { + let sh = self.size_hint(); + match sh { + (lo, Some(hi)) if lo == hi => Ok(lo), + _ => Err(sh), + } } } diff --git a/src/size_hint.rs b/src/size_hint.rs index dbd5624e6..71ea1412b 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -117,12 +117,3 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { }; (lower, upper) } - -/// Returns the length of the iterator if one exists. -#[inline] -pub fn try_len(sh: SizeHint) -> Result { - match sh { - (lo, Some(hi)) if lo == hi => Ok(lo), - _ => Err(sh) - } -} \ No newline at end of file From e85f3f6fbbef6077adc287dbee66864e54b8ef84 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 15 Aug 2023 22:06:08 +0200 Subject: [PATCH 230/633] Fuse the iterator in `LazyBuffer` --- src/combinations.rs | 4 ++-- src/lazy_buffer.rs | 16 ++++------------ 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 68a59c5e4..18adfc70e 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,5 +1,5 @@ use std::fmt; -use std::iter::FusedIterator; +use std::iter::{Fuse, FusedIterator}; use super::lazy_buffer::LazyBuffer; use alloc::vec::Vec; @@ -54,7 +54,7 @@ impl Combinations { /// Returns a reference to the source iterator. #[inline] - pub(crate) fn src(&self) -> &I { &self.pool.it } + pub(crate) fn src(&self) -> &Fuse { &self.pool.it } /// Resets this `Combinations` back to an initial state for combinations of length /// `k` over the same pool data source. If `k` is larger than the current length diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index ca24062aa..65cc8e2cd 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -1,10 +1,10 @@ +use std::iter::Fuse; use std::ops::Index; use alloc::vec::Vec; #[derive(Debug, Clone)] pub struct LazyBuffer { - pub it: I, - done: bool, + pub it: Fuse, buffer: Vec, } @@ -14,8 +14,7 @@ where { pub fn new(it: I) -> LazyBuffer { LazyBuffer { - it, - done: false, + it: it.fuse(), buffer: Vec::new(), } } @@ -25,26 +24,19 @@ where } pub fn get_next(&mut self) -> bool { - if self.done { - return false; - } if let Some(x) = self.it.next() { self.buffer.push(x); true } else { - self.done = true; false } } pub fn prefill(&mut self, len: usize) { let buffer_len = self.buffer.len(); - - if !self.done && len > buffer_len { + if len > buffer_len { let delta = len - buffer_len; - self.buffer.extend(self.it.by_ref().take(delta)); - self.done = self.buffer.len() < len; } } } From 80a22ac2706c6f9b1318fe724a4e6727b9532f05 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 12:37:02 +0200 Subject: [PATCH 231/633] `LazyBuffer::size_hint` --- src/lazy_buffer.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 65cc8e2cd..88ee06c7c 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -2,6 +2,8 @@ use std::iter::Fuse; use std::ops::Index; use alloc::vec::Vec; +use crate::size_hint::{self, SizeHint}; + #[derive(Debug, Clone)] pub struct LazyBuffer { pub it: Fuse, @@ -23,6 +25,10 @@ where self.buffer.len() } + pub fn size_hint(&self) -> SizeHint { + size_hint::add_scalar(self.it.size_hint(), self.len()) + } + pub fn get_next(&mut self) -> bool { if let Some(x) = self.it.next() { self.buffer.push(x); From d9f20e9a672707b14bfbfa57d0d26853d0cfb196 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 13:13:41 +0200 Subject: [PATCH 232/633] Checked binomial See the end of this: https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages --- src/combinations.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index 18adfc70e..f4adde995 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -126,3 +126,17 @@ impl FusedIterator for Combinations where I: Iterator, I::Item: Clone {} + +pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { + if n < k { + return Some(0); + } + // `factorial(n) / factorial(n - k) / factorial(k)` but trying to avoid it overflows: + k = (n - k).min(k); // symmetry + let mut c = 1; + for i in 1..=k { + c = (c / i).checked_mul(n)?.checked_add((c % i).checked_mul(n)? / i)?; + n -= 1; + } + Some(c) +} From bd0b08047a2b04bf6fa008d7eda194c1bdbefec6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 13:19:34 +0200 Subject: [PATCH 233/633] Remaining combinations for an unknown `n` Because we give combinations in the lexicographic order, we can use the indices to count the remaining combinations to come. We are gonna heavily test it thought. --- src/combinations.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index f4adde995..8b23eca15 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -77,6 +77,21 @@ impl Combinations { self.pool.prefill(k); } } + + /// For a given size `n`, return the count of remaining elements or None if it would overflow. + fn remaining_for(&self, n: usize) -> Option { + let k = self.k(); + if self.first { + checked_binomial(n, k) + } else { + self.indices + .iter() + .enumerate() + .fold(Some(0), |sum, (k0, n0)| { + sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - k0)?)) + }) + } + } } impl Iterator for Combinations From 76efb2555a01f66dd96f4608d236f93685b109f1 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 13:25:26 +0200 Subject: [PATCH 234/633] `LazyBuffer::fill` The whole point of the lazy buffer is to not fill the pool all at once but in some cases we want to, and then it's better than do it with `while lazy_buffer.get_next() {}` that would be weird. --- src/lazy_buffer.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 88ee06c7c..7fcb165fc 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -45,6 +45,10 @@ where self.buffer.extend(self.it.by_ref().take(delta)); } } + + pub fn fill(&mut self) { + self.buffer.extend(self.it.by_ref()); + } } impl Index for LazyBuffer From 85c7980b9c034614df34e517c624ffe6c51bc16a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 13:30:46 +0200 Subject: [PATCH 235/633] `Combinations::count` tested --- src/combinations.rs | 5 +++++ tests/test_std.rs | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index 8b23eca15..c67af0fb1 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -135,6 +135,11 @@ impl Iterator for Combinations // Create result vector based on the indices Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) } + + fn count(mut self) -> usize { + self.pool.fill(); + self.remaining_for(self.n()).expect("Iterator count greater than usize::MAX") + } } impl FusedIterator for Combinations diff --git a/tests/test_std.rs b/tests/test_std.rs index 77207d87e..b70c206a2 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -909,6 +909,20 @@ fn combinations_zero() { it::assert_equal((0..0).combinations(0), vec![vec![]]); } +#[test] +fn combinations_range_count() { + for n in 0..6 { + for k in 0..=n { + let len = (n - k + 1..=n).product::() / (1..=k).product::(); + let mut it = (0..n).combinations(k); + for count in (0..=len).rev() { + assert_eq!(it.clone().count(), count); + assert_eq!(it.next().is_none(), count == 0); + } + } + } +} + #[test] fn permutations_zero() { it::assert_equal((1..3).permutations(0), vec![vec![]]); From ed998ba41c25251ffcaf52dbc92be6383f57c787 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 16 Aug 2023 13:41:17 +0200 Subject: [PATCH 236/633] `Combinations::size_hint` --- src/combinations.rs | 7 +++++++ tests/test_std.rs | 1 + 2 files changed, 8 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index c67af0fb1..ca30a57d0 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -136,6 +136,13 @@ impl Iterator for Combinations Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) } + fn size_hint(&self) -> (usize, Option) { + let (mut low, mut upp) = self.pool.size_hint(); + low = self.remaining_for(low).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| self.remaining_for(upp)); + (low, upp) + } + fn count(mut self) -> usize { self.pool.fill(); self.remaining_for(self.n()).expect("Iterator count greater than usize::MAX") diff --git a/tests/test_std.rs b/tests/test_std.rs index b70c206a2..701ef72db 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -916,6 +916,7 @@ fn combinations_range_count() { let len = (n - k + 1..=n).product::() / (1..=k).product::(); let mut it = (0..n).combinations(k); for count in (0..=len).rev() { + assert_eq!(it.size_hint(), (count, Some(count))); assert_eq!(it.clone().count(), count); assert_eq!(it.next().is_none(), count == 0); } From 2ab0ff199293b68f1e8ff38db373c439dac24222 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 17 Aug 2023 00:14:26 +0200 Subject: [PATCH 237/633] Remove `LazyBuffer::fill`, count instead `pool.it.count()` consumes `pool` before we call `remaining_for` so it can not be a method and therefore becomes a free function. --- src/combinations.rs | 41 +++++++++++++++++++++-------------------- src/lazy_buffer.rs | 4 ---- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index ca30a57d0..5c7a60a20 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -77,21 +77,6 @@ impl Combinations { self.pool.prefill(k); } } - - /// For a given size `n`, return the count of remaining elements or None if it would overflow. - fn remaining_for(&self, n: usize) -> Option { - let k = self.k(); - if self.first { - checked_binomial(n, k) - } else { - self.indices - .iter() - .enumerate() - .fold(Some(0), |sum, (k0, n0)| { - sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - k0)?)) - }) - } - } } impl Iterator for Combinations @@ -138,14 +123,15 @@ impl Iterator for Combinations fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); - low = self.remaining_for(low).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| self.remaining_for(upp)); + low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); (low, upp) } - fn count(mut self) -> usize { - self.pool.fill(); - self.remaining_for(self.n()).expect("Iterator count greater than usize::MAX") + fn count(self) -> usize { + let Self { indices, pool, first } = self; + let n = pool.len() + pool.it.count(); + remaining_for(n, first, &indices).expect("Iterator count greater than usize::MAX") } } @@ -167,3 +153,18 @@ pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { } Some(c) } + +/// For a given size `n`, return the count of remaining combinations or None if it would overflow. +fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { + let k = indices.len(); + if first { + checked_binomial(n, k) + } else { + indices + .iter() + .enumerate() + .fold(Some(0), |sum, (k0, n0)| { + sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - k0)?)) + }) + } +} diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 7fcb165fc..88ee06c7c 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -45,10 +45,6 @@ where self.buffer.extend(self.it.by_ref().take(delta)); } } - - pub fn fill(&mut self) { - self.buffer.extend(self.it.by_ref()); - } } impl Index for LazyBuffer From 1f48da730fa47a565317e0cee746f894561d66a5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 12:14:27 +0200 Subject: [PATCH 238/633] `checked_binomial`: url algorithm --- src/combinations.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/combinations.rs b/src/combinations.rs index 5c7a60a20..f0215e341 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -140,6 +140,7 @@ impl FusedIterator for Combinations I::Item: Clone {} +// https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { if n < k { return Some(0); From 95c3e277d4dbf96e4153fd9cb5d3fe7530a3aad7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 12:20:41 +0200 Subject: [PATCH 239/633] remaining combinations: handle `n < k` --- src/combinations.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/combinations.rs b/src/combinations.rs index f0215e341..f99df521c 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -158,7 +158,9 @@ pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { /// For a given size `n`, return the count of remaining combinations or None if it would overflow. fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); - if first { + if n < k { + Some(0) + } else if first { checked_binomial(n, k) } else { indices From da190e303be006efa6edc4e8f5d2ded5f6b78d43 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 12:39:00 +0200 Subject: [PATCH 240/633] update `combinations_range_count` test --- tests/test_std.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 701ef72db..a351f4b3e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -911,15 +911,22 @@ fn combinations_zero() { #[test] fn combinations_range_count() { - for n in 0..6 { + for n in 0..=10 { for k in 0..=n { let len = (n - k + 1..=n).product::() / (1..=k).product::(); let mut it = (0..n).combinations(k); - for count in (0..=len).rev() { - assert_eq!(it.size_hint(), (count, Some(count))); - assert_eq!(it.clone().count(), count); - assert_eq!(it.next().is_none(), count == 0); + assert_eq!(len, it.clone().count()); + assert_eq!(len, it.size_hint().0); + assert_eq!(Some(len), it.size_hint().1); + for count in (0..len).rev() { + let elem = it.next(); + assert!(elem.is_some()); + assert_eq!(count, it.clone().count()); + assert_eq!(count, it.size_hint().0); + assert_eq!(Some(count), it.size_hint().1); } + let should_be_none = it.next(); + assert!(should_be_none.is_none()); } } } From baf6708aa1d44494609d469bb2fa2018ba498ae7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 12:12:47 +0200 Subject: [PATCH 241/633] `Combinations::count`: unwrap instead of expect And add todo --- src/combinations.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/combinations.rs b/src/combinations.rs index f99df521c..be73f29fe 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -130,8 +130,9 @@ impl Iterator for Combinations fn count(self) -> usize { let Self { indices, pool, first } = self; + // TODO: make `pool.it` private let n = pool.len() + pool.it.count(); - remaining_for(n, first, &indices).expect("Iterator count greater than usize::MAX") + remaining_for(n, first, &indices).unwrap() } } From d8d4d61e4e027bf84d1799688f61b064d2f0c0b4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 13:02:28 +0200 Subject: [PATCH 242/633] `remaining_for`: add an explanation comment --- src/combinations.rs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index be73f29fe..da5c41377 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -164,11 +164,29 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { } else if first { checked_binomial(n, k) } else { + // https://en.wikipedia.org/wiki/Combinatorial_number_system + // http://www.site.uottawa.ca/~lucia/courses/5165-09/GenCombObj.pdf + + // The combinations generated after the current one can be counted by counting as follows: + // - The subsequent combinations that differ in indices[0]: + // If subsequent combinations differ in indices[0], then their value for indices[0] + // must be at least 1 greater than the current indices[0]. + // As indices is strictly monotonically sorted, this means we can effectively choose k values + // from (n - 1 - indices[0]), leading to binomial(n - 1 - indices[0], k) possibilities. + // - The subsequent combinations with same indices[0], but differing indices[1]: + // Here we can choose k - 1 values from (n - 1 - indices[1]) values, + // leading to binomial(n - 1 - indices[1], k - 1) possibilities. + // - (...) + // - The subsequent combinations with same indices[0..=i], but differing indices[i]: + // Here we can choose k - i values from (n - 1 - indices[i]) values: binomial(n - 1 - indices[i], k - i). + // Since subsequent combinations can in any index, we must sum up the aforementioned binomial coefficients. + + // Below, `n0` resembles indices[i]. indices .iter() .enumerate() - .fold(Some(0), |sum, (k0, n0)| { - sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - k0)?)) + .fold(Some(0), |sum, (i, n0)| { + sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - i)?)) }) } } From cfb2ec99fc24db4f98a057e9144dcf8ac4477a49 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 13:11:22 +0200 Subject: [PATCH 243/633] TODO: simplify when MSRV hits 1.37 --- src/combinations.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index da5c41377..df49c6ab2 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -185,6 +185,9 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { indices .iter() .enumerate() + // TODO: Once the MSRV hits 1.37.0, we can sum options instead: + // .map(|(i, n0)| checked_binomial(n - 1 - *n0, k - i)) + // .sum() .fold(Some(0), |sum, (i, n0)| { sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - i)?)) }) From 65e08d49659f41e46e6d4452a3c195daddcc330d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 18 Aug 2023 13:35:36 +0200 Subject: [PATCH 244/633] Test `checked_binomial` Only test for `n <= 500` but only a few values of the last row do not already have overflow on a 64 bits computer. NOTE: Not faster with an array updated in-place. --- src/combinations.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index df49c6ab2..c50e95d01 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -156,6 +156,25 @@ pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { Some(c) } +#[test] +fn test_checked_binomial() { + // With the first row: [1, 0, 0, ...] and the first column full of 1s, we check + // row by row the recurrence relation of binomials (which is an equivalent definition). + // For n >= 1 and k >= 1 we have: + // binomial(n, k) == binomial(n - 1, k - 1) + binomial(n - 1, k) + const LIMIT: usize = 500; + let mut row = vec![Some(0); LIMIT + 1]; + row[0] = Some(1); + for n in 0..=LIMIT { + for k in 0..=LIMIT { + assert_eq!(row[k], checked_binomial(n, k)); + } + row = std::iter::once(Some(1)) + .chain((1..=LIMIT).map(|k| row[k - 1]?.checked_add(row[k]?))) + .collect(); + } +} + /// For a given size `n`, return the count of remaining combinations or None if it would overflow. fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); From e866da2b37c4b0ce015bb0a71b2ec985944f4023 Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 18 Aug 2023 15:35:04 +0200 Subject: [PATCH 245/633] Introduce LazyBuffer::count --- src/combinations.rs | 3 +-- src/lazy_buffer.rs | 4 ++++ src/permutations.rs | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index c50e95d01..cc0df7893 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -130,8 +130,7 @@ impl Iterator for Combinations fn count(self) -> usize { let Self { indices, pool, first } = self; - // TODO: make `pool.it` private - let n = pool.len() + pool.it.count(); + let n = pool.count(); remaining_for(n, first, &indices).unwrap() } } diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 88ee06c7c..20e4a32d9 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -29,6 +29,10 @@ where size_hint::add_scalar(self.it.size_hint(), self.len()) } + pub fn count(self) -> usize { + self.len() + self.it.count() + } + pub fn get_next(&mut self) -> bool { if let Some(x) = self.it.next() { self.buffer.push(x); diff --git a/src/permutations.rs b/src/permutations.rs index d03b85262..a8885a411 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -134,14 +134,14 @@ where let Permutations { vals, state } = self; match state { PermutationState::StartUnknownLen { k } => { - let n = vals.len() + vals.it.count(); + let n = vals.count(); let complete_state = CompleteState::Start { n, k }; from_complete(complete_state) } PermutationState::OngoingUnknownLen { k, min_n } => { let prev_iteration_count = min_n - k + 1; - let n = vals.len() + vals.it.count(); + let n = vals.count(); let complete_state = CompleteState::Start { n, k }; from_complete(complete_state) - prev_iteration_count From ad81ee19116e4cf9169f670290c5754713e0d8cb Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 18 Aug 2023 15:40:08 +0200 Subject: [PATCH 246/633] Use LazyBuffer::size_hint --- src/combinations.rs | 6 +++--- src/powerset.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index cc0df7893..a5b34fc95 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,5 +1,5 @@ use std::fmt; -use std::iter::{Fuse, FusedIterator}; +use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; use alloc::vec::Vec; @@ -52,9 +52,9 @@ impl Combinations { #[inline] pub fn n(&self) -> usize { self.pool.len() } - /// Returns a reference to the source iterator. + /// Returns a reference to the source pool. #[inline] - pub(crate) fn src(&self) -> &Fuse { &self.pool.it } + pub(crate) fn src(&self) -> &LazyBuffer { &self.pool } /// Resets this `Combinations` back to an initial state for combinations of length /// `k` over the same pool data source. If `k` is larger than the current length diff --git a/src/powerset.rs b/src/powerset.rs index 4d7685b12..4175f013b 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -68,7 +68,7 @@ impl Iterator for Powerset fn size_hint(&self) -> (usize, Option) { // Total bounds for source iterator. - let src_total = size_hint::add_scalar(self.combs.src().size_hint(), self.combs.n()); + let src_total = self.combs.src().size_hint(); // Total bounds for self ( length(powerset(set) == 2 ^ length(set) ) let self_total = size_hint::pow_scalar_base(2, src_total); From 638a33bf1699c769f75a30d460f4a34d7513140c Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 18 Aug 2023 15:40:32 +0200 Subject: [PATCH 247/633] Make LazyBuffer::it private --- src/lazy_buffer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 20e4a32d9..80c171896 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -6,7 +6,7 @@ use crate::size_hint::{self, SizeHint}; #[derive(Debug, Clone)] pub struct LazyBuffer { - pub it: Fuse, + it: Fuse, buffer: Vec, } From cb46a5fa773ddadb7aa49270610eae7f4ef4731b Mon Sep 17 00:00:00 2001 From: philipp Date: Fri, 18 Aug 2023 16:01:54 +0200 Subject: [PATCH 248/633] Test Combinations::size_hint/count also for k>n --- tests/test_std.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index a351f4b3e..3d6d66cf1 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -912,8 +912,12 @@ fn combinations_zero() { #[test] fn combinations_range_count() { for n in 0..=10 { - for k in 0..=n { - let len = (n - k + 1..=n).product::() / (1..=k).product::(); + for k in 0..=10 { + let len = if k<=n { + (n - k + 1..=n).product::() / (1..=k).product::() + } else { + 0 + }; let mut it = (0..n).combinations(k); assert_eq!(len, it.clone().count()); assert_eq!(len, it.size_hint().0); From f920a9c75879a37d2ed2487bd882c6b9e92e41f4 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Sat, 17 Jun 2023 17:56:11 +0200 Subject: [PATCH 249/633] Move `Merge/MergeBy` from "adaptors" to "merge_join" --- src/adaptors/mod.rs | 138 +------------------------------------------- src/free.rs | 3 +- src/lib.rs | 6 +- src/merge_join.rs | 138 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+), 143 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 1695bbd65..a02c6b6e0 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -15,7 +15,7 @@ pub use self::map::MapResults; pub use self::multi_product::*; use std::fmt; -use std::iter::{Fuse, Peekable, FromIterator, FusedIterator}; +use std::iter::{Fuse, FromIterator, FusedIterator}; use std::marker::PhantomData; use crate::size_hint; @@ -466,142 +466,6 @@ impl ExactSizeIterator for Step where I: ExactSizeIterator {} -pub trait MergePredicate { - fn merge_pred(&mut self, a: &T, b: &T) -> bool; -} - -#[derive(Clone, Debug)] -pub struct MergeLte; - -impl MergePredicate for MergeLte { - fn merge_pred(&mut self, a: &T, b: &T) -> bool { - a <= b - } -} - -/// An iterator adaptor that merges the two base iterators in ascending order. -/// If both base iterators are sorted (ascending), the result is sorted. -/// -/// Iterator element type is `I::Item`. -/// -/// See [`.merge()`](crate::Itertools::merge_by) for more information. -pub type Merge = MergeBy; - -/// Create an iterator that merges elements in `i` and `j`. -/// -/// [`IntoIterator`] enabled version of [`Itertools::merge`](crate::Itertools::merge). -/// -/// ``` -/// use itertools::merge; -/// -/// for elt in merge(&[1, 2, 3], &[2, 3, 4]) { -/// /* loop body */ -/// } -/// ``` -pub fn merge(i: I, j: J) -> Merge<::IntoIter, ::IntoIter> - where I: IntoIterator, - J: IntoIterator, - I::Item: PartialOrd -{ - merge_by_new(i, j, MergeLte) -} - -/// An iterator adaptor that merges the two base iterators in ascending order. -/// If both base iterators are sorted (ascending), the result is sorted. -/// -/// Iterator element type is `I::Item`. -/// -/// See [`.merge_by()`](crate::Itertools::merge_by) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MergeBy - where I: Iterator, - J: Iterator -{ - a: Peekable, - b: Peekable, - fused: Option, - cmp: F, -} - -impl fmt::Debug for MergeBy - where I: Iterator + fmt::Debug, J: Iterator + fmt::Debug, - I::Item: fmt::Debug, -{ - debug_fmt_fields!(MergeBy, a, b); -} - -implbool> MergePredicate for F { - fn merge_pred(&mut self, a: &T, b: &T) -> bool { - self(a, b) - } -} - -/// Create a `MergeBy` iterator. -pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy - where I: IntoIterator, - J: IntoIterator, - F: MergePredicate, -{ - MergeBy { - a: a.into_iter().peekable(), - b: b.into_iter().peekable(), - fused: None, - cmp, - } -} - -impl Clone for MergeBy - where I: Iterator, - J: Iterator, - Peekable: Clone, - Peekable: Clone, - F: Clone -{ - clone_fields!(a, b, fused, cmp); -} - -impl Iterator for MergeBy - where I: Iterator, - J: Iterator, - F: MergePredicate -{ - type Item = I::Item; - - fn next(&mut self) -> Option { - let less_than = match self.fused { - Some(lt) => lt, - None => match (self.a.peek(), self.b.peek()) { - (Some(a), Some(b)) => self.cmp.merge_pred(a, b), - (Some(_), None) => { - self.fused = Some(true); - true - } - (None, Some(_)) => { - self.fused = Some(false); - false - } - (None, None) => return None, - } - }; - if less_than { - self.a.next() - } else { - self.b.next() - } - } - - fn size_hint(&self) -> (usize, Option) { - // Not ExactSizeIterator because size may be larger than usize - size_hint::add(self.a.size_hint(), self.b.size_hint()) - } -} - -impl FusedIterator for MergeBy - where I: FusedIterator, - J: FusedIterator, - F: MergePredicate -{} - /// An iterator adaptor that borrows from a `Clone`-able iterator /// to only pick off elements while the predicate returns `true`. /// diff --git a/src/free.rs b/src/free.rs index 19e3e2869..c9a87cd0a 100644 --- a/src/free.rs +++ b/src/free.rs @@ -19,7 +19,6 @@ use crate::intersperse::{Intersperse, IntersperseWith}; pub use crate::adaptors::{ interleave, - merge, put_back, }; #[cfg(feature = "use_alloc")] @@ -31,7 +30,7 @@ pub use crate::peek_nth::peek_nth; #[cfg(feature = "use_alloc")] pub use crate::kmerge_impl::kmerge; pub use crate::zip_eq_impl::zip_eq; -pub use crate::merge_join::merge_join_by; +pub use crate::merge_join::{merge, merge_join_by}; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::rciter; diff --git a/src/lib.rs b/src/lib.rs index 41bedfcab..9b7002486 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,8 +99,6 @@ pub mod structs { Batching, MapInto, MapOk, - Merge, - MergeBy, TakeWhileRef, WhileSome, Coalesce, @@ -127,7 +125,7 @@ pub mod structs { pub use crate::intersperse::{Intersperse, IntersperseWith}; #[cfg(feature = "use_alloc")] pub use crate::kmerge_impl::{KMerge, KMergeBy}; - pub use crate::merge_join::MergeJoinBy; + pub use crate::merge_join::{Merge, MergeBy, MergeJoinBy}; #[cfg(feature = "use_alloc")] pub use crate::multipeek_impl::MultiPeek; #[cfg(feature = "use_alloc")] @@ -1008,7 +1006,7 @@ pub trait Itertools : Iterator { J: IntoIterator, F: FnMut(&Self::Item, &Self::Item) -> bool { - adaptors::merge_by_new(self, other.into_iter(), is_first) + merge_join::merge_by_new(self, other.into_iter(), is_first) } /// Create an iterator that merges items from both this and the specified diff --git a/src/merge_join.rs b/src/merge_join.rs index 84f7d0333..0304e5f8e 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -1,5 +1,6 @@ use std::cmp::Ordering; use std::iter::Fuse; +use std::iter::{Peekable, FusedIterator}; use std::fmt; use either::Either; @@ -10,6 +11,143 @@ use crate::size_hint::{self, SizeHint}; #[cfg(doc)] use crate::Itertools; +pub trait MergePredicate { + fn merge_pred(&mut self, a: &T, b: &T) -> bool; +} + +#[derive(Clone, Debug)] +pub struct MergeLte; + +impl MergePredicate for MergeLte { + fn merge_pred(&mut self, a: &T, b: &T) -> bool { + a <= b + } +} + +/// An iterator adaptor that merges the two base iterators in ascending order. +/// If both base iterators are sorted (ascending), the result is sorted. +/// +/// Iterator element type is `I::Item`. +/// +/// See [`.merge()`](crate::Itertools::merge_by) for more information. +pub type Merge = MergeBy; + +/// Create an iterator that merges elements in `i` and `j`. +/// +/// [`IntoIterator`] enabled version of [`Itertools::merge`](crate::Itertools::merge). +/// +/// ``` +/// use itertools::merge; +/// +/// for elt in merge(&[1, 2, 3], &[2, 3, 4]) { +/// /* loop body */ +/// } +/// ``` +pub fn merge(i: I, j: J) -> Merge<::IntoIter, ::IntoIter> + where I: IntoIterator, + J: IntoIterator, + I::Item: PartialOrd +{ + merge_by_new(i, j, MergeLte) +} + +/// An iterator adaptor that merges the two base iterators in ascending order. +/// If both base iterators are sorted (ascending), the result is sorted. +/// +/// Iterator element type is `I::Item`. +/// +/// See [`.merge_by()`](crate::Itertools::merge_by) for more information. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct MergeBy + where I: Iterator, + J: Iterator +{ + a: Peekable, + b: Peekable, + fused: Option, + cmp: F, +} + +impl fmt::Debug for MergeBy + where I: Iterator + fmt::Debug, J: Iterator + fmt::Debug, + I::Item: fmt::Debug, +{ + debug_fmt_fields!(MergeBy, a, b); +} + +implbool> MergePredicate for F { + fn merge_pred(&mut self, a: &T, b: &T) -> bool { + self(a, b) + } +} + +/// Create a `MergeBy` iterator. +pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy + where I: IntoIterator, + J: IntoIterator, + F: MergePredicate, +{ + MergeBy { + a: a.into_iter().peekable(), + b: b.into_iter().peekable(), + fused: None, + cmp, + } +} + +impl Clone for MergeBy + where I: Iterator, + J: Iterator, + Peekable: Clone, + Peekable: Clone, + F: Clone +{ + clone_fields!(a, b, fused, cmp); +} + +impl Iterator for MergeBy + where I: Iterator, + J: Iterator, + F: MergePredicate +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + let less_than = match self.fused { + Some(lt) => lt, + None => match (self.a.peek(), self.b.peek()) { + (Some(a), Some(b)) => self.cmp.merge_pred(a, b), + (Some(_), None) => { + self.fused = Some(true); + true + } + (None, Some(_)) => { + self.fused = Some(false); + false + } + (None, None) => return None, + } + }; + if less_than { + self.a.next() + } else { + self.b.next() + } + } + + fn size_hint(&self) -> (usize, Option) { + // Not ExactSizeIterator because size may be larger than usize + size_hint::add(self.a.size_hint(), self.b.size_hint()) + } +} + +impl FusedIterator for MergeBy + where I: FusedIterator, + J: FusedIterator, + F: MergePredicate +{} + + /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// [`IntoIterator`] enabled version of [`Itertools::merge_join_by`]. From ed13b5c5d2bf199672dd6d9128c65a01cd4301b0 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 10:06:26 +0200 Subject: [PATCH 250/633] `Combinations::n_and_count` `Powerset::count` will need both `n` and the count of combinations. --- src/combinations.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index a5b34fc95..4b0742a0d 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -77,6 +77,12 @@ impl Combinations { self.pool.prefill(k); } } + + pub(crate) fn n_and_count(self) -> (usize, usize) { + let Self { indices, pool, first } = self; + let n = pool.count(); + (n, remaining_for(n, first, &indices).unwrap()) + } } impl Iterator for Combinations @@ -128,10 +134,9 @@ impl Iterator for Combinations (low, upp) } + #[inline] fn count(self) -> usize { - let Self { indices, pool, first } = self; - let n = pool.count(); - remaining_for(n, first, &indices).unwrap() + self.n_and_count().1 } } From e3b17a393a7fac44d4a4a320afd6f42c15ed45ef Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 10:07:46 +0200 Subject: [PATCH 251/633] `Powerset::count` --- src/powerset.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/powerset.rs b/src/powerset.rs index 4175f013b..f5d79d807 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -3,7 +3,7 @@ use std::iter::FusedIterator; use std::usize; use alloc::vec::Vec; -use super::combinations::{Combinations, combinations}; +use super::combinations::{Combinations, checked_binomial, combinations}; use super::size_hint; /// An iterator to iterate through the powerset of the elements from an iterator. @@ -81,6 +81,12 @@ impl Iterator for Powerset (0, self_total.1) } } + + fn count(self) -> usize { + let k = self.combs.k(); + let (n, combs_count) = self.combs.n_and_count(); + combs_count + (k + 1..=n).map(|i| checked_binomial(n, i).unwrap()).sum::() + } } impl FusedIterator for Powerset From 516d24ff2673f7132018bbc06a8a1e8c2a1b7863 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 10:17:39 +0200 Subject: [PATCH 252/633] Test `Powerset::count` --- tests/test_std.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 3d6d66cf1..c6034122f 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -989,6 +989,19 @@ fn powerset() { assert_eq!((0..4).powerset().count(), 1 << 4); assert_eq!((0..8).powerset().count(), 1 << 8); assert_eq!((0..16).powerset().count(), 1 << 16); + + for n in 0..=10 { + let mut it = (0..n).powerset(); + let len = 1 << n; + assert_eq!(len, it.clone().count()); + for count in (0..len).rev() { + let elem = it.next(); + assert!(elem.is_some()); + assert_eq!(count, it.clone().count()); + } + let should_be_none = it.next(); + assert!(should_be_none.is_none()); + } } #[test] From 913678a07166120fc59257402d2102e0eca2fafb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 11:48:48 +0200 Subject: [PATCH 253/633] Remove the `pos` field of `Powerset` --- src/powerset.rs | 30 +++--------------------------- src/size_hint.rs | 1 + 2 files changed, 4 insertions(+), 27 deletions(-) diff --git a/src/powerset.rs b/src/powerset.rs index f5d79d807..dde7d80e6 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -4,7 +4,6 @@ use std::usize; use alloc::vec::Vec; use super::combinations::{Combinations, checked_binomial, combinations}; -use super::size_hint; /// An iterator to iterate through the powerset of the elements from an iterator. /// @@ -13,22 +12,20 @@ use super::size_hint; #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Powerset { combs: Combinations, - // Iterator `position` (equal to count of yielded elements). - pos: usize, } impl Clone for Powerset where I: Clone + Iterator, I::Item: Clone, { - clone_fields!(combs, pos); + clone_fields!(combs); } impl fmt::Debug for Powerset where I: Iterator + fmt::Debug, I::Item: fmt::Debug, { - debug_fmt_fields!(Powerset, combs, pos); + debug_fmt_fields!(Powerset, combs); } /// Create a new `Powerset` from a clonable iterator. @@ -38,7 +35,6 @@ pub fn powerset(src: I) -> Powerset { Powerset { combs: combinations(src, 0), - pos: 0, } } @@ -51,37 +47,17 @@ impl Iterator for Powerset fn next(&mut self) -> Option { if let Some(elt) = self.combs.next() { - self.pos = self.pos.saturating_add(1); Some(elt) } else if self.combs.k() < self.combs.n() || self.combs.k() == 0 { self.combs.reset(self.combs.k() + 1); - self.combs.next().map(|elt| { - self.pos = self.pos.saturating_add(1); - elt - }) + self.combs.next() } else { None } } - fn size_hint(&self) -> (usize, Option) { - // Total bounds for source iterator. - let src_total = self.combs.src().size_hint(); - - // Total bounds for self ( length(powerset(set) == 2 ^ length(set) ) - let self_total = size_hint::pow_scalar_base(2, src_total); - - if self.pos < usize::MAX { - // Subtract count of elements already yielded from total. - size_hint::sub_scalar(self_total, self.pos) - } else { - // Fallback: self.pos is saturated and no longer reliable. - (0, self_total.1) - } - } - fn count(self) -> usize { let k = self.combs.k(); let (n, combs_count) = self.combs.n_and_count(); diff --git a/src/size_hint.rs b/src/size_hint.rs index 71ea1412b..1146445b5 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -77,6 +77,7 @@ pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint { /// Raise `base` correctly by a `SizeHint` exponent. #[inline] +#[allow(dead_code)] pub fn pow_scalar_base(base: usize, exp: SizeHint) -> SizeHint { let exp_low = cmp::min(exp.0, u32::MAX as usize) as u32; let low = base.saturating_pow(exp_low); From 6d92ac3c1910dc3c8b04c8aa6ccfc693185dc0fb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 11:52:47 +0200 Subject: [PATCH 254/633] Add free function `remaining_for` It will be used two other times making this function useful. --- src/powerset.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/powerset.rs b/src/powerset.rs index dde7d80e6..9b2cb797b 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -61,7 +61,7 @@ impl Iterator for Powerset fn count(self) -> usize { let k = self.combs.k(); let (n, combs_count) = self.combs.n_and_count(); - combs_count + (k + 1..=n).map(|i| checked_binomial(n, i).unwrap()).sum::() + remaining_for(combs_count, n, k).unwrap() } } @@ -70,3 +70,9 @@ impl FusedIterator for Powerset I: Iterator, I::Item: Clone, {} + +fn remaining_for(init_count: usize, n: usize, k: usize) -> Option { + (k + 1..=n).fold(Some(init_count), |sum, i| { + sum.and_then(|s| s.checked_add(checked_binomial(n, i)?)) + }) +} From bbc1885c96354dde4a2161e204168d9fcc6d975a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 12:03:35 +0200 Subject: [PATCH 255/633] Add back `Powerset::size_hint` --- src/powerset.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/powerset.rs b/src/powerset.rs index 9b2cb797b..18ca82b32 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -58,6 +58,17 @@ impl Iterator for Powerset } } + fn size_hint(&self) -> (usize, Option) { + let k = self.combs.k(); + // Total bounds for source iterator. + let (n_min, n_max) = self.combs.src().size_hint(); + // Total bounds for the current combinations. + let (mut low, mut upp) = self.combs.size_hint(); + low = remaining_for(low, n_min, k).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, n_max?, k)); + (low, upp) + } + fn count(self) -> usize { let k = self.combs.k(); let (n, combs_count) = self.combs.n_and_count(); From 75ead8ea9cc52ef4511e6275d6ed89f72e99697c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 22 Aug 2023 12:05:11 +0200 Subject: [PATCH 256/633] Test size hint alongside `Powerset` counts --- tests/test_std.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index c6034122f..7ed875144 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -994,10 +994,14 @@ fn powerset() { let mut it = (0..n).powerset(); let len = 1 << n; assert_eq!(len, it.clone().count()); + assert_eq!(len, it.size_hint().0); + assert_eq!(Some(len), it.size_hint().1); for count in (0..len).rev() { let elem = it.next(); assert!(elem.is_some()); assert_eq!(count, it.clone().count()); + assert_eq!(count, it.size_hint().0); + assert_eq!(Some(count), it.size_hint().1); } let should_be_none = it.next(); assert!(should_be_none.is_none()); From 467134594c41f61a296f16b3aa3d41200c5e62b7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 23:26:29 +0200 Subject: [PATCH 257/633] Remove `size_hint::pow_scalar_base` --- src/size_hint.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/size_hint.rs b/src/size_hint.rs index 1146445b5..f7278aec9 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -3,7 +3,6 @@ use std::usize; use std::cmp; -use std::u32; /// `SizeHint` is the return type of `Iterator::size_hint()`. pub type SizeHint = (usize, Option); @@ -75,21 +74,6 @@ pub fn mul_scalar(sh: SizeHint, x: usize) -> SizeHint { (low, hi) } -/// Raise `base` correctly by a `SizeHint` exponent. -#[inline] -#[allow(dead_code)] -pub fn pow_scalar_base(base: usize, exp: SizeHint) -> SizeHint { - let exp_low = cmp::min(exp.0, u32::MAX as usize) as u32; - let low = base.saturating_pow(exp_low); - - let hi = exp.1.and_then(|exp| { - let exp_hi = cmp::min(exp, u32::MAX as usize) as u32; - base.checked_pow(exp_hi) - }); - - (low, hi) -} - /// Return the maximum #[inline] pub fn max(a: SizeHint, b: SizeHint) -> SizeHint { From bb661d78d55bb66ba7aeac40c58218a8cace033a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 23:36:04 +0200 Subject: [PATCH 258/633] Use `size_hint::add` in `Powerset::size_hint` --- src/powerset.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/powerset.rs b/src/powerset.rs index 18ca82b32..f84cecfbd 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -4,6 +4,7 @@ use std::usize; use alloc::vec::Vec; use super::combinations::{Combinations, checked_binomial, combinations}; +use crate::size_hint::{self, SizeHint}; /// An iterator to iterate through the powerset of the elements from an iterator. /// @@ -58,21 +59,19 @@ impl Iterator for Powerset } } - fn size_hint(&self) -> (usize, Option) { + fn size_hint(&self) -> SizeHint { let k = self.combs.k(); // Total bounds for source iterator. let (n_min, n_max) = self.combs.src().size_hint(); - // Total bounds for the current combinations. - let (mut low, mut upp) = self.combs.size_hint(); - low = remaining_for(low, n_min, k).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, n_max?, k)); - (low, upp) + let low = remaining_for(n_min, k).unwrap_or(usize::MAX); + let upp = n_max.and_then(|n| remaining_for(n, k)); + size_hint::add(self.combs.size_hint(), (low, upp)) } fn count(self) -> usize { let k = self.combs.k(); let (n, combs_count) = self.combs.n_and_count(); - remaining_for(combs_count, n, k).unwrap() + combs_count + remaining_for(n, k).unwrap() } } @@ -82,8 +81,8 @@ impl FusedIterator for Powerset I::Item: Clone, {} -fn remaining_for(init_count: usize, n: usize, k: usize) -> Option { - (k + 1..=n).fold(Some(init_count), |sum, i| { +fn remaining_for(n: usize, k: usize) -> Option { + (k + 1..=n).fold(Some(0), |sum, i| { sum.and_then(|s| s.checked_add(checked_binomial(n, i)?)) }) } From 6683db44f7feaec098025f9ae60155a1cc783d74 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 23:41:33 +0200 Subject: [PATCH 259/633] `2.pow` in powerset test --- tests/test_std.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 7ed875144..470807c7b 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -992,7 +992,7 @@ fn powerset() { for n in 0..=10 { let mut it = (0..n).powerset(); - let len = 1 << n; + let len = 2_usize.pow(n); assert_eq!(len, it.clone().count()); assert_eq!(len, it.size_hint().0); assert_eq!(Some(len), it.size_hint().1); From 04d7573a4b65e9cf0ef870b42d788f2fea341f80 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 25 Aug 2023 10:38:53 +0200 Subject: [PATCH 260/633] Test `combinations_inexact_size_hints` --- tests/test_std.rs | 55 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 470807c7b..8ea992183 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -909,15 +909,19 @@ fn combinations_zero() { it::assert_equal((0..0).combinations(0), vec![vec![]]); } +fn binomial(n: usize, k: usize) -> usize { + if k > n { + 0 + } else { + (n - k + 1..=n).product::() / (1..=k).product::() + } +} + #[test] fn combinations_range_count() { for n in 0..=10 { for k in 0..=10 { - let len = if k<=n { - (n - k + 1..=n).product::() / (1..=k).product::() - } else { - 0 - }; + let len = binomial(n, k); let mut it = (0..n).combinations(k); assert_eq!(len, it.clone().count()); assert_eq!(len, it.size_hint().0); @@ -935,6 +939,47 @@ fn combinations_range_count() { } } +#[test] +fn combinations_inexact_size_hints() { + for k in 0..=10 { + let mut numbers = (0..18).filter(|i| i % 2 == 0); // 9 elements + let mut it = numbers.clone().combinations(k); + let real_n = numbers.clone().count(); + let len = binomial(real_n, k); + assert_eq!(len, it.clone().count()); + + let mut nb_loaded = numbers.by_ref().take(k).count(); // because of `LazyBuffer::prefill(k)` + let sh = numbers.size_hint(); + assert_eq!(binomial(sh.0 + nb_loaded, k), it.size_hint().0); + assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k)), it.size_hint().1); + + for next_count in 1..=len { + let elem = it.next(); + assert!(elem.is_some()); + assert_eq!(len - next_count, it.clone().count()); + // It does not load anything more the very first time (it's prefilled). + if next_count > 1 { + // Then it loads one item each time until exhausted. + let nb = numbers.next(); + if nb.is_some() { + nb_loaded += 1; + } + } + let sh = numbers.size_hint(); + if next_count > real_n - k + 1 { + assert_eq!(0, sh.0); + assert_eq!(Some(0), sh.1); + assert_eq!(real_n, nb_loaded); + // Once it's fully loaded, size hints of `it` are exacts. + } + assert_eq!(binomial(sh.0 + nb_loaded, k) - next_count, it.size_hint().0); + assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k) - next_count), it.size_hint().1); + } + let should_be_none = it.next(); + assert!(should_be_none.is_none()); + } +} + #[test] fn permutations_zero() { it::assert_equal((1..3).permutations(0), vec![vec![]]); From 630f78d3b161d54a955b51e573fdba187224eba9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 09:43:27 +0200 Subject: [PATCH 261/633] Remaining combinations with replacement Only two differences with combinations *without* replacement: - this `count` instead of `checked_binomial` ; - `k` can be bigger than `n`. --- src/combinations_with_replacement.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 0fec9671a..e42a0f5a4 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -3,6 +3,7 @@ use std::fmt; use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; +use crate::combinations::checked_binomial; /// An iterator to iterate through all the `n`-length combinations in an iterator, with replacement. /// @@ -107,3 +108,22 @@ where I: Iterator, I::Item: Clone, {} + +/// For a given size `n`, return the count of remaining combinations with replacement or None if it would overflow. +fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { + let count = |n: usize, k: usize| checked_binomial((n + k).saturating_sub(1), k); + let k = indices.len(); + if first { + count(n, k) + } else { + indices + .iter() + .enumerate() + // TODO: Once the MSRV hits 1.37.0, we can sum options instead: + // .map(|(i, n0)| count(n - 1 - *n0, k - i)) + // .sum() + .fold(Some(0), |sum, (i, n0)| { + sum.and_then(|s| s.checked_add(count(n - 1 - *n0, k - i)?)) + }) + } +} From c9b2fdb3909f14c2bc3fae82af63cd42f21ceb11 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 09:43:53 +0200 Subject: [PATCH 262/633] `CombinationsWithReplacement::size_hint` --- src/combinations_with_replacement.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index e42a0f5a4..701756d56 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -101,6 +101,13 @@ where None => None, } } + + fn size_hint(&self) -> (usize, Option) { + let (mut low, mut upp) = self.pool.size_hint(); + low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); + (low, upp) + } } impl FusedIterator for CombinationsWithReplacement From b9bc9e5e05522ad65b867a333e030995907f4dc3 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 09:44:09 +0200 Subject: [PATCH 263/633] `CombinationsWithReplacement::count` --- src/combinations_with_replacement.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 701756d56..8dc277a88 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -108,6 +108,12 @@ where upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); (low, upp) } + + fn count(self) -> usize { + let Self { indices, pool, first } = self; + let n = pool.count(); + remaining_for(n, first, &indices).unwrap() + } } impl FusedIterator for CombinationsWithReplacement From a163451e2af50d95d95eb8a93e8a31c8e530a230 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 09:50:18 +0200 Subject: [PATCH 264/633] Test `combinations_with_replacement` counts For a fast test: `n` and `k` are only up to 7. --- tests/test_std.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 8ea992183..94918598a 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1019,6 +1019,28 @@ fn combinations_with_replacement() { ); } +#[test] +fn combinations_with_replacement_range_count() { + for n in 0..=7 { + for k in 0..=7 { + let len = binomial(usize::saturating_sub(n + k, 1), k); + let mut it = (0..n).combinations_with_replacement(k); + assert_eq!(len, it.clone().count()); + assert_eq!(len, it.size_hint().0); + assert_eq!(Some(len), it.size_hint().1); + for count in (0..len).rev() { + let elem = it.next(); + assert!(elem.is_some()); + assert_eq!(count, it.clone().count()); + assert_eq!(count, it.size_hint().0); + assert_eq!(Some(count), it.size_hint().1); + } + let should_be_none = it.next(); + assert!(should_be_none.is_none()); + } + } +} + #[test] fn powerset() { it::assert_equal((0..0).powerset(), vec![vec![]]); From 6683438d30456c8916d5374be6f30b7acec7c312 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 10:51:00 +0200 Subject: [PATCH 265/633] Update rust/itertools versions in the doc --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9b7002486..79b4f481d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,8 +42,8 @@ //! //! ## Rust Version //! -//! This version of itertools requires Rust 1.32 or later. -#![doc(html_root_url="https://docs.rs/itertools/0.8/")] +//! This version of itertools requires Rust 1.36 or later. +#![doc(html_root_url="https://docs.rs/itertools/0.11/")] #[cfg(not(feature = "use_std"))] extern crate core as std; From 9cb35430ee0398528bfe8e173adfd8786eb72357 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 26 Aug 2023 14:05:36 +0200 Subject: [PATCH 266/633] Fix debug type name --- src/combinations_with_replacement.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 8dc277a88..d64cc2d90 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -25,7 +25,7 @@ where I: Iterator + fmt::Debug, I::Item: fmt::Debug + Clone, { - debug_fmt_fields!(Combinations, indices, pool, first); + debug_fmt_fields!(CombinationsWithReplacement, indices, pool, first); } impl CombinationsWithReplacement From 087be299515c2b6766c7c5dc92e853a922d172ea Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 27 Aug 2023 10:37:58 +0200 Subject: [PATCH 267/633] Replace `CompleteStateRemaining` by `Option` Known/Overflow variants have the advantage of the readability but it's less handy than options. A doc comment seems enough to me to say that None means it would overflow. --- src/permutations.rs | 44 +++++++++++--------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index a8885a411..51637416b 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -47,11 +47,6 @@ enum CompleteState { } } -enum CompleteStateRemaining { - Known(usize), - Overflow, -} - impl fmt::Debug for Permutations where I: Iterator + fmt::Debug, I::Item: fmt::Debug, @@ -123,12 +118,7 @@ where fn count(self) -> usize { fn from_complete(complete_state: CompleteState) -> usize { - match complete_state.remaining() { - CompleteStateRemaining::Known(count) => count, - CompleteStateRemaining::Overflow => { - panic!("Iterator count greater than usize::MAX"); - } - } + complete_state.remaining().expect("Iterator count greater than usize::MAX") } let Permutations { vals, state } = self; @@ -156,8 +146,8 @@ where PermutationState::StartUnknownLen { .. } | PermutationState::OngoingUnknownLen { .. } => (0, None), // TODO can we improve this lower bound? PermutationState::Complete(ref state) => match state.remaining() { - CompleteStateRemaining::Known(count) => (count, Some(count)), - CompleteStateRemaining::Overflow => (::std::usize::MAX, None) + Some(count) => (count, Some(count)), + None => (::std::usize::MAX, None) } PermutationState::Empty => (0, Some(0)) } @@ -238,39 +228,27 @@ impl CompleteState { } } - fn remaining(&self) -> CompleteStateRemaining { - use self::CompleteStateRemaining::{Known, Overflow}; - + /// Returns the count of remaining permutations, or None if it would overflow. + fn remaining(&self) -> Option { match *self { CompleteState::Start { n, k } => { if n < k { - return Known(0); + return Some(0); } - - let count: Option = (n - k + 1..n + 1).fold(Some(1), |acc, i| { + (n - k + 1..n + 1).fold(Some(1), |acc, i| { acc.and_then(|acc| acc.checked_mul(i)) - }); - - match count { - Some(count) => Known(count), - None => Overflow - } + }) } CompleteState::Ongoing { ref indices, ref cycles } => { let mut count: usize = 0; for (i, &c) in cycles.iter().enumerate() { let radix = indices.len() - i; - let next_count = count.checked_mul(radix) - .and_then(|count| count.checked_add(c)); - - count = match next_count { - Some(count) => count, - None => { return Overflow; } - }; + count = count.checked_mul(radix) + .and_then(|count| count.checked_add(c))?; } - Known(count) + Some(count) } } } From ff7742365f7a39be9715a4a65f2f12a82e382fc2 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 27 Aug 2023 10:44:02 +0200 Subject: [PATCH 268/633] permutations: simplify `enough_vals` Clearer. And `prefill` extending the buffer seems better performance wise than push items to the buffer one by one with `get_next`. --- src/permutations.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 51637416b..dbcc3e39d 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -67,14 +67,8 @@ pub fn permutations(iter: I, k: usize) -> Permutations { }; } - let mut enough_vals = true; - - while vals.len() < k { - if !vals.get_next() { - enough_vals = false; - break; - } - } + vals.prefill(k); + let enough_vals = vals.len() == k; let state = if enough_vals { PermutationState::StartUnknownLen { k } From 11d8b6bfcabdcc78b8ef5c02696e995a6910ea37 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 27 Aug 2023 11:06:39 +0200 Subject: [PATCH 269/633] size hint: `PermutationState::StartUnknownLen` We are at the very beginning. We prefilled `k` values, but did not generate any item yet. There are `n!/(n-k)!` items to come (safely done in remaining). But `n` might be unknown. --- src/permutations.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/permutations.rs b/src/permutations.rs index dbcc3e39d..d834ed8ad 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -137,7 +137,12 @@ where fn size_hint(&self) -> (usize, Option) { match self.state { - PermutationState::StartUnknownLen { .. } | + PermutationState::StartUnknownLen { k } => { + let (mut low, mut upp) = self.vals.size_hint(); + low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); + upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); + (low, upp) + } PermutationState::OngoingUnknownLen { .. } => (0, None), // TODO can we improve this lower bound? PermutationState::Complete(ref state) => match state.remaining() { Some(count) => (count, Some(count)), From 7786d2ea7ba3d036553fbd63af1d520c87312960 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 27 Aug 2023 11:15:44 +0200 Subject: [PATCH 270/633] size hint: `PermutationState::OngoingUnknownLen` We generated `prev_iteration_count` items (`n` still unknown). So the same as `PermutationState::StartUnknownLen` minus `prev_iteration_count`. --- src/permutations.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/permutations.rs b/src/permutations.rs index d834ed8ad..2c4c4fbb9 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -143,7 +143,20 @@ where upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); (low, upp) } - PermutationState::OngoingUnknownLen { .. } => (0, None), // TODO can we improve this lower bound? + PermutationState::OngoingUnknownLen { k, min_n } => { + let prev_iteration_count = min_n - k + 1; + let (mut low, mut upp) = self.vals.size_hint(); + low = CompleteState::Start { n: low, k } + .remaining() + .unwrap_or(usize::MAX) + .saturating_sub(prev_iteration_count); + upp = upp.and_then(|n| { + CompleteState::Start { n, k } + .remaining() + .map(|count| count.saturating_sub(prev_iteration_count)) + }); + (low, upp) + } PermutationState::Complete(ref state) => match state.remaining() { Some(count) => (count, Some(count)), None => (::std::usize::MAX, None) From 92d3be2c2e607b0e59a84cac708ca9e73a79e38d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 27 Aug 2023 11:28:37 +0200 Subject: [PATCH 271/633] Test permutations' `size_hint/count` --- tests/test_std.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 8ea992183..3b07d1097 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -986,6 +986,32 @@ fn permutations_zero() { it::assert_equal((0..0).permutations(0), vec![vec![]]); } +#[test] +fn permutations_range_count() { + for n in 0..=7 { + for k in 0..=7 { + let len = if k <= n { + (n - k + 1..=n).product() + } else { + 0 + }; + let mut it = (0..n).permutations(k); + assert_eq!(len, it.clone().count()); + assert_eq!(len, it.size_hint().0); + assert_eq!(Some(len), it.size_hint().1); + for count in (0..len).rev() { + let elem = it.next(); + assert!(elem.is_some()); + assert_eq!(count, it.clone().count()); + assert_eq!(count, it.size_hint().0); + assert_eq!(Some(count), it.size_hint().1); + } + let should_be_none = it.next(); + assert!(should_be_none.is_none()); + } + } +} + #[test] fn combinations_with_replacement() { // Pool smaller than n From 95536d6413cb340a7c0c90ca5b3925bfa198ba24 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 31 Aug 2023 17:58:35 +0200 Subject: [PATCH 272/633] Comment `Permutations::size_hint` --- src/permutations.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/permutations.rs b/src/permutations.rs index 2c4c4fbb9..9414a5fc8 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -138,12 +138,14 @@ where fn size_hint(&self) -> (usize, Option) { match self.state { PermutationState::StartUnknownLen { k } => { + // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. let (mut low, mut upp) = self.vals.size_hint(); low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); (low, upp) } PermutationState::OngoingUnknownLen { k, min_n } => { + // Same as `StartUnknownLen` minus the `prev_iteration_count` previously generated items. let prev_iteration_count = min_n - k + 1; let (mut low, mut upp) = self.vals.size_hint(); low = CompleteState::Start { n: low, k } From dd0e66c0bae3ad2c7fdd075711d8163a4adb10ae Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 4 Sep 2023 00:02:46 +0200 Subject: [PATCH 273/633] `remaining_for`: add "stars and bars" explanation --- src/combinations_with_replacement.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index d64cc2d90..897ed78ea 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -124,6 +124,10 @@ where /// For a given size `n`, return the count of remaining combinations with replacement or None if it would overflow. fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { + // With a "stars and bars" representation, choose k values with replacement from n values is + // like choosing k out of k + n − 1 positions (hence binomial(k + n - 1, k) possibilities) + // to place k stars and therefore n - 1 bars. + // Example (n=4, k=6): ***|*||** represents [0,0,0,1,3,3]. let count = |n: usize, k: usize| checked_binomial((n + k).saturating_sub(1), k); let k = indices.len(); if first { From c13ee581247fb009dbd12f7685b155e8c6bdcff5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 4 Sep 2023 00:06:03 +0200 Subject: [PATCH 274/633] `remaining_for`: handle `n + k - 1` overflowing Note that `n + k` might overflow but maybe `n + k - 1` should not. --- src/combinations_with_replacement.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 897ed78ea..3a544c55b 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -128,7 +128,10 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { // like choosing k out of k + n − 1 positions (hence binomial(k + n - 1, k) possibilities) // to place k stars and therefore n - 1 bars. // Example (n=4, k=6): ***|*||** represents [0,0,0,1,3,3]. - let count = |n: usize, k: usize| checked_binomial((n + k).saturating_sub(1), k); + let count = |n: usize, k: usize| { + let positions = if n == 0 { k.saturating_sub(1) } else { (n - 1).checked_add(k)? }; + checked_binomial(positions, k) + }; let k = indices.len(); if first { count(n, k) From 8ce17a97004df965610d7854a1e760ab36049706 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 4 Sep 2023 00:08:05 +0200 Subject: [PATCH 275/633] `remaining_for`: copy/update previous explanation --- src/combinations_with_replacement.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 3a544c55b..0e3a20e0c 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -136,6 +136,24 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { if first { count(n, k) } else { + // The algorithm is similar to the one for combinations *without replacement*, + // except we choose values *with replacement* and indices are *non-strictly* monotonically sorted. + + // The combinations generated after the current one can be counted by counting as follows: + // - The subsequent combinations that differ in indices[0]: + // If subsequent combinations differ in indices[0], then their value for indices[0] + // must be at least 1 greater than the current indices[0]. + // As indices is monotonically sorted, this means we can effectively choose k values with + // replacement from (n - 1 - indices[0]), leading to count(n - 1 - indices[0], k) possibilities. + // - The subsequent combinations with same indices[0], but differing indices[1]: + // Here we can choose k - 1 values with replacement from (n - 1 - indices[1]) values, + // leading to count(n - 1 - indices[1], k - 1) possibilities. + // - (...) + // - The subsequent combinations with same indices[0..=i], but differing indices[i]: + // Here we can choose k - i values with replacement from (n - 1 - indices[i]) values: count(n - 1 - indices[i], k - i). + // Since subsequent combinations can in any index, we must sum up the aforementioned binomial coefficients. + + // Below, `n0` resembles indices[i]. indices .iter() .enumerate() From c12692e87b45ce1a2520d161d2a1035fa0e9adab Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 5 Sep 2023 21:46:47 +0200 Subject: [PATCH 276/633] Use `size_hint::sub_scalar` in `Permutations::size_hint` --- src/permutations.rs | 16 +++++----------- src/size_hint.rs | 1 - 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 9414a5fc8..ebabbd7e2 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -3,6 +3,7 @@ use std::fmt; use std::iter::once; use super::lazy_buffer::LazyBuffer; +use crate::size_hint::{self, SizeHint}; /// An iterator adaptor that iterates through all the `k`-permutations of the /// elements from an iterator. @@ -135,7 +136,7 @@ where } } - fn size_hint(&self) -> (usize, Option) { + fn size_hint(&self) -> SizeHint { match self.state { PermutationState::StartUnknownLen { k } => { // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. @@ -148,16 +149,9 @@ where // Same as `StartUnknownLen` minus the `prev_iteration_count` previously generated items. let prev_iteration_count = min_n - k + 1; let (mut low, mut upp) = self.vals.size_hint(); - low = CompleteState::Start { n: low, k } - .remaining() - .unwrap_or(usize::MAX) - .saturating_sub(prev_iteration_count); - upp = upp.and_then(|n| { - CompleteState::Start { n, k } - .remaining() - .map(|count| count.saturating_sub(prev_iteration_count)) - }); - (low, upp) + low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); + upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); + size_hint::sub_scalar((low, upp), prev_iteration_count) } PermutationState::Complete(ref state) => match state.remaining() { Some(count) => (count, Some(count)), diff --git a/src/size_hint.rs b/src/size_hint.rs index f7278aec9..76ccd13a2 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -30,7 +30,6 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint { /// Subtract `x` correctly from a `SizeHint`. #[inline] -#[allow(dead_code)] pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; low = low.saturating_sub(x); From 06abc3eda3a51550a5cd9362872dbc9954cb3fec Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 5 Sep 2023 22:37:46 +0200 Subject: [PATCH 277/633] Test overflowed `Permutations::size_hint` Ideally, the lower bound of the size hint would remain `usize::MAX` but I don't see how we could make this happen. Small bugfix: `n+1` might overflow. --- src/permutations.rs | 2 +- tests/test_std.rs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/permutations.rs b/src/permutations.rs index ebabbd7e2..c53bf20b7 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -243,7 +243,7 @@ impl CompleteState { if n < k { return Some(0); } - (n - k + 1..n + 1).fold(Some(1), |acc, i| { + (n - k + 1..=n).fold(Some(1), |acc, i| { acc.and_then(|acc| acc.checked_mul(i)) }) } diff --git a/tests/test_std.rs b/tests/test_std.rs index 3b07d1097..77d210fb2 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1012,6 +1012,18 @@ fn permutations_range_count() { } } +#[test] +fn permutations_overflowed_size_hints() { + let mut it = std::iter::repeat(()).permutations(2); + assert_eq!(it.size_hint().0, usize::MAX); + assert_eq!(it.size_hint().1, None); + for nb_generated in 1..=1000 { + it.next(); + assert!(it.size_hint().0 >= usize::MAX - nb_generated); + assert_eq!(it.size_hint().1, None); + } +} + #[test] fn combinations_with_replacement() { // Pool smaller than n From 65d0c60004f1a8a7f49831695f85256dc59cfbdb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 5 Sep 2023 22:50:52 +0200 Subject: [PATCH 278/633] Share code in `Permutations::size_hint` --- src/permutations.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index c53bf20b7..21a84c401 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -137,21 +137,18 @@ where } fn size_hint(&self) -> SizeHint { + let at_start = |k| { + // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. + let (mut low, mut upp) = self.vals.size_hint(); + low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); + upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); + (low, upp) + }; match self.state { - PermutationState::StartUnknownLen { k } => { - // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. - let (mut low, mut upp) = self.vals.size_hint(); - low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); - upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); - (low, upp) - } + PermutationState::StartUnknownLen { k } => at_start(k), PermutationState::OngoingUnknownLen { k, min_n } => { - // Same as `StartUnknownLen` minus the `prev_iteration_count` previously generated items. - let prev_iteration_count = min_n - k + 1; - let (mut low, mut upp) = self.vals.size_hint(); - low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); - upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); - size_hint::sub_scalar((low, upp), prev_iteration_count) + // Same as `StartUnknownLen` minus the previously generated items. + size_hint::sub_scalar(at_start(k), min_n - k + 1) } PermutationState::Complete(ref state) => match state.remaining() { Some(count) => (count, Some(count)), From 79a1b1572bbf5c24498a00e377be775bd1b83801 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 6 Sep 2023 09:10:14 +0200 Subject: [PATCH 279/633] Do not sum options A mistake on my part. It only returns None if the iterator yields None. But it would overflow when an addition does. I guess a `checked_sum` method would be nice. PS: Do not product options either, as multiplications could overflow the same way. That's how I found out. --- src/combinations.rs | 3 --- src/combinations_with_replacement.rs | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 4b0742a0d..e230119c1 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -208,9 +208,6 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { indices .iter() .enumerate() - // TODO: Once the MSRV hits 1.37.0, we can sum options instead: - // .map(|(i, n0)| checked_binomial(n - 1 - *n0, k - i)) - // .sum() .fold(Some(0), |sum, (i, n0)| { sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - i)?)) }) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 0e3a20e0c..5d6a034b5 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -157,9 +157,6 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { indices .iter() .enumerate() - // TODO: Once the MSRV hits 1.37.0, we can sum options instead: - // .map(|(i, n0)| count(n - 1 - *n0, k - i)) - // .sum() .fold(Some(0), |sum, (i, n0)| { sum.and_then(|s| s.checked_add(count(n - 1 - *n0, k - i)?)) }) From 09a4bd8df1f6410a78c268556a21622505843a87 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:39:33 +0000 Subject: [PATCH 280/633] Bump actions/checkout from 3 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 92122cca0..bddc8a777 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,7 +31,7 @@ jobs: # we only care about the MSRV with respect to the lib target features: "--all-targets --all-features" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} @@ -41,7 +41,7 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features From b30cff705a566c2f6de2e7ad9194877ab7533eb4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 6 Sep 2023 10:48:39 +0200 Subject: [PATCH 281/633] Use `try_fold` in `combinations::remaining_for` --- src/combinations.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index e230119c1..06764cc85 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -208,8 +208,8 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { indices .iter() .enumerate() - .fold(Some(0), |sum, (i, n0)| { - sum.and_then(|s| s.checked_add(checked_binomial(n - 1 - *n0, k - i)?)) + .try_fold(0usize, |sum, (i, n0)| { + sum.checked_add(checked_binomial(n - 1 - *n0, k - i)?) }) } } From 2a7b5093df3b728c5051f585d29066397cff57f7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 6 Sep 2023 10:48:55 +0200 Subject: [PATCH 282/633] Use `try_fold` in `combinations_with_replacement::remaining_for` --- src/combinations_with_replacement.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 5d6a034b5..07c64c2c0 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -157,8 +157,8 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { indices .iter() .enumerate() - .fold(Some(0), |sum, (i, n0)| { - sum.and_then(|s| s.checked_add(count(n - 1 - *n0, k - i)?)) + .try_fold(0usize, |sum, (i, n0)| { + sum.checked_add(count(n - 1 - *n0, k - i)?) }) } } From bef0014c12960ab0f99d0f9d25ed1e994f0fabe5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 6 Sep 2023 10:49:51 +0200 Subject: [PATCH 283/633] Use `try_fold` in `powerset::remaining_for` --- src/powerset.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/powerset.rs b/src/powerset.rs index f84cecfbd..9f0965bc5 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -82,7 +82,7 @@ impl FusedIterator for Powerset {} fn remaining_for(n: usize, k: usize) -> Option { - (k + 1..=n).fold(Some(0), |sum, i| { - sum.and_then(|s| s.checked_add(checked_binomial(n, i)?)) + (k + 1..=n).try_fold(0usize, |sum, i| { + sum.checked_add(checked_binomial(n, i)?) }) } From 58a25fbbbd36fc0b12e7491f3cd566f78cb5d3cc Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 6 Sep 2023 17:41:25 +0200 Subject: [PATCH 284/633] Use `try_fold` in `CompleteState::remaining` --- src/permutations.rs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 21a84c401..cfdb06b2a 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -240,20 +240,16 @@ impl CompleteState { if n < k { return Some(0); } - (n - k + 1..=n).fold(Some(1), |acc, i| { - acc.and_then(|acc| acc.checked_mul(i)) - }) + (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i)) } CompleteState::Ongoing { ref indices, ref cycles } => { - let mut count: usize = 0; - - for (i, &c) in cycles.iter().enumerate() { - let radix = indices.len() - i; - count = count.checked_mul(radix) - .and_then(|count| count.checked_add(c))?; - } - - Some(count) + cycles + .iter() + .enumerate() + .try_fold(0usize, |acc, (i, &c)| { + acc.checked_mul(indices.len() - i) + .and_then(|count| count.checked_add(c)) + }) } } } From 30512ca7e02eed0862e276e5543554bfc1c68fb0 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 6 Sep 2023 22:15:13 +0200 Subject: [PATCH 285/633] Simplify Permutations (1.1) move impl Permutations This simply moves code around so that the subsequent diff is simpler. --- src/permutations.rs | 76 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index cfdb06b2a..4f96d8f68 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -83,6 +83,44 @@ pub fn permutations(iter: I, k: usize) -> Permutations { } } +impl Permutations +where + I: Iterator, + I::Item: Clone +{ + fn advance(&mut self) { + let &mut Permutations { ref mut vals, ref mut state } = self; + + *state = match *state { + PermutationState::StartUnknownLen { k } => { + PermutationState::OngoingUnknownLen { k, min_n: k } + } + PermutationState::OngoingUnknownLen { k, min_n } => { + if vals.get_next() { + PermutationState::OngoingUnknownLen { k, min_n: min_n + 1 } + } else { + let n = min_n; + let prev_iteration_count = n - k + 1; + let mut complete_state = CompleteState::Start { n, k }; + + // Advance the complete-state iterator to the correct point + for _ in 0..(prev_iteration_count + 1) { + complete_state.advance(); + } + + PermutationState::Complete(complete_state) + } + } + PermutationState::Complete(ref mut state) => { + state.advance(); + + return; + } + PermutationState::Empty => { return; } + }; + } +} + impl Iterator for Permutations where I: Iterator, @@ -159,44 +197,6 @@ where } } -impl Permutations -where - I: Iterator, - I::Item: Clone -{ - fn advance(&mut self) { - let &mut Permutations { ref mut vals, ref mut state } = self; - - *state = match *state { - PermutationState::StartUnknownLen { k } => { - PermutationState::OngoingUnknownLen { k, min_n: k } - } - PermutationState::OngoingUnknownLen { k, min_n } => { - if vals.get_next() { - PermutationState::OngoingUnknownLen { k, min_n: min_n + 1 } - } else { - let n = min_n; - let prev_iteration_count = n - k + 1; - let mut complete_state = CompleteState::Start { n, k }; - - // Advance the complete-state iterator to the correct point - for _ in 0..(prev_iteration_count + 1) { - complete_state.advance(); - } - - PermutationState::Complete(complete_state) - } - } - PermutationState::Complete(ref mut state) => { - state.advance(); - - return; - } - PermutationState::Empty => { return; } - }; - } -} - impl CompleteState { fn advance(&mut self) { *self = match *self { From 93730e171500c75537127accc31563288983057b Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 6 Sep 2023 22:15:13 +0200 Subject: [PATCH 286/633] Simplify Permutations (1.2) inline Permutations::advance This at least makes clear why the panic! in Permutations::next cannot happen. --- src/permutations.rs | 67 ++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 40 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 4f96d8f68..92ba7e6a3 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -83,44 +83,6 @@ pub fn permutations(iter: I, k: usize) -> Permutations { } } -impl Permutations -where - I: Iterator, - I::Item: Clone -{ - fn advance(&mut self) { - let &mut Permutations { ref mut vals, ref mut state } = self; - - *state = match *state { - PermutationState::StartUnknownLen { k } => { - PermutationState::OngoingUnknownLen { k, min_n: k } - } - PermutationState::OngoingUnknownLen { k, min_n } => { - if vals.get_next() { - PermutationState::OngoingUnknownLen { k, min_n: min_n + 1 } - } else { - let n = min_n; - let prev_iteration_count = n - k + 1; - let mut complete_state = CompleteState::Start { n, k }; - - // Advance the complete-state iterator to the correct point - for _ in 0..(prev_iteration_count + 1) { - complete_state.advance(); - } - - PermutationState::Complete(complete_state) - } - } - PermutationState::Complete(ref mut state) => { - state.advance(); - - return; - } - PermutationState::Empty => { return; } - }; - } -} - impl Iterator for Permutations where I: Iterator, @@ -129,10 +91,35 @@ where type Item = Vec; fn next(&mut self) -> Option { - self.advance(); + { + let &mut Permutations { ref mut vals, ref mut state } = self; + match *state { + PermutationState::StartUnknownLen { k } => { + *state = PermutationState::OngoingUnknownLen { k, min_n: k }; + }, + PermutationState::OngoingUnknownLen { k, min_n } => { + if vals.get_next() { + *state = PermutationState::OngoingUnknownLen { k, min_n: min_n + 1 }; + } else { + let n = min_n; + let prev_iteration_count = n - k + 1; + let mut complete_state = CompleteState::Start { n, k }; - let &mut Permutations { ref vals, ref state } = self; + // Advance the complete-state iterator to the correct point + for _ in 0..(prev_iteration_count + 1) { + complete_state.advance(); + } + *state = PermutationState::Complete(complete_state); + } + }, + PermutationState::Complete(ref mut state) => { + state.advance(); + }, + PermutationState::Empty => {}, + }; + } + let &mut Permutations { ref vals, ref state } = self; match *state { PermutationState::StartUnknownLen { .. } => panic!("unexpected iterator state"), PermutationState::OngoingUnknownLen { k, min_n } => { From 6fc77d3411ba99365ceb5a8881b56ba7b06cd795 Mon Sep 17 00:00:00 2001 From: "Shahar \"Dawn\" Or" Date: Sun, 10 Sep 2023 22:55:48 +0700 Subject: [PATCH 287/633] build: enforce formatting --- .github/workflows/ci.yml | 11 +++++++++++ .rustfmt.toml | 3 --- 2 files changed, 11 insertions(+), 3 deletions(-) delete mode 100644 .rustfmt.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bddc8a777..45e399a0d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,17 @@ jobs: - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features + check-format: + name: check format + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: stable + components: rustfmt + - run: cargo fmt --check + # https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 end-success: name: bors build finished diff --git a/.rustfmt.toml b/.rustfmt.toml deleted file mode 100644 index 06eb57a17..000000000 --- a/.rustfmt.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Temporarily disable rustfmt completely to avoid conflicts of newly formatted -# code with old PRs. -ignore = ["/"] From 9b553d74ce11c900f9099a7e1795d4ddc4eb6d67 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 15 Sep 2023 14:50:11 +0000 Subject: [PATCH 288/633] run cargo fmt --- benches/bench1.rs | 123 ++-- benches/combinations.rs | 10 +- benches/extra/zipslices.rs | 48 +- benches/fold_specialization.rs | 20 +- benches/powerset.rs | 26 +- benches/tree_fold1.rs | 72 +-- benches/tuples.rs | 27 +- examples/iris.rs | 28 +- src/adaptors/mod.rs | 330 ++++++----- src/adaptors/multi_product.rs | 91 +-- src/combinations.rs | 60 +- src/combinations_with_replacement.rs | 26 +- src/concat_impl.rs | 15 +- src/cons_tuples_impl.rs | 14 +- src/diff.rs | 30 +- src/duplicates_impl.rs | 3 +- src/either_or_both.rs | 2 +- src/exactly_one_err.rs | 44 +- src/free.rs | 106 ++-- src/group_map.rs | 15 +- src/groupbylazy.rs | 195 ++++--- src/grouping_map.rs | 239 ++++---- src/impl_macros.rs | 2 +- src/intersperse.rs | 39 +- src/k_smallest.rs | 4 +- src/kmerge_impl.rs | 91 +-- src/lazy_buffer.rs | 4 +- src/lib.rs | 816 +++++++++++++++------------ src/merge_join.rs | 122 ++-- src/minmax.rs | 39 +- src/multipeek_impl.rs | 38 +- src/pad_tail.rs | 36 +- src/peeking_take_while.rs | 60 +- src/permutations.rs | 113 ++-- src/powerset.rs | 42 +- src/process_results_impl.rs | 13 +- src/put_back_n_impl.rs | 4 +- src/rciter_impl.rs | 24 +- src/repeatn.rs | 24 +- src/size_hint.rs | 3 +- src/sources.rs | 33 +- src/take_while_inclusive.rs | 13 +- src/tee.rs | 37 +- src/tuple_impl.rs | 118 ++-- src/unique_impl.rs | 75 ++- src/with_position.rs | 20 +- src/zip_eq_impl.rs | 23 +- src/zip_longest.rs | 33 +- src/ziptuple.rs | 5 +- tests/adaptors_no_collect.rs | 11 +- tests/merge_join.rs | 55 +- tests/quick.rs | 182 +++--- tests/specializations.rs | 11 +- tests/test_core.rs | 76 ++- tests/test_std.rs | 388 ++++++++----- tests/zip.rs | 18 +- 56 files changed, 2312 insertions(+), 1784 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 71278d17b..084567177 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,10 +1,10 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use itertools::Itertools; use itertools::free::cloned; use itertools::iproduct; +use itertools::Itertools; -use std::iter::repeat; use std::cmp; +use std::iter::repeat; use std::ops::{Add, Range}; mod extra; @@ -15,8 +15,10 @@ fn slice_iter(c: &mut Criterion) { let xs: Vec<_> = repeat(1i32).take(20).collect(); c.bench_function("slice iter", move |b| { - b.iter(|| for elt in xs.iter() { - black_box(elt); + b.iter(|| { + for elt in xs.iter() { + black_box(elt); + } }) }); } @@ -25,8 +27,10 @@ fn slice_iter_rev(c: &mut Criterion) { let xs: Vec<_> = repeat(1i32).take(20).collect(); c.bench_function("slice iter rev", move |b| { - b.iter(|| for elt in xs.iter().rev() { - black_box(elt); + b.iter(|| { + for elt in xs.iter().rev() { + black_box(elt); + } }) }); } @@ -307,10 +311,10 @@ fn zip_unchecked_counted_loop(c: &mut Criterion) { let len = cmp::min(xs.len(), ys.len()); for i in 0..len { unsafe { - let x = *xs.get_unchecked(i); - let y = *ys.get_unchecked(i); - black_box(x); - black_box(y); + let x = *xs.get_unchecked(i); + let y = *ys.get_unchecked(i); + black_box(x); + black_box(y); } } }) @@ -329,9 +333,9 @@ fn zipdot_i32_unchecked_counted_loop(c: &mut Criterion) { let mut s = 0i32; for i in 0..len { unsafe { - let x = *xs.get_unchecked(i); - let y = *ys.get_unchecked(i); - s += x * y; + let x = *xs.get_unchecked(i); + let y = *ys.get_unchecked(i); + s += x * y; } } s @@ -351,9 +355,9 @@ fn zipdot_f32_unchecked_counted_loop(c: &mut Criterion) { let mut s = 0f32; for i in 0..len { unsafe { - let x = *xs.get_unchecked(i); - let y = *ys.get_unchecked(i); - s += x * y; + let x = *xs.get_unchecked(i); + let y = *ys.get_unchecked(i); + s += x * y; } } s @@ -374,12 +378,12 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) { let len = cmp::min(xs.len(), cmp::min(ys.len(), zs.len())); for i in 0..len { unsafe { - let x = *xs.get_unchecked(i); - let y = *ys.get_unchecked(i); - let z = *zs.get_unchecked(i); - black_box(x); - black_box(y); - black_box(z); + let x = *xs.get_unchecked(i); + let y = *ys.get_unchecked(i); + let z = *zs.get_unchecked(i); + black_box(x); + black_box(y); + black_box(z); } } }) @@ -464,11 +468,7 @@ fn equal(c: &mut Criterion) { let alpha = black_box(&data[1..]); let beta = black_box(&data[..l - 1]); - c.bench_function("equal", move |b| { - b.iter(|| { - itertools::equal(alpha, beta) - }) - }); + c.bench_function("equal", move |b| b.iter(|| itertools::equal(alpha, beta))); } fn merge_default(c: &mut Criterion) { @@ -493,9 +493,7 @@ fn merge_default(c: &mut Criterion) { let data2 = black_box(data2); c.bench_function("merge default", move |b| { - b.iter(|| { - data1.iter().merge(&data2).count() - }) + b.iter(|| data1.iter().merge(&data2).count()) }); } @@ -521,9 +519,7 @@ fn merge_by_cmp(c: &mut Criterion) { let data2 = black_box(data2); c.bench_function("merge by cmp", move |b| { - b.iter(|| { - data1.iter().merge_by(&data2, PartialOrd::le).count() - }) + b.iter(|| data1.iter().merge_by(&data2, PartialOrd::le).count()) }); } @@ -549,9 +545,7 @@ fn merge_by_lt(c: &mut Criterion) { let data2 = black_box(data2); c.bench_function("merge by lt", move |b| { - b.iter(|| { - data1.iter().merge_by(&data2, |a, b| a <= b).count() - }) + b.iter(|| data1.iter().merge_by(&data2, |a, b| a <= b).count()) }); } @@ -578,9 +572,7 @@ fn kmerge_default(c: &mut Criterion) { let its = &[data1.iter(), data2.iter()]; c.bench_function("kmerge default", move |b| { - b.iter(|| { - its.iter().cloned().kmerge().count() - }) + b.iter(|| its.iter().cloned().kmerge().count()) }); } @@ -603,7 +595,7 @@ fn kmerge_tenway(c: &mut Criterion) { while rest.len() > 0 { let chunk_len = 1 + rng(&mut state) % 512; let chunk_len = cmp::min(rest.len(), chunk_len as usize); - let (fst, tail) = {rest}.split_at_mut(chunk_len); + let (fst, tail) = { rest }.split_at_mut(chunk_len); fst.sort(); chunks.push(fst.iter().cloned()); rest = tail; @@ -612,15 +604,14 @@ fn kmerge_tenway(c: &mut Criterion) { // println!("Chunk lengths: {}", chunks.iter().format_with(", ", |elt, f| f(&elt.len()))); c.bench_function("kmerge tenway", move |b| { - b.iter(|| { - chunks.iter().cloned().kmerge().count() - }) + b.iter(|| chunks.iter().cloned().kmerge().count()) }); } fn fast_integer_sum(iter: I) -> I::Item - where I: IntoIterator, - I::Item: Default + Add +where + I: IntoIterator, + I::Item: Default + Add, { iter.into_iter().fold(<_>::default(), |x, y| x + y) } @@ -629,9 +620,7 @@ fn step_vec_2(c: &mut Criterion) { let v = vec![0; 1024]; c.bench_function("step vec 2", move |b| { - b.iter(|| { - fast_integer_sum(cloned(v.iter().step_by(2))) - }) + b.iter(|| fast_integer_sum(cloned(v.iter().step_by(2)))) }); } @@ -639,9 +628,7 @@ fn step_vec_10(c: &mut Criterion) { let v = vec![0; 1024]; c.bench_function("step vec 10", move |b| { - b.iter(|| { - fast_integer_sum(cloned(v.iter().step_by(10))) - }) + b.iter(|| fast_integer_sum(cloned(v.iter().step_by(10)))) }); } @@ -649,9 +636,7 @@ fn step_range_2(c: &mut Criterion) { let v = black_box(0..1024); c.bench_function("step range 2", move |b| { - b.iter(|| { - fast_integer_sum(v.clone().step_by(2)) - }) + b.iter(|| fast_integer_sum(v.clone().step_by(2))) }); } @@ -659,9 +644,7 @@ fn step_range_10(c: &mut Criterion) { let v = black_box(0..1024); c.bench_function("step range 10", move |b| { - b.iter(|| { - fast_integer_sum(v.clone().step_by(10)) - }) + b.iter(|| fast_integer_sum(v.clone().step_by(10))) }); } @@ -753,9 +736,7 @@ fn all_equal(c: &mut Criterion) { let mut xs = vec![0; 5_000_000]; xs.extend(vec![1; 5_000_000]); - c.bench_function("all equal", move |b| { - b.iter(|| xs.iter().all_equal()) - }); + c.bench_function("all equal", move |b| b.iter(|| xs.iter().all_equal())); } fn all_equal_for(c: &mut Criterion) { @@ -797,21 +778,17 @@ fn permutations_iter(c: &mut Criterion) { } c.bench_function("permutations iter", move |b| { - b.iter(|| { - for _ in NewIterator(0..PERM_COUNT).permutations(PERM_COUNT) { - - } - }) + b.iter( + || { + for _ in NewIterator(0..PERM_COUNT).permutations(PERM_COUNT) {} + }, + ) }); } fn permutations_range(c: &mut Criterion) { c.bench_function("permutations range", move |b| { - b.iter(|| { - for _ in (0..PERM_COUNT).permutations(PERM_COUNT) { - - } - }) + b.iter(|| for _ in (0..PERM_COUNT).permutations(PERM_COUNT) {}) }); } @@ -819,11 +796,7 @@ fn permutations_slice(c: &mut Criterion) { let v = (0..PERM_COUNT).collect_vec(); c.bench_function("permutations slice", move |b| { - b.iter(|| { - for _ in v.as_slice().iter().permutations(PERM_COUNT) { - - } - }) + b.iter(|| for _ in v.as_slice().iter().permutations(PERM_COUNT) {}) }); } diff --git a/benches/combinations.rs b/benches/combinations.rs index e7433a4cb..42a452111 100644 --- a/benches/combinations.rs +++ b/benches/combinations.rs @@ -111,15 +111,7 @@ fn comb_c14(c: &mut Criterion) { } criterion_group!( - benches, - comb_for1, - comb_for2, - comb_for3, - comb_for4, - comb_c1, - comb_c2, - comb_c3, - comb_c4, + benches, comb_for1, comb_for2, comb_for3, comb_for4, comb_c1, comb_c2, comb_c3, comb_c4, comb_c14, ); criterion_main!(benches); diff --git a/benches/extra/zipslices.rs b/benches/extra/zipslices.rs index 633be5906..5476c0cb8 100644 --- a/benches/extra/zipslices.rs +++ b/benches/extra/zipslices.rs @@ -46,8 +46,9 @@ impl<'a, 'b, A, B> ZipSlices<&'a [A], &'b [B]> { } impl ZipSlices - where T: Slice, - U: Slice +where + T: Slice, + U: Slice, { /// Create a new `ZipSlices` from slices `a` and `b`. /// @@ -67,8 +68,9 @@ impl ZipSlices } impl Iterator for ZipSlices - where T: Slice, - U: Slice +where + T: Slice, + U: Slice, { type Item = (T::Item, U::Item); @@ -80,9 +82,7 @@ impl Iterator for ZipSlices } else { let i = self.index; self.index += 1; - Some(( - self.t.get_unchecked(i), - self.u.get_unchecked(i))) + Some((self.t.get_unchecked(i), self.u.get_unchecked(i))) } } } @@ -95,8 +95,9 @@ impl Iterator for ZipSlices } impl DoubleEndedIterator for ZipSlices - where T: Slice, - U: Slice +where + T: Slice, + U: Slice, { #[inline(always)] fn next_back(&mut self) -> Option { @@ -106,22 +107,23 @@ impl DoubleEndedIterator for ZipSlices } else { self.len -= 1; let i = self.len; - Some(( - self.t.get_unchecked(i), - self.u.get_unchecked(i))) + Some((self.t.get_unchecked(i), self.u.get_unchecked(i))) } } } } impl ExactSizeIterator for ZipSlices - where T: Slice, - U: Slice -{} +where + T: Slice, + U: Slice, +{ +} unsafe impl Slice for ZipSlices - where T: Slice, - U: Slice +where + T: Slice, + U: Slice, { type Item = (T::Item, U::Item); @@ -130,8 +132,7 @@ unsafe impl Slice for ZipSlices } unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item { - (self.t.get_unchecked(i), - self.u.get_unchecked(i)) + (self.t.get_unchecked(i), self.u.get_unchecked(i)) } } @@ -152,7 +153,9 @@ pub unsafe trait Slice { unsafe impl<'a, T> Slice for &'a [T] { type Item = &'a T; #[inline(always)] - fn len(&self) -> usize { (**self).len() } + fn len(&self) -> usize { + (**self).len() + } #[inline(always)] unsafe fn get_unchecked(&mut self, i: usize) -> &'a T { debug_assert!(i < self.len()); @@ -163,7 +166,9 @@ unsafe impl<'a, T> Slice for &'a [T] { unsafe impl<'a, T> Slice for &'a mut [T] { type Item = &'a mut T; #[inline(always)] - fn len(&self) -> usize { (**self).len() } + fn len(&self) -> usize { + (**self).len() + } #[inline(always)] unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut T { debug_assert!(i < self.len()); @@ -174,7 +179,6 @@ unsafe impl<'a, T> Slice for &'a mut [T] { #[test] fn zipslices() { - let xs = [1, 2, 3, 4, 5, 6]; let ys = [1, 2, 3, 7]; ::itertools::assert_equal(ZipSlices::new(&xs, &ys), xs.iter().zip(&ys)); diff --git a/benches/fold_specialization.rs b/benches/fold_specialization.rs index 5de4671e9..ef7f47c43 100644 --- a/benches/fold_specialization.rs +++ b/benches/fold_specialization.rs @@ -4,7 +4,8 @@ use itertools::Itertools; struct Unspecialized(I); impl Iterator for Unspecialized -where I: Iterator +where + I: Iterator, { type Item = I::Item; @@ -25,8 +26,7 @@ mod specialization { pub mod intersperse { use super::*; - pub fn external(c: &mut Criterion) - { + pub fn external(c: &mut Criterion) { let arr = [1; 1024]; c.bench_function("external", move |b| { @@ -40,25 +40,19 @@ mod specialization { }); } - pub fn internal_specialized(c: &mut Criterion) - { + pub fn internal_specialized(c: &mut Criterion) { let arr = [1; 1024]; c.bench_function("internal specialized", move |b| { - b.iter(|| { - arr.iter().intersperse(&0).fold(0, |acc, x| acc + x) - }) + b.iter(|| arr.iter().intersperse(&0).fold(0, |acc, x| acc + x)) }); } - pub fn internal_unspecialized(c: &mut Criterion) - { + pub fn internal_unspecialized(c: &mut Criterion) { let arr = [1; 1024]; c.bench_function("internal unspecialized", move |b| { - b.iter(|| { - Unspecialized(arr.iter().intersperse(&0)).fold(0, |acc, x| acc + x) - }) + b.iter(|| Unspecialized(arr.iter().intersperse(&0)).fold(0, |acc, x| acc + x)) }); } } diff --git a/benches/powerset.rs b/benches/powerset.rs index 074550bc4..f5862a416 100644 --- a/benches/powerset.rs +++ b/benches/powerset.rs @@ -20,17 +20,29 @@ fn powerset_n(c: &mut Criterion, n: usize) { }); } -fn powerset_0(c: &mut Criterion) { powerset_n(c, 0); } +fn powerset_0(c: &mut Criterion) { + powerset_n(c, 0); +} -fn powerset_1(c: &mut Criterion) { powerset_n(c, 1); } +fn powerset_1(c: &mut Criterion) { + powerset_n(c, 1); +} -fn powerset_2(c: &mut Criterion) { powerset_n(c, 2); } +fn powerset_2(c: &mut Criterion) { + powerset_n(c, 2); +} -fn powerset_4(c: &mut Criterion) { powerset_n(c, 4); } +fn powerset_4(c: &mut Criterion) { + powerset_n(c, 4); +} -fn powerset_8(c: &mut Criterion) { powerset_n(c, 8); } +fn powerset_8(c: &mut Criterion) { + powerset_n(c, 8); +} -fn powerset_12(c: &mut Criterion) { powerset_n(c, 12); } +fn powerset_12(c: &mut Criterion) { + powerset_n(c, 12); +} criterion_group!( benches, @@ -41,4 +53,4 @@ criterion_group!( powerset_8, powerset_12, ); -criterion_main!(benches); \ No newline at end of file +criterion_main!(benches); diff --git a/benches/tree_fold1.rs b/benches/tree_fold1.rs index f12995db8..77af505f1 100644 --- a/benches/tree_fold1.rs +++ b/benches/tree_fold1.rs @@ -1,12 +1,13 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use itertools::{Itertools, cloned}; +use itertools::{cloned, Itertools}; -trait IterEx : Iterator { +trait IterEx: Iterator { // Another efficient implementation against which to compare, // but needs `std` so is less desirable. fn tree_fold1_vec(self, mut f: F) -> Option - where F: FnMut(Self::Item, Self::Item) -> Self::Item, - Self: Sized, + where + F: FnMut(Self::Item, Self::Item) -> Self::Item, + Self: Sized, { let hint = self.size_hint().0; let cap = std::mem::size_of::() * 8 - hint.leading_zeros() as usize; @@ -21,24 +22,23 @@ trait IterEx : Iterator { stack.into_iter().fold1(f) } } -impl IterEx for T {} +impl IterEx for T {} macro_rules! def_benchs { ($N:expr, $FUN:ident, $BENCH_NAME:ident, - ) => ( + ) => { mod $BENCH_NAME { use super::*; pub fn sum(c: &mut Criterion) { - let v: Vec = (0.. $N).collect(); + let v: Vec = (0..$N).collect(); - c.bench_function(&(stringify!($BENCH_NAME).replace('_', " ") + " sum"), move |b| { - b.iter(|| { - cloned(&v).$FUN(|x, y| x + y) - }) - }); + c.bench_function( + &(stringify!($BENCH_NAME).replace('_', " ") + " sum"), + move |b| b.iter(|| cloned(&v).$FUN(|x, y| x + y)), + ); } pub fn complex_iter(c: &mut Criterion) { @@ -46,11 +46,10 @@ macro_rules! def_benchs { let v = (5..).take($N / 2); let it = u.chain(v); - c.bench_function(&(stringify!($BENCH_NAME).replace('_', " ") + " complex iter"), move |b| { - b.iter(|| { - it.clone().map(|x| x as f32).$FUN(f32::atan2) - }) - }); + c.bench_function( + &(stringify!($BENCH_NAME).replace('_', " ") + " complex iter"), + move |b| b.iter(|| it.clone().map(|x| x as f32).$FUN(f32::atan2)), + ); } pub fn string_format(c: &mut Criterion) { @@ -58,13 +57,18 @@ macro_rules! def_benchs { // size to not waste too much time in travis. The allocations // in here are so expensive anyway that it'll still take // way longer per iteration than the other two benchmarks. - let v: Vec = (0.. ($N/4)).collect(); - - c.bench_function(&(stringify!($BENCH_NAME).replace('_', " ") + " string format"), move |b| { - b.iter(|| { - cloned(&v).map(|x| x.to_string()).$FUN(|x, y| format!("{} + {}", x, y)) - }) - }); + let v: Vec = (0..($N / 4)).collect(); + + c.bench_function( + &(stringify!($BENCH_NAME).replace('_', " ") + " string format"), + move |b| { + b.iter(|| { + cloned(&v) + .map(|x| x.to_string()) + .$FUN(|x, y| format!("{} + {}", x, y)) + }) + }, + ); } } @@ -74,58 +78,58 @@ macro_rules! def_benchs { $BENCH_NAME::complex_iter, $BENCH_NAME::string_format, ); - ) + }; } -def_benchs!{ +def_benchs! { 10_000, fold1, fold1_10k, } -def_benchs!{ +def_benchs! { 10_000, tree_fold1, tree_fold1_stack_10k, } -def_benchs!{ +def_benchs! { 10_000, tree_fold1_vec, tree_fold1_vec_10k, } -def_benchs!{ +def_benchs! { 100, fold1, fold1_100, } -def_benchs!{ +def_benchs! { 100, tree_fold1, tree_fold1_stack_100, } -def_benchs!{ +def_benchs! { 100, tree_fold1_vec, tree_fold1_vec_100, } -def_benchs!{ +def_benchs! { 8, fold1, fold1_08, } -def_benchs!{ +def_benchs! { 8, tree_fold1, tree_fold1_stack_08, } -def_benchs!{ +def_benchs! { 8, tree_fold1_vec, tree_fold1_vec_08, diff --git a/benches/tuples.rs b/benches/tuples.rs index ea50aaaee..2eca34712 100644 --- a/benches/tuples.rs +++ b/benches/tuples.rs @@ -33,7 +33,7 @@ fn sum_s4(s: &[u32]) -> u32 { s4(s[0], s[1], s[2], s[3]) } -fn sum_t1(s: &(&u32, )) -> u32 { +fn sum_t1(s: &(&u32,)) -> u32 { s1(*s.0) } @@ -60,9 +60,9 @@ macro_rules! def_benchs { $WINDOWS:ident; $FOR_CHUNKS:ident, $FOR_WINDOWS:ident - ) => ( + ) => { fn $FOR_CHUNKS(c: &mut Criterion) { - let v: Vec = (0.. $N * 1_000).collect(); + let v: Vec = (0..$N * 1_000).collect(); let mut s = 0; c.bench_function(&stringify!($FOR_CHUNKS).replace('_', " "), move |b| { b.iter(|| { @@ -90,7 +90,7 @@ macro_rules! def_benchs { } fn $TUPLES(c: &mut Criterion) { - let v: Vec = (0.. $N * 1_000).collect(); + let v: Vec = (0..$N * 1_000).collect(); let mut s = 0; c.bench_function(&stringify!($TUPLES).replace('_', " "), move |b| { b.iter(|| { @@ -103,7 +103,7 @@ macro_rules! def_benchs { } fn $CHUNKS(c: &mut Criterion) { - let v: Vec = (0.. $N * 1_000).collect(); + let v: Vec = (0..$N * 1_000).collect(); let mut s = 0; c.bench_function(&stringify!($CHUNKS).replace('_', " "), move |b| { b.iter(|| { @@ -150,10 +150,10 @@ macro_rules! def_benchs { $TUPLE_WINDOWS, $WINDOWS, ); - ) + }; } -def_benchs!{ +def_benchs! { 1; benches_1, sum_t1, @@ -166,7 +166,7 @@ def_benchs!{ for_windows_1 } -def_benchs!{ +def_benchs! { 2; benches_2, sum_t2, @@ -179,7 +179,7 @@ def_benchs!{ for_windows_2 } -def_benchs!{ +def_benchs! { 3; benches_3, sum_t3, @@ -192,7 +192,7 @@ def_benchs!{ for_windows_3 } -def_benchs!{ +def_benchs! { 4; benches_4, sum_t4, @@ -205,9 +205,4 @@ def_benchs!{ for_windows_4 } -criterion_main!( - benches_1, - benches_2, - benches_3, - benches_4, -); +criterion_main!(benches_1, benches_2, benches_3, benches_4,); diff --git a/examples/iris.rs b/examples/iris.rs index 987d9e9cb..af64322d6 100644 --- a/examples/iris.rs +++ b/examples/iris.rs @@ -3,7 +3,6 @@ /// and does some simple manipulations. /// /// Iterators and itertools functionality are used throughout. - use itertools::Itertools; use std::collections::HashMap; use std::iter::repeat; @@ -35,7 +34,10 @@ impl FromStr for Iris { type Err = ParseError; fn from_str(s: &str) -> Result { - let mut iris = Iris { name: "".into(), data: [0.; 4] }; + let mut iris = Iris { + name: "".into(), + data: [0.; 4], + }; let mut parts = s.split(",").map(str::trim); // using Iterator::by_ref() @@ -45,7 +47,7 @@ impl FromStr for Iris { if let Some(name) = parts.next() { iris.name = name.into(); } else { - return Err(ParseError::Other("Missing name")) + return Err(ParseError::Other("Missing name")); } Ok(iris) } @@ -53,12 +55,13 @@ impl FromStr for Iris { fn main() { // using Itertools::fold_results to create the result of parsing - let irises = DATA.lines() - .map(str::parse) - .fold_ok(Vec::new(), |mut v, iris: Iris| { - v.push(iris); - v - }); + let irises = DATA + .lines() + .map(str::parse) + .fold_ok(Vec::new(), |mut v, iris: Iris| { + v.push(iris); + v + }); let mut irises = match irises { Err(e) => { println!("Error parsing: {:?}", e); @@ -77,16 +80,15 @@ fn main() { // using Itertools::group_by for (species, species_group) in &irises.iter().group_by(|iris| &iris.name) { // assign a plot symbol - symbolmap.entry(species).or_insert_with(|| { - plot_symbols.next().unwrap() - }); + symbolmap + .entry(species) + .or_insert_with(|| plot_symbols.next().unwrap()); println!("{} (symbol={})", species, symbolmap[species]); for iris in species_group { // using Itertools::format for lazy formatting println!("{:>3.1}", iris.data.iter().format(", ")); } - } // Look at all combinations of the four columns diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a02c6b6e0..4bc50eaa0 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -8,16 +8,16 @@ mod coalesce; mod map; mod multi_product; pub use self::coalesce::*; -pub use self::map::{map_into, map_ok, MapInto, MapOk}; #[allow(deprecated)] pub use self::map::MapResults; +pub use self::map::{map_into, map_ok, MapInto, MapOk}; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; +use crate::size_hint; use std::fmt; -use std::iter::{Fuse, FromIterator, FusedIterator}; +use std::iter::{FromIterator, Fuse, FusedIterator}; use std::marker::PhantomData; -use crate::size_hint; /// An iterator adaptor that alternates elements from two iterators until both /// run out. @@ -36,9 +36,13 @@ pub struct Interleave { /// Create an iterator that interleaves elements in `i` and `j`. /// /// [`IntoIterator`] enabled version of `[Itertools::interleave]`. -pub fn interleave(i: I, j: J) -> Interleave<::IntoIter, ::IntoIter> - where I: IntoIterator, - J: IntoIterator +pub fn interleave( + i: I, + j: J, +) -> Interleave<::IntoIter, ::IntoIter> +where + I: IntoIterator, + J: IntoIterator, { Interleave { a: i.into_iter().fuse(), @@ -48,8 +52,9 @@ pub fn interleave(i: I, j: J) -> Interleave<::IntoIter, } impl Iterator for Interleave - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { type Item = I::Item; #[inline] @@ -74,9 +79,11 @@ impl Iterator for Interleave } impl FusedIterator for Interleave - where I: Iterator, - J: Iterator -{} +where + I: Iterator, + J: Iterator, +{ +} /// An iterator adaptor that alternates elements from the two iterators until /// one of them runs out. @@ -88,8 +95,9 @@ impl FusedIterator for Interleave #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct InterleaveShortest - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { it0: I, it1: J, @@ -98,8 +106,9 @@ pub struct InterleaveShortest /// Create a new `InterleaveShortest` iterator. pub fn interleave_shortest(a: I, b: J) -> InterleaveShortest - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { InterleaveShortest { it0: a, @@ -109,14 +118,19 @@ pub fn interleave_shortest(a: I, b: J) -> InterleaveShortest } impl Iterator for InterleaveShortest - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { type Item = I::Item; #[inline] fn next(&mut self) -> Option { - let e = if self.phase { self.it1.next() } else { self.it0.next() }; + let e = if self.phase { + self.it1.next() + } else { + self.it0.next() + }; if e.is_some() { self.phase = !self.phase; } @@ -138,12 +152,11 @@ impl Iterator for InterleaveShortest let (next_lower, next_upper) = next_hint; let (combined_lower, combined_upper) = size_hint::mul_scalar(size_hint::min(curr_hint, next_hint), 2); - let lower = - if curr_lower > next_lower { - combined_lower + 1 - } else { - combined_lower - }; + let lower = if curr_lower > next_lower { + combined_lower + 1 + } else { + combined_lower + }; let upper = { let extra_elem = match (curr_upper, next_upper) { (_, None) => false, @@ -161,9 +174,11 @@ impl Iterator for InterleaveShortest } impl FusedIterator for InterleaveShortest - where I: FusedIterator, - J: FusedIterator -{} +where + I: FusedIterator, + J: FusedIterator, +{ +} #[derive(Clone, Debug)] /// An iterator adaptor that allows putting back a single @@ -171,7 +186,8 @@ impl FusedIterator for InterleaveShortest /// /// Iterator element type is `I::Item`. pub struct PutBack - where I: Iterator +where + I: Iterator, { top: Option, iter: I, @@ -179,7 +195,8 @@ pub struct PutBack /// Create an iterator where you can put back a single item pub fn put_back(iterable: I) -> PutBack - where I: IntoIterator +where + I: IntoIterator, { PutBack { top: None, @@ -188,7 +205,8 @@ pub fn put_back(iterable: I) -> PutBack } impl PutBack - where I: Iterator +where + I: Iterator, { /// put back value `value` (builder method) pub fn with_value(mut self, value: I::Item) -> Self { @@ -199,7 +217,7 @@ impl PutBack /// Split the `PutBack` into its parts. #[inline] pub fn into_parts(self) -> (Option, I) { - let PutBack{top, iter} = self; + let PutBack { top, iter } = self; (top, iter) } @@ -213,7 +231,8 @@ impl PutBack } impl Iterator for PutBack - where I: Iterator +where + I: Iterator, { type Item = I::Item; #[inline] @@ -252,7 +271,8 @@ impl Iterator for PutBack } fn all(&mut self, mut f: G) -> bool - where G: FnMut(Self::Item) -> bool + where + G: FnMut(Self::Item) -> bool, { if let Some(elt) = self.top.take() { if !f(elt) { @@ -263,7 +283,8 @@ impl Iterator for PutBack } fn fold(mut self, init: Acc, mut f: G) -> Acc - where G: FnMut(Acc, Self::Item) -> Acc, + where + G: FnMut(Acc, Self::Item) -> Acc, { let mut accum = init; if let Some(elt) = self.top.take() { @@ -282,7 +303,8 @@ impl Iterator for PutBack /// See [`.cartesian_product()`](crate::Itertools::cartesian_product) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Product - where I: Iterator +where + I: Iterator, { a: I, a_cur: Option, @@ -294,9 +316,10 @@ pub struct Product /// /// Iterator element type is `(I::Item, J::Item)`. pub fn cartesian_product(mut i: I, j: J) -> Product - where I: Iterator, - J: Clone + Iterator, - I::Item: Clone +where + I: Iterator, + J: Clone + Iterator, + I::Item: Clone, { Product { a_cur: i.next(), @@ -307,9 +330,10 @@ pub fn cartesian_product(mut i: I, j: J) -> Product } impl Iterator for Product - where I: Iterator, - J: Clone + Iterator, - I::Item: Clone +where + I: Iterator, + J: Clone + Iterator, + I::Item: Clone, { type Item = (I::Item, J::Item); @@ -325,7 +349,7 @@ impl Iterator for Product } } } - Some(x) => x + Some(x) => x, }; self.a_cur.as_ref().map(|a| (a.clone(), elt_b)) } @@ -338,11 +362,13 @@ impl Iterator for Product // Compute a * b_orig + b for both lower and upper bound size_hint::add( size_hint::mul(self.a.size_hint(), self.b_orig.size_hint()), - (b_min * has_cur, b_max.map(move |x| x * has_cur))) + (b_min * has_cur, b_max.map(move |x| x * has_cur)), + ) } fn fold(mut self, mut accum: Acc, mut f: G) -> Acc - where G: FnMut(Acc, Self::Item) -> Acc, + where + G: FnMut(Acc, Self::Item) -> Acc, { // use a split loop to handle the loose a_cur as well as avoiding to // clone b_orig at the end. @@ -365,10 +391,12 @@ impl Iterator for Product } impl FusedIterator for Product - where I: FusedIterator, - J: Clone + FusedIterator, - I::Item: Clone -{} +where + I: FusedIterator, + J: Clone + FusedIterator, + I::Item: Clone, +{ +} /// A “meta iterator adaptor”. Its closure receives a reference to the iterator /// and may pick off as many elements as it likes, to produce the next iterator element. @@ -383,7 +411,10 @@ pub struct Batching { iter: I, } -impl fmt::Debug for Batching where I: fmt::Debug { +impl fmt::Debug for Batching +where + I: fmt::Debug, +{ debug_fmt_fields!(Batching, iter); } @@ -393,8 +424,9 @@ pub fn batching(iter: I, f: F) -> Batching { } impl Iterator for Batching - where I: Iterator, - F: FnMut(&mut I) -> Option +where + I: Iterator, + F: FnMut(&mut I) -> Option, { type Item = B; #[inline] @@ -410,7 +442,7 @@ impl Iterator for Batching /// then skipping forward *n-1* elements. /// /// See [`.step()`](crate::Itertools::step) for more information. -#[deprecated(note="Use std .step_by() instead", since="0.8.0")] +#[deprecated(note = "Use std .step_by() instead", since = "0.8.0")] #[allow(deprecated)] #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -424,7 +456,8 @@ pub struct Step { /// **Panics** if the step is 0. #[allow(deprecated)] pub fn step(iter: I, step: usize) -> Step - where I: Iterator +where + I: Iterator, { assert!(step != 0); Step { @@ -435,7 +468,8 @@ pub fn step(iter: I, step: usize) -> Step #[allow(deprecated)] impl Iterator for Step - where I: Iterator +where + I: Iterator, { type Item = I::Item; #[inline] @@ -462,9 +496,7 @@ impl Iterator for Step // known size #[allow(deprecated)] -impl ExactSizeIterator for Step - where I: ExactSizeIterator -{} +impl ExactSizeIterator for Step where I: ExactSizeIterator {} /// An iterator adaptor that borrows from a `Clone`-able iterator /// to only pick off elements while the predicate returns `true`. @@ -477,21 +509,24 @@ pub struct TakeWhileRef<'a, I: 'a, F> { } impl<'a, I, F> fmt::Debug for TakeWhileRef<'a, I, F> - where I: Iterator + fmt::Debug, +where + I: Iterator + fmt::Debug, { debug_fmt_fields!(TakeWhileRef, iter); } /// Create a new `TakeWhileRef` from a reference to clonable iterator. pub fn take_while_ref(iter: &mut I, f: F) -> TakeWhileRef - where I: Iterator + Clone +where + I: Iterator + Clone, { TakeWhileRef { iter, f } } impl<'a, I, F> Iterator for TakeWhileRef<'a, I, F> - where I: Iterator + Clone, - F: FnMut(&I::Item) -> bool +where + I: Iterator + Clone, + F: FnMut(&I::Item) -> bool, { type Item = I::Item; @@ -531,7 +566,8 @@ pub fn while_some(iter: I) -> WhileSome { } impl Iterator for WhileSome - where I: Iterator> +where + I: Iterator>, { type Item = A; @@ -555,8 +591,9 @@ impl Iterator for WhileSome #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct TupleCombinations - where I: Iterator, - T: HasCombination +where + I: Iterator, + T: HasCombination, { iter: T::Combination, _mi: PhantomData, @@ -568,9 +605,10 @@ pub trait HasCombination: Sized { /// Create a new `TupleCombinations` from a clonable iterator. pub fn tuple_combinations(iter: I) -> TupleCombinations - where I: Iterator + Clone, - I::Item: Clone, - T: HasCombination, +where + I: Iterator + Clone, + I::Item: Clone, + T: HasCombination, { TupleCombinations { iter: T::Combination::from(iter), @@ -579,8 +617,9 @@ pub fn tuple_combinations(iter: I) -> TupleCombinations } impl Iterator for TupleCombinations - where I: Iterator, - T: HasCombination, +where + I: Iterator, + T: HasCombination, { type Item = T; @@ -590,9 +629,11 @@ impl Iterator for TupleCombinations } impl FusedIterator for TupleCombinations - where I: FusedIterator, - T: HasCombination, -{} +where + I: FusedIterator, + T: HasCombination, +{ +} #[derive(Clone, Debug)] pub struct Tuple1Combination { @@ -702,7 +743,7 @@ impl_tuple_combination!(Tuple12Combination Tuple11Combination; a b c d e f g h i #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct FilterOk { iter: I, - f: F + f: F, } impl fmt::Debug for FilterOk @@ -714,18 +755,17 @@ where /// Create a new `FilterOk` iterator. pub fn filter_ok(iter: I, f: F) -> FilterOk - where I: Iterator>, - F: FnMut(&T) -> bool, +where + I: Iterator>, + F: FnMut(&T) -> bool, { - FilterOk { - iter, - f, - } + FilterOk { iter, f } } impl Iterator for FilterOk - where I: Iterator>, - F: FnMut(&T) -> bool, +where + I: Iterator>, + F: FnMut(&T) -> bool, { type Item = Result; @@ -736,7 +776,7 @@ impl Iterator for FilterOk if (self.f)(&v) { return Some(Ok(v)); } - }, + } Some(Err(e)) => return Some(Err(e)), None => return None, } @@ -748,28 +788,32 @@ impl Iterator for FilterOk } fn fold(self, init: Acc, fold_f: Fold) -> Acc - where Fold: FnMut(Acc, Self::Item) -> Acc, + where + Fold: FnMut(Acc, Self::Item) -> Acc, { let mut f = self.f; - self.iter.filter(|v| { - v.as_ref().map(&mut f).unwrap_or(true) - }).fold(init, fold_f) + self.iter + .filter(|v| v.as_ref().map(&mut f).unwrap_or(true)) + .fold(init, fold_f) } fn collect(self) -> C - where C: FromIterator + where + C: FromIterator, { let mut f = self.f; - self.iter.filter(|v| { - v.as_ref().map(&mut f).unwrap_or(true) - }).collect() + self.iter + .filter(|v| v.as_ref().map(&mut f).unwrap_or(true)) + .collect() } } impl FusedIterator for FilterOk - where I: FusedIterator>, - F: FnMut(&T) -> bool, -{} +where + I: FusedIterator>, + F: FnMut(&T) -> bool, +{ +} /// An iterator adapter to filter and apply a transformation on values within a nested `Result::Ok`. /// @@ -777,7 +821,7 @@ impl FusedIterator for FilterOk #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct FilterMapOk { iter: I, - f: F + f: F, } impl fmt::Debug for FilterMapOk @@ -797,18 +841,17 @@ fn transpose_result(result: Result, E>) -> Option> /// Create a new `FilterOk` iterator. pub fn filter_map_ok(iter: I, f: F) -> FilterMapOk - where I: Iterator>, - F: FnMut(T) -> Option, +where + I: Iterator>, + F: FnMut(T) -> Option, { - FilterMapOk { - iter, - f, - } + FilterMapOk { iter, f } } impl Iterator for FilterMapOk - where I: Iterator>, - F: FnMut(T) -> Option, +where + I: Iterator>, + F: FnMut(T) -> Option, { type Item = Result; @@ -819,7 +862,7 @@ impl Iterator for FilterMapOk if let Some(v) = (self.f)(v) { return Some(Ok(v)); } - }, + } Some(Err(e)) => return Some(Err(e)), None => return None, } @@ -831,28 +874,32 @@ impl Iterator for FilterMapOk } fn fold(self, init: Acc, fold_f: Fold) -> Acc - where Fold: FnMut(Acc, Self::Item) -> Acc, + where + Fold: FnMut(Acc, Self::Item) -> Acc, { let mut f = self.f; - self.iter.filter_map(|v| { - transpose_result(v.map(&mut f)) - }).fold(init, fold_f) + self.iter + .filter_map(|v| transpose_result(v.map(&mut f))) + .fold(init, fold_f) } fn collect(self) -> C - where C: FromIterator + where + C: FromIterator, { let mut f = self.f; - self.iter.filter_map(|v| { - transpose_result(v.map(&mut f)) - }).collect() + self.iter + .filter_map(|v| transpose_result(v.map(&mut f))) + .collect() } } impl FusedIterator for FilterMapOk - where I: FusedIterator>, - F: FnMut(T) -> Option, -{} +where + I: FusedIterator>, + F: FnMut(T) -> Option, +{ +} /// An iterator adapter to get the positions of each element that matches a predicate. /// @@ -874,19 +921,17 @@ where /// Create a new `Positions` iterator. pub fn positions(iter: I, f: F) -> Positions - where I: Iterator, - F: FnMut(I::Item) -> bool, +where + I: Iterator, + F: FnMut(I::Item) -> bool, { - Positions { - iter, - f, - count: 0 - } + Positions { iter, f, count: 0 } } impl Iterator for Positions - where I: Iterator, - F: FnMut(I::Item) -> bool, +where + I: Iterator, + F: FnMut(I::Item) -> bool, { type Item = usize; @@ -907,13 +952,14 @@ impl Iterator for Positions } impl DoubleEndedIterator for Positions - where I: DoubleEndedIterator + ExactSizeIterator, - F: FnMut(I::Item) -> bool, +where + I: DoubleEndedIterator + ExactSizeIterator, + F: FnMut(I::Item) -> bool, { fn next_back(&mut self) -> Option { while let Some(v) = self.iter.next_back() { if (self.f)(v) { - return Some(self.count + self.iter.len()) + return Some(self.count + self.iter.len()); } } None @@ -921,9 +967,11 @@ impl DoubleEndedIterator for Positions } impl FusedIterator for Positions - where I: FusedIterator, - F: FnMut(I::Item) -> bool, -{} +where + I: FusedIterator, + F: FnMut(I::Item) -> bool, +{ +} /// An iterator adapter to apply a mutating function to each element before yielding it. /// @@ -972,18 +1020,28 @@ where } fn fold(self, init: Acc, mut g: G) -> Acc - where G: FnMut(Acc, Self::Item) -> Acc, + where + G: FnMut(Acc, Self::Item) -> Acc, { let mut f = self.f; - self.iter.fold(init, move |acc, mut v| { f(&mut v); g(acc, v) }) + self.iter.fold(init, move |acc, mut v| { + f(&mut v); + g(acc, v) + }) } // if possible, re-use inner iterator specializations in collect fn collect(self) -> C - where C: FromIterator + where + C: FromIterator, { let mut f = self.f; - self.iter.map(move |mut v| { f(&mut v); v }).collect() + self.iter + .map(move |mut v| { + f(&mut v); + v + }) + .collect() } } @@ -991,7 +1049,8 @@ impl ExactSizeIterator for Update where I: ExactSizeIterator, F: FnMut(&mut I::Item), -{} +{ +} impl DoubleEndedIterator for Update where @@ -1012,4 +1071,5 @@ impl FusedIterator for Update where I: FusedIterator, F: FnMut(&mut I::Item), -{} +{ +} diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 0b3840698..ef7fadba8 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -15,8 +15,9 @@ use alloc::vec::Vec; /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MultiProduct(Vec>) - where I: Iterator + Clone, - I::Item: Clone; +where + I: Iterator + Clone, + I::Item: Clone; impl std::fmt::Debug for MultiProduct where @@ -31,19 +32,25 @@ where /// /// Iterator element is of type `Vec`. pub fn multi_cartesian_product(iters: H) -> MultiProduct<::IntoIter> - where H: Iterator, - H::Item: IntoIterator, - ::IntoIter: Clone, - ::Item: Clone +where + H: Iterator, + H::Item: IntoIterator, + ::IntoIter: Clone, + ::Item: Clone, { - MultiProduct(iters.map(|i| MultiProductIter::new(i.into_iter())).collect()) + MultiProduct( + iters + .map(|i| MultiProductIter::new(i.into_iter())) + .collect(), + ) } #[derive(Clone, Debug)] /// Holds the state of a single iterator within a `MultiProduct`. struct MultiProductIter - where I: Iterator + Clone, - I::Item: Clone +where + I: Iterator + Clone, + I::Item: Clone, { cur: Option, iter: I, @@ -58,8 +65,9 @@ enum MultiProductIterState { } impl MultiProduct - where I: Iterator + Clone, - I::Item: Clone +where + I: Iterator + Clone, + I::Item: Clone, { /// Iterates the rightmost iterator, then recursively iterates iterators /// to the left if necessary. @@ -67,7 +75,7 @@ impl MultiProduct /// Returns true if the iteration succeeded, else false. fn iterate_last( multi_iters: &mut [MultiProductIter], - mut state: MultiProductIterState + mut state: MultiProductIterState, ) -> bool { use self::MultiProductIterState::*; @@ -77,8 +85,8 @@ impl MultiProduct let on_first_iter = !last.in_progress(); state = MidIter { on_first_iter }; on_first_iter - }, - MidIter { on_first_iter } => on_first_iter + } + MidIter { on_first_iter } => on_first_iter, }; if !on_first_iter { @@ -101,16 +109,17 @@ impl MultiProduct // At end of iteration (final iterator finishes), finish. match state { StartOfIter => false, - MidIter { on_first_iter } => on_first_iter + MidIter { on_first_iter } => on_first_iter, } } } /// Returns the unwrapped value of the next iteration. fn curr_iterator(&self) -> Vec { - self.0.iter().map(|multi_iter| { - multi_iter.cur.clone().unwrap() - }).collect() + self.0 + .iter() + .map(|multi_iter| multi_iter.cur.clone().unwrap()) + .collect() } /// Returns true if iteration has started and has not yet finished; false @@ -125,14 +134,15 @@ impl MultiProduct } impl MultiProductIter - where I: Iterator + Clone, - I::Item: Clone +where + I: Iterator + Clone, + I::Item: Clone, { fn new(iter: I) -> Self { MultiProductIter { cur: None, iter: iter.clone(), - iter_orig: iter + iter_orig: iter, } } @@ -154,16 +164,14 @@ impl MultiProductIter } impl Iterator for MultiProduct - where I: Iterator + Clone, - I::Item: Clone +where + I: Iterator + Clone, + I::Item: Clone, { type Item = Vec; fn next(&mut self) -> Option { - if MultiProduct::iterate_last( - &mut self.0, - MultiProductIterState::StartOfIter - ) { + if MultiProduct::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { Some(self.curr_iterator()) } else { None @@ -176,18 +184,24 @@ impl Iterator for MultiProduct } if !self.in_progress() { - return self.0.into_iter().fold(1, |acc, multi_iter| { - acc * multi_iter.iter.count() - }); + return self + .0 + .into_iter() + .fold(1, |acc, multi_iter| acc * multi_iter.iter.count()); } self.0.into_iter().fold( 0, - |acc, MultiProductIter { iter, iter_orig, cur: _ }| { + |acc, + MultiProductIter { + iter, + iter_orig, + cur: _, + }| { let total_count = iter_orig.count(); let cur_count = iter.count(); acc * total_count + cur_count - } + }, ) } @@ -205,18 +219,25 @@ impl Iterator for MultiProduct self.0.iter().fold( (0, Some(0)), - |acc, &MultiProductIter { ref iter, ref iter_orig, cur: _ }| { + |acc, + &MultiProductIter { + ref iter, + ref iter_orig, + cur: _, + }| { let cur_size = iter.size_hint(); let total_size = iter_orig.size_hint(); size_hint::add(size_hint::mul(acc, total_size), cur_size) - } + }, ) } fn last(self) -> Option { let iter_count = self.0.len(); - let lasts: Self::Item = self.0.into_iter() + let lasts: Self::Item = self + .0 + .into_iter() .map(|multi_iter| multi_iter.iter.last()) .while_some() .collect(); diff --git a/src/combinations.rs b/src/combinations.rs index 06764cc85..332bcb3e2 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -15,22 +15,25 @@ pub struct Combinations { } impl Clone for Combinations - where I: Clone + Iterator, - I::Item: Clone, +where + I: Clone + Iterator, + I::Item: Clone, { clone_fields!(indices, pool, first); } impl fmt::Debug for Combinations - where I: Iterator + fmt::Debug, - I::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug, { debug_fmt_fields!(Combinations, indices, pool, first); } /// Create a new `Combinations` from a clonable iterator. pub fn combinations(iter: I, k: usize) -> Combinations - where I: Iterator +where + I: Iterator, { let mut pool = LazyBuffer::new(iter); pool.prefill(k); @@ -45,16 +48,22 @@ pub fn combinations(iter: I, k: usize) -> Combinations impl Combinations { /// Returns the length of a combination produced by this iterator. #[inline] - pub fn k(&self) -> usize { self.indices.len() } + pub fn k(&self) -> usize { + self.indices.len() + } /// Returns the (current) length of the pool from which combination elements are /// selected. This value can change between invocations of [`next`](Combinations::next). #[inline] - pub fn n(&self) -> usize { self.pool.len() } + pub fn n(&self) -> usize { + self.pool.len() + } /// Returns a reference to the source pool. #[inline] - pub(crate) fn src(&self) -> &LazyBuffer { &self.pool } + pub(crate) fn src(&self) -> &LazyBuffer { + &self.pool + } /// Resets this `Combinations` back to an initial state for combinations of length /// `k` over the same pool data source. If `k` is larger than the current length @@ -68,7 +77,6 @@ impl Combinations { for i in 0..k { self.indices[i] = i; } - } else { for i in 0..self.indices.len() { self.indices[i] = i; @@ -79,15 +87,20 @@ impl Combinations { } pub(crate) fn n_and_count(self) -> (usize, usize) { - let Self { indices, pool, first } = self; + let Self { + indices, + pool, + first, + } = self; let n = pool.count(); (n, remaining_for(n, first, &indices).unwrap()) } } impl Iterator for Combinations - where I: Iterator, - I::Item: Clone +where + I: Iterator, + I::Item: Clone, { type Item = Vec; fn next(&mut self) -> Option { @@ -118,7 +131,7 @@ impl Iterator for Combinations // Increment index, and reset the ones to its right self.indices[i] += 1; - for j in i+1..self.indices.len() { + for j in i + 1..self.indices.len() { self.indices[j] = self.indices[j - 1] + 1; } } @@ -141,9 +154,11 @@ impl Iterator for Combinations } impl FusedIterator for Combinations - where I: Iterator, - I::Item: Clone -{} +where + I: Iterator, + I::Item: Clone, +{ +} // https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { @@ -154,7 +169,9 @@ pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { k = (n - k).min(k); // symmetry let mut c = 1; for i in 1..=k { - c = (c / i).checked_mul(n)?.checked_add((c % i).checked_mul(n)? / i)?; + c = (c / i) + .checked_mul(n)? + .checked_add((c % i).checked_mul(n)? / i)?; n -= 1; } Some(c) @@ -205,11 +222,8 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { // Since subsequent combinations can in any index, we must sum up the aforementioned binomial coefficients. // Below, `n0` resembles indices[i]. - indices - .iter() - .enumerate() - .try_fold(0usize, |sum, (i, n0)| { - sum.checked_add(checked_binomial(n - 1 - *n0, k - i)?) - }) + indices.iter().enumerate().try_fold(0usize, |sum, (i, n0)| { + sum.checked_add(checked_binomial(n - 1 - *n0, k - i)?) + }) } } diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 07c64c2c0..e4f1cda13 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -81,7 +81,7 @@ where // Work out where we need to update our indices let mut increment: Option<(usize, usize)> = None; for (i, indices_int) in self.indices.iter().enumerate().rev() { - if *indices_int < self.pool.len()-1 { + if *indices_int < self.pool.len() - 1 { increment = Some((i, indices_int + 1)); break; } @@ -110,7 +110,11 @@ where } fn count(self) -> usize { - let Self { indices, pool, first } = self; + let Self { + indices, + pool, + first, + } = self; let n = pool.count(); remaining_for(n, first, &indices).unwrap() } @@ -120,7 +124,8 @@ impl FusedIterator for CombinationsWithReplacement where I: Iterator, I::Item: Clone, -{} +{ +} /// For a given size `n`, return the count of remaining combinations with replacement or None if it would overflow. fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { @@ -129,7 +134,11 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { // to place k stars and therefore n - 1 bars. // Example (n=4, k=6): ***|*||** represents [0,0,0,1,3,3]. let count = |n: usize, k: usize| { - let positions = if n == 0 { k.saturating_sub(1) } else { (n - 1).checked_add(k)? }; + let positions = if n == 0 { + k.saturating_sub(1) + } else { + (n - 1).checked_add(k)? + }; checked_binomial(positions, k) }; let k = indices.len(); @@ -154,11 +163,8 @@ fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { // Since subsequent combinations can in any index, we must sum up the aforementioned binomial coefficients. // Below, `n0` resembles indices[i]. - indices - .iter() - .enumerate() - .try_fold(0usize, |sum, (i, n0)| { - sum.checked_add(count(n - 1 - *n0, k - i)?) - }) + indices.iter().enumerate().try_fold(0usize, |sum, (i, n0)| { + sum.checked_add(count(n - 1 - *n0, k - i)?) + }) } } diff --git a/src/concat_impl.rs b/src/concat_impl.rs index f022ec90a..ec7b91c60 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -10,14 +10,21 @@ use crate::Itertools; /// /// ```rust /// use itertools::concat; -/// +/// /// let input = vec![vec![1], vec![2, 3], vec![4, 5, 6]]; /// assert_eq!(concat(input), vec![1, 2, 3, 4, 5, 6]); /// ``` pub fn concat(iterable: I) -> I::Item - where I: IntoIterator, - I::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default +where + I: IntoIterator, + I::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default, { #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` - iterable.into_iter().fold1(|mut a, b| { a.extend(b); a }).unwrap_or_default() + iterable + .into_iter() + .fold1(|mut a, b| { + a.extend(b); + a + }) + .unwrap_or_default() } diff --git a/src/cons_tuples_impl.rs b/src/cons_tuples_impl.rs index ae0f48f34..3cae0b06e 100644 --- a/src/cons_tuples_impl.rs +++ b/src/cons_tuples_impl.rs @@ -1,4 +1,3 @@ - macro_rules! impl_cons_iter( ($_A:ident, $_B:ident, ) => (); // stop @@ -44,13 +43,15 @@ impl_cons_iter!(A, B, C, D, E, F, G, H, I, J, K, L,); #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug)] pub struct ConsTuples - where I: Iterator, +where + I: Iterator, { iter: I, } impl Clone for ConsTuples - where I: Clone + Iterator, +where + I: Clone + Iterator, { clone_fields!(iter); } @@ -58,7 +59,10 @@ impl Clone for ConsTuples /// Create an iterator that maps for example iterators of /// `((A, B), C)` to `(A, B, C)`. pub fn cons_tuples(iterable: I) -> ConsTuples - where I: IntoIterator +where + I: IntoIterator, { - ConsTuples { iter: iterable.into_iter() } + ConsTuples { + iter: iterable.into_iter(), + } } diff --git a/src/diff.rs b/src/diff.rs index 1731f0639..0d3d358b5 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -13,8 +13,9 @@ use crate::structs::PutBack; /// `Diff` represents the way in which the elements yielded by the iterator `I` differ to some /// iterator `J`. pub enum Diff - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { /// The index of the first non-matching element along with both iterator's remaining elements /// starting with the first mis-match. @@ -37,11 +38,11 @@ pub enum Diff /// /// If `i` becomes exhausted before `j` becomes exhausted, the number of elements in `i` along with /// the remaining `j` elements will be returned as `Diff::Longer`. -pub fn diff_with(i: I, j: J, is_equal: F) - -> Option> - where I: IntoIterator, - J: IntoIterator, - F: Fn(&I::Item, &J::Item) -> bool +pub fn diff_with(i: I, j: J, is_equal: F) -> Option> +where + I: IntoIterator, + J: IntoIterator, + F: Fn(&I::Item, &J::Item) -> bool, { let mut i = i.into_iter(); let mut j = j.into_iter(); @@ -49,13 +50,16 @@ pub fn diff_with(i: I, j: J, is_equal: F) while let Some(i_elem) = i.next() { match j.next() { None => return Some(Diff::Shorter(idx, put_back(i).with_value(i_elem))), - Some(j_elem) => if !is_equal(&i_elem, &j_elem) { - let remaining_i = put_back(i).with_value(i_elem); - let remaining_j = put_back(j).with_value(j_elem); - return Some(Diff::FirstMismatch(idx, remaining_i, remaining_j)); - }, + Some(j_elem) => { + if !is_equal(&i_elem, &j_elem) { + let remaining_i = put_back(i).with_value(i_elem); + let remaining_j = put_back(j).with_value(j_elem); + return Some(Diff::FirstMismatch(idx, remaining_i, remaining_j)); + } + } } idx += 1; } - j.next().map(|j_elem| Diff::Longer(idx, put_back(j).with_value(j_elem))) + j.next() + .map(|j_elem| Diff::Longer(idx, put_back(j).with_value(j_elem))) } diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 28eda44a9..a80316a7c 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -2,8 +2,8 @@ use std::hash::Hash; mod private { use std::collections::HashMap; - use std::hash::Hash; use std::fmt; + use std::hash::Hash; #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] @@ -213,4 +213,3 @@ where { Duplicates::new(iter, private::ById) } - diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 681f3ed7c..d39747eaa 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -475,7 +475,7 @@ impl EitherOrBoth { /// /// This function can be used to preferrably extract the left resp. right value, /// but fall back to the other (i.e. right resp. left) if the preferred one is not present. - /// + /// /// # Examples /// ``` /// # use itertools::EitherOrBoth; diff --git a/src/exactly_one_err.rs b/src/exactly_one_err.rs index c54ae77ca..e24d33fc5 100644 --- a/src/exactly_one_err.rs +++ b/src/exactly_one_err.rs @@ -54,13 +54,9 @@ where Some(Either::Left([first, second])) => { self.first_two = Some(Either::Right(second)); Some(first) - }, - Some(Either::Right(second)) => { - Some(second) - } - None => { - self.inner.next() } + Some(Either::Right(second)) => Some(second), + None => self.inner.next(), } } @@ -69,11 +65,11 @@ where } } - impl ExactSizeIterator for ExactlyOneError where I: ExactSizeIterator {} -impl Display for ExactlyOneError - where I: Iterator, +impl Display for ExactlyOneError +where + I: Iterator, { fn fmt(&self, f: &mut Formatter) -> FmtResult { let additional = self.additional_len(); @@ -85,17 +81,26 @@ impl Display for ExactlyOneError } } -impl Debug for ExactlyOneError - where I: Iterator + Debug, - I::Item: Debug, +impl Debug for ExactlyOneError +where + I: Iterator + Debug, + I::Item: Debug, { fn fmt(&self, f: &mut Formatter) -> FmtResult { match &self.first_two { Some(Either::Left([first, second])) => { - write!(f, "ExactlyOneError[First: {:?}, Second: {:?}, RemainingIter: {:?}]", first, second, self.inner) - }, + write!( + f, + "ExactlyOneError[First: {:?}, Second: {:?}, RemainingIter: {:?}]", + first, second, self.inner + ) + } Some(Either::Right(second)) => { - write!(f, "ExactlyOneError[Second: {:?}, RemainingIter: {:?}]", second, self.inner) + write!( + f, + "ExactlyOneError[Second: {:?}, RemainingIter: {:?}]", + second, self.inner + ) } None => { write!(f, "ExactlyOneError[RemainingIter: {:?}]", self.inner) @@ -105,6 +110,9 @@ impl Debug for ExactlyOneError } #[cfg(feature = "use_std")] -impl Error for ExactlyOneError where I: Iterator + Debug, I::Item: Debug, {} - - +impl Error for ExactlyOneError +where + I: Iterator + Debug, + I::Item: Debug, +{ +} diff --git a/src/free.rs b/src/free.rs index c9a87cd0a..6de0a0daf 100644 --- a/src/free.rs +++ b/src/free.rs @@ -10,29 +10,24 @@ use std::iter::{self, Zip}; type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] -use alloc::{ - string::String, -}; +use alloc::string::String; -use crate::Itertools; use crate::intersperse::{Intersperse, IntersperseWith}; +use crate::Itertools; -pub use crate::adaptors::{ - interleave, - put_back, -}; +pub use crate::adaptors::{interleave, put_back}; #[cfg(feature = "use_alloc")] -pub use crate::put_back_n_impl::put_back_n; +pub use crate::kmerge_impl::kmerge; +pub use crate::merge_join::{merge, merge_join_by}; #[cfg(feature = "use_alloc")] pub use crate::multipeek_impl::multipeek; #[cfg(feature = "use_alloc")] pub use crate::peek_nth::peek_nth; #[cfg(feature = "use_alloc")] -pub use crate::kmerge_impl::kmerge; -pub use crate::zip_eq_impl::zip_eq; -pub use crate::merge_join::{merge, merge_join_by}; +pub use crate::put_back_n_impl::put_back_n; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::rciter; +pub use crate::zip_eq_impl::zip_eq; /// Iterate `iterable` with a particular value inserted between each element. /// @@ -44,8 +39,9 @@ pub use crate::rciter_impl::rciter; /// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]); /// ``` pub fn intersperse(iterable: I, element: I::Item) -> Intersperse - where I: IntoIterator, - ::Item: Clone +where + I: IntoIterator, + ::Item: Clone, { Itertools::intersperse(iterable.into_iter(), element) } @@ -63,8 +59,9 @@ pub fn intersperse(iterable: I, element: I::Item) -> Intersperse /// assert_eq!(i, 8); /// ``` pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith - where I: IntoIterator, - F: FnMut() -> I::Item +where + I: IntoIterator, + F: FnMut() -> I::Item, { Itertools::intersperse_with(iterable.into_iter(), element) } @@ -81,7 +78,8 @@ pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith(iterable: I) -> iter::Enumerate - where I: IntoIterator +where + I: IntoIterator, { iterable.into_iter().enumerate() } @@ -98,8 +96,9 @@ pub fn enumerate(iterable: I) -> iter::Enumerate /// } /// ``` pub fn rev(iterable: I) -> iter::Rev - where I: IntoIterator, - I::IntoIter: DoubleEndedIterator +where + I: IntoIterator, + I::IntoIter: DoubleEndedIterator, { iterable.into_iter().rev() } @@ -107,7 +106,7 @@ pub fn rev(iterable: I) -> iter::Rev /// Converts the arguments to iterators and zips them. /// /// [`IntoIterator`] enabled version of [`Iterator::zip`]. -/// +/// /// ## Example /// /// ``` @@ -120,23 +119,26 @@ pub fn rev(iterable: I) -> iter::Rev /// } /// assert_eq!(result, vec![(1, 'a'),(2, 'b'),(3, 'c')]); /// ``` -#[deprecated(note="Use [std::iter::zip](https://doc.rust-lang.org/std/iter/fn.zip.html) instead", since="0.10.4")] +#[deprecated( + note = "Use [std::iter::zip](https://doc.rust-lang.org/std/iter/fn.zip.html) instead", + since = "0.10.4" +)] pub fn zip(i: I, j: J) -> Zip - where I: IntoIterator, - J: IntoIterator +where + I: IntoIterator, + J: IntoIterator, { i.into_iter().zip(j) } - -/// Takes two iterables and creates a new iterator over both in sequence. +/// Takes two iterables and creates a new iterator over both in sequence. /// /// [`IntoIterator`] enabled version of [`Iterator::chain`]. /// /// ## Example /// ``` /// use itertools::chain; -/// +/// /// let mut result:Vec = Vec::new(); /// /// for element in chain(&[1, 2, 3], &[4]) { @@ -144,9 +146,13 @@ pub fn zip(i: I, j: J) -> Zip /// } /// assert_eq!(result, vec![1, 2, 3, 4]); /// ``` -pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, ::IntoIter> - where I: IntoIterator, - J: IntoIterator +pub fn chain( + i: I, + j: J, +) -> iter::Chain<::IntoIter, ::IntoIter> +where + I: IntoIterator, + J: IntoIterator, { i.into_iter().chain(j) } @@ -161,8 +167,9 @@ pub fn chain(i: I, j: J) -> iter::Chain<::IntoIter, (iterable: I) -> iter::Cloned - where I: IntoIterator, - T: Clone, +where + I: IntoIterator, + T: Clone, { iterable.into_iter().cloned() } @@ -177,8 +184,9 @@ pub fn cloned<'a, I, T: 'a>(iterable: I) -> iter::Cloned /// assert_eq!(fold(&[1., 2., 3.], 0., |a, &b| f32::max(a, b)), 3.); /// ``` pub fn fold(iterable: I, init: B, f: F) -> B - where I: IntoIterator, - F: FnMut(B, I::Item) -> B +where + I: IntoIterator, + F: FnMut(B, I::Item) -> B, { iterable.into_iter().fold(init, f) } @@ -193,8 +201,9 @@ pub fn fold(iterable: I, init: B, f: F) -> B /// assert!(all(&[1, 2, 3], |elt| *elt > 0)); /// ``` pub fn all(iterable: I, f: F) -> bool - where I: IntoIterator, - F: FnMut(I::Item) -> bool +where + I: IntoIterator, + F: FnMut(I::Item) -> bool, { iterable.into_iter().all(f) } @@ -209,8 +218,9 @@ pub fn all(iterable: I, f: F) -> bool /// assert!(any(&[0, -1, 2], |elt| *elt > 0)); /// ``` pub fn any(iterable: I, f: F) -> bool - where I: IntoIterator, - F: FnMut(I::Item) -> bool +where + I: IntoIterator, + F: FnMut(I::Item) -> bool, { iterable.into_iter().any(f) } @@ -225,8 +235,9 @@ pub fn any(iterable: I, f: F) -> bool /// assert_eq!(max(0..10), Some(9)); /// ``` pub fn max(iterable: I) -> Option - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().max() } @@ -241,13 +252,13 @@ pub fn max(iterable: I) -> Option /// assert_eq!(min(0..10), Some(0)); /// ``` pub fn min(iterable: I) -> Option - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().min() } - /// Combine all iterator elements into one String, separated by `sep`. /// /// [`IntoIterator`] enabled version of [`Itertools::join`]. @@ -259,8 +270,9 @@ pub fn min(iterable: I) -> Option /// ``` #[cfg(feature = "use_alloc")] pub fn join(iterable: I, sep: &str) -> String - where I: IntoIterator, - I::Item: Display +where + I: IntoIterator, + I::Item: Display, { iterable.into_iter().join(sep) } @@ -277,9 +289,9 @@ pub fn join(iterable: I, sep: &str) -> String /// ``` #[cfg(feature = "use_alloc")] pub fn sorted(iterable: I) -> VecIntoIter - where I: IntoIterator, - I::Item: Ord +where + I: IntoIterator, + I::Item: Ord, { iterable.into_iter().sorted() } - diff --git a/src/group_map.rs b/src/group_map.rs index a2d0ebb2a..8891f95ac 100644 --- a/src/group_map.rs +++ b/src/group_map.rs @@ -9,8 +9,9 @@ use std::iter::Iterator; /// See [`.into_group_map()`](crate::Itertools::into_group_map) /// for more information. pub fn into_group_map(iter: I) -> HashMap> - where I: Iterator, - K: Hash + Eq, +where + I: Iterator, + K: Hash + Eq, { let mut lookup = HashMap::new(); @@ -22,11 +23,9 @@ pub fn into_group_map(iter: I) -> HashMap> } pub fn into_group_map_by(iter: I, f: impl Fn(&V) -> K) -> HashMap> - where - I: Iterator, - K: Hash + Eq, +where + I: Iterator, + K: Hash + Eq, { - into_group_map( - iter.map(|v| (f(&v), v)) - ) + into_group_map(iter.map(|v| (f(&v), v))) } diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 80c6f09f3..6cf33838c 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -1,5 +1,5 @@ -use std::cell::{Cell, RefCell}; use alloc::vec::{self, Vec}; +use std::cell::{Cell, RefCell}; /// A trait to unify `FnMut` for `GroupBy` with the chunk key in `IntoChunks` trait KeyFunction { @@ -8,7 +8,8 @@ trait KeyFunction { } impl KeyFunction for F - where F: FnMut(A) -> K +where + F: FnMut(A) -> K, { type Key = K; #[inline] @@ -17,7 +18,6 @@ impl KeyFunction for F } } - /// `ChunkIndex` acts like the grouping key function for `IntoChunks` #[derive(Debug, Clone)] struct ChunkIndex { @@ -52,7 +52,8 @@ impl KeyFunction for ChunkIndex { #[derive(Clone)] struct GroupInner - where I: Iterator +where + I: Iterator, { key: F, iter: I, @@ -75,9 +76,10 @@ struct GroupInner } impl GroupInner - where I: Iterator, - F: for<'a> KeyFunction<&'a I::Item, Key=K>, - K: PartialEq, +where + I: Iterator, + F: for<'a> KeyFunction<&'a I::Item, Key = K>, + K: PartialEq, { /// `client`: Index of group that requests next element #[inline(always)] @@ -90,9 +92,8 @@ impl GroupInner */ if client < self.oldest_buffered_group { None - } else if client < self.top_group || - (client == self.top_group && - self.buffer.len() > self.top_group - self.bottom_group) + } else if client < self.top_group + || (client == self.top_group && self.buffer.len() > self.top_group - self.bottom_group) { self.lookup_buffer(client) } else if self.done { @@ -118,8 +119,10 @@ impl GroupInner // `bottom_group..oldest_buffered_group` is unused, and if it's large enough, erase it. self.oldest_buffered_group += 1; // skip forward further empty queues too - while self.buffer.get(self.oldest_buffered_group - self.bottom_group) - .map_or(false, |buf| buf.len() == 0) + while self + .buffer + .get(self.oldest_buffered_group - self.bottom_group) + .map_or(false, |buf| buf.len() == 0) { self.oldest_buffered_group += 1; } @@ -144,12 +147,14 @@ impl GroupInner fn next_element(&mut self) -> Option { debug_assert!(!self.done); match self.iter.next() { - None => { self.done = true; None } + None => { + self.done = true; + None + } otherwise => otherwise, } } - #[inline(never)] fn step_buffering(&mut self, client: usize) -> Option { // requested a later group -- walk through the current group up to @@ -171,11 +176,13 @@ impl GroupInner let key = self.key.call_mut(&elt); match self.current_key.take() { None => {} - Some(old_key) => if old_key != key { - self.current_key = Some(key); - first_elt = Some(elt); - break; - }, + Some(old_key) => { + if old_key != key { + self.current_key = Some(key); + first_elt = Some(elt); + break; + } + } } self.current_key = Some(key); if self.top_group != self.dropped_group { @@ -220,12 +227,14 @@ impl GroupInner let key = self.key.call_mut(&elt); match self.current_key.take() { None => {} - Some(old_key) => if old_key != key { - self.current_key = Some(key); - self.current_elt = Some(elt); - self.top_group += 1; - return None; - }, + Some(old_key) => { + if old_key != key { + self.current_key = Some(key); + self.current_elt = Some(elt); + self.top_group += 1; + return None; + } + } } self.current_key = Some(key); Some(elt) @@ -261,7 +270,8 @@ impl GroupInner } impl GroupInner - where I: Iterator, +where + I: Iterator, { /// Called when a group is dropped fn drop_group(&mut self, client: usize) { @@ -287,7 +297,8 @@ impl GroupInner /// See [`.group_by()`](crate::Itertools::group_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct GroupBy - where I: Iterator, +where + I: Iterator, { inner: RefCell>, // the group iterator's current index. Keep this in the main value @@ -297,8 +308,9 @@ pub struct GroupBy /// Create a new pub fn new(iter: J, f: F) -> GroupBy - where J: IntoIterator, - F: FnMut(&J::Item) -> K, +where + J: IntoIterator, + F: FnMut(&J::Item) -> K, { GroupBy { inner: RefCell::new(GroupInner { @@ -318,12 +330,14 @@ pub fn new(iter: J, f: F) -> GroupBy } impl GroupBy - where I: Iterator, +where + I: Iterator, { /// `client`: Index of group that requests next element fn step(&self, client: usize) -> Option - where F: FnMut(&I::Item) -> K, - K: PartialEq, + where + F: FnMut(&I::Item) -> K, + K: PartialEq, { self.inner.borrow_mut().step(client) } @@ -335,10 +349,11 @@ impl GroupBy } impl<'a, K, I, F> IntoIterator for &'a GroupBy - where I: Iterator, - I::Item: 'a, - F: FnMut(&I::Item) -> K, - K: PartialEq +where + I: Iterator, + I::Item: 'a, + F: FnMut(&I::Item) -> K, + K: PartialEq, { type Item = (K, Group<'a, K, I, F>); type IntoIter = Groups<'a, K, I, F>; @@ -348,7 +363,6 @@ impl<'a, K, I, F> IntoIterator for &'a GroupBy } } - /// An iterator that yields the Group iterators. /// /// Iterator element type is `(K, Group)`: @@ -357,17 +371,19 @@ impl<'a, K, I, F> IntoIterator for &'a GroupBy /// See [`.group_by()`](crate::Itertools::group_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Groups<'a, K: 'a, I: 'a, F: 'a> - where I: Iterator, - I::Item: 'a +where + I: Iterator, + I::Item: 'a, { parent: &'a GroupBy, } impl<'a, K, I, F> Iterator for Groups<'a, K, I, F> - where I: Iterator, - I::Item: 'a, - F: FnMut(&I::Item) -> K, - K: PartialEq +where + I: Iterator, + I::Item: 'a, + F: FnMut(&I::Item) -> K, + K: PartialEq, { type Item = (K, Group<'a, K, I, F>); @@ -378,11 +394,14 @@ impl<'a, K, I, F> Iterator for Groups<'a, K, I, F> let inner = &mut *self.parent.inner.borrow_mut(); inner.step(index).map(|elt| { let key = inner.group_key(index); - (key, Group { - parent: self.parent, - index, - first: Some(elt), - }) + ( + key, + Group { + parent: self.parent, + index, + first: Some(elt), + }, + ) }) } } @@ -391,8 +410,9 @@ impl<'a, K, I, F> Iterator for Groups<'a, K, I, F> /// /// Iterator element type is `I::Item`. pub struct Group<'a, K: 'a, I: 'a, F: 'a> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { parent: &'a GroupBy, index: usize, @@ -400,8 +420,9 @@ pub struct Group<'a, K: 'a, I: 'a, F: 'a> } impl<'a, K, I, F> Drop for Group<'a, K, I, F> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { fn drop(&mut self) { self.parent.drop_group(self.index); @@ -409,10 +430,11 @@ impl<'a, K, I, F> Drop for Group<'a, K, I, F> } impl<'a, K, I, F> Iterator for Group<'a, K, I, F> - where I: Iterator, - I::Item: 'a, - F: FnMut(&I::Item) -> K, - K: PartialEq, +where + I: Iterator, + I::Item: 'a, + F: FnMut(&I::Item) -> K, + K: PartialEq, { type Item = I::Item; #[inline] @@ -428,7 +450,8 @@ impl<'a, K, I, F> Iterator for Group<'a, K, I, F> /// Create a new pub fn new_chunks(iter: J, size: usize) -> IntoChunks - where J: IntoIterator, +where + J: IntoIterator, { IntoChunks { inner: RefCell::new(GroupInner { @@ -447,7 +470,6 @@ pub fn new_chunks(iter: J, size: usize) -> IntoChunks } } - /// `ChunkLazy` is the storage for a lazy chunking operation. /// /// `IntoChunks` behaves just like `GroupBy`: it is iterable, and @@ -463,7 +485,8 @@ pub fn new_chunks(iter: J, size: usize) -> IntoChunks /// See [`.chunks()`](crate::Itertools::chunks) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct IntoChunks - where I: Iterator, +where + I: Iterator, { inner: RefCell>, // the chunk iterator's current index. Keep this in the main value @@ -472,15 +495,16 @@ pub struct IntoChunks } impl Clone for IntoChunks - where I: Clone + Iterator, - I::Item: Clone, +where + I: Clone + Iterator, + I::Item: Clone, { clone_fields!(inner, index); } - impl IntoChunks - where I: Iterator, +where + I: Iterator, { /// `client`: Index of chunk that requests next element fn step(&self, client: usize) -> Option { @@ -494,20 +518,18 @@ impl IntoChunks } impl<'a, I> IntoIterator for &'a IntoChunks - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { type Item = Chunk<'a, I>; type IntoIter = Chunks<'a, I>; fn into_iter(self) -> Self::IntoIter { - Chunks { - parent: self, - } + Chunks { parent: self } } } - /// An iterator that yields the Chunk iterators. /// /// Iterator element type is `Chunk`. @@ -516,15 +538,17 @@ impl<'a, I> IntoIterator for &'a IntoChunks #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone)] pub struct Chunks<'a, I: 'a> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { parent: &'a IntoChunks, } impl<'a, I> Iterator for Chunks<'a, I> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { type Item = Chunk<'a, I>; @@ -533,12 +557,10 @@ impl<'a, I> Iterator for Chunks<'a, I> let index = self.parent.index.get(); self.parent.index.set(index + 1); let inner = &mut *self.parent.inner.borrow_mut(); - inner.step(index).map(|elt| { - Chunk { - parent: self.parent, - index, - first: Some(elt), - } + inner.step(index).map(|elt| Chunk { + parent: self.parent, + index, + first: Some(elt), }) } } @@ -547,8 +569,9 @@ impl<'a, I> Iterator for Chunks<'a, I> /// /// Iterator element type is `I::Item`. pub struct Chunk<'a, I: 'a> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { parent: &'a IntoChunks, index: usize, @@ -556,8 +579,9 @@ pub struct Chunk<'a, I: 'a> } impl<'a, I> Drop for Chunk<'a, I> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { fn drop(&mut self) { self.parent.drop_group(self.index); @@ -565,8 +589,9 @@ impl<'a, I> Drop for Chunk<'a, I> } impl<'a, I> Iterator for Chunk<'a, I> - where I: Iterator, - I::Item: 'a, +where + I: Iterator, + I::Item: 'a, { type Item = I::Item; #[inline] diff --git a/src/grouping_map.rs b/src/grouping_map.rs index bb5b582c9..affcb2121 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -1,8 +1,8 @@ #![cfg(feature = "use_std")] use crate::MinMaxResult; -use std::collections::HashMap; use std::cmp::Ordering; +use std::collections::HashMap; use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; @@ -18,9 +18,10 @@ impl MapForGrouping { } impl Iterator for MapForGrouping - where I: Iterator, - K: Hash + Eq, - F: FnMut(&V) -> K, +where + I: Iterator, + K: Hash + Eq, + F: FnMut(&V) -> K, { type Item = (K, V); fn next(&mut self) -> Option { @@ -30,21 +31,22 @@ impl Iterator for MapForGrouping /// Creates a new `GroupingMap` from `iter` pub fn new(iter: I) -> GroupingMap - where I: Iterator, - K: Hash + Eq, +where + I: Iterator, + K: Hash + Eq, { GroupingMap { iter } } /// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations. -/// +/// /// See [`GroupingMap`] for more informations. pub type GroupingMapBy = GroupingMap>; /// `GroupingMap` is an intermediate struct for efficient group-and-fold operations. /// It groups elements by their key and at the same time fold each group /// using some aggregating operation. -/// +/// /// No method on this struct performs temporary allocations. #[derive(Clone, Debug)] #[must_use = "GroupingMap is lazy and do nothing unless consumed"] @@ -53,13 +55,14 @@ pub struct GroupingMap { } impl GroupingMap - where I: Iterator, - K: Hash + Eq, +where + I: Iterator, + K: Hash + Eq, { /// This is the generic way to perform any operation on a `GroupingMap`. /// It's suggested to use this method only to implement custom operations /// when the already provided ones are not enough. - /// + /// /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in an `HashMap`. @@ -68,17 +71,17 @@ impl GroupingMap /// - the current value of the accumulator of the group if there is currently one; /// - a reference to the key of the group this element belongs to; /// - the element from the source being aggregated; - /// + /// /// If `operation` returns `Some(element)` then the accumulator is updated with `element`, /// otherwise the previous accumulation is discarded. /// /// Return a `HashMap` associating the key of each group with the result of aggregation of /// that group's elements. If the aggregation of the last element of a group discards the /// accumulator then there won't be an entry associated to that group's key. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let data = vec![2, 8, 5, 7, 9, 0, 4, 10]; /// let lookup = data.into_iter() /// .into_grouping_map_by(|&n| n % 4) @@ -89,7 +92,7 @@ impl GroupingMap /// Some(acc.unwrap_or(0) + val) /// } /// }); - /// + /// /// assert_eq!(lookup[&0], 4); // 0 resets the accumulator so only 4 is summed /// assert_eq!(lookup[&1], 5 + 9); /// assert_eq!(lookup.get(&2), None); // 10 resets the accumulator and nothing is summed afterward @@ -97,7 +100,8 @@ impl GroupingMap /// assert_eq!(lookup.len(), 3); // The final keys are only 0, 1 and 2 /// ``` pub fn aggregate(self, mut operation: FO) -> HashMap - where FO: FnMut(Option, &K, V) -> Option, + where + FO: FnMut(Option, &K, V) -> Option, { let mut destination_map = HashMap::new(); @@ -123,22 +127,23 @@ impl GroupingMap /// - the element from the source being accumulated. /// /// Return a `HashMap` associating the key of each group with the result of folding that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = (1..=7) /// .into_grouping_map_by(|&n| n % 3) /// .fold(0, |acc, _key, val| acc + val); - /// + /// /// assert_eq!(lookup[&0], 3 + 6); /// assert_eq!(lookup[&1], 1 + 4 + 7); /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` pub fn fold(self, init: R, mut operation: FO) -> HashMap - where R: Clone, - FO: FnMut(R, &K, V) -> R, + where + R: Clone, + FO: FnMut(R, &K, V) -> R, { self.aggregate(|acc, key, val| { let acc = acc.unwrap_or_else(|| init.clone()); @@ -158,23 +163,24 @@ impl GroupingMap /// - the element from the source being accumulated. /// /// Return a `HashMap` associating the key of each group with the result of folding that group's elements. - /// + /// /// [`fold`]: GroupingMap::fold - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = (1..=7) /// .into_grouping_map_by(|&n| n % 3) /// .fold_first(|acc, _key, val| acc + val); - /// + /// /// assert_eq!(lookup[&0], 3 + 6); /// assert_eq!(lookup[&1], 1 + 4 + 7); /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` pub fn fold_first(self, mut operation: FO) -> HashMap - where FO: FnMut(V, &K, V) -> V, + where + FO: FnMut(V, &K, V) -> V, { self.aggregate(|acc, key, val| { Some(match acc { @@ -185,249 +191,261 @@ impl GroupingMap } /// Groups elements from the `GroupingMap` source by key and collects the elements of each group in - /// an instance of `C`. The iteration order is preserved when inserting elements. - /// + /// an instance of `C`. The iteration order is preserved when inserting elements. + /// /// Return a `HashMap` associating the key of each group with the collection containing that group's elements. - /// + /// /// ``` /// use itertools::Itertools; /// use std::collections::HashSet; - /// + /// /// let lookup = vec![0, 1, 2, 3, 4, 5, 6, 2, 3, 6].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .collect::>(); - /// + /// /// assert_eq!(lookup[&0], vec![0, 3, 6].into_iter().collect::>()); /// assert_eq!(lookup[&1], vec![1, 4].into_iter().collect::>()); /// assert_eq!(lookup[&2], vec![2, 5].into_iter().collect::>()); /// assert_eq!(lookup.len(), 3); /// ``` pub fn collect(self) -> HashMap - where C: Default + Extend, + where + C: Default + Extend, { let mut destination_map = HashMap::new(); self.iter.for_each(|(key, val)| { - destination_map.entry(key).or_insert_with(C::default).extend(Some(val)); + destination_map + .entry(key) + .or_insert_with(C::default) + .extend(Some(val)); }); destination_map } /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group. - /// + /// /// If several elements are equally maximum, the last element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .max(); - /// + /// /// assert_eq!(lookup[&0], 12); /// assert_eq!(lookup[&1], 7); /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` pub fn max(self) -> HashMap - where V: Ord, + where + V: Ord, { self.max_by(|_, v1, v2| V::cmp(v1, v2)) } /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group /// with respect to the specified comparison function. - /// + /// /// If several elements are equally maximum, the last element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .max_by(|_key, x, y| y.cmp(x)); - /// + /// /// assert_eq!(lookup[&0], 3); /// assert_eq!(lookup[&1], 1); /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` pub fn max_by(self, mut compare: F) -> HashMap - where F: FnMut(&K, &V, &V) -> Ordering, + where + F: FnMut(&K, &V, &V) -> Ordering, { self.fold_first(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => val, - Ordering::Greater => acc + Ordering::Greater => acc, }) } /// Groups elements from the `GroupingMap` source by key and finds the element of each group /// that gives the maximum from the specified function. - /// + /// /// If several elements are equally maximum, the last element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .max_by_key(|_key, &val| val % 4); - /// + /// /// assert_eq!(lookup[&0], 3); /// assert_eq!(lookup[&1], 7); /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` pub fn max_by_key(self, mut f: F) -> HashMap - where F: FnMut(&K, &V) -> CK, - CK: Ord, + where + F: FnMut(&K, &V) -> CK, + CK: Ord, { self.max_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group. - /// + /// /// If several elements are equally minimum, the first element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .min(); - /// + /// /// assert_eq!(lookup[&0], 3); /// assert_eq!(lookup[&1], 1); /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` pub fn min(self) -> HashMap - where V: Ord, + where + V: Ord, { self.min_by(|_, v1, v2| V::cmp(v1, v2)) } /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group /// with respect to the specified comparison function. - /// + /// /// If several elements are equally minimum, the first element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .min_by(|_key, x, y| y.cmp(x)); - /// + /// /// assert_eq!(lookup[&0], 12); /// assert_eq!(lookup[&1], 7); /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` pub fn min_by(self, mut compare: F) -> HashMap - where F: FnMut(&K, &V, &V) -> Ordering, + where + F: FnMut(&K, &V, &V) -> Ordering, { self.fold_first(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => acc, - Ordering::Greater => val + Ordering::Greater => val, }) } /// Groups elements from the `GroupingMap` source by key and finds the element of each group /// that gives the minimum from the specified function. - /// + /// /// If several elements are equally minimum, the first element is picked. - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .min_by_key(|_key, &val| val % 4); - /// + /// /// assert_eq!(lookup[&0], 12); /// assert_eq!(lookup[&1], 4); /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` pub fn min_by_key(self, mut f: F) -> HashMap - where F: FnMut(&K, &V) -> CK, - CK: Ord, + where + F: FnMut(&K, &V) -> CK, + CK: Ord, { self.min_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of /// each group. - /// + /// /// If several elements are equally maximum, the last element is picked. /// If several elements are equally minimum, the first element is picked. - /// + /// /// See [.minmax()](crate::Itertools::minmax) for the non-grouping version. - /// + /// /// Differences from the non grouping version: /// - It never produces a `MinMaxResult::NoElements` /// - It doesn't have any speedup - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; /// use itertools::MinMaxResult::{OneElement, MinMax}; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .minmax(); - /// + /// /// assert_eq!(lookup[&0], MinMax(3, 12)); /// assert_eq!(lookup[&1], MinMax(1, 7)); /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` pub fn minmax(self) -> HashMap> - where V: Ord, + where + V: Ord, { self.minmax_by(|_, v1, v2| V::cmp(v1, v2)) } /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of /// each group with respect to the specified comparison function. - /// + /// /// If several elements are equally maximum, the last element is picked. /// If several elements are equally minimum, the first element is picked. - /// + /// /// It has the same differences from the non-grouping version as `minmax`. - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; /// use itertools::MinMaxResult::{OneElement, MinMax}; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .minmax_by(|_key, x, y| y.cmp(x)); - /// + /// /// assert_eq!(lookup[&0], MinMax(12, 3)); /// assert_eq!(lookup[&1], MinMax(7, 1)); /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` pub fn minmax_by(self, mut compare: F) -> HashMap> - where F: FnMut(&K, &V, &V) -> Ordering, + where + F: FnMut(&K, &V, &V) -> Ordering, { self.aggregate(|acc, key, val| { Some(match acc { @@ -455,80 +473,83 @@ impl GroupingMap /// Groups elements from the `GroupingMap` source by key and find the elements of each group /// that gives the minimum and maximum from the specified function. - /// + /// /// If several elements are equally maximum, the last element is picked. /// If several elements are equally minimum, the first element is picked. - /// + /// /// It has the same differences from the non-grouping version as `minmax`. - /// + /// /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; /// use itertools::MinMaxResult::{OneElement, MinMax}; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .minmax_by_key(|_key, &val| val % 4); - /// + /// /// assert_eq!(lookup[&0], MinMax(12, 3)); /// assert_eq!(lookup[&1], MinMax(4, 7)); /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` pub fn minmax_by_key(self, mut f: F) -> HashMap> - where F: FnMut(&K, &V) -> CK, - CK: Ord, + where + F: FnMut(&K, &V) -> CK, + CK: Ord, { self.minmax_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } - + /// Groups elements from the `GroupingMap` source by key and sums them. - /// + /// /// This is just a shorthand for `self.fold_first(|acc, _, val| acc + val)`. /// It is more limited than `Iterator::sum` since it doesn't use the `Sum` trait. - /// + /// /// Returns a `HashMap` associating the key of each group with the sum of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .sum(); - /// + /// /// assert_eq!(lookup[&0], 3 + 9 + 12); /// assert_eq!(lookup[&1], 1 + 4 + 7); /// assert_eq!(lookup[&2], 5 + 8); /// assert_eq!(lookup.len(), 3); /// ``` pub fn sum(self) -> HashMap - where V: Add + where + V: Add, { self.fold_first(|acc, _, val| acc + val) } /// Groups elements from the `GroupingMap` source by key and multiply them. - /// + /// /// This is just a shorthand for `self.fold_first(|acc, _, val| acc * val)`. /// It is more limited than `Iterator::product` since it doesn't use the `Product` trait. - /// + /// /// Returns a `HashMap` associating the key of each group with the product of that group's elements. - /// + /// /// ``` /// use itertools::Itertools; - /// + /// /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() /// .into_grouping_map_by(|&n| n % 3) /// .product(); - /// + /// /// assert_eq!(lookup[&0], 3 * 9 * 12); /// assert_eq!(lookup[&1], 1 * 4 * 7); /// assert_eq!(lookup[&2], 5 * 8); /// assert_eq!(lookup.len(), 3); /// ``` pub fn product(self) -> HashMap - where V: Mul, + where + V: Mul, { self.fold_first(|acc, _, val| acc * val) } diff --git a/src/impl_macros.rs b/src/impl_macros.rs index a029843b0..5fd78cb4d 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -1,4 +1,4 @@ -//! +//! //! Implementation's internal macros macro_rules! debug_fmt_fields { diff --git a/src/intersperse.rs b/src/intersperse.rs index 10a3a5389..035a7a3e4 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -1,5 +1,5 @@ -use std::iter::{Fuse, FusedIterator}; use super::size_hint; +use std::iter::{Fuse, FusedIterator}; pub trait IntersperseElement { fn generate(&mut self) -> Item; @@ -26,12 +26,13 @@ pub type Intersperse = IntersperseWith(iter: I, elt: I::Item) -> Intersperse - where I: Iterator, +where + I: Iterator, { intersperse_with(iter, IntersperseElementSimple(elt)) } -implItem> IntersperseElement for F { +impl Item> IntersperseElement for F { fn generate(&mut self) -> Item { self() } @@ -48,7 +49,8 @@ implItem> IntersperseElement for F { #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct IntersperseWith - where I: Iterator, +where + I: Iterator, { element: ElemF, iter: Fuse, @@ -57,7 +59,8 @@ pub struct IntersperseWith /// Create a new `IntersperseWith` iterator pub fn intersperse_with(iter: I, elt: ElemF) -> IntersperseWith - where I: Iterator, +where + I: Iterator, { let mut iter = iter.fuse(); IntersperseWith { @@ -68,8 +71,9 @@ pub fn intersperse_with(iter: I, elt: ElemF) -> IntersperseWith Iterator for IntersperseWith - where I: Iterator, - ElemF: IntersperseElement +where + I: Iterator, + ElemF: IntersperseElement, { type Item = I::Item; #[inline] @@ -93,8 +97,10 @@ impl Iterator for IntersperseWith size_hint::add_scalar(size_hint::add(sh, sh), has_peek) } - fn fold(mut self, init: B, mut f: F) -> B where - Self: Sized, F: FnMut(B, Self::Item) -> B, + fn fold(mut self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, { let mut accum = init; @@ -104,15 +110,16 @@ impl Iterator for IntersperseWith let element = &mut self.element; - self.iter.fold(accum, - |accum, x| { - let accum = f(accum, element.generate()); - f(accum, x) + self.iter.fold(accum, |accum, x| { + let accum = f(accum, element.generate()); + f(accum, x) }) } } impl FusedIterator for IntersperseWith - where I: Iterator, - ElemF: IntersperseElement -{} +where + I: Iterator, + ElemF: IntersperseElement, +{ +} diff --git a/src/k_smallest.rs b/src/k_smallest.rs index acaea5941..6af66cfaf 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -2,7 +2,9 @@ use alloc::collections::BinaryHeap; use core::cmp::Ord; pub(crate) fn k_smallest>(mut iter: I, k: usize) -> BinaryHeap { - if k == 0 { return BinaryHeap::new(); } + if k == 0 { + return BinaryHeap::new(); + } let mut heap = iter.by_ref().take(k).collect::>(); diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 509d5fc6a..c077cdda1 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -2,9 +2,9 @@ use crate::size_hint; use crate::Itertools; use alloc::vec::Vec; +use std::fmt; use std::iter::FusedIterator; use std::mem::replace; -use std::fmt; /// Head element and Tail iterator pair /// @@ -15,24 +15,21 @@ use std::fmt; /// `KMerge` into a min-heap. #[derive(Debug)] struct HeadTail - where I: Iterator +where + I: Iterator, { head: I::Item, tail: I, } impl HeadTail - where I: Iterator +where + I: Iterator, { /// Constructs a `HeadTail` from an `Iterator`. Returns `None` if the `Iterator` is empty. fn new(mut it: I) -> Option> { let head = it.next(); - head.map(|h| { - HeadTail { - head: h, - tail: it, - } - }) + head.map(|h| HeadTail { head: h, tail: it }) } /// Get the next element and update `head`, returning the old head in `Some`. @@ -53,15 +50,17 @@ impl HeadTail } impl Clone for HeadTail - where I: Iterator + Clone, - I::Item: Clone +where + I: Iterator + Clone, + I::Item: Clone, { clone_fields!(head, tail); } /// Make `data` a heap (min-heap w.r.t the sorting). fn heapify(data: &mut [T], mut less_than: S) - where S: FnMut(&T, &T) -> bool +where + S: FnMut(&T, &T) -> bool, { for i in (0..data.len() / 2).rev() { sift_down(data, i, &mut less_than); @@ -70,7 +69,8 @@ fn heapify(data: &mut [T], mut less_than: S) /// Sift down element at `index` (`heap` is a min-heap wrt the ordering) fn sift_down(heap: &mut [T], index: usize, mut less_than: S) - where S: FnMut(&T, &T) -> bool +where + S: FnMut(&T, &T) -> bool, { debug_assert!(index <= heap.len()); let mut pos = index; @@ -81,7 +81,7 @@ fn sift_down(heap: &mut [T], index: usize, mut less_than: S) while child + 1 < heap.len() { // pick the smaller of the two children // use arithmetic to avoid an unpredictable branch - child += less_than(&heap[child+1], &heap[child]) as usize; + child += less_than(&heap[child + 1], &heap[child]) as usize; // sift down is done if we are already in order if !less_than(&heap[child], &heap[pos]) { @@ -119,7 +119,7 @@ impl KMergePredicate for KMergeByLt { } } -implbool> KMergePredicate for F { +impl bool> KMergePredicate for F { fn kmerge_pred(&mut self, a: &T, b: &T) -> bool { self(a, b) } @@ -138,9 +138,10 @@ implbool> KMergePredicate for F { /// } /// ``` pub fn kmerge(iterable: I) -> KMerge<::IntoIter> - where I: IntoIterator, - I::Item: IntoIterator, - <::Item as IntoIterator>::Item: PartialOrd +where + I: IntoIterator, + I::Item: IntoIterator, + <::Item as IntoIterator>::Item: PartialOrd, { kmerge_by(iterable, KMergeByLt) } @@ -154,15 +155,17 @@ pub fn kmerge(iterable: I) -> KMerge<::IntoIter> /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct KMergeBy - where I: Iterator, +where + I: Iterator, { heap: Vec>, less_than: F, } impl fmt::Debug for KMergeBy - where I: Iterator + fmt::Debug, - I::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug, { debug_fmt_fields!(KMergeBy, heap); } @@ -170,11 +173,14 @@ impl fmt::Debug for KMergeBy /// Create an iterator that merges elements of the contained iterators. /// /// [`IntoIterator`] enabled version of [`Itertools::kmerge_by`]. -pub fn kmerge_by(iterable: I, mut less_than: F) - -> KMergeBy<::IntoIter, F> - where I: IntoIterator, - I::Item: IntoIterator, - F: KMergePredicate<<::Item as IntoIterator>::Item>, +pub fn kmerge_by( + iterable: I, + mut less_than: F, +) -> KMergeBy<::IntoIter, F> +where + I: IntoIterator, + I::Item: IntoIterator, + F: KMergePredicate<<::Item as IntoIterator>::Item>, { let iter = iterable.into_iter(); let (lower, _) = iter.size_hint(); @@ -185,16 +191,18 @@ pub fn kmerge_by(iterable: I, mut less_than: F) } impl Clone for KMergeBy - where I: Iterator + Clone, - I::Item: Clone, - F: Clone, +where + I: Iterator + Clone, + I::Item: Clone, + F: Clone, { clone_fields!(heap, less_than); } impl Iterator for KMergeBy - where I: Iterator, - F: KMergePredicate +where + I: Iterator, + F: KMergePredicate, { type Item = I::Item; @@ -208,20 +216,25 @@ impl Iterator for KMergeBy self.heap.swap_remove(0).head }; let less_than = &mut self.less_than; - sift_down(&mut self.heap, 0, |a, b| less_than.kmerge_pred(&a.head, &b.head)); + sift_down(&mut self.heap, 0, |a, b| { + less_than.kmerge_pred(&a.head, &b.head) + }); Some(result) } fn size_hint(&self) -> (usize, Option) { #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` - self.heap.iter() - .map(|i| i.size_hint()) - .fold1(size_hint::add) - .unwrap_or((0, Some(0))) + self.heap + .iter() + .map(|i| i.size_hint()) + .fold1(size_hint::add) + .unwrap_or((0, Some(0))) } } impl FusedIterator for KMergeBy - where I: Iterator, - F: KMergePredicate -{} +where + I: Iterator, + F: KMergePredicate, +{ +} diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 80c171896..38c7d405b 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -1,6 +1,6 @@ +use alloc::vec::Vec; use std::iter::Fuse; use std::ops::Index; -use alloc::vec::Vec; use crate::size_hint::{self, SizeHint}; @@ -55,7 +55,7 @@ impl Index for LazyBuffer where I: Iterator, I::Item: Sized, - Vec: Index + Vec: Index, { type Output = as Index>::Output; diff --git a/src/lib.rs b/src/lib.rs index 79b4f481d..7dc40370b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ #![warn(missing_docs)] -#![crate_name="itertools"] +#![crate_name = "itertools"] #![cfg_attr(not(feature = "use_std"), no_std)] //! Extra iterator adaptors, functions and macros. @@ -43,7 +43,7 @@ //! ## Rust Version //! //! This version of itertools requires Rust 1.36 or later. -#![doc(html_root_url="https://docs.rs/itertools/0.11/")] +#![doc(html_root_url = "https://docs.rs/itertools/0.11/")] #[cfg(not(feature = "use_std"))] extern crate core as std; @@ -52,25 +52,22 @@ extern crate core as std; extern crate alloc; #[cfg(feature = "use_alloc")] -use alloc::{ - string::String, - vec::Vec, -}; +use alloc::{string::String, vec::Vec}; pub use either::Either; use core::borrow::Borrow; +use std::cmp::Ordering; #[cfg(feature = "use_std")] use std::collections::HashMap; -use std::iter::{IntoIterator, once}; -use std::cmp::Ordering; -use std::fmt; #[cfg(feature = "use_std")] use std::collections::HashSet; -#[cfg(feature = "use_std")] -use std::hash::Hash; +use std::fmt; #[cfg(feature = "use_alloc")] use std::fmt::Write; +#[cfg(feature = "use_std")] +use std::hash::Hash; +use std::iter::{once, IntoIterator}; #[cfg(feature = "use_alloc")] type VecIntoIter = alloc::vec::IntoIter; #[cfg(feature = "use_alloc")] @@ -85,71 +82,55 @@ pub use std::iter as __std_iter; /// The concrete iterator types. pub mod structs { + #[cfg(feature = "use_alloc")] + pub use crate::adaptors::MultiProduct; pub use crate::adaptors::{ - Dedup, - DedupBy, - DedupWithCount, - DedupByWithCount, - Interleave, - InterleaveShortest, - FilterMapOk, - FilterOk, - Product, - PutBack, - Batching, - MapInto, - MapOk, - TakeWhileRef, - WhileSome, - Coalesce, - TupleCombinations, - Positions, - Update, + Batching, Coalesce, Dedup, DedupBy, DedupByWithCount, DedupWithCount, FilterMapOk, + FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, + TakeWhileRef, TupleCombinations, Update, WhileSome, }; #[allow(deprecated)] pub use crate::adaptors::{MapResults, Step}; #[cfg(feature = "use_alloc")] - pub use crate::adaptors::MultiProduct; - #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; #[cfg(feature = "use_alloc")] pub use crate::combinations_with_replacement::CombinationsWithReplacement; pub use crate::cons_tuples_impl::ConsTuples; + #[cfg(feature = "use_std")] + pub use crate::duplicates_impl::{Duplicates, DuplicatesBy}; pub use crate::exactly_one_err::ExactlyOneError; - pub use crate::format::{Format, FormatWith}; pub use crate::flatten_ok::FlattenOk; + pub use crate::format::{Format, FormatWith}; + #[cfg(feature = "use_alloc")] + pub use crate::groupbylazy::{Chunk, Chunks, Group, GroupBy, Groups, IntoChunks}; #[cfg(feature = "use_std")] pub use crate::grouping_map::{GroupingMap, GroupingMapBy}; - #[cfg(feature = "use_alloc")] - pub use crate::groupbylazy::{IntoChunks, Chunk, Chunks, GroupBy, Group, Groups}; pub use crate::intersperse::{Intersperse, IntersperseWith}; #[cfg(feature = "use_alloc")] pub use crate::kmerge_impl::{KMerge, KMergeBy}; pub use crate::merge_join::{Merge, MergeBy, MergeJoinBy}; #[cfg(feature = "use_alloc")] pub use crate::multipeek_impl::MultiPeek; + pub use crate::pad_tail::PadUsing; #[cfg(feature = "use_alloc")] pub use crate::peek_nth::PeekNth; - pub use crate::pad_tail::PadUsing; pub use crate::peeking_take_while::PeekingTakeWhile; #[cfg(feature = "use_alloc")] pub use crate::permutations::Permutations; - pub use crate::process_results_impl::ProcessResults; #[cfg(feature = "use_alloc")] pub use crate::powerset::Powerset; + pub use crate::process_results_impl::ProcessResults; #[cfg(feature = "use_alloc")] pub use crate::put_back_n_impl::PutBackN; #[cfg(feature = "use_alloc")] pub use crate::rciter_impl::RcIter; pub use crate::repeatn::RepeatN; #[allow(deprecated)] - pub use crate::sources::{RepeatCall, Unfold, Iterate}; + pub use crate::sources::{Iterate, RepeatCall, Unfold}; pub use crate::take_while_inclusive::TakeWhileInclusive; #[cfg(feature = "use_alloc")] pub use crate::tee::Tee; - pub use crate::tuple_impl::{TupleBuffer, TupleWindows, CircularTupleWindows, Tuples}; - #[cfg(feature = "use_std")] - pub use crate::duplicates_impl::{Duplicates, DuplicatesBy}; + pub use crate::tuple_impl::{CircularTupleWindows, TupleBuffer, TupleWindows, Tuples}; #[cfg(feature = "use_std")] pub use crate::unique_impl::{Unique, UniqueBy}; pub use crate::with_position::WithPosition; @@ -163,22 +144,22 @@ pub mod traits { pub use crate::tuple_impl::HomogeneousTuple; } -#[allow(deprecated)] -pub use crate::structs::*; pub use crate::concat_impl::concat; pub use crate::cons_tuples_impl::cons_tuples; pub use crate::diff::diff_with; pub use crate::diff::Diff; #[cfg(feature = "use_alloc")] -pub use crate::kmerge_impl::{kmerge_by}; +pub use crate::kmerge_impl::kmerge_by; pub use crate::minmax::MinMaxResult; pub use crate::peeking_take_while::PeekingNext; pub use crate::process_results_impl::process_results; pub use crate::repeatn::repeat_n; #[allow(deprecated)] -pub use crate::sources::{repeat_call, unfold, iterate}; -pub use crate::with_position::Position; +pub use crate::sources::{iterate, repeat_call, unfold}; +#[allow(deprecated)] +pub use crate::structs::*; pub use crate::unziptuple::{multiunzip, MultiUnzip}; +pub use crate::with_position::Position; pub use crate::ziptuple::multizip; mod adaptors; mod either_or_both; @@ -187,24 +168,26 @@ pub use crate::either_or_both::EitherOrBoth; pub mod free; #[doc(inline)] pub use crate::free::*; -mod concat_impl; -mod cons_tuples_impl; #[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] mod combinations_with_replacement; -mod exactly_one_err; +mod concat_impl; +mod cons_tuples_impl; mod diff; -mod flatten_ok; +#[cfg(feature = "use_std")] +mod duplicates_impl; +mod exactly_one_err; #[cfg(feature = "use_std")] mod extrema_set; +mod flatten_ok; mod format; -#[cfg(feature = "use_std")] -mod grouping_map; #[cfg(feature = "use_alloc")] mod group_map; #[cfg(feature = "use_alloc")] mod groupbylazy; +#[cfg(feature = "use_std")] +mod grouping_map; mod intersperse; #[cfg(feature = "use_alloc")] mod k_smallest; @@ -237,8 +220,6 @@ mod take_while_inclusive; mod tee; mod tuple_impl; #[cfg(feature = "use_std")] -mod duplicates_impl; -#[cfg(feature = "use_std")] mod unique_impl; mod unziptuple; mod with_position; @@ -428,7 +409,7 @@ macro_rules! chain { /// return a regular value of some other kind. /// [`.next_tuple()`](Itertools::next_tuple) is an example and the first regular /// method in the list. -pub trait Itertools : Iterator { +pub trait Itertools: Iterator { // adaptors /// Alternate elements from two iterators until both have run out. @@ -444,8 +425,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![1, -1, 2, -2, 3, 4, 5, 6]); /// ``` fn interleave(self, other: J) -> Interleave - where J: IntoIterator, - Self: Sized + where + J: IntoIterator, + Self: Sized, { interleave(self, other) } @@ -462,8 +444,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![1, -1, 2, -2, 3]); /// ``` fn interleave_shortest(self, other: J) -> InterleaveShortest - where J: IntoIterator, - Self: Sized + where + J: IntoIterator, + Self: Sized, { adaptors::interleave_shortest(self, other.into_iter()) } @@ -481,8 +464,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal((0..3).intersperse(8), vec![0, 8, 1, 8, 2]); /// ``` fn intersperse(self, element: Self::Item) -> Intersperse - where Self: Sized, - Self::Item: Clone + where + Self: Sized, + Self::Item: Clone, { intersperse::intersperse(self, element) } @@ -502,8 +486,9 @@ pub trait Itertools : Iterator { /// assert_eq!(i, 8); /// ``` fn intersperse_with(self, element: F) -> IntersperseWith - where Self: Sized, - F: FnMut() -> Self::Item + where + Self: Sized, + F: FnMut() -> Self::Item, { intersperse::intersperse_with(self, element) } @@ -536,8 +521,9 @@ pub trait Itertools : Iterator { /// ``` #[inline] fn zip_longest(self, other: J) -> ZipLongest - where J: IntoIterator, - Self: Sized + where + J: IntoIterator, + Self: Sized, { zip_longest::zip_longest(self, other.into_iter()) } @@ -549,8 +535,9 @@ pub trait Itertools : Iterator { /// lengths. #[inline] fn zip_eq(self, other: J) -> ZipEq - where J: IntoIterator, - Self: Sized + where + J: IntoIterator, + Self: Sized, { zip_eq(self, other) } @@ -579,8 +566,9 @@ pub trait Itertools : Iterator { /// ``` /// fn batching(self, f: F) -> Batching - where F: FnMut(&mut Self) -> Option, - Self: Sized + where + F: FnMut(&mut Self) -> Option, + Self: Sized, { adaptors::batching(self, f) } @@ -621,9 +609,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn group_by(self, key: F) -> GroupBy - where Self: Sized, - F: FnMut(&Self::Item) -> K, - K: PartialEq, + where + Self: Sized, + F: FnMut(&Self::Item) -> K, + K: PartialEq, { groupbylazy::new(self, key) } @@ -657,7 +646,8 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn chunks(self, size: usize) -> IntoChunks - where Self: Sized, + where + Self: Sized, { assert!(size != 0); groupbylazy::new_chunks(self, size) @@ -697,9 +687,10 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(1, 2, 3), (2, 3, 4)]); /// ``` fn tuple_windows(self) -> TupleWindows - where Self: Sized + Iterator, - T: traits::HomogeneousTuple, - T::Item: Clone + where + Self: Sized + Iterator, + T: traits::HomogeneousTuple, + T::Item: Clone, { tuple_impl::tuple_windows(self) } @@ -732,9 +723,10 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(1, 2, 3), (2, 3, 4), (3, 4, 1), (4, 1, 2)]); /// ``` fn circular_tuple_windows(self) -> CircularTupleWindows - where Self: Sized + Clone + Iterator + ExactSizeIterator, - T: tuple_impl::TupleCollect + Clone, - T::Item: Clone + where + Self: Sized + Clone + Iterator + ExactSizeIterator, + T: tuple_impl::TupleCollect + Clone, + T::Item: Clone, { tuple_impl::circular_tuple_windows(self) } @@ -770,8 +762,9 @@ pub trait Itertools : Iterator { /// /// See also [`Tuples::into_buffer`]. fn tuples(self) -> Tuples - where Self: Sized + Iterator, - T: traits::HomogeneousTuple + where + Self: Sized + Iterator, + T: traits::HomogeneousTuple, { tuple_impl::tuples(self) } @@ -795,8 +788,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn tee(self) -> (Tee, Tee) - where Self: Sized, - Self::Item: Clone + where + Self: Sized, + Self::Item: Clone, { tee::new(self) } @@ -817,10 +811,11 @@ pub trait Itertools : Iterator { /// let it = (0..8).step(3); /// itertools::assert_equal(it, vec![0, 3, 6]); /// ``` - #[deprecated(note="Use std .step_by() instead", since="0.8.0")] + #[deprecated(note = "Use std .step_by() instead", since = "0.8.0")] #[allow(deprecated)] fn step(self, n: usize) -> Step - where Self: Sized + where + Self: Sized, { adaptors::step(self, n) } @@ -833,17 +828,19 @@ pub trait Itertools : Iterator { /// (1i32..42i32).map_into::().collect_vec(); /// ``` fn map_into(self) -> MapInto - where Self: Sized, - Self::Item: Into, + where + Self: Sized, + Self::Item: Into, { adaptors::map_into(self) } /// See [`.map_ok()`](Itertools::map_ok). - #[deprecated(note="Use .map_ok() instead", since="0.10.0")] + #[deprecated(note = "Use .map_ok() instead", since = "0.10.0")] fn map_results(self, f: F) -> MapOk - where Self: Iterator> + Sized, - F: FnMut(T) -> U, + where + Self: Iterator> + Sized, + F: FnMut(T) -> U, { self.map_ok(f) } @@ -860,8 +857,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![Ok(42), Err(false), Ok(12)]); /// ``` fn map_ok(self, f: F) -> MapOk - where Self: Iterator> + Sized, - F: FnMut(T) -> U, + where + Self: Iterator> + Sized, + F: FnMut(T) -> U, { adaptors::map_ok(self, f) } @@ -878,8 +876,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![Ok(22), Err(false)]); /// ``` fn filter_ok(self, f: F) -> FilterOk - where Self: Iterator> + Sized, - F: FnMut(&T) -> bool, + where + Self: Iterator> + Sized, + F: FnMut(&T) -> bool, { adaptors::filter_ok(self, f) } @@ -896,8 +895,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![Ok(44), Err(false)]); /// ``` fn filter_map_ok(self, f: F) -> FilterMapOk - where Self: Iterator> + Sized, - F: FnMut(T) -> Option, + where + Self: Iterator> + Sized, + F: FnMut(T) -> Option, { adaptors::filter_map_ok(self, f) } @@ -920,8 +920,9 @@ pub trait Itertools : Iterator { /// assert_eq!(output_result, Err(false)); /// ``` fn flatten_ok(self) -> FlattenOk - where Self: Iterator> + Sized, - T: IntoIterator + where + Self: Iterator> + Sized, + T: IntoIterator, { flatten_ok::flatten_ok(self) } @@ -957,8 +958,9 @@ pub trait Itertools : Iterator { /// assert!(second_max.is_err()); /// ``` fn process_results(self, processor: F) -> Result - where Self: Iterator> + Sized, - F: FnOnce(ProcessResults) -> R + where + Self: Iterator> + Sized, + F: FnOnce(ProcessResults) -> R, { process_results(self, processor) } @@ -978,9 +980,10 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![0, 0, 3, 5, 6, 9, 10]); /// ``` fn merge(self, other: J) -> Merge - where Self: Sized, - Self::Item: PartialOrd, - J: IntoIterator + where + Self: Sized, + Self::Item: PartialOrd, + J: IntoIterator, { merge(self, other) } @@ -1002,9 +1005,10 @@ pub trait Itertools : Iterator { /// ``` fn merge_by(self, other: J, is_first: F) -> MergeBy - where Self: Sized, - J: IntoIterator, - F: FnMut(&Self::Item, &Self::Item) -> bool + where + Self: Sized, + J: IntoIterator, + F: FnMut(&Self::Item, &Self::Item) -> bool, { merge_join::merge_by_new(self, other.into_iter(), is_first) } @@ -1068,10 +1072,11 @@ pub trait Itertools : Iterator { /// ``` #[inline] fn merge_join_by(self, other: J, cmp_fn: F) -> MergeJoinBy - where J: IntoIterator, - F: FnMut(&Self::Item, &J::Item) -> T, - T: merge_join::OrderingOrBool, - Self: Sized + where + J: IntoIterator, + F: FnMut(&Self::Item, &J::Item) -> T, + T: merge_join::OrderingOrBool, + Self: Sized, { merge_join_by(self, other, cmp_fn) } @@ -1094,9 +1099,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn kmerge(self) -> KMerge<::IntoIter> - where Self: Sized, - Self::Item: IntoIterator, - ::Item: PartialOrd, + where + Self: Sized, + Self::Item: IntoIterator, + ::Item: PartialOrd, { kmerge(self) } @@ -1122,12 +1128,11 @@ pub trait Itertools : Iterator { /// assert_eq!(it.last(), Some(-7.)); /// ``` #[cfg(feature = "use_alloc")] - fn kmerge_by(self, first: F) - -> KMergeBy<::IntoIter, F> - where Self: Sized, - Self::Item: IntoIterator, - F: FnMut(&::Item, - &::Item) -> bool + fn kmerge_by(self, first: F) -> KMergeBy<::IntoIter, F> + where + Self: Sized, + Self::Item: IntoIterator, + F: FnMut(&::Item, &::Item) -> bool, { kmerge_by(self, first) } @@ -1144,10 +1149,11 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(0, 'α'), (0, 'β'), (1, 'α'), (1, 'β')]); /// ``` fn cartesian_product(self, other: J) -> Product - where Self: Sized, - Self::Item: Clone, - J: IntoIterator, - J::IntoIter: Clone + where + Self: Sized, + Self::Item: Clone, + J: IntoIterator, + J::IntoIter: Clone, { adaptors::cartesian_product(self, other.into_iter()) } @@ -1179,10 +1185,11 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn multi_cartesian_product(self) -> MultiProduct<::IntoIter> - where Self: Sized, - Self::Item: IntoIterator, - ::IntoIter: Clone, - ::Item: Clone + where + Self: Sized, + Self::Item: IntoIterator, + ::IntoIter: Clone, + ::Item: Clone, { adaptors::multi_cartesian_product(self) } @@ -1216,9 +1223,9 @@ pub trait Itertools : Iterator { /// vec![-6., 4., -1.]); /// ``` fn coalesce(self, f: F) -> Coalesce - where Self: Sized, - F: FnMut(Self::Item, Self::Item) - -> Result + where + Self: Sized, + F: FnMut(Self::Item, Self::Item) -> Result, { adaptors::coalesce(self, f) } @@ -1238,8 +1245,9 @@ pub trait Itertools : Iterator { /// vec![1., 2., 3., 2.]); /// ``` fn dedup(self) -> Dedup - where Self: Sized, - Self::Item: PartialEq, + where + Self: Sized, + Self::Item: PartialEq, { adaptors::dedup(self) } @@ -1260,8 +1268,9 @@ pub trait Itertools : Iterator { /// vec![(0, 1.), (0, 2.), (0, 3.), (1, 2.)]); /// ``` fn dedup_by(self, cmp: Cmp) -> DedupBy - where Self: Sized, - Cmp: FnMut(&Self::Item, &Self::Item)->bool, + where + Self: Sized, + Cmp: FnMut(&Self::Item, &Self::Item) -> bool, { adaptors::dedup_by(self, cmp) } @@ -1328,8 +1337,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn duplicates(self) -> Duplicates - where Self: Sized, - Self::Item: Eq + Hash + where + Self: Sized, + Self::Item: Eq + Hash, { duplicates_impl::duplicates(self) } @@ -1353,9 +1363,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn duplicates_by(self, f: F) -> DuplicatesBy - where Self: Sized, - V: Eq + Hash, - F: FnMut(&Self::Item) -> V + where + Self: Sized, + V: Eq + Hash, + F: FnMut(&Self::Item) -> V, { duplicates_impl::duplicates_by(self, f) } @@ -1380,8 +1391,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn unique(self) -> Unique - where Self: Sized, - Self::Item: Clone + Eq + Hash + where + Self: Sized, + Self::Item: Clone + Eq + Hash, { unique_impl::unique(self) } @@ -1406,9 +1418,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn unique_by(self, f: F) -> UniqueBy - where Self: Sized, - V: Eq + Hash, - F: FnMut(&Self::Item) -> V + where + Self: Sized, + V: Eq + Hash, + F: FnMut(&Self::Item) -> V, { unique_impl::unique_by(self, f) } @@ -1426,8 +1439,9 @@ pub trait Itertools : Iterator { /// See also [`.take_while_ref()`](Itertools::take_while_ref) /// which is a similar adaptor. fn peeking_take_while(&mut self, accept: F) -> PeekingTakeWhile - where Self: Sized + PeekingNext, - F: FnMut(&Self::Item) -> bool, + where + Self: Sized + PeekingNext, + F: FnMut(&Self::Item) -> bool, { peeking_take_while::peeking_take_while(self, accept) } @@ -1451,8 +1465,9 @@ pub trait Itertools : Iterator { /// /// ``` fn take_while_ref(&mut self, accept: F) -> TakeWhileRef - where Self: Clone, - F: FnMut(&Self::Item) -> bool + where + Self: Clone, + F: FnMut(&Self::Item) -> bool, { adaptors::take_while_ref(self, accept) } @@ -1540,7 +1555,8 @@ pub trait Itertools : Iterator { /// /// ``` fn while_some(self) -> WhileSome - where Self: Sized + Iterator> + where + Self: Sized + Iterator>, { adaptors::while_some(self) } @@ -1579,9 +1595,10 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]); /// ``` fn tuple_combinations(self) -> TupleCombinations - where Self: Sized + Clone, - Self::Item: Clone, - T: adaptors::HasCombination, + where + Self: Sized + Clone, + Self::Item: Clone, + T: adaptors::HasCombination, { adaptors::tuple_combinations(self) } @@ -1617,8 +1634,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn combinations(self, k: usize) -> Combinations - where Self: Sized, - Self::Item: Clone + where + Self: Sized, + Self::Item: Clone, { combinations::combinations(self, k) } @@ -1690,8 +1708,9 @@ pub trait Itertools : Iterator { /// re-iterated if the permutations adaptor is completed and re-iterated. #[cfg(feature = "use_alloc")] fn permutations(self, k: usize) -> Permutations - where Self: Sized, - Self::Item: Clone + where + Self: Sized, + Self::Item: Clone, { permutations::permutations(self, k) } @@ -1726,8 +1745,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn powerset(self) -> Powerset - where Self: Sized, - Self::Item: Clone, + where + Self: Sized, + Self::Item: Clone, { powerset::powerset(self) } @@ -1750,8 +1770,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![18, 16, 14, 12, 10, 4, 3, 2, 1, 0]); /// ``` fn pad_using(self, min: usize, f: F) -> PadUsing - where Self: Sized, - F: FnMut(usize) -> Self::Item + where + Self: Sized, + F: FnMut(usize) -> Self::Item, { pad_tail::pad_using(self, min, f) } @@ -1776,7 +1797,8 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![(Position::Only, 0)]); /// ``` fn with_position(self) -> WithPosition - where Self: Sized, + where + Self: Sized, { with_position::with_position(self) } @@ -1795,8 +1817,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(data.iter().positions(|v| v % 2 == 1).rev(), vec![7, 6, 3, 2, 0]); /// ``` fn positions

(self, predicate: P) -> Positions - where Self: Sized, - P: FnMut(Self::Item) -> bool, + where + Self: Sized, + P: FnMut(Self::Item) -> bool, { adaptors::positions(self, predicate) } @@ -1812,8 +1835,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(it, vec![vec![1, 0], vec![3, 2, 1, 0]]); /// ``` fn update(self, updater: F) -> Update - where Self: Sized, - F: FnMut(&mut Self::Item), + where + Self: Sized, + F: FnMut(&mut Self::Item), { adaptors::update(self, updater) } @@ -1833,8 +1857,9 @@ pub trait Itertools : Iterator { /// assert_eq!(Some((1, 2)), iter.next_tuple()); /// ``` fn next_tuple(&mut self) -> Option - where Self: Sized + Iterator, - T: traits::HomogeneousTuple + where + Self: Sized + Iterator, + T: traits::HomogeneousTuple, { T::collect_from_iter_no_buf(self) } @@ -1858,19 +1883,19 @@ pub trait Itertools : Iterator { /// } /// ``` fn collect_tuple(mut self) -> Option - where Self: Sized + Iterator, - T: traits::HomogeneousTuple + where + Self: Sized + Iterator, + T: traits::HomogeneousTuple, { match self.next_tuple() { elt @ Some(_) => match self.next() { Some(_) => None, None => elt, }, - _ => None + _ => None, } } - /// Find the position and value of the first element satisfying a predicate. /// /// The iterator is not advanced past the first element found. @@ -1882,7 +1907,8 @@ pub trait Itertools : Iterator { /// assert_eq!(text.chars().find_position(|ch| ch.is_lowercase()), Some((1, 'α'))); /// ``` fn find_position

(&mut self, mut pred: P) -> Option<(usize, Self::Item)> - where P: FnMut(&Self::Item) -> bool + where + P: FnMut(&Self::Item) -> bool, { for (index, elt) in self.enumerate() { if pred(&elt) { @@ -1904,12 +1930,20 @@ pub trait Itertools : Iterator { /// assert_eq!(std::iter::empty::().find_or_last(|&x| x > 5), None); /// ``` fn find_or_last

(mut self, mut predicate: P) -> Option - where Self: Sized, - P: FnMut(&Self::Item) -> bool, + where + Self: Sized, + P: FnMut(&Self::Item) -> bool, { let mut prev = None; - self.find_map(|x| if predicate(&x) { Some(x) } else { prev = Some(x); None }) - .or(prev) + self.find_map(|x| { + if predicate(&x) { + Some(x) + } else { + prev = Some(x); + None + } + }) + .or(prev) } /// Find the value of the first element satisfying a predicate or return the first element, if any. /// @@ -1924,8 +1958,9 @@ pub trait Itertools : Iterator { /// assert_eq!(std::iter::empty::().find_or_first(|&x| x > 5), None); /// ``` fn find_or_first

(mut self, mut predicate: P) -> Option - where Self: Sized, - P: FnMut(&Self::Item) -> bool, + where + Self: Sized, + P: FnMut(&Self::Item) -> bool, { let first = self.next()?; Some(if predicate(&first) { @@ -1985,8 +2020,9 @@ pub trait Itertools : Iterator { /// assert!(data.into_iter().all_equal()); /// ``` fn all_equal(&mut self) -> bool - where Self: Sized, - Self::Item: PartialEq, + where + Self: Sized, + Self::Item: PartialEq, { match self.next() { None => true, @@ -2012,9 +2048,9 @@ pub trait Itertools : Iterator { /// assert_eq!(data.into_iter().all_equal_value(), Err(None)); /// ``` fn all_equal_value(&mut self) -> Result> - where - Self: Sized, - Self::Item: PartialEq + where + Self: Sized, + Self::Item: PartialEq, { let first = self.next().ok_or(None)?; let other = self.find(|x| x != &first); @@ -2042,8 +2078,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn all_unique(&mut self) -> bool - where Self: Sized, - Self::Item: Eq + Hash + where + Self: Sized, + Self::Item: Eq + Hash, { let mut used = HashSet::new(); self.all(move |elt| used.insert(elt)) @@ -2065,7 +2102,8 @@ pub trait Itertools : Iterator { /// *Fusing notes: if the iterator is exhausted by dropping, /// the result of calling `.next()` again depends on the iterator implementation.* fn dropping(mut self, n: usize) -> Self - where Self: Sized + where + Self: Sized, { if n > 0 { self.nth(n - 1); @@ -2089,8 +2127,9 @@ pub trait Itertools : Iterator { /// itertools::assert_equal(init, vec![0, 3, 6]); /// ``` fn dropping_back(mut self, n: usize) -> Self - where Self: Sized, - Self: DoubleEndedIterator + where + Self: Sized, + Self: DoubleEndedIterator, { if n > 0 { (&mut self).rev().nth(n - 1); @@ -2115,10 +2154,11 @@ pub trait Itertools : Iterator { /// /// itertools::assert_equal(rx.iter(), vec![1, 3, 5, 7, 9]); /// ``` - #[deprecated(note="Use .for_each() instead", since="0.8.0")] + #[deprecated(note = "Use .for_each() instead", since = "0.8.0")] fn foreach(self, f: F) - where F: FnMut(Self::Item), - Self: Sized, + where + F: FnMut(Self::Item), + Self: Sized, { self.for_each(f); } @@ -2137,8 +2177,10 @@ pub trait Itertools : Iterator { /// vec![1, 2, 3, 4, 5, 6]); /// ``` fn concat(self) -> Self::Item - where Self: Sized, - Self::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default + where + Self: Sized, + Self::Item: + Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default, { concat(self) } @@ -2147,7 +2189,8 @@ pub trait Itertools : Iterator { /// for convenience. #[cfg(feature = "use_alloc")] fn collect_vec(self) -> Vec - where Self: Sized + where + Self: Sized, { self.collect() } @@ -2198,8 +2241,9 @@ pub trait Itertools : Iterator { /// ``` #[inline] fn set_from<'a, A: 'a, J>(&mut self, from: J) -> usize - where Self: Iterator, - J: IntoIterator + where + Self: Iterator, + J: IntoIterator, { let mut count = 0; for elt in from { @@ -2224,7 +2268,8 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn join(&mut self, sep: &str) -> String - where Self::Item: std::fmt::Display + where + Self::Item: std::fmt::Display, { match self.next() { None => String::new(), @@ -2258,7 +2303,8 @@ pub trait Itertools : Iterator { /// "1.10, 2.72, -3.00"); /// ``` fn format(self, sep: &str) -> Format - where Self: Sized, + where + Self: Sized, { format::new_format_default(self, sep) } @@ -2296,17 +2342,19 @@ pub trait Itertools : Iterator { /// /// ``` fn format_with(self, sep: &str, format: F) -> FormatWith - where Self: Sized, - F: FnMut(Self::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, + where + Self: Sized, + F: FnMut(Self::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, { format::new_format(self, sep, format) } /// See [`.fold_ok()`](Itertools::fold_ok). - #[deprecated(note="Use .fold_ok() instead", since="0.10.0")] + #[deprecated(note = "Use .fold_ok() instead", since = "0.10.0")] fn fold_results(&mut self, start: B, f: F) -> Result - where Self: Iterator>, - F: FnMut(B, A) -> B + where + Self: Iterator>, + F: FnMut(B, A) -> B, { self.fold_ok(start, f) } @@ -2354,8 +2402,9 @@ pub trait Itertools : Iterator { /// ); /// ``` fn fold_ok(&mut self, mut start: B, mut f: F) -> Result - where Self: Iterator>, - F: FnMut(B, A) -> B + where + Self: Iterator>, + F: FnMut(B, A) -> B, { for elt in self { match elt { @@ -2386,8 +2435,9 @@ pub trait Itertools : Iterator { /// assert_eq!(more_values.next().unwrap(), Some(0)); /// ``` fn fold_options(&mut self, mut start: B, mut f: F) -> Option - where Self: Iterator>, - F: FnMut(B, A) -> B + where + Self: Iterator>, + F: FnMut(B, A) -> B, { for elt in self { match elt { @@ -2412,8 +2462,9 @@ pub trait Itertools : Iterator { /// ``` #[deprecated(since = "0.10.2", note = "Use `Iterator::reduce` instead")] fn fold1(mut self, f: F) -> Option - where F: FnMut(Self::Item, Self::Item) -> Self::Item, - Self: Sized, + where + F: FnMut(Self::Item, Self::Item) -> Self::Item, + Self: Sized, { self.next().map(move |x| self.fold(x, f)) } @@ -2467,44 +2518,48 @@ pub trait Itertools : Iterator { /// (0..10).fold1(|x, y| x - y)); /// ``` fn tree_fold1(mut self, mut f: F) -> Option - where F: FnMut(Self::Item, Self::Item) -> Self::Item, - Self: Sized, + where + F: FnMut(Self::Item, Self::Item) -> Self::Item, + Self: Sized, { type State = Result>; fn inner0(it: &mut II, f: &mut FF) -> State - where - II: Iterator, - FF: FnMut(T, T) -> T + where + II: Iterator, + FF: FnMut(T, T) -> T, { // This function could be replaced with `it.next().ok_or(None)`, // but half the useful tree_fold1 work is combining adjacent items, // so put that in a form that LLVM is more likely to optimize well. - let a = - if let Some(v) = it.next() { v } - else { return Err(None) }; - let b = - if let Some(v) = it.next() { v } - else { return Err(Some(a)) }; + let a = if let Some(v) = it.next() { + v + } else { + return Err(None); + }; + let b = if let Some(v) = it.next() { + v + } else { + return Err(Some(a)); + }; Ok(f(a, b)) } fn inner(stop: usize, it: &mut II, f: &mut FF) -> State - where - II: Iterator, - FF: FnMut(T, T) -> T + where + II: Iterator, + FF: FnMut(T, T) -> T, { let mut x = inner0(it, f)?; for height in 0..stop { // Try to get another tree the same size with which to combine it, // creating a new tree that's twice as big for next time around. - let next = - if height == 0 { - inner0(it, f) - } else { - inner(height, it, f) - }; + let next = if height == 0 { + inner0(it, f) + } else { + inner(height, it, f) + }; match next { Ok(y) => x = f(x, y), @@ -2565,19 +2620,19 @@ pub trait Itertools : Iterator { /// `fold()` called the provided closure for every item of the callee iterator, /// `fold_while()` actually stopped iterating as soon as it encountered `Fold::Done(_)`. fn fold_while(&mut self, init: B, mut f: F) -> FoldWhile - where Self: Sized, - F: FnMut(B, Self::Item) -> FoldWhile - { - use Result::{ - Ok as Continue, - Err as Break, - }; - - let result = self.try_fold(init, #[inline(always)] |acc, v| - match f(acc, v) { - FoldWhile::Continue(acc) => Continue(acc), - FoldWhile::Done(acc) => Break(acc), - } + where + Self: Sized, + F: FnMut(B, Self::Item) -> FoldWhile, + { + use Result::{Err as Break, Ok as Continue}; + + let result = self.try_fold( + init, + #[inline(always)] + |acc, v| match f(acc, v) { + FoldWhile::Continue(acc) => Continue(acc), + FoldWhile::Done(acc) => Break(acc), + }, ); match result { @@ -2608,11 +2663,11 @@ pub trait Itertools : Iterator { /// assert_eq!(nonempty_sum, Some(55)); /// ``` fn sum1(mut self) -> Option - where Self: Sized, - S: std::iter::Sum, + where + Self: Sized, + S: std::iter::Sum, { - self.next() - .map(|first| once(first).chain(self).sum()) + self.next().map(|first| once(first).chain(self).sum()) } /// Iterate over the entire iterator and multiply all the elements. @@ -2636,11 +2691,11 @@ pub trait Itertools : Iterator { /// assert_eq!(nonempty_product, Some(3628800)); /// ``` fn product1

(mut self) -> Option

- where Self: Sized, - P: std::iter::Product, + where + Self: Sized, + P: std::iter::Product, { - self.next() - .map(|first| once(first).chain(self).product()) + self.next().map(|first| once(first).chain(self).product()) } /// Sort all iterator elements into a new iterator in ascending order. @@ -2664,8 +2719,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted_unstable(self) -> VecIntoIter - where Self: Sized, - Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { // Use .sort_unstable() directly since it is not quite identical with // .sort_by(Ord::cmp) @@ -2701,8 +2757,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted_unstable_by(self, cmp: F) -> VecIntoIter - where Self: Sized, - F: FnMut(&Self::Item, &Self::Item) -> Ordering, + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { let mut v = Vec::from_iter(self); v.sort_unstable_by(cmp); @@ -2736,9 +2793,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted_unstable_by_key(self, f: F) -> VecIntoIter - where Self: Sized, - K: Ord, - F: FnMut(&Self::Item) -> K, + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { let mut v = Vec::from_iter(self); v.sort_unstable_by_key(f); @@ -2766,8 +2824,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted(self) -> VecIntoIter - where Self: Sized, - Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { // Use .sort() directly since it is not quite identical with // .sort_by(Ord::cmp) @@ -2803,8 +2862,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted_by(self, cmp: F) -> VecIntoIter - where Self: Sized, - F: FnMut(&Self::Item, &Self::Item) -> Ordering, + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { let mut v = Vec::from_iter(self); v.sort_by(cmp); @@ -2838,9 +2898,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn sorted_by_key(self, f: F) -> VecIntoIter - where Self: Sized, - K: Ord, - F: FnMut(&Self::Item) -> K, + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { let mut v = Vec::from_iter(self); v.sort_by_key(f); @@ -2914,8 +2975,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_alloc")] fn k_smallest(self, k: usize) -> VecIntoIter - where Self: Sized, - Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { crate::k_smallest::k_smallest(self, k) .into_sorted_vec() @@ -2944,10 +3006,11 @@ pub trait Itertools : Iterator { /// assert_eq!(failures, [false, true]); /// ``` fn partition_map(self, mut predicate: F) -> (A, B) - where Self: Sized, - F: FnMut(Self::Item) -> Either, - A: Default + Extend, - B: Default + Extend, + where + Self: Sized, + F: FnMut(Self::Item) -> Either, + A: Default + Extend, + B: Default + Extend, { let mut left = A::default(); let mut right = B::default(); @@ -2976,10 +3039,10 @@ pub trait Itertools : Iterator { /// assert_eq!(failures, [false, true]); /// ``` fn partition_result(self) -> (A, B) - where - Self: Iterator> + Sized, - A: Default + Extend, - B: Default + Extend, + where + Self: Iterator> + Sized, + A: Default + Extend, + B: Default + Extend, { self.partition_map(|r| match r { Ok(v) => Either::Left(v), @@ -3005,8 +3068,9 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn into_group_map(self) -> HashMap> - where Self: Iterator + Sized, - K: Hash + Eq, + where + Self: Iterator + Sized, + K: Hash + Eq, { group_map::into_group_map(self) } @@ -3040,10 +3104,10 @@ pub trait Itertools : Iterator { /// ``` #[cfg(feature = "use_std")] fn into_group_map_by(self, f: F) -> HashMap> - where - Self: Iterator + Sized, - K: Hash + Eq, - F: Fn(&V) -> K, + where + Self: Iterator + Sized, + K: Hash + Eq, + F: Fn(&V) -> K, { group_map::into_group_map_by(self, f) } @@ -3059,8 +3123,9 @@ pub trait Itertools : Iterator { /// on what operations are available. #[cfg(feature = "use_std")] fn into_grouping_map(self) -> GroupingMap - where Self: Iterator + Sized, - K: Hash + Eq, + where + Self: Iterator + Sized, + K: Hash + Eq, { grouping_map::new(self) } @@ -3075,9 +3140,10 @@ pub trait Itertools : Iterator { /// on what operations are available. #[cfg(feature = "use_std")] fn into_grouping_map_by(self, key_mapper: F) -> GroupingMapBy - where Self: Iterator + Sized, - K: Hash + Eq, - F: FnMut(&V) -> K + where + Self: Iterator + Sized, + K: Hash + Eq, + F: FnMut(&V) -> K, { grouping_map::new(grouping_map::MapForGrouping::new(self, key_mapper)) } @@ -3106,7 +3172,9 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn min_set(self) -> Vec - where Self: Sized, Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x.cmp(y)) } @@ -3137,13 +3205,11 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn min_set_by(self, mut compare: F) -> Vec - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { - extrema_set::min_set_impl( - self, - |_| (), - |x, y, _, _| compare(x, y) - ) + extrema_set::min_set_impl(self, |_| (), |x, y, _, _| compare(x, y)) } /// Return all minimum elements of an iterator, as determined by @@ -3171,7 +3237,10 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn min_set_by_key(self, key: F) -> Vec - where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky)) } @@ -3200,7 +3269,9 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn max_set(self) -> Vec - where Self: Sized, Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x.cmp(y)) } @@ -3231,13 +3302,11 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn max_set_by(self, mut compare: F) -> Vec - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { - extrema_set::max_set_impl( - self, - |_| (), - |x, y, _, _| compare(x, y) - ) + extrema_set::max_set_impl(self, |_| (), |x, y, _, _| compare(x, y)) } /// Return all maximum elements of an iterator, as determined by @@ -3265,7 +3334,10 @@ pub trait Itertools : Iterator { /// if an element is NaN. #[cfg(feature = "use_std")] fn max_set_by_key(self, key: F) -> Vec - where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky)) } @@ -3306,7 +3378,9 @@ pub trait Itertools : Iterator { /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. fn minmax(self) -> MinMaxResult - where Self: Sized, Self::Item: PartialOrd + where + Self: Sized, + Self::Item: PartialOrd, { minmax::minmax_impl(self, |_| (), |x, y, _, _| x < y) } @@ -3323,7 +3397,10 @@ pub trait Itertools : Iterator { /// The keys can be floats but no particular result is guaranteed /// if a key is NaN. fn minmax_by_key(self, key: F) -> MinMaxResult - where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: PartialOrd, + F: FnMut(&Self::Item) -> K, { minmax::minmax_impl(self, key, |_, _, xk, yk| xk < yk) } @@ -3337,13 +3414,11 @@ pub trait Itertools : Iterator { /// the last maximal element wins. This matches the behavior of the standard /// [`Iterator::min`] and [`Iterator::max`] methods. fn minmax_by(self, mut compare: F) -> MinMaxResult - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { - minmax::minmax_impl( - self, - |_| (), - |x, y, _, _| Ordering::Less == compare(x, y) - ) + minmax::minmax_impl(self, |_| (), |x, y, _, _| Ordering::Less == compare(x, y)) } /// Return the position of the maximum element in the iterator. @@ -3366,7 +3441,9 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_max(), Some(1)); /// ``` fn position_max(self) -> Option - where Self: Sized, Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { self.enumerate() .max_by(|x, y| Ord::cmp(&x.1, &y.1)) @@ -3394,7 +3471,10 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_max_by_key(|x| x.abs()), Some(3)); /// ``` fn position_max_by_key(self, mut key: F) -> Option - where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { self.enumerate() .max_by(|x, y| Ord::cmp(&key(&x.1), &key(&y.1))) @@ -3422,7 +3502,9 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_max_by(|x, y| x.cmp(y)), Some(1)); /// ``` fn position_max_by(self, mut compare: F) -> Option - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { self.enumerate() .max_by(|x, y| compare(&x.1, &y.1)) @@ -3449,7 +3531,9 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_min(), Some(2)); /// ``` fn position_min(self) -> Option - where Self: Sized, Self::Item: Ord + where + Self: Sized, + Self::Item: Ord, { self.enumerate() .min_by(|x, y| Ord::cmp(&x.1, &y.1)) @@ -3477,7 +3561,10 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_min_by_key(|x| x.abs()), Some(0)); /// ``` fn position_min_by_key(self, mut key: F) -> Option - where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: Ord, + F: FnMut(&Self::Item) -> K, { self.enumerate() .min_by(|x, y| Ord::cmp(&key(&x.1), &key(&y.1))) @@ -3505,7 +3592,9 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_min_by(|x, y| x.cmp(y)), Some(2)); /// ``` fn position_min_by(self, mut compare: F) -> Option - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { self.enumerate() .min_by(|x, y| compare(&x.1, &y.1)) @@ -3555,9 +3644,11 @@ pub trait Itertools : Iterator { /// assert_eq!(a.iter().position_minmax(), MinMax(2, 1)); /// ``` fn position_minmax(self) -> MinMaxResult - where Self: Sized, Self::Item: PartialOrd + where + Self: Sized, + Self::Item: PartialOrd, { - use crate::MinMaxResult::{NoElements, OneElement, MinMax}; + use crate::MinMaxResult::{MinMax, NoElements, OneElement}; match minmax::minmax_impl(self.enumerate(), |_| (), |x, y, _, _| x.1 < y.1) { NoElements => NoElements, OneElement(x) => OneElement(x.0), @@ -3600,9 +3691,12 @@ pub trait Itertools : Iterator { /// /// [`position_minmax`]: Self::position_minmax fn position_minmax_by_key(self, mut key: F) -> MinMaxResult - where Self: Sized, K: PartialOrd, F: FnMut(&Self::Item) -> K + where + Self: Sized, + K: PartialOrd, + F: FnMut(&Self::Item) -> K, { - use crate::MinMaxResult::{NoElements, OneElement, MinMax}; + use crate::MinMaxResult::{MinMax, NoElements, OneElement}; match self.enumerate().minmax_by_key(|e| key(&e.1)) { NoElements => NoElements, OneElement(x) => OneElement(x.0), @@ -3642,9 +3736,11 @@ pub trait Itertools : Iterator { /// /// [`position_minmax`]: Self::position_minmax fn position_minmax_by(self, mut compare: F) -> MinMaxResult - where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { - use crate::MinMaxResult::{NoElements, OneElement, MinMax}; + use crate::MinMaxResult::{MinMax, NoElements, OneElement}; match self.enumerate().minmax_by(|x, y| compare(&x.1, &y.1)) { NoElements => NoElements, OneElement(x) => OneElement(x.0), @@ -3674,16 +3770,13 @@ pub trait Itertools : Iterator { Self: Sized, { match self.next() { - Some(first) => { - match self.next() { - Some(second) => { - Err(ExactlyOneError::new(Some(Either::Left([first, second])), self)) - } - None => { - Ok(first) - } - } - } + Some(first) => match self.next() { + Some(second) => Err(ExactlyOneError::new( + Some(Either::Left([first, second])), + self, + )), + None => Ok(first), + }, None => Err(ExactlyOneError::new(None, self)), } } @@ -3710,16 +3803,13 @@ pub trait Itertools : Iterator { Self: Sized, { match self.next() { - Some(first) => { - match self.next() { - Some(second) => { - Err(ExactlyOneError::new(Some(Either::Left([first, second])), self)) - } - None => { - Ok(Some(first)) - } - } - } + Some(first) => match self.next() { + Some(second) => Err(ExactlyOneError::new( + Some(Either::Left([first, second])), + self, + )), + None => Ok(Some(first)), + }, None => Ok(None), } } @@ -3864,7 +3954,7 @@ pub trait Itertools : Iterator { } } -impl Itertools for T where T: Iterator { } +impl Itertools for T where T: Iterator {} /// Return `true` if both iterables produce equal sequences /// (elements pairwise equal and sequences of the same length), @@ -3877,9 +3967,10 @@ impl Itertools for T where T: Iterator { } /// assert!(!itertools::equal(&[0, 0], &[0, 0, 0])); /// ``` pub fn equal(a: I, b: J) -> bool - where I: IntoIterator, - J: IntoIterator, - I::Item: PartialEq +where + I: IntoIterator, + J: IntoIterator, + I::Item: PartialEq, { a.into_iter().eq(b) } @@ -3895,10 +3986,11 @@ pub fn equal(a: I, b: J) -> bool /// // ^PANIC: panicked at 'Failed assertion Some("eed") == Some("ess") for iteration 1', /// ``` pub fn assert_equal(a: I, b: J) - where I: IntoIterator, - J: IntoIterator, - I::Item: fmt::Debug + PartialEq, - J::Item: fmt::Debug, +where + I: IntoIterator, + J: IntoIterator, + I::Item: fmt::Debug + PartialEq, + J::Item: fmt::Debug, { let mut ia = a.into_iter(); let mut ib = b.into_iter(); @@ -3911,8 +4003,13 @@ pub fn assert_equal(a: I, b: J) (&Some(ref a), &Some(ref b)) => a == b, _ => false, }; - assert!(equal, "Failed assertion {a:?} == {b:?} for iteration {i}", - i=i, a=a, b=b); + assert!( + equal, + "Failed assertion {a:?} == {b:?} for iteration {i}", + i = i, + a = a, + b = b + ); i += 1; } } @@ -3937,9 +4034,10 @@ pub fn assert_equal(a: I, b: J) /// assert_eq!(split_index, 3); /// ``` pub fn partition<'a, A: 'a, I, F>(iter: I, mut pred: F) -> usize - where I: IntoIterator, - I::IntoIter: DoubleEndedIterator, - F: FnMut(&A) -> bool +where + I: IntoIterator, + I::IntoIter: DoubleEndedIterator, + F: FnMut(&A) -> bool, { let mut split_index = 0; let mut iter = iter.into_iter(); @@ -3947,10 +4045,12 @@ pub fn partition<'a, A: 'a, I, F>(iter: I, mut pred: F) -> usize if !pred(front) { loop { match iter.next_back() { - Some(back) => if pred(back) { - std::mem::swap(front, back); - break; - }, + Some(back) => { + if pred(back) { + std::mem::swap(front, back); + break; + } + } None => break 'main, } } diff --git a/src/merge_join.rs b/src/merge_join.rs index 0304e5f8e..1769aaf3a 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -1,11 +1,11 @@ use std::cmp::Ordering; -use std::iter::Fuse; -use std::iter::{Peekable, FusedIterator}; use std::fmt; +use std::iter::Fuse; +use std::iter::{FusedIterator, Peekable}; use either::Either; -use super::adaptors::{PutBack, put_back}; +use super::adaptors::{put_back, PutBack}; use crate::either_or_both::EitherOrBoth; use crate::size_hint::{self, SizeHint}; #[cfg(doc)] @@ -43,10 +43,14 @@ pub type Merge = MergeBy; /// /* loop body */ /// } /// ``` -pub fn merge(i: I, j: J) -> Merge<::IntoIter, ::IntoIter> - where I: IntoIterator, - J: IntoIterator, - I::Item: PartialOrd +pub fn merge( + i: I, + j: J, +) -> Merge<::IntoIter, ::IntoIter> +where + I: IntoIterator, + J: IntoIterator, + I::Item: PartialOrd, { merge_by_new(i, j, MergeLte) } @@ -59,8 +63,9 @@ pub fn merge(i: I, j: J) -> Merge<::IntoIter, - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { a: Peekable, b: Peekable, @@ -69,13 +74,15 @@ pub struct MergeBy } impl fmt::Debug for MergeBy - where I: Iterator + fmt::Debug, J: Iterator + fmt::Debug, - I::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + J: Iterator + fmt::Debug, + I::Item: fmt::Debug, { debug_fmt_fields!(MergeBy, a, b); } -implbool> MergePredicate for F { +impl bool> MergePredicate for F { fn merge_pred(&mut self, a: &T, b: &T) -> bool { self(a, b) } @@ -83,9 +90,10 @@ implbool> MergePredicate for F { /// Create a `MergeBy` iterator. pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy - where I: IntoIterator, - J: IntoIterator, - F: MergePredicate, +where + I: IntoIterator, + J: IntoIterator, + F: MergePredicate, { MergeBy { a: a.into_iter().peekable(), @@ -96,19 +104,21 @@ pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy Clone for MergeBy - where I: Iterator, - J: Iterator, - Peekable: Clone, - Peekable: Clone, - F: Clone +where + I: Iterator, + J: Iterator, + Peekable: Clone, + Peekable: Clone, + F: Clone, { clone_fields!(a, b, fused, cmp); } impl Iterator for MergeBy - where I: Iterator, - J: Iterator, - F: MergePredicate +where + I: Iterator, + J: Iterator, + F: MergePredicate, { type Item = I::Item; @@ -126,7 +136,7 @@ impl Iterator for MergeBy false } (None, None) => return None, - } + }, }; if less_than { self.a.next() @@ -142,21 +152,26 @@ impl Iterator for MergeBy } impl FusedIterator for MergeBy - where I: FusedIterator, - J: FusedIterator, - F: MergePredicate -{} - +where + I: FusedIterator, + J: FusedIterator, + F: MergePredicate, +{ +} /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// [`IntoIterator`] enabled version of [`Itertools::merge_join_by`]. -pub fn merge_join_by(left: I, right: J, cmp_fn: F) - -> MergeJoinBy - where I: IntoIterator, - J: IntoIterator, - F: FnMut(&I::Item, &J::Item) -> T, - T: OrderingOrBool, +pub fn merge_join_by( + left: I, + right: J, + cmp_fn: F, +) -> MergeJoinBy +where + I: IntoIterator, + J: IntoIterator, + F: FnMut(&I::Item, &J::Item) -> T, + T: OrderingOrBool, { MergeJoinBy { left: put_back(left.into_iter().fuse()), @@ -235,29 +250,32 @@ impl OrderingOrBool for bool { } impl Clone for MergeJoinBy - where I: Iterator, - J: Iterator, - PutBack>: Clone, - PutBack>: Clone, - F: Clone, +where + I: Iterator, + J: Iterator, + PutBack>: Clone, + PutBack>: Clone, + F: Clone, { clone_fields!(left, right, cmp_fn); } impl fmt::Debug for MergeJoinBy - where I: Iterator + fmt::Debug, - I::Item: fmt::Debug, - J: Iterator + fmt::Debug, - J::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug, + J: Iterator + fmt::Debug, + J::Item: fmt::Debug, { debug_fmt_fields!(MergeJoinBy, left, right); } impl Iterator for MergeJoinBy - where I: Iterator, - J: Iterator, - F: FnMut(&I::Item, &J::Item) -> T, - T: OrderingOrBool, +where + I: Iterator, + J: Iterator, + F: FnMut(&I::Item, &J::Item) -> T, + T: OrderingOrBool, { type Item = T::MergeResult; @@ -310,14 +328,10 @@ impl Iterator for MergeJoinBy match (self.left.next(), self.right.next()) { (None, None) => break previous_element, (Some(left), None) => { - break Some(T::left( - self.left.into_parts().1.last().unwrap_or(left), - )) + break Some(T::left(self.left.into_parts().1.last().unwrap_or(left))) } (None, Some(right)) => { - break Some(T::right( - self.right.into_parts().1.last().unwrap_or(right), - )) + break Some(T::right(self.right.into_parts().1.last().unwrap_or(right))) } (Some(left), Some(right)) => { let (left, right, elem) = (self.cmp_fn)(&left, &right).merge(left, right); diff --git a/src/minmax.rs b/src/minmax.rs index 52b2f115d..f04e5adba 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -1,4 +1,3 @@ - /// `MinMaxResult` is an enum returned by `minmax`. /// /// See [`.minmax()`](crate::Itertools::minmax) for more detail. @@ -12,7 +11,7 @@ pub enum MinMaxResult { /// More than one element in the iterator, the first element is not larger /// than the second - MinMax(T, T) + MinMax(T, T), } impl MinMaxResult { @@ -36,34 +35,36 @@ impl MinMaxResult { /// let r = MinMax(1, 2); /// assert_eq!(r.into_option(), Some((1, 2))); /// ``` - pub fn into_option(self) -> Option<(T,T)> { + pub fn into_option(self) -> Option<(T, T)> { match self { MinMaxResult::NoElements => None, MinMaxResult::OneElement(x) => Some((x.clone(), x)), - MinMaxResult::MinMax(x, y) => Some((x, y)) + MinMaxResult::MinMax(x, y) => Some((x, y)), } } } /// Implementation guts for `minmax` and `minmax_by_key`. -pub fn minmax_impl(mut it: I, mut key_for: F, - mut lt: L) -> MinMaxResult - where I: Iterator, - F: FnMut(&I::Item) -> K, - L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, +pub fn minmax_impl(mut it: I, mut key_for: F, mut lt: L) -> MinMaxResult +where + I: Iterator, + F: FnMut(&I::Item) -> K, + L: FnMut(&I::Item, &I::Item, &K, &K) -> bool, { let (mut min, mut max, mut min_key, mut max_key) = match it.next() { None => return MinMaxResult::NoElements, - Some(x) => { - match it.next() { - None => return MinMaxResult::OneElement(x), - Some(y) => { - let xk = key_for(&x); - let yk = key_for(&y); - if !lt(&y, &x, &yk, &xk) {(x, y, xk, yk)} else {(y, x, yk, xk)} + Some(x) => match it.next() { + None => return MinMaxResult::OneElement(x), + Some(y) => { + let xk = key_for(&x); + let yk = key_for(&y); + if !lt(&y, &x, &yk, &xk) { + (x, y, xk, yk) + } else { + (y, x, yk, xk) } } - } + }, }; loop { @@ -74,7 +75,7 @@ pub fn minmax_impl(mut it: I, mut key_for: F, // for 2 elements. let first = match it.next() { None => break, - Some(x) => x + Some(x) => x, }; let second = match it.next() { None => { @@ -86,7 +87,7 @@ pub fn minmax_impl(mut it: I, mut key_for: F, } break; } - Some(x) => x + Some(x) => x, }; let first_key = key_for(&first); let second_key = key_for(&second); diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 8b49c695e..339cf9781 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -1,14 +1,15 @@ -use std::iter::Fuse; -use alloc::collections::VecDeque; use crate::size_hint; -use crate::PeekingNext; #[cfg(doc)] use crate::Itertools; +use crate::PeekingNext; +use alloc::collections::VecDeque; +use std::iter::Fuse; /// See [`multipeek()`] for more information. #[derive(Clone, Debug)] pub struct MultiPeek - where I: Iterator +where + I: Iterator, { iter: Fuse, buf: VecDeque, @@ -20,7 +21,8 @@ pub struct MultiPeek /// /// [`IntoIterator`] enabled version of [`Itertools::multipeek`]. pub fn multipeek(iterable: I) -> MultiPeek - where I: IntoIterator +where + I: IntoIterator, { MultiPeek { iter: iterable.into_iter().fuse(), @@ -30,7 +32,8 @@ pub fn multipeek(iterable: I) -> MultiPeek } impl MultiPeek - where I: Iterator +where + I: Iterator, { /// Reset the peeking “cursor” pub fn reset_peek(&mut self) { @@ -62,24 +65,31 @@ impl MultiPeek { } impl PeekingNext for MultiPeek - where I: Iterator, +where + I: Iterator, { fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool + where + F: FnOnce(&Self::Item) -> bool, { if self.buf.is_empty() { if let Some(r) = self.peek() { - if !accept(r) { return None } + if !accept(r) { + return None; + } } } else if let Some(r) = self.buf.get(0) { - if !accept(r) { return None } + if !accept(r) { + return None; + } } self.next() } } impl Iterator for MultiPeek - where I: Iterator +where + I: Iterator, { type Item = I::Item; @@ -94,8 +104,4 @@ impl Iterator for MultiPeek } // Same size -impl ExactSizeIterator for MultiPeek - where I: ExactSizeIterator -{} - - +impl ExactSizeIterator for MultiPeek where I: ExactSizeIterator {} diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 248a43243..47e62b2cf 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -1,5 +1,5 @@ -use std::iter::{Fuse, FusedIterator}; use crate::size_hint; +use std::iter::{Fuse, FusedIterator}; /// An iterator adaptor that pads a sequence to a minimum length by filling /// missing elements using a closure. @@ -25,8 +25,9 @@ where /// Create a new `PadUsing` iterator. pub fn pad_using(iter: I, min: usize, filler: F) -> PadUsing - where I: Iterator, - F: FnMut(usize) -> I::Item +where + I: Iterator, + F: FnMut(usize) -> I::Item, { PadUsing { iter: iter.fuse(), @@ -37,8 +38,9 @@ pub fn pad_using(iter: I, min: usize, filler: F) -> PadUsing } impl Iterator for PadUsing - where I: Iterator, - F: FnMut(usize) -> I::Item +where + I: Iterator, + F: FnMut(usize) -> I::Item, { type Item = I::Item; @@ -53,7 +55,7 @@ impl Iterator for PadUsing } else { None } - }, + } e => { self.pos += 1; e @@ -68,8 +70,9 @@ impl Iterator for PadUsing } impl DoubleEndedIterator for PadUsing - where I: DoubleEndedIterator + ExactSizeIterator, - F: FnMut(usize) -> I::Item +where + I: DoubleEndedIterator + ExactSizeIterator, + F: FnMut(usize) -> I::Item, { fn next_back(&mut self) -> Option { if self.min == 0 { @@ -85,12 +88,15 @@ impl DoubleEndedIterator for PadUsing } impl ExactSizeIterator for PadUsing - where I: ExactSizeIterator, - F: FnMut(usize) -> I::Item -{} - +where + I: ExactSizeIterator, + F: FnMut(usize) -> I::Item, +{ +} impl FusedIterator for PadUsing - where I: FusedIterator, - F: FnMut(usize) -> I::Item -{} +where + I: FusedIterator, + F: FnMut(usize) -> I::Item, +{ +} diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index 3a3722812..b08794a8d 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -1,7 +1,7 @@ -use std::iter::Peekable; use crate::PutBack; #[cfg(feature = "use_alloc")] use crate::PutBackN; +use std::iter::Peekable; /// An iterator that allows peeking at an element before deciding to accept it. /// @@ -11,30 +11,35 @@ use crate::PutBackN; /// This is implemented by peeking adaptors like peekable and put back, /// but also by a few iterators that can be peeked natively, like the slice’s /// by reference iterator (`std::slice::Iter`). -pub trait PeekingNext : Iterator { +pub trait PeekingNext: Iterator { /// Pass a reference to the next iterator element to the closure `accept`; /// if `accept` returns true, return it as the next element, /// else None. fn peeking_next(&mut self, accept: F) -> Option - where Self: Sized, - F: FnOnce(&Self::Item) -> bool; + where + Self: Sized, + F: FnOnce(&Self::Item) -> bool; } impl<'a, I> PeekingNext for &'a mut I - where I: PeekingNext, +where + I: PeekingNext, { fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool + where + F: FnOnce(&Self::Item) -> bool, { (*self).peeking_next(accept) } } impl PeekingNext for Peekable - where I: Iterator, +where + I: Iterator, { fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool + where + F: FnOnce(&Self::Item) -> bool, { if let Some(r) = self.peek() { if !accept(r) { @@ -46,10 +51,12 @@ impl PeekingNext for Peekable } impl PeekingNext for PutBack - where I: Iterator, +where + I: Iterator, { fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool + where + F: FnOnce(&Self::Item) -> bool, { if let Some(r) = self.next() { if !accept(&r) { @@ -65,10 +72,12 @@ impl PeekingNext for PutBack #[cfg(feature = "use_alloc")] impl PeekingNext for PutBackN - where I: Iterator, +where + I: Iterator, { fn peeking_next(&mut self, accept: F) -> Option - where F: FnOnce(&Self::Item) -> bool + where + F: FnOnce(&Self::Item) -> bool, { if let Some(r) = self.next() { if !accept(&r) { @@ -88,7 +97,8 @@ impl PeekingNext for PutBackN /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PeekingTakeWhile<'a, I: 'a, F> - where I: Iterator, +where + I: Iterator, { iter: &'a mut I, f: F, @@ -103,18 +113,16 @@ where /// Create a `PeekingTakeWhile` pub fn peeking_take_while(iter: &mut I, f: F) -> PeekingTakeWhile - where I: Iterator, +where + I: Iterator, { - PeekingTakeWhile { - iter, - f, - } + PeekingTakeWhile { iter, f } } impl<'a, I, F> Iterator for PeekingTakeWhile<'a, I, F> - where I: PeekingNext, - F: FnMut(&I::Item) -> bool, - +where + I: PeekingNext, + F: FnMut(&I::Item) -> bool, { type Item = I::Item; fn next(&mut self) -> Option { @@ -127,11 +135,13 @@ impl<'a, I, F> Iterator for PeekingTakeWhile<'a, I, F> } impl<'a, I, F> PeekingNext for PeekingTakeWhile<'a, I, F> - where I: PeekingNext, - F: FnMut(&I::Item) -> bool, +where + I: PeekingNext, + F: FnMut(&I::Item) -> bool, { fn peeking_next(&mut self, g: G) -> Option - where G: FnOnce(&Self::Item) -> bool, + where + G: FnOnce(&Self::Item) -> bool, { let f = &mut self.f; self.iter.peeking_next(|r| f(r) && g(r)) @@ -174,4 +184,4 @@ peeking_next_by_clone! { ['a, T] alloc::collections::vec_deque::Iter<'a, T> } // cloning a Rev has no extra overhead; peekable and put backs are never DEI. peeking_next_by_clone! { [I: Clone + PeekingNext + DoubleEndedIterator] - ::std::iter::Rev } +::std::iter::Rev } diff --git a/src/permutations.rs b/src/permutations.rs index 92ba7e6a3..b6ff5c4fa 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -17,21 +17,17 @@ pub struct Permutations { } impl Clone for Permutations - where I: Clone + Iterator, - I::Item: Clone, +where + I: Clone + Iterator, + I::Item: Clone, { clone_fields!(vals, state); } #[derive(Clone, Debug)] enum PermutationState { - StartUnknownLen { - k: usize, - }, - OngoingUnknownLen { - k: usize, - min_n: usize, - }, + StartUnknownLen { k: usize }, + OngoingUnknownLen { k: usize, min_n: usize }, Complete(CompleteState), Empty, } @@ -45,12 +41,13 @@ enum CompleteState { Ongoing { indices: Vec, cycles: Vec, - } + }, } impl fmt::Debug for Permutations - where I: Iterator + fmt::Debug, - I::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug, { debug_fmt_fields!(Permutations, vals, state); } @@ -62,10 +59,7 @@ pub fn permutations(iter: I, k: usize) -> Permutations { // Special case, yields single empty vec; `n` is irrelevant let state = PermutationState::Complete(CompleteState::Start { n: 0, k: 0 }); - return Permutations { - vals, - state - }; + return Permutations { vals, state }; } vals.prefill(k); @@ -77,29 +71,32 @@ pub fn permutations(iter: I, k: usize) -> Permutations { PermutationState::Empty }; - Permutations { - vals, - state - } + Permutations { vals, state } } impl Iterator for Permutations where I: Iterator, - I::Item: Clone + I::Item: Clone, { type Item = Vec; fn next(&mut self) -> Option { { - let &mut Permutations { ref mut vals, ref mut state } = self; + let &mut Permutations { + ref mut vals, + ref mut state, + } = self; match *state { PermutationState::StartUnknownLen { k } => { *state = PermutationState::OngoingUnknownLen { k, min_n: k }; - }, + } PermutationState::OngoingUnknownLen { k, min_n } => { if vals.get_next() { - *state = PermutationState::OngoingUnknownLen { k, min_n: min_n + 1 }; + *state = PermutationState::OngoingUnknownLen { + k, + min_n: min_n + 1, + }; } else { let n = min_n; let prev_iteration_count = n - k + 1; @@ -112,14 +109,17 @@ where *state = PermutationState::Complete(complete_state); } - }, + } PermutationState::Complete(ref mut state) => { state.advance(); - }, - PermutationState::Empty => {}, + } + PermutationState::Empty => {} }; } - let &mut Permutations { ref vals, ref state } = self; + let &mut Permutations { + ref vals, + ref state, + } = self; match *state { PermutationState::StartUnknownLen { .. } => panic!("unexpected iterator state"), PermutationState::OngoingUnknownLen { k, min_n } => { @@ -128,17 +128,24 @@ where Some(indices.map(|i| vals[i].clone()).collect()) } - PermutationState::Complete(CompleteState::Ongoing { ref indices, ref cycles }) => { + PermutationState::Complete(CompleteState::Ongoing { + ref indices, + ref cycles, + }) => { let k = cycles.len(); Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) - }, - PermutationState::Complete(CompleteState::Start { .. }) | PermutationState::Empty => None + } + PermutationState::Complete(CompleteState::Start { .. }) | PermutationState::Empty => { + None + } } } fn count(self) -> usize { fn from_complete(complete_state: CompleteState) -> usize { - complete_state.remaining().expect("Iterator count greater than usize::MAX") + complete_state + .remaining() + .expect("Iterator count greater than usize::MAX") } let Permutations { vals, state } = self; @@ -155,9 +162,9 @@ where let complete_state = CompleteState::Start { n, k }; from_complete(complete_state) - prev_iteration_count - }, + } PermutationState::Complete(state) => from_complete(state), - PermutationState::Empty => 0 + PermutationState::Empty => 0, } } @@ -165,7 +172,9 @@ where let at_start = |k| { // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. let (mut low, mut upp) = self.vals.size_hint(); - low = CompleteState::Start { n: low, k }.remaining().unwrap_or(usize::MAX); + low = CompleteState::Start { n: low, k } + .remaining() + .unwrap_or(usize::MAX); upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); (low, upp) }; @@ -177,9 +186,9 @@ where } PermutationState::Complete(ref state) => match state.remaining() { Some(count) => (count, Some(count)), - None => (::std::usize::MAX, None) - } - PermutationState::Empty => (0, Some(0)) + None => (::std::usize::MAX, None), + }, + PermutationState::Empty => (0, Some(0)), } } } @@ -191,12 +200,12 @@ impl CompleteState { let indices = (0..n).collect(); let cycles = ((n - k)..n).rev().collect(); - CompleteState::Ongoing { - cycles, - indices - } - }, - CompleteState::Ongoing { ref mut indices, ref mut cycles } => { + CompleteState::Ongoing { cycles, indices } + } + CompleteState::Ongoing { + ref mut indices, + ref mut cycles, + } => { let n = indices.len(); let k = cycles.len(); @@ -229,15 +238,13 @@ impl CompleteState { } (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i)) } - CompleteState::Ongoing { ref indices, ref cycles } => { - cycles - .iter() - .enumerate() - .try_fold(0usize, |acc, (i, &c)| { - acc.checked_mul(indices.len() - i) - .and_then(|count| count.checked_add(c)) - }) - } + CompleteState::Ongoing { + ref indices, + ref cycles, + } => cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { + acc.checked_mul(indices.len() - i) + .and_then(|count| count.checked_add(c)) + }), } } } diff --git a/src/powerset.rs b/src/powerset.rs index 9f0965bc5..811cfce7f 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -1,9 +1,9 @@ +use alloc::vec::Vec; use std::fmt; use std::iter::FusedIterator; use std::usize; -use alloc::vec::Vec; -use super::combinations::{Combinations, checked_binomial, combinations}; +use super::combinations::{checked_binomial, combinations, Combinations}; use crate::size_hint::{self, SizeHint}; /// An iterator to iterate through the powerset of the elements from an iterator. @@ -16,23 +16,26 @@ pub struct Powerset { } impl Clone for Powerset - where I: Clone + Iterator, - I::Item: Clone, +where + I: Clone + Iterator, + I::Item: Clone, { clone_fields!(combs); } impl fmt::Debug for Powerset - where I: Iterator + fmt::Debug, - I::Item: fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: fmt::Debug, { debug_fmt_fields!(Powerset, combs); } /// Create a new `Powerset` from a clonable iterator. pub fn powerset(src: I) -> Powerset - where I: Iterator, - I::Item: Clone, +where + I: Iterator, + I::Item: Clone, { Powerset { combs: combinations(src, 0), @@ -40,18 +43,16 @@ pub fn powerset(src: I) -> Powerset } impl Iterator for Powerset - where - I: Iterator, - I::Item: Clone, +where + I: Iterator, + I::Item: Clone, { type Item = Vec; fn next(&mut self) -> Option { if let Some(elt) = self.combs.next() { Some(elt) - } else if self.combs.k() < self.combs.n() - || self.combs.k() == 0 - { + } else if self.combs.k() < self.combs.n() || self.combs.k() == 0 { self.combs.reset(self.combs.k() + 1); self.combs.next() } else { @@ -76,13 +77,12 @@ impl Iterator for Powerset } impl FusedIterator for Powerset - where - I: Iterator, - I::Item: Clone, -{} +where + I: Iterator, + I::Item: Clone, +{ +} fn remaining_for(n: usize, k: usize) -> Option { - (k + 1..=n).try_fold(0usize, |sum, i| { - sum.checked_add(checked_binomial(n, i)?) - }) + (k + 1..=n).try_fold(0usize, |sum, i| sum.checked_add(checked_binomial(n, i)?)) } diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index 713db4551..7b40cc015 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -14,7 +14,8 @@ pub struct ProcessResults<'a, I, E: 'a> { } impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> - where I: Iterator> +where + I: Iterator>, { type Item = T; @@ -56,13 +57,17 @@ impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> /// /// [`IntoIterator`] enabled version of [`Itertools::process_results`]. pub fn process_results(iterable: I, processor: F) -> Result - where I: IntoIterator>, - F: FnOnce(ProcessResults) -> R +where + I: IntoIterator>, + F: FnOnce(ProcessResults) -> R, { let iter = iterable.into_iter(); let mut error = Ok(()); - let result = processor(ProcessResults { error: &mut error, iter }); + let result = processor(ProcessResults { + error: &mut error, + iter, + }); error.map(|_| result) } diff --git a/src/put_back_n_impl.rs b/src/put_back_n_impl.rs index 60ea8e649..5fdbd5a98 100644 --- a/src/put_back_n_impl.rs +++ b/src/put_back_n_impl.rs @@ -17,7 +17,8 @@ pub struct PutBackN { /// /// Iterator element type is `I::Item`. pub fn put_back_n(iterable: I) -> PutBackN - where I: IntoIterator +where + I: IntoIterator, { PutBackN { top: Vec::new(), @@ -58,4 +59,3 @@ impl Iterator for PutBackN { size_hint::add_scalar(self.iter.size_hint(), self.top.len()) } } - diff --git a/src/rciter_impl.rs b/src/rciter_impl.rs index 7298350a8..1282ae865 100644 --- a/src/rciter_impl.rs +++ b/src/rciter_impl.rs @@ -1,7 +1,6 @@ - -use std::iter::{FusedIterator, IntoIterator}; use alloc::rc::Rc; use std::cell::RefCell; +use std::iter::{FusedIterator, IntoIterator}; /// A wrapper for `Rc>`, that implements the `Iterator` trait. #[derive(Debug)] @@ -45,9 +44,12 @@ pub struct RcIter { /// `.next()`, i.e. if it somehow participates in an “iterator knot” /// where it is an adaptor of itself. pub fn rciter(iterable: I) -> RcIter - where I: IntoIterator +where + I: IntoIterator, { - RcIter { rciter: Rc::new(RefCell::new(iterable.into_iter())) } + RcIter { + rciter: Rc::new(RefCell::new(iterable.into_iter())), + } } impl Clone for RcIter { @@ -55,7 +57,8 @@ impl Clone for RcIter { } impl Iterator for RcIter - where I: Iterator +where + I: Iterator, { type Item = A; #[inline] @@ -73,7 +76,8 @@ impl Iterator for RcIter } impl DoubleEndedIterator for RcIter - where I: DoubleEndedIterator +where + I: DoubleEndedIterator, { #[inline] fn next_back(&mut self) -> Option { @@ -83,7 +87,8 @@ impl DoubleEndedIterator for RcIter /// Return an iterator from `&RcIter` (by simply cloning it). impl<'a, I> IntoIterator for &'a RcIter - where I: Iterator +where + I: Iterator, { type Item = I::Item; type IntoIter = RcIter; @@ -93,7 +98,4 @@ impl<'a, I> IntoIterator for &'a RcIter } } - -impl FusedIterator for RcIter - where I: FusedIterator -{} +impl FusedIterator for RcIter where I: FusedIterator {} diff --git a/src/repeatn.rs b/src/repeatn.rs index e025f6f6a..512d057f3 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -12,17 +12,22 @@ pub struct RepeatN { /// Create an iterator that produces `n` repetitions of `element`. pub fn repeat_n(element: A, n: usize) -> RepeatN - where A: Clone, +where + A: Clone, { if n == 0 { - RepeatN { elt: None, n, } + RepeatN { elt: None, n } } else { - RepeatN { elt: Some(element), n, } + RepeatN { + elt: Some(element), + n, + } } } impl Iterator for RepeatN - where A: Clone +where + A: Clone, { type Item = A; @@ -42,7 +47,8 @@ impl Iterator for RepeatN } impl DoubleEndedIterator for RepeatN - where A: Clone +where + A: Clone, { #[inline] fn next_back(&mut self) -> Option { @@ -50,10 +56,6 @@ impl DoubleEndedIterator for RepeatN } } -impl ExactSizeIterator for RepeatN - where A: Clone -{} +impl ExactSizeIterator for RepeatN where A: Clone {} -impl FusedIterator for RepeatN - where A: Clone -{} +impl FusedIterator for RepeatN where A: Clone {} diff --git a/src/size_hint.rs b/src/size_hint.rs index 76ccd13a2..7ba6f443b 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -1,8 +1,8 @@ //! Arithmetic on `Iterator.size_hint()` values. //! -use std::usize; use std::cmp; +use std::usize; /// `SizeHint` is the return type of `Iterator::size_hint()`. pub type SizeHint = (usize, Option); @@ -37,7 +37,6 @@ pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { (low, hi) } - /// Multiply `SizeHint` correctly /// /// ```ignore diff --git a/src/sources.rs b/src/sources.rs index 3877ce3c8..bd520c21d 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -7,14 +7,13 @@ use std::mem; /// See [`repeat_call`](crate::repeat_call) for more information. #[derive(Clone)] -#[deprecated(note="Use std repeat_with() instead", since="0.8.0")] +#[deprecated(note = "Use std repeat_with() instead", since = "0.8.0")] pub struct RepeatCall { f: F, } -impl fmt::Debug for RepeatCall -{ - debug_fmt_fields!(RepeatCall, ); +impl fmt::Debug for RepeatCall { + debug_fmt_fields!(RepeatCall,); } /// An iterator source that produces elements indefinitely by calling @@ -39,15 +38,17 @@ impl fmt::Debug for RepeatCall /// vec![1, 1, 1, 1, 1] /// ); /// ``` -#[deprecated(note="Use std repeat_with() instead", since="0.8.0")] +#[deprecated(note = "Use std repeat_with() instead", since = "0.8.0")] pub fn repeat_call(function: F) -> RepeatCall - where F: FnMut() -> A +where + F: FnMut() -> A, { RepeatCall { f: function } } impl Iterator for RepeatCall - where F: FnMut() -> A +where + F: FnMut() -> A, { type Item = A; @@ -98,7 +99,8 @@ impl Iterator for RepeatCall /// assert_eq!(fibonacci.last(), Some(2_971_215_073)) /// ``` pub fn unfold(initial_state: St, f: F) -> Unfold - where F: FnMut(&mut St) -> Option +where + F: FnMut(&mut St) -> Option, { Unfold { f, @@ -107,7 +109,8 @@ pub fn unfold(initial_state: St, f: F) -> Unfold } impl fmt::Debug for Unfold - where St: fmt::Debug, +where + St: fmt::Debug, { debug_fmt_fields!(Unfold, state); } @@ -122,7 +125,8 @@ pub struct Unfold { } impl Iterator for Unfold - where F: FnMut(&mut St) -> Option +where + F: FnMut(&mut St) -> Option, { type Item = A; @@ -144,13 +148,15 @@ pub struct Iterate { } impl fmt::Debug for Iterate - where St: fmt::Debug, +where + St: fmt::Debug, { debug_fmt_fields!(Iterate, state); } impl Iterator for Iterate - where F: FnMut(&St) -> St +where + F: FnMut(&St) -> St, { type Item = St; @@ -174,7 +180,8 @@ impl Iterator for Iterate /// itertools::assert_equal(iterate(1, |&i| i * 3).take(5), vec![1, 3, 9, 27, 81]); /// ``` pub fn iterate(initial_value: St, f: F) -> Iterate - where F: FnMut(&St) -> St +where + F: FnMut(&St) -> St, { Iterate { state: initial_value, diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index 5ef1953d2..46c7a9ba8 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -21,12 +21,17 @@ where { /// Create a new [`TakeWhileInclusive`] from an iterator and a predicate. pub fn new(iter: I, predicate: F) -> Self { - Self { iter, predicate, done: false} + Self { + iter, + predicate, + done: false, + } } } impl fmt::Debug for TakeWhileInclusive - where I: Iterator + fmt::Debug, +where + I: Iterator + fmt::Debug, { debug_fmt_fields!(TakeWhileInclusive, iter); } @@ -34,7 +39,7 @@ impl fmt::Debug for TakeWhileInclusive impl Iterator for TakeWhileInclusive where I: Iterator, - F: FnMut(&I::Item) -> bool + F: FnMut(&I::Item) -> bool, { type Item = I::Item; @@ -63,6 +68,6 @@ where impl FusedIterator for TakeWhileInclusive where I: Iterator, - F: FnMut(&I::Item) -> bool + F: FnMut(&I::Item) -> bool, { } diff --git a/src/tee.rs b/src/tee.rs index ea4752906..0984c5de9 100644 --- a/src/tee.rs +++ b/src/tee.rs @@ -1,8 +1,8 @@ use super::size_hint; -use std::cell::RefCell; use alloc::collections::VecDeque; use alloc::rc::Rc; +use std::cell::RefCell; /// Common buffer object for the two tee halves #[derive(Debug)] @@ -19,24 +19,37 @@ struct TeeBuffer { #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug)] pub struct Tee - where I: Iterator +where + I: Iterator, { rcbuffer: Rc>>, id: bool, } pub fn new(iter: I) -> (Tee, Tee) - where I: Iterator +where + I: Iterator, { - let buffer = TeeBuffer{backlog: VecDeque::new(), iter, owner: false}; - let t1 = Tee{rcbuffer: Rc::new(RefCell::new(buffer)), id: true}; - let t2 = Tee{rcbuffer: t1.rcbuffer.clone(), id: false}; + let buffer = TeeBuffer { + backlog: VecDeque::new(), + iter, + owner: false, + }; + let t1 = Tee { + rcbuffer: Rc::new(RefCell::new(buffer)), + id: true, + }; + let t2 = Tee { + rcbuffer: t1.rcbuffer.clone(), + id: false, + }; (t1, t2) } impl Iterator for Tee - where I: Iterator, - I::Item: Clone +where + I: Iterator, + I::Item: Clone, { type Item = I::Item; fn next(&mut self) -> Option { @@ -73,6 +86,8 @@ impl Iterator for Tee } impl ExactSizeIterator for Tee - where I: ExactSizeIterator, - I::Item: Clone -{} +where + I: ExactSizeIterator, + I::Item: Clone, +{ +} diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index fdf086585..0aea97716 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -1,9 +1,9 @@ //! Some iterator that produces tuples +use std::iter::Cycle; use std::iter::Fuse; use std::iter::FusedIterator; use std::iter::Take; -use std::iter::Cycle; use std::marker::PhantomData; // `HomogeneousTuple` is a public facade for `TupleCollect`, allowing @@ -12,9 +12,7 @@ use std::marker::PhantomData; // See https://github.com/rust-itertools/itertools/issues/387 /// Implemented for homogeneous tuples of size up to 12. -pub trait HomogeneousTuple - : TupleCollect -{} +pub trait HomogeneousTuple: TupleCollect {} impl HomogeneousTuple for T {} @@ -24,25 +22,25 @@ impl HomogeneousTuple for T {} /// [`Tuples::into_buffer()`]. #[derive(Clone, Debug)] pub struct TupleBuffer - where T: HomogeneousTuple +where + T: HomogeneousTuple, { cur: usize, buf: T::Buffer, } impl TupleBuffer - where T: HomogeneousTuple +where + T: HomogeneousTuple, { fn new(buf: T::Buffer) -> Self { - TupleBuffer { - cur: 0, - buf, - } + TupleBuffer { cur: 0, buf } } } impl Iterator for TupleBuffer - where T: HomogeneousTuple +where + T: HomogeneousTuple, { type Item = T::Item; @@ -61,18 +59,16 @@ impl Iterator for TupleBuffer let len = if buffer.is_empty() { 0 } else { - buffer.iter() - .position(|x| x.is_none()) - .unwrap_or_else(|| buffer.len()) + buffer + .iter() + .position(|x| x.is_none()) + .unwrap_or_else(|| buffer.len()) }; (len, Some(len)) } } -impl ExactSizeIterator for TupleBuffer - where T: HomogeneousTuple -{ -} +impl ExactSizeIterator for TupleBuffer where T: HomogeneousTuple {} /// An iterator that groups the items in tuples of a specific size. /// @@ -80,8 +76,9 @@ impl ExactSizeIterator for TupleBuffer #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Tuples - where I: Iterator, - T: HomogeneousTuple +where + I: Iterator, + T: HomogeneousTuple, { iter: Fuse, buf: T::Buffer, @@ -89,8 +86,9 @@ pub struct Tuples /// Create a new tuples iterator. pub fn tuples(iter: I) -> Tuples - where I: Iterator, - T: HomogeneousTuple +where + I: Iterator, + T: HomogeneousTuple, { Tuples { iter: iter.fuse(), @@ -99,8 +97,9 @@ pub fn tuples(iter: I) -> Tuples } impl Iterator for Tuples - where I: Iterator, - T: HomogeneousTuple +where + I: Iterator, + T: HomogeneousTuple, { type Item = T; @@ -110,8 +109,9 @@ impl Iterator for Tuples } impl Tuples - where I: Iterator, - T: HomogeneousTuple +where + I: Iterator, + T: HomogeneousTuple, { /// Return a buffer with the produced items that was not enough to be grouped in a tuple. /// @@ -128,7 +128,6 @@ impl Tuples } } - /// An iterator over all contiguous windows that produces tuples of a specific size. /// /// See [`.tuple_windows()`](crate::Itertools::tuple_windows) for more @@ -136,8 +135,9 @@ impl Tuples #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct TupleWindows - where I: Iterator, - T: HomogeneousTuple +where + I: Iterator, + T: HomogeneousTuple, { iter: I, last: Option, @@ -145,9 +145,10 @@ pub struct TupleWindows /// Create a new tuple windows iterator. pub fn tuple_windows(mut iter: I) -> TupleWindows - where I: Iterator, - T: HomogeneousTuple, - T::Item: Clone +where + I: Iterator, + T: HomogeneousTuple, + T::Item: Clone, { use std::iter::once; @@ -161,22 +162,20 @@ pub fn tuple_windows(mut iter: I) -> TupleWindows } } - TupleWindows { - iter, - last, - } + TupleWindows { iter, last } } impl Iterator for TupleWindows - where I: Iterator, - T: HomogeneousTuple + Clone, - T::Item: Clone +where + I: Iterator, + T: HomogeneousTuple + Clone, + T::Item: Clone, { type Item = T; fn next(&mut self) -> Option { if T::num_items() == 1 { - return T::collect_from_iter_no_buf(&mut self.iter) + return T::collect_from_iter_no_buf(&mut self.iter); } if let Some(ref mut last) = self.last { if let Some(new) = self.iter.next() { @@ -189,10 +188,12 @@ impl Iterator for TupleWindows } impl FusedIterator for TupleWindows - where I: FusedIterator, - T: HomogeneousTuple + Clone, - T::Item: Clone -{} +where + I: FusedIterator, + T: HomogeneousTuple + Clone, + T::Item: Clone, +{ +} /// An iterator over all windows, wrapping back to the first elements when the /// window would otherwise exceed the length of the iterator, producing tuples @@ -203,31 +204,34 @@ impl FusedIterator for TupleWindows #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug, Clone)] pub struct CircularTupleWindows - where I: Iterator + Clone, - T: TupleCollect + Clone +where + I: Iterator + Clone, + T: TupleCollect + Clone, { iter: Take, T>>, - phantom_data: PhantomData + phantom_data: PhantomData, } pub fn circular_tuple_windows(iter: I) -> CircularTupleWindows - where I: Iterator + Clone + ExactSizeIterator, - T: TupleCollect + Clone, - T::Item: Clone +where + I: Iterator + Clone + ExactSizeIterator, + T: TupleCollect + Clone, + T::Item: Clone, { let len = iter.len(); let iter = tuple_windows(iter.cycle()).take(len); CircularTupleWindows { iter, - phantom_data: PhantomData{} + phantom_data: PhantomData {}, } } impl Iterator for CircularTupleWindows - where I: Iterator + Clone, - T: TupleCollect + Clone, - T::Item: Clone +where + I: Iterator + Clone, + T: TupleCollect + Clone, + T::Item: Clone, { type Item = T; @@ -241,10 +245,12 @@ pub trait TupleCollect: Sized { type Buffer: Default + AsRef<[Option]> + AsMut<[Option]>; fn collect_from_iter(iter: I, buf: &mut Self::Buffer) -> Option - where I: IntoIterator; + where + I: IntoIterator; fn collect_from_iter_no_buf(iter: I) -> Option - where I: IntoIterator; + where + I: IntoIterator; fn num_items() -> usize; diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 4e81e78ec..a0b46b785 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -1,7 +1,7 @@ -use std::collections::HashMap; use std::collections::hash_map::Entry; -use std::hash::Hash; +use std::collections::HashMap; use std::fmt; +use std::hash::Hash; use std::iter::FusedIterator; /// An iterator adapter to filter out duplicate elements. @@ -19,17 +19,19 @@ pub struct UniqueBy { } impl fmt::Debug for UniqueBy - where I: Iterator + fmt::Debug, - V: fmt::Debug + Hash + Eq, +where + I: Iterator + fmt::Debug, + V: fmt::Debug + Hash + Eq, { debug_fmt_fields!(UniqueBy, iter, used); } /// Create a new `UniqueBy` iterator. pub fn unique_by(iter: I, f: F) -> UniqueBy - where V: Eq + Hash, - F: FnMut(&I::Item) -> V, - I: Iterator, +where + V: Eq + Hash, + F: FnMut(&I::Item) -> V, + I: Iterator, { UniqueBy { iter, @@ -40,8 +42,9 @@ pub fn unique_by(iter: I, f: F) -> UniqueBy // count the number of new unique keys in iterable (`used` is the set already seen) fn count_new_keys(mut used: HashMap, iterable: I) -> usize - where I: IntoIterator, - K: Hash + Eq, +where + I: IntoIterator, + K: Hash + Eq, { let iter = iterable.into_iter(); let current_used = used.len(); @@ -50,9 +53,10 @@ fn count_new_keys(mut used: HashMap, iterable: I) -> usize } impl Iterator for UniqueBy - where I: Iterator, - V: Eq + Hash, - F: FnMut(&I::Item) -> V +where + I: Iterator, + V: Eq + Hash, + F: FnMut(&I::Item) -> V, { type Item = I::Item; @@ -79,9 +83,10 @@ impl Iterator for UniqueBy } impl DoubleEndedIterator for UniqueBy - where I: DoubleEndedIterator, - V: Eq + Hash, - F: FnMut(&I::Item) -> V +where + I: DoubleEndedIterator, + V: Eq + Hash, + F: FnMut(&I::Item) -> V, { fn next_back(&mut self) -> Option { while let Some(v) = self.iter.next_back() { @@ -95,14 +100,17 @@ impl DoubleEndedIterator for UniqueBy } impl FusedIterator for UniqueBy - where I: FusedIterator, - V: Eq + Hash, - F: FnMut(&I::Item) -> V -{} +where + I: FusedIterator, + V: Eq + Hash, + F: FnMut(&I::Item) -> V, +{ +} impl Iterator for Unique - where I: Iterator, - I::Item: Eq + Hash + Clone +where + I: Iterator, + I::Item: Eq + Hash + Clone, { type Item = I::Item; @@ -129,8 +137,9 @@ impl Iterator for Unique } impl DoubleEndedIterator for Unique - where I: DoubleEndedIterator, - I::Item: Eq + Hash + Clone +where + I: DoubleEndedIterator, + I::Item: Eq + Hash + Clone, { fn next_back(&mut self) -> Option { while let Some(v) = self.iter.iter.next_back() { @@ -145,9 +154,11 @@ impl DoubleEndedIterator for Unique } impl FusedIterator for Unique - where I: FusedIterator, - I::Item: Eq + Hash + Clone -{} +where + I: FusedIterator, + I::Item: Eq + Hash + Clone, +{ +} /// An iterator adapter to filter out duplicate elements. /// @@ -159,21 +170,23 @@ pub struct Unique { } impl fmt::Debug for Unique - where I: Iterator + fmt::Debug, - I::Item: Hash + Eq + fmt::Debug, +where + I: Iterator + fmt::Debug, + I::Item: Hash + Eq + fmt::Debug, { debug_fmt_fields!(Unique, iter); } pub fn unique(iter: I) -> Unique - where I: Iterator, - I::Item: Eq + Hash, +where + I: Iterator, + I::Item: Eq + Hash, { Unique { iter: UniqueBy { iter, used: HashMap::new(), f: (), - } + }, } } diff --git a/src/with_position.rs b/src/with_position.rs index dda9b25dc..9cdc8319e 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -1,4 +1,4 @@ -use std::iter::{Fuse,Peekable, FusedIterator}; +use std::iter::{Fuse, FusedIterator, Peekable}; /// An iterator adaptor that wraps each element in an [`Position`]. /// @@ -7,22 +7,25 @@ use std::iter::{Fuse,Peekable, FusedIterator}; /// See [`.with_position()`](crate::Itertools::with_position) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct WithPosition - where I: Iterator, +where + I: Iterator, { handled_first: bool, peekable: Peekable>, } impl Clone for WithPosition - where I: Clone + Iterator, - I::Item: Clone, +where + I: Clone + Iterator, + I::Item: Clone, { clone_fields!(handled_first, peekable); } /// Create a new `WithPosition` iterator. pub fn with_position(iter: I) -> WithPosition - where I: Iterator, +where + I: Iterator, { WithPosition { handled_first: false, @@ -80,9 +83,6 @@ impl Iterator for WithPosition { } } -impl ExactSizeIterator for WithPosition - where I: ExactSizeIterator, -{ } +impl ExactSizeIterator for WithPosition where I: ExactSizeIterator {} -impl FusedIterator for WithPosition -{} +impl FusedIterator for WithPosition {} diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index a079b326a..09bc6988c 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -25,8 +25,9 @@ pub struct ZipEq { /// } /// ``` pub fn zip_eq(i: I, j: J) -> ZipEq - where I: IntoIterator, - J: IntoIterator +where + I: IntoIterator, + J: IntoIterator, { ZipEq { a: i.into_iter(), @@ -35,8 +36,9 @@ pub fn zip_eq(i: I, j: J) -> ZipEq } impl Iterator for ZipEq - where I: Iterator, - J: Iterator +where + I: Iterator, + J: Iterator, { type Item = (I::Item, J::Item); @@ -44,8 +46,9 @@ impl Iterator for ZipEq match (self.a.next(), self.b.next()) { (None, None) => None, (Some(a), Some(b)) => Some((a, b)), - (None, Some(_)) | (Some(_), None) => - panic!("itertools: .zip_eq() reached end of one iterator before the other") + (None, Some(_)) | (Some(_), None) => { + panic!("itertools: .zip_eq() reached end of one iterator before the other") + } } } @@ -55,6 +58,8 @@ impl Iterator for ZipEq } impl ExactSizeIterator for ZipEq - where I: ExactSizeIterator, - J: ExactSizeIterator -{} +where + I: ExactSizeIterator, + J: ExactSizeIterator, +{ +} diff --git a/src/zip_longest.rs b/src/zip_longest.rs index cb9a7bacb..98ce4e63e 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -1,5 +1,5 @@ -use std::cmp::Ordering::{Equal, Greater, Less}; use super::size_hint; +use std::cmp::Ordering::{Equal, Greater, Less}; use std::iter::{Fuse, FusedIterator}; use crate::either_or_both::EitherOrBoth; @@ -21,8 +21,9 @@ pub struct ZipLongest { /// Create a new `ZipLongest` iterator. pub fn zip_longest(a: T, b: U) -> ZipLongest - where T: Iterator, - U: Iterator +where + T: Iterator, + U: Iterator, { ZipLongest { a: a.fuse(), @@ -31,8 +32,9 @@ pub fn zip_longest(a: T, b: U) -> ZipLongest } impl Iterator for ZipLongest - where T: Iterator, - U: Iterator +where + T: Iterator, + U: Iterator, { type Item = EitherOrBoth; @@ -53,8 +55,9 @@ impl Iterator for ZipLongest } impl DoubleEndedIterator for ZipLongest - where T: DoubleEndedIterator + ExactSizeIterator, - U: DoubleEndedIterator + ExactSizeIterator +where + T: DoubleEndedIterator + ExactSizeIterator, + U: DoubleEndedIterator + ExactSizeIterator, { #[inline] fn next_back(&mut self) -> Option { @@ -73,11 +76,15 @@ impl DoubleEndedIterator for ZipLongest } impl ExactSizeIterator for ZipLongest - where T: ExactSizeIterator, - U: ExactSizeIterator -{} +where + T: ExactSizeIterator, + U: ExactSizeIterator, +{ +} impl FusedIterator for ZipLongest - where T: Iterator, - U: Iterator -{} +where + T: Iterator, + U: Iterator, +{ +} diff --git a/src/ziptuple.rs b/src/ziptuple.rs index 6d3a584c4..82760ae8f 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -38,8 +38,9 @@ pub struct Zip { /// ``` /// [`izip!()`]: crate::izip pub fn multizip(t: U) -> Zip - where Zip: From, - Zip: Iterator, +where + Zip: From, + Zip: Iterator, { Zip::from(t) } diff --git a/tests/adaptors_no_collect.rs b/tests/adaptors_no_collect.rs index 103db23f1..977224af2 100644 --- a/tests/adaptors_no_collect.rs +++ b/tests/adaptors_no_collect.rs @@ -22,9 +22,14 @@ impl Iterator for PanickingCounter { } fn no_collect_test(to_adaptor: T) - where A: Iterator, T: Fn(PanickingCounter) -> A +where + A: Iterator, + T: Fn(PanickingCounter) -> A, { - let counter = PanickingCounter { curr: 0, max: 10_000 }; + let counter = PanickingCounter { + curr: 0, + max: 10_000, + }; let adaptor = to_adaptor(counter); for _ in adaptor.take(5) {} @@ -43,4 +48,4 @@ fn combinations_no_collect() { #[test] fn combinations_with_replacement_no_collect() { no_collect_test(|iter| iter.combinations_with_replacement(5)) -} \ No newline at end of file +} diff --git a/tests/merge_join.rs b/tests/merge_join.rs index 3280b7d4e..870e1faff 100644 --- a/tests/merge_join.rs +++ b/tests/merge_join.rs @@ -1,108 +1,101 @@ -use itertools::EitherOrBoth; use itertools::free::merge_join_by; +use itertools::EitherOrBoth; #[test] fn empty() { let left: Vec = vec![]; let right: Vec = vec![]; let expected_result: Vec> = vec![]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn left_only() { - let left: Vec = vec![1,2,3]; + let left: Vec = vec![1, 2, 3]; let right: Vec = vec![]; let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Left(2), - EitherOrBoth::Left(3) + EitherOrBoth::Left(3), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn right_only() { let left: Vec = vec![]; - let right: Vec = vec![1,2,3]; + let right: Vec = vec![1, 2, 3]; let expected_result: Vec> = vec![ EitherOrBoth::Right(1), EitherOrBoth::Right(2), - EitherOrBoth::Right(3) + EitherOrBoth::Right(3), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn first_left_then_right() { - let left: Vec = vec![1,2,3]; - let right: Vec = vec![4,5,6]; + let left: Vec = vec![1, 2, 3]; + let right: Vec = vec![4, 5, 6]; let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Left(2), EitherOrBoth::Left(3), EitherOrBoth::Right(4), EitherOrBoth::Right(5), - EitherOrBoth::Right(6) + EitherOrBoth::Right(6), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn first_right_then_left() { - let left: Vec = vec![4,5,6]; - let right: Vec = vec![1,2,3]; + let left: Vec = vec![4, 5, 6]; + let right: Vec = vec![1, 2, 3]; let expected_result: Vec> = vec![ EitherOrBoth::Right(1), EitherOrBoth::Right(2), EitherOrBoth::Right(3), EitherOrBoth::Left(4), EitherOrBoth::Left(5), - EitherOrBoth::Left(6) + EitherOrBoth::Left(6), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn interspersed_left_and_right() { - let left: Vec = vec![1,3,5]; - let right: Vec = vec![2,4,6]; + let left: Vec = vec![1, 3, 5]; + let right: Vec = vec![2, 4, 6]; let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Right(2), EitherOrBoth::Left(3), EitherOrBoth::Right(4), EitherOrBoth::Left(5), - EitherOrBoth::Right(6) + EitherOrBoth::Right(6), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } #[test] fn overlapping_left_and_right() { - let left: Vec = vec![1,3,4,6]; - let right: Vec = vec![2,3,4,5]; + let left: Vec = vec![1, 3, 4, 6]; + let right: Vec = vec![2, 3, 4, 5]; let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Right(2), EitherOrBoth::Both(3, 3), EitherOrBoth::Both(4, 4), EitherOrBoth::Right(5), - EitherOrBoth::Left(6) + EitherOrBoth::Left(6), ]; - let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)) - .collect::>(); + let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } diff --git a/tests/quick.rs b/tests/quick.rs index c19af6c1e..1a19d60e8 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -3,34 +3,21 @@ //! //! In particular we test the tedious size_hint and exact size correctness. +use itertools::free::{ + cloned, enumerate, multipeek, peek_nth, put_back, put_back_n, rciter, zip, zip_eq, +}; +use itertools::Itertools; +use itertools::{iproduct, izip, multizip, EitherOrBoth}; use quickcheck as qc; +use std::cmp::{max, min, Ordering}; +use std::collections::{HashMap, HashSet}; use std::default::Default; use std::num::Wrapping; use std::ops::Range; -use std::cmp::{max, min, Ordering}; -use std::collections::{HashMap, HashSet}; -use itertools::Itertools; -use itertools::{ - multizip, - EitherOrBoth, - iproduct, - izip, -}; -use itertools::free::{ - cloned, - enumerate, - multipeek, - peek_nth, - put_back, - put_back_n, - rciter, - zip, - zip_eq, -}; -use rand::Rng; -use rand::seq::SliceRandom; use quickcheck::TestResult; +use rand::seq::SliceRandom; +use rand::Rng; /// Trait for size hint modifier types trait HintKind: Copy + Send + qc::Arbitrary { @@ -66,8 +53,10 @@ struct Inexact { impl HintKind for Inexact { fn loosen_bounds(&self, org_hint: (usize, Option)) -> (usize, Option) { let (org_lower, org_upper) = org_hint; - (org_lower.saturating_sub(self.underestimate), - org_upper.and_then(move |x| x.checked_add(self.overestimate))) + ( + org_lower.saturating_sub(self.underestimate), + org_upper.and_then(move |x| x.checked_add(self.overestimate)), + ) } } @@ -84,19 +73,15 @@ impl qc::Arbitrary for Inexact { } } - fn shrink(&self) -> Box> { + fn shrink(&self) -> Box> { let underestimate_value = self.underestimate; let overestimate_value = self.overestimate; - Box::new( - underestimate_value.shrink().flat_map(move |ue_value| - overestimate_value.shrink().map(move |oe_value| - Inexact { - underestimate: ue_value, - overestimate: oe_value, - } - ) - ) - ) + Box::new(underestimate_value.shrink().flat_map(move |ue_value| { + overestimate_value.shrink().map(move |oe_value| Inexact { + underestimate: ue_value, + overestimate: oe_value, + }) + })) } } @@ -116,7 +101,9 @@ struct Iter { hint_kind: SK, } -impl Iter where HK: HintKind +impl Iter +where + HK: HintKind, { fn new(it: Range, hint_kind: HK) -> Self { Iter { @@ -128,64 +115,66 @@ impl Iter where HK: HintKind } impl Iterator for Iter - where Range: Iterator, - as Iterator>::Item: Default, - HK: HintKind, +where + Range: Iterator, + as Iterator>::Item: Default, + HK: HintKind, { type Item = as Iterator>::Item; - fn next(&mut self) -> Option - { + fn next(&mut self) -> Option { let elt = self.iterator.next(); if elt.is_none() { self.fuse_flag += 1; // check fuse flag if self.fuse_flag == 2 { - return Some(Default::default()) + return Some(Default::default()); } } elt } - fn size_hint(&self) -> (usize, Option) - { + fn size_hint(&self) -> (usize, Option) { let org_hint = self.iterator.size_hint(); self.hint_kind.loosen_bounds(org_hint) } } impl DoubleEndedIterator for Iter - where Range: DoubleEndedIterator, - as Iterator>::Item: Default, - HK: HintKind +where + Range: DoubleEndedIterator, + as Iterator>::Item: Default, + HK: HintKind, { - fn next_back(&mut self) -> Option { self.iterator.next_back() } + fn next_back(&mut self) -> Option { + self.iterator.next_back() + } } -impl ExactSizeIterator for Iter where Range: ExactSizeIterator, +impl ExactSizeIterator for Iter +where + Range: ExactSizeIterator, as Iterator>::Item: Default, -{ } +{ +} impl qc::Arbitrary for Iter - where T: qc::Arbitrary, - HK: HintKind, +where + T: qc::Arbitrary, + HK: HintKind, { - fn arbitrary(g: &mut G) -> Self - { + fn arbitrary(g: &mut G) -> Self { Iter::new(T::arbitrary(g)..T::arbitrary(g), HK::arbitrary(g)) } - fn shrink(&self) -> Box>> - { + fn shrink(&self) -> Box>> { let r = self.iterator.clone(); let hint_kind = self.hint_kind; - Box::new( - r.start.shrink().flat_map(move |a| - r.end.shrink().map(move |b| - Iter::new(a.clone()..b, hint_kind) - ) - ) - ) + Box::new(r.start.shrink().flat_map(move |a| { + r.end + .shrink() + .map(move |b| Iter::new(a.clone()..b, hint_kind)) + })) } } @@ -201,7 +190,10 @@ struct ShiftRange { hint_kind: HK, } -impl Iterator for ShiftRange where HK: HintKind { +impl Iterator for ShiftRange +where + HK: HintKind, +{ type Item = Iter; fn next(&mut self) -> Option { @@ -219,10 +211,11 @@ impl Iterator for ShiftRange where HK: HintKind { } } -impl ExactSizeIterator for ShiftRange { } +impl ExactSizeIterator for ShiftRange {} impl qc::Arbitrary for ShiftRange - where HK: HintKind +where + HK: HintKind, { fn arbitrary(g: &mut G) -> Self { const MAX_STARTING_RANGE_DIFF: i32 = 32; @@ -250,7 +243,7 @@ impl qc::Arbitrary for ShiftRange fn correct_count(get_it: F) -> bool where I: Iterator, - F: Fn() -> I + F: Fn() -> I, { let mut counts = vec![get_it().count()]; @@ -276,7 +269,10 @@ where for (i, returned_count) in counts.into_iter().enumerate() { let actual_count = total_actual_count - i; if actual_count != returned_count { - println!("Total iterations: {} True count: {} returned count: {}", i, actual_count, returned_count); + println!( + "Total iterations: {} True count: {} returned count: {}", + i, actual_count, returned_count + ); return false; } @@ -299,12 +295,10 @@ fn correct_size_hint(mut it: I) -> bool { // check all the size hints for &(low, hi) in &hints { true_count -= 1; - if low > true_count || - (hi.is_some() && hi.unwrap() < true_count) - { + if low > true_count || (hi.is_some() && hi.unwrap() < true_count) { println!("True size: {:?}, size hint: {:?}", true_count, (low, hi)); //println!("All hints: {:?}", hints); - return false + return false; } } true @@ -313,13 +307,19 @@ fn correct_size_hint(mut it: I) -> bool { fn exact_size(mut it: I) -> bool { // check every iteration let (mut low, mut hi) = it.size_hint(); - if Some(low) != hi { return false; } + if Some(low) != hi { + return false; + } while let Some(_) = it.next() { let (xlow, xhi) = it.size_hint(); - if low != xlow + 1 { return false; } + if low != xlow + 1 { + return false; + } low = xlow; hi = xhi; - if Some(low) != hi { return false; } + if Some(low) != hi { + return false; + } } let (low, hi) = it.size_hint(); low == 0 && hi == Some(0) @@ -329,13 +329,19 @@ fn exact_size(mut it: I) -> bool { fn exact_size_for_this(mut it: I) -> bool { // check every iteration let (mut low, mut hi) = it.size_hint(); - if Some(low) != hi { return false; } + if Some(low) != hi { + return false; + } while let Some(_) = it.next() { let (xlow, xhi) = it.size_hint(); - if low != xlow + 1 { return false; } + if low != xlow + 1 { + return false; + } low = xlow; hi = xhi; - if Some(low) != hi { return false; } + if Some(low) != hi { + return false; + } } let (low, hi) = it.size_hint(); low == 0 && hi == Some(0) @@ -1332,7 +1338,7 @@ quickcheck! { Some(acc.unwrap_or(0) + val) } }); - + let group_map_lookup = a.iter() .map(|&b| b as u64) .map(|i| (i % modulo, i)) @@ -1352,7 +1358,7 @@ quickcheck! { for m in 0..modulo { assert_eq!( - lookup.get(&m).copied(), + lookup.get(&m).copied(), a.iter() .map(|&b| b as u64) .filter(|&val| val % modulo == m) @@ -1472,7 +1478,7 @@ quickcheck! { assert_eq!(Some(max), a.iter().copied().filter(|&val| val % modulo == key).max_by_key(|&val| val)); } } - + fn correct_grouping_map_by_min_modulo_key(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo }; // Avoid `% 0` let lookup = a.iter().copied().into_grouping_map_by(|i| i % modulo).min(); @@ -1523,7 +1529,7 @@ quickcheck! { assert_eq!(Some(min), a.iter().copied().filter(|&val| val % modulo == key).min_by_key(|&val| val)); } } - + fn correct_grouping_map_by_minmax_modulo_key(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo }; // Avoid `% 0` let lookup = a.iter().copied().into_grouping_map_by(|i| i % modulo).minmax(); @@ -1636,7 +1642,7 @@ quickcheck! { .min_by(|_, _, _| Ordering::Equal); assert_eq!(lookup[&0], 0); - + let lookup = (0..=10) .into_grouping_map_by(|_| 0) .minmax_by(|_, _, _| Ordering::Equal); @@ -1694,12 +1700,10 @@ quickcheck! { } } - -fn is_fused(mut it: I) -> bool -{ +fn is_fused(mut it: I) -> bool { for _ in it.by_ref() {} - for _ in 0..10{ - if it.next().is_some(){ + for _ in 0..10 { + if it.next().is_some() { return false; } } @@ -1740,7 +1744,7 @@ quickcheck! { !is_fused(a.clone().interleave_shortest(b.clone())) && is_fused(a.fuse().interleave_shortest(b.fuse())) } - + fn fused_product(a: Iter, b: Iter) -> bool { is_fused(a.fuse().cartesian_product(b.fuse())) diff --git a/tests/specializations.rs b/tests/specializations.rs index 057e11c9f..efc8ba6ef 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -1,6 +1,6 @@ use itertools::Itertools; -use std::fmt::Debug; use quickcheck::quickcheck; +use std::fmt::Debug; struct Unspecialized(I); impl Iterator for Unspecialized @@ -24,12 +24,11 @@ macro_rules! check_specialized { let v2 = $closure; assert_eq!(v1, v2); - } + }; } -fn test_specializations( - it: &Iter, -) where +fn test_specializations(it: &Iter) +where IterItem: Eq + Debug + Clone, Iter: Iterator + Clone, { @@ -50,7 +49,7 @@ fn test_specializations( let first = i.next(); let all_result = i.all(|x| { parameters_from_all.push(x.clone()); - Some(x)==first + Some(x) == first }); (parameters_from_all, all_result) }); diff --git a/tests/test_core.rs b/tests/test_core.rs index df94eb665..729d7c3b8 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -5,17 +5,17 @@ //! except according to those terms. #![no_std] -use core::iter; -use itertools as it; -use crate::it::Itertools; +use crate::it::chain; +use crate::it::free::put_back; use crate::it::interleave; use crate::it::intersperse; use crate::it::intersperse_with; -use crate::it::multizip; -use crate::it::free::put_back; use crate::it::iproduct; use crate::it::izip; -use crate::it::chain; +use crate::it::multizip; +use crate::it::Itertools; +use core::iter; +use itertools as it; #[test] fn product2() { @@ -34,13 +34,12 @@ fn product_temporary() { for (_x, _y, _z) in iproduct!( [0, 1, 2].iter().cloned(), [0, 1, 2].iter().cloned(), - [0, 1, 2].iter().cloned()) - { + [0, 1, 2].iter().cloned() + ) { // ok } } - #[test] fn izip_macro() { let mut zip = izip!(2..3); @@ -61,7 +60,7 @@ fn izip_macro() { #[test] fn izip2() { let _zip1: iter::Zip<_, _> = izip!(1.., 2..); - let _zip2: iter::Zip<_, _> = izip!(1.., 2.., ); + let _zip2: iter::Zip<_, _> = izip!(1.., 2..,); } #[test] @@ -109,7 +108,7 @@ fn chain_macro() { #[test] fn chain2() { let _ = chain!(1.., 2..); - let _ = chain!(1.., 2.., ); + let _ = chain!(1.., 2..,); } #[test] @@ -127,7 +126,7 @@ fn write_to() { #[test] fn test_interleave() { - let xs: [u8; 0] = []; + let xs: [u8; 0] = []; let ys = [7u8, 9, 8, 10]; let zs = [2u8, 77]; let it = interleave(xs.iter(), ys.iter()); @@ -211,7 +210,6 @@ fn merge() { it::assert_equal((0..10).step(2).merge((1..10).step(2)), 0..10); } - #[test] fn repeatn() { let s = "α"; @@ -231,29 +229,33 @@ fn count_clones() { use core::cell::Cell; #[derive(PartialEq, Debug)] struct Foo { - n: Cell + n: Cell, } - impl Clone for Foo - { - fn clone(&self) -> Self - { + impl Clone for Foo { + fn clone(&self) -> Self { let n = self.n.get(); self.n.set(n + 1); - Foo { n: Cell::new(n + 1) } + Foo { + n: Cell::new(n + 1), + } } } - for n in 0..10 { - let f = Foo{n: Cell::new(0)}; + let f = Foo { n: Cell::new(0) }; let it = it::repeat_n(f, n); // drain it let last = it.last(); if n == 0 { assert_eq!(last, None); } else { - assert_eq!(last, Some(Foo{n: Cell::new(n - 1)})); + assert_eq!( + last, + Some(Foo { + n: Cell::new(n - 1) + }) + ); } } } @@ -285,16 +287,36 @@ fn tree_fold1() { #[test] fn exactly_one() { assert_eq!((0..10).filter(|&x| x == 2).exactly_one().unwrap(), 2); - assert!((0..10).filter(|&x| x > 1 && x < 4).exactly_one().unwrap_err().eq(2..4)); - assert!((0..10).filter(|&x| x > 1 && x < 5).exactly_one().unwrap_err().eq(2..5)); - assert!((0..10).filter(|&_| false).exactly_one().unwrap_err().eq(0..0)); + assert!((0..10) + .filter(|&x| x > 1 && x < 4) + .exactly_one() + .unwrap_err() + .eq(2..4)); + assert!((0..10) + .filter(|&x| x > 1 && x < 5) + .exactly_one() + .unwrap_err() + .eq(2..5)); + assert!((0..10) + .filter(|&_| false) + .exactly_one() + .unwrap_err() + .eq(0..0)); } #[test] fn at_most_one() { assert_eq!((0..10).filter(|&x| x == 2).at_most_one().unwrap(), Some(2)); - assert!((0..10).filter(|&x| x > 1 && x < 4).at_most_one().unwrap_err().eq(2..4)); - assert!((0..10).filter(|&x| x > 1 && x < 5).at_most_one().unwrap_err().eq(2..5)); + assert!((0..10) + .filter(|&x| x > 1 && x < 4) + .at_most_one() + .unwrap_err() + .eq(2..4)); + assert!((0..10) + .filter(|&x| x > 1 && x < 5) + .at_most_one() + .unwrap_err() + .eq(2..5)); assert_eq!((0..10).filter(|&_| false).at_most_one().unwrap(), None); } diff --git a/tests/test_std.rs b/tests/test_std.rs index c730a7368..900d7174e 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1,19 +1,23 @@ -use quickcheck as qc; -use rand::{distributions::{Distribution, Standard}, Rng, SeedableRng, rngs::StdRng}; -use rand::{seq::SliceRandom, thread_rng}; -use std::{cmp::min, fmt::Debug, marker::PhantomData}; -use itertools as it; -use crate::it::Itertools; -use crate::it::ExactlyOneError; -use crate::it::multizip; -use crate::it::multipeek; -use crate::it::peek_nth; -use crate::it::free::rciter; -use crate::it::free::put_back_n; -use crate::it::FoldWhile; use crate::it::cloned; +use crate::it::free::put_back_n; +use crate::it::free::rciter; use crate::it::iproduct; use crate::it::izip; +use crate::it::multipeek; +use crate::it::multizip; +use crate::it::peek_nth; +use crate::it::ExactlyOneError; +use crate::it::FoldWhile; +use crate::it::Itertools; +use itertools as it; +use quickcheck as qc; +use rand::{ + distributions::{Distribution, Standard}, + rngs::StdRng, + Rng, SeedableRng, +}; +use rand::{seq::SliceRandom, thread_rng}; +use std::{cmp::min, fmt::Debug, marker::PhantomData}; #[test] fn product3() { @@ -27,9 +31,7 @@ fn product3() { } } } - for (_, _, _, _) in iproduct!(0..3, 0..2, 0..2, 0..3) { - /* test compiles */ - } + for (_, _, _, _) in iproduct!(0..3, 0..2, 0..2, 0..3) { /* test compiles */ } } #[test] @@ -62,9 +64,15 @@ fn duplicates_by() { let xs = ["aaa", "bbbbb", "aa", "ccc", "bbbb", "aaaaa", "cccc"]; let ys = ["aa", "bbbb", "cccc"]; it::assert_equal(ys.iter(), xs.iter().duplicates_by(|x| x[..2].to_string())); - it::assert_equal(ys.iter(), xs.iter().rev().duplicates_by(|x| x[..2].to_string()).rev()); + it::assert_equal( + ys.iter(), + xs.iter().rev().duplicates_by(|x| x[..2].to_string()).rev(), + ); let ys_rev = ["ccc", "aa", "bbbbb"]; - it::assert_equal(ys_rev.iter(), xs.iter().duplicates_by(|x| x[..2].to_string()).rev()); + it::assert_equal( + ys_rev.iter(), + xs.iter().duplicates_by(|x| x[..2].to_string()).rev(), + ); } #[test] @@ -86,7 +94,10 @@ fn duplicates() { let xs = vec![0, 1, 2, 1, 2]; let ys = vec![1, 2]; assert_eq!(ys, xs.iter().duplicates().cloned().collect_vec()); - assert_eq!(ys, xs.iter().rev().duplicates().rev().cloned().collect_vec()); + assert_eq!( + ys, + xs.iter().rev().duplicates().rev().cloned().collect_vec() + ); let ys_rev = vec![2, 1]; assert_eq!(ys_rev, xs.iter().duplicates().rev().cloned().collect_vec()); } @@ -96,9 +107,15 @@ fn unique_by() { let xs = ["aaa", "bbbbb", "aa", "ccc", "bbbb", "aaaaa", "cccc"]; let ys = ["aaa", "bbbbb", "ccc"]; it::assert_equal(ys.iter(), xs.iter().unique_by(|x| x[..2].to_string())); - it::assert_equal(ys.iter(), xs.iter().rev().unique_by(|x| x[..2].to_string()).rev()); + it::assert_equal( + ys.iter(), + xs.iter().rev().unique_by(|x| x[..2].to_string()).rev(), + ); let ys_rev = ["cccc", "aaaaa", "bbbb"]; - it::assert_equal(ys_rev.iter(), xs.iter().unique_by(|x| x[..2].to_string()).rev()); + it::assert_equal( + ys_rev.iter(), + xs.iter().unique_by(|x| x[..2].to_string()).rev(), + ); } #[test] @@ -149,13 +166,13 @@ fn dedup() { #[test] fn coalesce() { let data = vec![-1., -2., -3., 3., 1., 0., -1.]; - let it = data.iter().cloned().coalesce(|x, y| + let it = data.iter().cloned().coalesce(|x, y| { if (x >= 0.) == (y >= 0.) { Ok(x + y) } else { Err((x, y)) } - ); + }); itertools::assert_equal(it.clone(), vec![-6., 4., -1.]); assert_eq!( it.fold(vec![], |mut v, n| { @@ -168,17 +185,37 @@ fn coalesce() { #[test] fn dedup_by() { - let xs = [(0, 0), (0, 1), (1, 1), (2, 1), (0, 2), (3, 1), (0, 3), (1, 3)]; + let xs = [ + (0, 0), + (0, 1), + (1, 1), + (2, 1), + (0, 2), + (3, 1), + (0, 3), + (1, 3), + ]; let ys = [(0, 0), (0, 1), (0, 2), (3, 1), (0, 3)]; - it::assert_equal(ys.iter(), xs.iter().dedup_by(|x, y| x.1==y.1)); + it::assert_equal(ys.iter(), xs.iter().dedup_by(|x, y| x.1 == y.1)); let xs = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]; let ys = [(0, 1)]; - it::assert_equal(ys.iter(), xs.iter().dedup_by(|x, y| x.0==y.0)); - - let xs = [(0, 0), (0, 1), (1, 1), (2, 1), (0, 2), (3, 1), (0, 3), (1, 3)]; + it::assert_equal(ys.iter(), xs.iter().dedup_by(|x, y| x.0 == y.0)); + + let xs = [ + (0, 0), + (0, 1), + (1, 1), + (2, 1), + (0, 2), + (3, 1), + (0, 3), + (1, 3), + ]; let ys = [(0, 0), (0, 1), (0, 2), (3, 1), (0, 3)]; let mut xs_d = Vec::new(); - xs.iter().dedup_by(|x, y| x.1==y.1).fold((), |(), &elt| xs_d.push(elt)); + xs.iter() + .dedup_by(|x, y| x.1 == y.1) + .fold((), |(), &elt| xs_d.push(elt)); assert_eq!(&xs_d, &ys); } @@ -195,18 +232,38 @@ fn dedup_with_count() { it::assert_equal(ys.iter().cloned(), xs.iter().dedup_with_count()); } - #[test] fn dedup_by_with_count() { - let xs = [(0, 0), (0, 1), (1, 1), (2, 1), (0, 2), (3, 1), (0, 3), (1, 3)]; - let ys = [(1, &(0, 0)), (3, &(0, 1)), (1, &(0, 2)), (1, &(3, 1)), (2, &(0, 3))]; + let xs = [ + (0, 0), + (0, 1), + (1, 1), + (2, 1), + (0, 2), + (3, 1), + (0, 3), + (1, 3), + ]; + let ys = [ + (1, &(0, 0)), + (3, &(0, 1)), + (1, &(0, 2)), + (1, &(3, 1)), + (2, &(0, 3)), + ]; - it::assert_equal(ys.iter().cloned(), xs.iter().dedup_by_with_count(|x, y| x.1==y.1)); + it::assert_equal( + ys.iter().cloned(), + xs.iter().dedup_by_with_count(|x, y| x.1 == y.1), + ); let xs = [(0, 1), (0, 2), (0, 3), (0, 4), (0, 5)]; - let ys = [( 5, &(0, 1))]; + let ys = [(5, &(0, 1))]; - it::assert_equal(ys.iter().cloned(), xs.iter().dedup_by_with_count(|x, y| x.0==y.0)); + it::assert_equal( + ys.iter().cloned(), + xs.iter().dedup_by_with_count(|x, y| x.0 == y.0), + ); } #[test] @@ -227,7 +284,7 @@ fn all_equal_value() { assert_eq!("AABBCCC".chars().all_equal_value(), Err(Some(('A', 'B')))); assert_eq!("AAAAAAA".chars().all_equal_value(), Ok('A')); { - let mut it = [1,2,3].iter().copied(); + let mut it = [1, 2, 3].iter().copied(); let result = it.all_equal_value(); assert_eq!(result, Err(Some((1, 2)))); let remaining = it.next(); @@ -256,7 +313,7 @@ fn test_put_back_n() { #[test] fn tee() { - let xs = [0, 1, 2, 3]; + let xs = [0, 1, 2, 3]; let (mut t1, mut t2) = xs.iter().cloned().tee(); assert_eq!(t1.next(), Some(0)); assert_eq!(t2.next(), Some(0)); @@ -280,7 +337,6 @@ fn tee() { it::assert_equal(t1.zip(t2), xs.iter().cloned().zip(xs.iter().cloned())); } - #[test] fn test_rciter() { let xs = [0, 1, 1, 1, 2, 1, 3, 5, 6]; @@ -304,19 +360,19 @@ fn test_rciter() { #[allow(deprecated)] #[test] fn trait_pointers() { - struct ByRef<'r, I: ?Sized>(&'r mut I) ; + struct ByRef<'r, I: ?Sized>(&'r mut I); - impl<'r, X, I: ?Sized> Iterator for ByRef<'r, I> where - I: 'r + Iterator + impl<'r, X, I: ?Sized> Iterator for ByRef<'r, I> + where + I: 'r + Iterator, { type Item = X; - fn next(&mut self) -> Option - { + fn next(&mut self) -> Option { self.0.next() } } - let mut it = Box::new(0..10) as Box>; + let mut it = Box::new(0..10) as Box>; assert_eq!(it.next(), Some(0)); { @@ -336,9 +392,16 @@ fn trait_pointers() { #[test] fn merge_by() { - let odd : Vec<(u32, &str)> = vec![(1, "hello"), (3, "world"), (5, "!")]; + let odd: Vec<(u32, &str)> = vec![(1, "hello"), (3, "world"), (5, "!")]; let even = vec![(2, "foo"), (4, "bar"), (6, "baz")]; - let expected = vec![(1, "hello"), (2, "foo"), (3, "world"), (4, "bar"), (5, "!"), (6, "baz")]; + let expected = vec![ + (1, "hello"), + (2, "foo"), + (3, "world"), + (4, "bar"), + (5, "!"), + (6, "baz"), + ]; let results = odd.iter().merge_by(even.iter(), |a, b| a.0 <= b.0); it::assert_equal(results, expected.iter()); } @@ -352,7 +415,7 @@ fn merge_by_btree() { let mut bt2 = BTreeMap::new(); bt2.insert("foo", 2); bt2.insert("bar", 4); - let results = bt1.into_iter().merge_by(bt2.into_iter(), |a, b| a.0 <= b.0 ); + let results = bt1.into_iter().merge_by(bt2.into_iter(), |a, b| a.0 <= b.0); let expected = vec![("bar", 4), ("foo", 2), ("hello", 1), ("world", 3)]; it::assert_equal(results, expected.into_iter()); } @@ -394,19 +457,17 @@ fn kmerge_empty_size_hint() { #[test] fn join() { let many = [1, 2, 3]; - let one = [1]; + let one = [1]; let none: Vec = vec![]; assert_eq!(many.iter().join(", "), "1, 2, 3"); - assert_eq!( one.iter().join(", "), "1"); + assert_eq!(one.iter().join(", "), "1"); assert_eq!(none.iter().join(", "), ""); } #[test] fn sorted_unstable_by() { - let sc = [3, 4, 1, 2].iter().cloned().sorted_by(|&a, &b| { - a.cmp(&b) - }); + let sc = [3, 4, 1, 2].iter().cloned().sorted_by(|&a, &b| a.cmp(&b)); it::assert_equal(sc, vec![1, 2, 3, 4]); let v = (0..5).sorted_unstable_by(|&a, &b| a.cmp(&b).reverse()); @@ -424,9 +485,7 @@ fn sorted_unstable_by_key() { #[test] fn sorted_by() { - let sc = [3, 4, 1, 2].iter().cloned().sorted_by(|&a, &b| { - a.cmp(&b) - }); + let sc = [3, 4, 1, 2].iter().cloned().sorted_by(|&a, &b| a.cmp(&b)); it::assert_equal(sc, vec![1, 2, 3, 4]); let v = (0..5).sorted_by(|&a, &b| a.cmp(&b).reverse()); @@ -459,11 +518,13 @@ struct RandIter + _t: PhantomData, } impl Iterator for RandIter -where Standard: Distribution { +where + Standard: Distribution, +{ type Item = T; fn next(&mut self) -> Option { if self.idx == self.len { @@ -481,7 +542,7 @@ impl qc::Arbitrary for Ran idx: 0, len: g.size(), rng: R::seed_from_u64(g.next_u64()), - _t : PhantomData{}, + _t: PhantomData {}, } } } @@ -495,10 +556,7 @@ where { let j = i.clone(); let k = k as usize; - it::assert_equal( - i.k_smallest(k), - j.sorted().take(k) - ) + it::assert_equal(i.k_smallest(k), j.sorted().take(k)) } macro_rules! generic_test { @@ -550,7 +608,7 @@ fn sorted_by_cached_key() { #[test] fn test_multipeek() { - let nums = vec![1u8,2,3,4,5]; + let nums = vec![1u8, 2, 3, 4, 5]; let mp = multipeek(nums.iter().copied()); assert_eq!(nums, mp.collect::>()); @@ -591,7 +649,7 @@ fn test_multipeek_reset() { #[test] fn test_multipeek_peeking_next() { use crate::it::PeekingNext; - let nums = vec![1u8,2,3,4,5,6,7]; + let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; let mut mp = multipeek(nums.iter().copied()); assert_eq!(mp.peeking_next(|&x| x != 0), Some(1)); @@ -616,7 +674,7 @@ fn test_multipeek_peeking_next() { #[test] fn test_peek_nth() { - let nums = vec![1u8,2,3,4,5]; + let nums = vec![1u8, 2, 3, 4, 5]; let iter = peek_nth(nums.iter().copied()); assert_eq!(nums, iter.collect::>()); @@ -651,7 +709,7 @@ fn test_peek_nth() { #[test] fn test_peek_nth_peeking_next() { use it::PeekingNext; - let nums = vec![1u8,2,3,4,5,6,7]; + let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; let mut iter = peek_nth(nums.iter().copied()); assert_eq!(iter.peeking_next(|&x| x != 0), Some(1)); @@ -717,11 +775,11 @@ fn group_by() { for &idx in &indices[..] { let (key, text) = match idx { - 0 => ('A', "Aaa".chars()), - 1 => ('B', "Bbb".chars()), - 2 => ('C', "ccCc".chars()), - 3 => ('D', "DDDD".chars()), - _ => unreachable!(), + 0 => ('A', "Aaa".chars()), + 1 => ('B', "Bbb".chars()), + 2 => ('C', "ccCc".chars()), + 3 => ('D', "DDDD".chars()), + _ => unreachable!(), }; assert_eq!(key, subs[idx].0); it::assert_equal(&mut subs[idx].1, text); @@ -746,9 +804,11 @@ fn group_by() { { let mut ntimes = 0; let text = "AABCCC"; - for (_, sub) in &text.chars().group_by(|&x| { ntimes += 1; x}) { - for _ in sub { - } + for (_, sub) in &text.chars().group_by(|&x| { + ntimes += 1; + x + }) { + for _ in sub {} } assert_eq!(ntimes, text.len()); } @@ -756,8 +816,10 @@ fn group_by() { { let mut ntimes = 0; let text = "AABCCC"; - for _ in &text.chars().group_by(|&x| { ntimes += 1; x}) { - } + for _ in &text.chars().group_by(|&x| { + ntimes += 1; + x + }) {} assert_eq!(ntimes, text.len()); } @@ -797,8 +859,7 @@ fn group_by_lazy_2() { if i < 2 { groups.push(group); } else if i < 4 { - for _ in group { - } + for _ in group {} } else { groups.push(group); } @@ -810,7 +871,11 @@ fn group_by_lazy_2() { // use groups as chunks let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let mut i = 0; - let grouper = data.iter().group_by(move |_| { let k = i / 3; i += 1; k }); + let grouper = data.iter().group_by(move |_| { + let k = i / 3; + i += 1; + k + }); for (i, group) in &grouper { match i { 0 => it::assert_equal(group, &[0, 0, 0]), @@ -861,8 +926,8 @@ fn concat_empty() { #[test] fn concat_non_empty() { - let data = vec![vec![1,2,3], vec![4,5,6], vec![7,8,9]]; - assert_eq!(data.into_iter().concat(), vec![1,2,3,4,5,6,7,8,9]) + let data = vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]; + assert_eq!(data.into_iter().concat(), vec![1, 2, 3, 4, 5, 6, 7, 8, 9]) } #[test] @@ -870,19 +935,20 @@ fn combinations() { assert!((1..3).combinations(5).next().is_none()); let it = (1..3).combinations(2); - it::assert_equal(it, vec![ - vec![1, 2], - ]); + it::assert_equal(it, vec![vec![1, 2]]); let it = (1..5).combinations(2); - it::assert_equal(it, vec![ - vec![1, 2], - vec![1, 3], - vec![1, 4], - vec![2, 3], - vec![2, 4], - vec![3, 4], - ]); + it::assert_equal( + it, + vec![ + vec![1, 2], + vec![1, 3], + vec![1, 4], + vec![2, 3], + vec![2, 4], + vec![3, 4], + ], + ); it::assert_equal((0..0).tuple_combinations::<(_, _)>(), >::new()); it::assert_equal((0..1).tuple_combinations::<(_, _)>(), >::new()); @@ -902,7 +968,6 @@ fn combinations_of_too_short() { } } - #[test] fn combinations_zero() { it::assert_equal((1..3).combinations(0), vec![vec![]]); @@ -973,7 +1038,10 @@ fn combinations_inexact_size_hints() { // Once it's fully loaded, size hints of `it` are exacts. } assert_eq!(binomial(sh.0 + nb_loaded, k) - next_count, it.size_hint().0); - assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k) - next_count), it.size_hint().1); + assert_eq!( + sh.1.map(|n| binomial(n + nb_loaded, k) - next_count), + it.size_hint().1 + ); } let should_be_none = it.next(); assert!(should_be_none.is_none()); @@ -990,11 +1058,7 @@ fn permutations_zero() { fn permutations_range_count() { for n in 0..=7 { for k in 0..=7 { - let len = if k <= n { - (n - k + 1..=n).product() - } else { - 0 - }; + let len = if k <= n { (n - k + 1..=n).product() } else { 0 }; let mut it = (0..n).permutations(k); assert_eq!(len, it.clone().count()); assert_eq!(len, it.size_hint().0); @@ -1041,15 +1105,9 @@ fn combinations_with_replacement() { ], ); // Zero size - it::assert_equal( - (0..3).combinations_with_replacement(0), - vec![vec![]], - ); + it::assert_equal((0..3).combinations_with_replacement(0), vec![vec![]]); // Zero size on empty pool - it::assert_equal( - (0..0).combinations_with_replacement(0), - vec![vec![]], - ); + it::assert_equal((0..0).combinations_with_replacement(0), vec![vec![]]); // Empty pool it::assert_equal( (0..0).combinations_with_replacement(2), @@ -1083,13 +1141,23 @@ fn combinations_with_replacement_range_count() { fn powerset() { it::assert_equal((0..0).powerset(), vec![vec![]]); it::assert_equal((0..1).powerset(), vec![vec![], vec![0]]); - it::assert_equal((0..2).powerset(), vec![vec![], vec![0], vec![1], vec![0, 1]]); - it::assert_equal((0..3).powerset(), vec![ - vec![], - vec![0], vec![1], vec![2], - vec![0, 1], vec![0, 2], vec![1, 2], - vec![0, 1, 2] - ]); + it::assert_equal( + (0..2).powerset(), + vec![vec![], vec![0], vec![1], vec![0, 1]], + ); + it::assert_equal( + (0..3).powerset(), + vec![ + vec![], + vec![0], + vec![1], + vec![2], + vec![0, 1], + vec![0, 2], + vec![1, 2], + vec![0, 1, 2], + ], + ); assert_eq!((0..4).powerset().count(), 1 << 4); assert_eq!((0..8).powerset().count(), 1 << 8); @@ -1135,8 +1203,7 @@ fn diff_longer() { let diff = it::diff_with(a.iter(), b_map, |a, b| *a == b); assert!(match diff { - Some(it::Diff::Longer(_, remaining)) => - remaining.collect::>() == vec![5, 6], + Some(it::Diff::Longer(_, remaining)) => remaining.collect::>() == vec![5, 6], _ => false, }); } @@ -1204,8 +1271,8 @@ fn extrema_set() { #[test] fn minmax() { - use std::cmp::Ordering; use crate::it::MinMaxResult; + use std::cmp::Ordering; // A peculiar type: Equality compares both tuple items, but ordering only the // first item. This is so we can check the stability property easily. @@ -1224,7 +1291,10 @@ fn minmax() { } } - assert_eq!(None::>.iter().minmax(), MinMaxResult::NoElements); + assert_eq!( + None::>.iter().minmax(), + MinMaxResult::NoElements + ); assert_eq!(Some(1u32).iter().minmax(), MinMaxResult::OneElement(&1)); @@ -1237,7 +1307,11 @@ fn minmax() { assert_eq!(min, &Val(2, 0)); assert_eq!(max, &Val(0, 2)); - let (min, max) = data.iter().minmax_by(|x, y| x.1.cmp(&y.1)).into_option().unwrap(); + let (min, max) = data + .iter() + .minmax_by(|x, y| x.1.cmp(&y.1)) + .into_option() + .unwrap(); assert_eq!(min, &Val(2, 0)); assert_eq!(max, &Val(0, 2)); } @@ -1260,8 +1334,9 @@ fn format() { #[test] fn while_some() { - let ns = (1..10).map(|x| if x % 5 != 0 { Some(x) } else { None }) - .while_some(); + let ns = (1..10) + .map(|x| if x % 5 != 0 { Some(x) } else { None }) + .while_some(); it::assert_equal(ns, vec![1, 2, 3, 4]); } @@ -1270,15 +1345,18 @@ fn while_some() { fn fold_while() { let mut iterations = 0; let vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - let sum = vec.into_iter().fold_while(0, |acc, item| { - iterations += 1; - let new_sum = acc + item; - if new_sum <= 20 { - FoldWhile::Continue(new_sum) - } else { - FoldWhile::Done(acc) - } - }).into_inner(); + let sum = vec + .into_iter() + .fold_while(0, |acc, item| { + iterations += 1; + let new_sum = acc + item; + if new_sum <= 20 { + FoldWhile::Continue(new_sum) + } else { + FoldWhile::Done(acc) + } + }) + .into_inner(); assert_eq!(iterations, 6); assert_eq!(sum, 15); } @@ -1305,7 +1383,11 @@ fn tree_fold1() { "0 1 x 2 3 x x 4 5 x 6 7 x x x 8 9 x 10 11 x x 12 13 x 14 15 x x x x", ]; for (i, &s) in x.iter().enumerate() { - let expected = if s.is_empty() { None } else { Some(s.to_string()) }; + let expected = if s.is_empty() { + None + } else { + Some(s.to_string()) + }; let num_strings = (0..i).map(|x| x.to_string()); let actual = num_strings.tree_fold1(|a, b| format!("{} {} x", a, b)); assert_eq!(actual, expected); @@ -1317,16 +1399,52 @@ fn exactly_one_question_mark_syntax_works() { exactly_one_question_mark_return().unwrap_err(); } -fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError>> { +fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError>> +{ [].iter().exactly_one()?; Ok(()) } #[test] fn multiunzip() { - let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); + let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)] + .iter() + .cloned() + .multiunzip(); assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); let (): () = [(), (), ()].iter().cloned().multiunzip(); - let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); - assert_eq!(t, (vec![0], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6], vec![7], vec![8], vec![9], vec![10], vec![11])); + let t: ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)] + .iter() + .cloned() + .multiunzip(); + assert_eq!( + t, + ( + vec![0], + vec![1], + vec![2], + vec![3], + vec![4], + vec![5], + vec![6], + vec![7], + vec![8], + vec![9], + vec![10], + vec![11] + ) + ); } diff --git a/tests/zip.rs b/tests/zip.rs index 75157d34f..f2554d7a4 100644 --- a/tests/zip.rs +++ b/tests/zip.rs @@ -1,17 +1,18 @@ -use itertools::Itertools; -use itertools::EitherOrBoth::{Both, Left, Right}; use itertools::free::zip_eq; use itertools::multizip; +use itertools::EitherOrBoth::{Both, Left, Right}; +use itertools::Itertools; #[test] fn zip_longest_fused() { let a = [Some(1), None, Some(3), Some(4)]; let b = [1, 2, 3]; - let unfused = a.iter().batching(|it| *it.next().unwrap()) + let unfused = a + .iter() + .batching(|it| *it.next().unwrap()) .zip_longest(b.iter().cloned()); - itertools::assert_equal(unfused, - vec![Both(1, 1), Right(2), Right(3)]); + itertools::assert_equal(unfused, vec![Both(1, 1), Right(2), Right(3)]); } #[test] @@ -55,11 +56,9 @@ fn test_double_ended_zip() { assert_eq!(it.next_back(), None); } - #[should_panic] #[test] -fn zip_eq_panic1() -{ +fn zip_eq_panic1() { let a = [1, 2]; let b = [1, 2, 3]; @@ -68,8 +67,7 @@ fn zip_eq_panic1() #[should_panic] #[test] -fn zip_eq_panic2() -{ +fn zip_eq_panic2() { let a: [i32; 0] = []; let b = [1, 2, 3]; From d7b2c3254544c0e2617d9a440d489d58c8ad068b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 18 Sep 2023 16:57:11 +0200 Subject: [PATCH 289/633] `CircularTupleWindows::size_hint` Add a field to know the length of the iterator. --- src/tuple_impl.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 0aea97716..6bbf81c55 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -209,6 +209,7 @@ where T: TupleCollect + Clone, { iter: Take, T>>, + len: usize, phantom_data: PhantomData, } @@ -223,6 +224,7 @@ where CircularTupleWindows { iter, + len, phantom_data: PhantomData {}, } } @@ -236,8 +238,13 @@ where type Item = T; fn next(&mut self) -> Option { + self.len = self.len.saturating_sub(1); self.iter.next() } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } } pub trait TupleCollect: Sized { From b3e3a8db720d32baf5c0d63b7c2e87fc197e8982 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 18 Sep 2023 17:00:09 +0200 Subject: [PATCH 290/633] `impl ExactSizeIterator for CircularTupleWindows` Implemented under the same conditions `Iterator` is implemented. It implements a method `len` which rely on an exact 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 6bbf81c55..3c37aab7a 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -247,6 +247,14 @@ where } } +impl ExactSizeIterator for CircularTupleWindows +where + I: Iterator + Clone, + T: TupleCollect + Clone, + T::Item: Clone, +{ +} + pub trait TupleCollect: Sized { type Item; type Buffer: Default + AsRef<[Option]> + AsMut<[Option]>; From d4dbbd407d0509d30a21ad6f5e63a7a9ef773539 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 18 Sep 2023 17:04:07 +0200 Subject: [PATCH 291/633] `impl FusedIterator for CircularTupleWindows` Implemented under the same conditions `Iterator` and `ExactSizeIterator` are implemented. --- src/tuple_impl.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 3c37aab7a..aaea476cb 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -255,6 +255,14 @@ where { } +impl FusedIterator for CircularTupleWindows +where + I: Iterator + Clone, + T: TupleCollect + Clone, + T::Item: Clone, +{ +} + pub trait TupleCollect: Sized { type Item; type Buffer: Default + AsRef<[Option]> + AsMut<[Option]>; From 9c0be45df0e4ddc96da40101437222b723e57c9d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 18 Sep 2023 17:18:01 +0200 Subject: [PATCH 292/633] `CircularTupleWindows`: exact size test --- tests/quick.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 1a19d60e8..0a9eea1cf 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1117,6 +1117,10 @@ quickcheck! { true } + fn circular_tuple_windows_exact_size(a: Vec) -> bool { + exact_size(a.iter().circular_tuple_windows::<(_, _, _, _)>()) + } + fn equal_tuple_windows_1(a: Vec) -> bool { let x = a.windows(1).map(|s| (&s[0], )); let y = a.iter().tuple_windows::<(_,)>(); From 6e1d6d0850b251306d5d4aa2aaa2daa3f7a8764b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 18 Sep 2023 23:17:05 +0200 Subject: [PATCH 293/633] `CircularTupleWindows`: unwrap `Take` type from `iter` field `len` was a duplicate since `Take` also had it. `CircularTupleWindows::next` is how is implemented `Take::next`. --- src/tuple_impl.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index aaea476cb..4792a786d 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 @@ -208,7 +207,7 @@ where I: Iterator + Clone, T: TupleCollect + Clone, { - iter: Take, T>>, + iter: TupleWindows, T>, len: usize, phantom_data: PhantomData, } @@ -220,7 +219,7 @@ where T::Item: Clone, { let len = iter.len(); - let iter = tuple_windows(iter.cycle()).take(len); + let iter = tuple_windows(iter.cycle()); CircularTupleWindows { iter, @@ -238,8 +237,12 @@ where type Item = T; fn next(&mut self) -> Option { - self.len = self.len.saturating_sub(1); - self.iter.next() + if self.len != 0 { + self.len -= 1; + self.iter.next() + } else { + None + } } fn size_hint(&self) -> (usize, Option) { From 5157128471917cf22a4aa85bf21768f109bfcfa0 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 11:05:34 +0200 Subject: [PATCH 294/633] Add `TupleWindows::size_hint` --- src/tuple_impl.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 4792a786d..dce58e9bb 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -184,6 +184,12 @@ where } None } + + fn size_hint(&self) -> (usize, Option) { + // At definition, `T::num_items() - 1` are collected + // so each remaining item in `iter` will lead to an item. + self.iter.size_hint() + } } impl FusedIterator for TupleWindows From 43cfe24ad3d8a43848375cb37c8d3b2c9beeda44 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 11:07:21 +0200 Subject: [PATCH 295/633] ExactSizeIterator for TupleWindows With the additional condition that `I` is an exact size iterator. --- src/tuple_impl.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index dce58e9bb..ba1c69df6 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -192,6 +192,14 @@ where } } +impl ExactSizeIterator for TupleWindows +where + I: ExactSizeIterator, + T: HomogeneousTuple + Clone, + T::Item: Clone, +{ +} + impl FusedIterator for TupleWindows where I: FusedIterator, From 882581f1853ed8581b527e1869f9f801c0d2f7cc Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 11:14:12 +0200 Subject: [PATCH 296/633] `TupleWindows`: exact size test --- tests/quick.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 0a9eea1cf..3dc23d704 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1145,6 +1145,14 @@ quickcheck! { itertools::equal(x, y) } + fn tuple_windows_exact_size_1(a: Vec) -> bool { + exact_size(a.iter().tuple_windows::<(_,)>()) + } + + fn tuple_windows_exact_size_4(a: Vec) -> bool { + exact_size(a.iter().tuple_windows::<(_, _, _, _)>()) + } + fn equal_tuples_1(a: Vec) -> bool { let x = a.chunks(1).map(|s| (&s[0], )); let y = a.iter().tuples::<(_,)>(); From 71a2a9fdc63d7113cf429cdb6353048e9dce95bc Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Tue, 19 Sep 2023 16:09:44 +0100 Subject: [PATCH 297/633] Document and test bumped MSRV. --- .github/workflows/ci.yml | 27 +++++++++++---------------- Cargo.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 45e399a0d..c412fc835 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,11 +9,9 @@ on: jobs: check: - name: check runs-on: ubuntu-latest strategy: matrix: - build: [msrv, stable] features: [ "", @@ -21,24 +19,22 @@ jobs: "--no-default-features --features use_alloc", "--all-targets --all-features", ] - include: - - build: msrv - rust: 1.62.1 - - build: stable - rust: stable - exclude: - - build: msrv - # we only care about the MSRV with respect to the lib target - features: "--all-targets --all-features" steps: - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.rust }} + - uses: dtolnay/rust-toolchain@stable - run: cargo check ${{ matrix.features }} + msrv: + runs-on: ubuntu-latest + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + steps: + - uses: actions/checkout@v4 + - uses: taiki-e/install-action@cargo-no-dev-deps + - uses: dtolnay/rust-toolchain@1.43.1 + - run: cargo no-dev-deps check + test: - name: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -62,7 +58,6 @@ jobs: if: success() runs-on: ubuntu-latest needs: [check, test] - steps: - name: Mark the job as successful run: exit 0 diff --git a/Cargo.toml b/Cargo.toml index a6dfefbf6..2a023a1f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["/bors.toml"] edition = "2018" -rust-version = "1.36.0" +rust-version = "1.43.1" [lib] bench = false diff --git a/src/lib.rs b/src/lib.rs index 7dc40370b..f51a9a993 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ //! //! ## Rust Version //! -//! This version of itertools requires Rust 1.36 or later. +//! This version of itertools requires Rust 1.43.1 or later. #![doc(html_root_url = "https://docs.rs/itertools/0.11/")] #[cfg(not(feature = "use_std"))] From dbdbf8762adc37387e787a1ac050c39bf8f4b458 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 16:13:25 +0200 Subject: [PATCH 298/633] Fix ignored doctest of `size_hint::mul` This doctest can not run because this function (and module) is private. --- src/size_hint.rs | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/src/size_hint.rs b/src/size_hint.rs index 7ba6f443b..857e0c4c6 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -38,20 +38,6 @@ pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { } /// Multiply `SizeHint` correctly -/// -/// ```ignore -/// use std::usize; -/// use itertools::size_hint; -/// -/// assert_eq!(size_hint::mul((3, Some(4)), (3, Some(4))), -/// (9, Some(16))); -/// -/// assert_eq!(size_hint::mul((3, Some(4)), (usize::MAX, None)), -/// (usize::MAX, None)); -/// -/// assert_eq!(size_hint::mul((3, None), (0, Some(0))), -/// (0, Some(0))); -/// ``` #[inline] pub fn mul(a: SizeHint, b: SizeHint) -> SizeHint { let low = a.0.saturating_mul(b.0); @@ -100,3 +86,10 @@ pub fn min(a: SizeHint, b: SizeHint) -> SizeHint { }; (lower, upper) } + +#[test] +fn mul_size_hints() { + assert_eq!(mul((3, Some(4)), (3, Some(4))), (9, Some(16))); + assert_eq!(mul((3, Some(4)), (usize::MAX, None)), (usize::MAX, None)); + assert_eq!(mul((3, None), (0, Some(0))), (0, Some(0))); +} From 8cef71f434a7687bc9305eca461bb5eee0e70074 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 16:21:16 +0200 Subject: [PATCH 299/633] `assert_equal` doctest should not be ignored but panic --- src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f51a9a993..cb2161130 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3981,9 +3981,10 @@ where /// **Panics** on assertion failure with a message that shows the /// two iteration elements. /// -/// ```ignore +/// ```should_panic +/// # use itertools::assert_equal; /// assert_equal("exceed".split('c'), "excess".split('c')); -/// // ^PANIC: panicked at 'Failed assertion Some("eed") == Some("ess") for iteration 1', +/// // ^PANIC: panicked at 'Failed assertion Some("eed") == Some("ess") for iteration 1'. /// ``` pub fn assert_equal(a: I, b: J) where From a1043218cbf5a9485c01a038790e634a5431e40c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 19 Sep 2023 16:27:40 +0200 Subject: [PATCH 300/633] `fold_ok` small doctest: no run But it should compile at it is code. --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index cb2161130..792ff1288 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2373,7 +2373,9 @@ pub trait Itertools: Iterator { /// For example the sequence *Ok(1), Ok(2), Ok(3)* will result in a /// computation like this: /// - /// ```ignore + /// ```no_run + /// # let start = 0; + /// # let f = |x, y| x + y; /// let mut accum = start; /// accum = f(accum, 1); /// accum = f(accum, 2); From 485a6b5715cdcd98736319ba76615156158b6d1c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 11:47:11 +0200 Subject: [PATCH 301/633] `MergeJoinBy` alias of new `InternalMergeJoinBy` And the trait `OrderingOrBool` is now for wrapped functions rather than for `Ordering` and `bool`. NOTES: - The new struct `MergeFuncLR` is useful to avoid conflicting implementations of `OrderingOrBool for F` because the compiler thinks that `F` could implement both `FnMut(&L, &R) -> Ordering` and `FnMut(&L, &R) -> bool`. - The new trait `FuncLR` is useful to be able to infer the Output type `T` without having to add a fourth parameter to `MergeJoinBy`. From what I understand, this is needed because the `FnMut` trait is not fully stabilized yet. - `OrderingOrBool` has a new associated type `Out`, which is useful in `impl Iterator` because otherwise the compiler would complain about `T` be unconstrained. This successfully pass the tests. --- src/lib.rs | 1 - src/merge_join.rs | 77 +++++++++++++++++++++++++++++------------------ 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 792ff1288..458ffe67c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1075,7 +1075,6 @@ pub trait Itertools: Iterator { where J: IntoIterator, F: FnMut(&Self::Item, &J::Item) -> T, - T: merge_join::OrderingOrBool, Self: Sized, { merge_join_by(self, other, cmp_fn) diff --git a/src/merge_join.rs b/src/merge_join.rs index 1769aaf3a..45560471f 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -2,6 +2,7 @@ use std::cmp::Ordering; use std::fmt; use std::iter::Fuse; use std::iter::{FusedIterator, Peekable}; +use std::marker::PhantomData; use either::Either; @@ -171,37 +172,55 @@ where I: IntoIterator, J: IntoIterator, F: FnMut(&I::Item, &J::Item) -> T, - T: OrderingOrBool, { - MergeJoinBy { + InternalMergeJoinBy { left: put_back(left.into_iter().fuse()), right: put_back(right.into_iter().fuse()), - cmp_fn, + cmp_fn: MergeFuncLR(cmp_fn, PhantomData), } } /// An iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// See [`.merge_join_by()`](crate::Itertools::merge_join_by) for more information. +pub type MergeJoinBy = InternalMergeJoinBy< + I, + J, + MergeFuncLR::Item, ::Item>>::T>, +>; + #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MergeJoinBy { +pub struct InternalMergeJoinBy { left: PutBack>, right: PutBack>, cmp_fn: F, } +#[derive(Clone, Debug)] +pub struct MergeFuncLR(F, PhantomData); + +pub trait FuncLR { + type T; +} + +impl T> FuncLR for F { + type T = T; +} + pub trait OrderingOrBool { + type Out; type MergeResult; fn left(left: L) -> Self::MergeResult; fn right(right: R) -> Self::MergeResult; // "merge" never returns (Some(...), Some(...), ...) so Option> // is appealing but it is always followed by two put_backs, so we think the compiler is // smart enough to optimize it. Or we could move put_backs into "merge". - fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult); + fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult); fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint; } -impl OrderingOrBool for Ordering { +impl Ordering> OrderingOrBool for MergeFuncLR { + type Out = Ordering; type MergeResult = EitherOrBoth; fn left(left: L) -> Self::MergeResult { EitherOrBoth::Left(left) @@ -209,8 +228,8 @@ impl OrderingOrBool for Ordering { fn right(right: R) -> Self::MergeResult { EitherOrBoth::Right(right) } - fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult) { - match self { + fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + match self.0(&left, &right) { Ordering::Equal => (None, None, EitherOrBoth::Both(left, right)), Ordering::Less => (None, Some(right), EitherOrBoth::Left(left)), Ordering::Greater => (Some(left), None, EitherOrBoth::Right(right)), @@ -228,7 +247,8 @@ impl OrderingOrBool for Ordering { } } -impl OrderingOrBool for bool { +impl bool> OrderingOrBool for MergeFuncLR { + type Out = bool; type MergeResult = Either; fn left(left: L) -> Self::MergeResult { Either::Left(left) @@ -236,8 +256,8 @@ impl OrderingOrBool for bool { fn right(right: R) -> Self::MergeResult { Either::Right(right) } - fn merge(self, left: L, right: R) -> (Option, Option, Self::MergeResult) { - if self { + fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + if self.0(&left, &right) { (None, Some(right), Either::Left(left)) } else { (Some(left), None, Either::Right(right)) @@ -249,7 +269,7 @@ impl OrderingOrBool for bool { } } -impl Clone for MergeJoinBy +impl Clone for InternalMergeJoinBy where I: Iterator, J: Iterator, @@ -260,32 +280,31 @@ where clone_fields!(left, right, cmp_fn); } -impl fmt::Debug for MergeJoinBy +impl fmt::Debug for InternalMergeJoinBy where I: Iterator + fmt::Debug, I::Item: fmt::Debug, J: Iterator + fmt::Debug, J::Item: fmt::Debug, { - debug_fmt_fields!(MergeJoinBy, left, right); + debug_fmt_fields!(InternalMergeJoinBy, left, right); } -impl Iterator for MergeJoinBy +impl Iterator for InternalMergeJoinBy where I: Iterator, J: Iterator, - F: FnMut(&I::Item, &J::Item) -> T, - T: OrderingOrBool, + F: OrderingOrBool, { - type Item = T::MergeResult; + type Item = F::MergeResult; fn next(&mut self) -> Option { match (self.left.next(), self.right.next()) { (None, None) => None, - (Some(left), None) => Some(T::left(left)), - (None, Some(right)) => Some(T::right(right)), + (Some(left), None) => Some(F::left(left)), + (None, Some(right)) => Some(F::right(right)), (Some(left), Some(right)) => { - let (left, right, next) = (self.cmp_fn)(&left, &right).merge(left, right); + let (left, right, next) = self.cmp_fn.merge(left, right); if let Some(left) = left { self.left.put_back(left); } @@ -298,7 +317,7 @@ where } fn size_hint(&self) -> SizeHint { - T::size_hint(self.left.size_hint(), self.right.size_hint()) + F::size_hint(self.left.size_hint(), self.right.size_hint()) } fn count(mut self) -> usize { @@ -310,7 +329,7 @@ where (None, Some(_right)) => break count + 1 + self.right.into_parts().1.count(), (Some(left), Some(right)) => { count += 1; - let (left, right, _) = (self.cmp_fn)(&left, &right).merge(left, right); + let (left, right, _) = self.cmp_fn.merge(left, right); if let Some(left) = left { self.left.put_back(left); } @@ -328,13 +347,13 @@ where match (self.left.next(), self.right.next()) { (None, None) => break previous_element, (Some(left), None) => { - break Some(T::left(self.left.into_parts().1.last().unwrap_or(left))) + break Some(F::left(self.left.into_parts().1.last().unwrap_or(left))) } (None, Some(right)) => { - break Some(T::right(self.right.into_parts().1.last().unwrap_or(right))) + break Some(F::right(self.right.into_parts().1.last().unwrap_or(right))) } (Some(left), Some(right)) => { - let (left, right, elem) = (self.cmp_fn)(&left, &right).merge(left, right); + let (left, right, elem) = self.cmp_fn.merge(left, right); if let Some(left) = left { self.left.put_back(left); } @@ -355,10 +374,10 @@ where n -= 1; match (self.left.next(), self.right.next()) { (None, None) => break None, - (Some(_left), None) => break self.left.nth(n).map(T::left), - (None, Some(_right)) => break self.right.nth(n).map(T::right), + (Some(_left), None) => break self.left.nth(n).map(F::left), + (None, Some(_right)) => break self.right.nth(n).map(F::right), (Some(left), Some(right)) => { - let (left, right, _) = (self.cmp_fn)(&left, &right).merge(left, right); + let (left, right, _) = self.cmp_fn.merge(left, right); if let Some(left) = left { self.left.put_back(left); } From fbf72622152abe91c92bf7903a00a7f6b52bc8ba Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 12:02:52 +0200 Subject: [PATCH 302/633] Make `MergeBy` an alias of `InternalMergeJoinBy` Being now an alias, we can remove various implementations. `FusedIterator` will be inserted back soon. We don't need `MergePredicate` anymore because we use two new implementations of `OrderingOrBool`. --- src/merge_join.rs | 148 +++++++++++++++------------------------------- 1 file changed, 49 insertions(+), 99 deletions(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 45560471f..08c6dda67 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -1,7 +1,6 @@ use std::cmp::Ordering; use std::fmt; use std::iter::Fuse; -use std::iter::{FusedIterator, Peekable}; use std::marker::PhantomData; use either::Either; @@ -12,19 +11,9 @@ use crate::size_hint::{self, SizeHint}; #[cfg(doc)] use crate::Itertools; -pub trait MergePredicate { - fn merge_pred(&mut self, a: &T, b: &T) -> bool; -} - #[derive(Clone, Debug)] pub struct MergeLte; -impl MergePredicate for MergeLte { - fn merge_pred(&mut self, a: &T, b: &T) -> bool { - a <= b - } -} - /// An iterator adaptor that merges the two base iterators in ascending order. /// If both base iterators are sorted (ascending), the result is sorted. /// @@ -62,104 +51,21 @@ where /// Iterator element type is `I::Item`. /// /// See [`.merge_by()`](crate::Itertools::merge_by) for more information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MergeBy -where - I: Iterator, - J: Iterator, -{ - a: Peekable, - b: Peekable, - fused: Option, - cmp: F, -} - -impl fmt::Debug for MergeBy -where - I: Iterator + fmt::Debug, - J: Iterator + fmt::Debug, - I::Item: fmt::Debug, -{ - debug_fmt_fields!(MergeBy, a, b); -} - -impl bool> MergePredicate for F { - fn merge_pred(&mut self, a: &T, b: &T) -> bool { - self(a, b) - } -} +pub type MergeBy = InternalMergeJoinBy; /// Create a `MergeBy` iterator. pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy where I: IntoIterator, J: IntoIterator, - F: MergePredicate, { - MergeBy { - a: a.into_iter().peekable(), - b: b.into_iter().peekable(), - fused: None, - cmp, - } -} - -impl Clone for MergeBy -where - I: Iterator, - J: Iterator, - Peekable: Clone, - Peekable: Clone, - F: Clone, -{ - clone_fields!(a, b, fused, cmp); -} - -impl Iterator for MergeBy -where - I: Iterator, - J: Iterator, - F: MergePredicate, -{ - type Item = I::Item; - - fn next(&mut self) -> Option { - let less_than = match self.fused { - Some(lt) => lt, - None => match (self.a.peek(), self.b.peek()) { - (Some(a), Some(b)) => self.cmp.merge_pred(a, b), - (Some(_), None) => { - self.fused = Some(true); - true - } - (None, Some(_)) => { - self.fused = Some(false); - false - } - (None, None) => return None, - }, - }; - if less_than { - self.a.next() - } else { - self.b.next() - } - } - - fn size_hint(&self) -> (usize, Option) { - // Not ExactSizeIterator because size may be larger than usize - size_hint::add(self.a.size_hint(), self.b.size_hint()) + InternalMergeJoinBy { + left: put_back(a.into_iter().fuse()), + right: put_back(b.into_iter().fuse()), + cmp_fn: cmp, } } -impl FusedIterator for MergeBy -where - I: FusedIterator, - J: FusedIterator, - F: MergePredicate, -{ -} - /// Return an iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// [`IntoIterator`] enabled version of [`Itertools::merge_join_by`]. @@ -269,6 +175,50 @@ impl bool> OrderingOrBool for MergeFuncLR bool> OrderingOrBool for F { + type Out = bool; + type MergeResult = T; + fn left(left: T) -> Self::MergeResult { + left + } + fn right(right: T) -> Self::MergeResult { + right + } + fn merge(&mut self, left: T, right: T) -> (Option, Option, Self::MergeResult) { + if self(&left, &right) { + (None, Some(right), left) + } else { + (Some(left), None, right) + } + } + fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { + // Not ExactSizeIterator because size may be larger than usize + size_hint::add(left, right) + } +} + +impl OrderingOrBool for MergeLte { + type Out = bool; + type MergeResult = T; + fn left(left: T) -> Self::MergeResult { + left + } + fn right(right: T) -> Self::MergeResult { + right + } + fn merge(&mut self, left: T, right: T) -> (Option, Option, Self::MergeResult) { + if left <= right { + (None, Some(right), left) + } else { + (Some(left), None, right) + } + } + fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { + // Not ExactSizeIterator because size may be larger than usize + size_hint::add(left, right) + } +} + impl Clone for InternalMergeJoinBy where I: Iterator, From 5f47287306aba5fd1df309ce975118a86c965473 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 24 Aug 2023 12:11:23 +0200 Subject: [PATCH 303/633] Implement `FusedIterator` for `InternalMergeJoinBy` `I` and `J` are fused by `InternalMergeJoinBy` so we don't need them to implement `FusedIterator`. --- src/merge_join.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 08c6dda67..6415b05c1 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; use std::fmt; -use std::iter::Fuse; +use std::iter::{Fuse, FusedIterator}; use std::marker::PhantomData; use either::Either; @@ -339,3 +339,11 @@ where } } } + +impl FusedIterator for InternalMergeJoinBy +where + I: Iterator, + J: Iterator, + F: OrderingOrBool, +{ +} From 506264e34614b3882990b6f2bcbd312fab576e21 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 31 Aug 2023 16:33:27 +0200 Subject: [PATCH 304/633] Unalias `MergeBy` --- src/merge_join.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 6415b05c1..848a5363c 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -51,7 +51,12 @@ where /// Iterator element type is `I::Item`. /// /// See [`.merge_by()`](crate::Itertools::merge_by) for more information. -pub type MergeBy = InternalMergeJoinBy; +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +pub struct MergeBy { + left: PutBack>, + right: PutBack>, + cmp_fn: F, +} /// Create a `MergeBy` iterator. pub fn merge_by_new(a: I, b: J, cmp: F) -> MergeBy @@ -59,7 +64,7 @@ where I: IntoIterator, J: IntoIterator, { - InternalMergeJoinBy { + MergeBy { left: put_back(a.into_iter().fuse()), right: put_back(b.into_iter().fuse()), cmp_fn: cmp, @@ -79,7 +84,7 @@ where J: IntoIterator, F: FnMut(&I::Item, &J::Item) -> T, { - InternalMergeJoinBy { + MergeBy { left: put_back(left.into_iter().fuse()), right: put_back(right.into_iter().fuse()), cmp_fn: MergeFuncLR(cmp_fn, PhantomData), @@ -89,18 +94,8 @@ where /// An iterator adaptor that merge-joins items from the two base iterators in ascending order. /// /// See [`.merge_join_by()`](crate::Itertools::merge_join_by) for more information. -pub type MergeJoinBy = InternalMergeJoinBy< - I, - J, - MergeFuncLR::Item, ::Item>>::T>, ->; - -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct InternalMergeJoinBy { - left: PutBack>, - right: PutBack>, - cmp_fn: F, -} +pub type MergeJoinBy = + MergeBy::Item, ::Item>>::T>>; #[derive(Clone, Debug)] pub struct MergeFuncLR(F, PhantomData); @@ -219,7 +214,7 @@ impl OrderingOrBool for MergeLte { } } -impl Clone for InternalMergeJoinBy +impl Clone for MergeBy where I: Iterator, J: Iterator, @@ -230,17 +225,17 @@ where clone_fields!(left, right, cmp_fn); } -impl fmt::Debug for InternalMergeJoinBy +impl fmt::Debug for MergeBy where I: Iterator + fmt::Debug, I::Item: fmt::Debug, J: Iterator + fmt::Debug, J::Item: fmt::Debug, { - debug_fmt_fields!(InternalMergeJoinBy, left, right); + debug_fmt_fields!(MergeBy, left, right); } -impl Iterator for InternalMergeJoinBy +impl Iterator for MergeBy where I: Iterator, J: Iterator, @@ -340,7 +335,7 @@ where } } -impl FusedIterator for InternalMergeJoinBy +impl FusedIterator for MergeBy where I: Iterator, J: Iterator, From 6ffdac103cf72dfd3b62a4de6dc25440b942e473 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 20 Sep 2023 16:50:42 +0200 Subject: [PATCH 305/633] Remove `OrderingOrBool::Out` Apparently not needed anymore. --- src/merge_join.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 848a5363c..c83159186 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -109,7 +109,6 @@ impl T> FuncLR for F { } pub trait OrderingOrBool { - type Out; type MergeResult; fn left(left: L) -> Self::MergeResult; fn right(right: R) -> Self::MergeResult; @@ -121,7 +120,6 @@ pub trait OrderingOrBool { } impl Ordering> OrderingOrBool for MergeFuncLR { - type Out = Ordering; type MergeResult = EitherOrBoth; fn left(left: L) -> Self::MergeResult { EitherOrBoth::Left(left) @@ -149,7 +147,6 @@ impl Ordering> OrderingOrBool for MergeFuncLR bool> OrderingOrBool for MergeFuncLR { - type Out = bool; type MergeResult = Either; fn left(left: L) -> Self::MergeResult { Either::Left(left) @@ -171,7 +168,6 @@ impl bool> OrderingOrBool for MergeFuncLR bool> OrderingOrBool for F { - type Out = bool; type MergeResult = T; fn left(left: T) -> Self::MergeResult { left @@ -193,7 +189,6 @@ impl bool> OrderingOrBool for F { } impl OrderingOrBool for MergeLte { - type Out = bool; type MergeResult = T; fn left(left: T) -> Self::MergeResult { left @@ -235,11 +230,11 @@ where debug_fmt_fields!(MergeBy, left, right); } -impl Iterator for MergeBy +impl Iterator for MergeBy where I: Iterator, J: Iterator, - F: OrderingOrBool, + F: OrderingOrBool, { type Item = F::MergeResult; @@ -335,10 +330,10 @@ where } } -impl FusedIterator for MergeBy +impl FusedIterator for MergeBy where I: Iterator, J: Iterator, - F: OrderingOrBool, + F: OrderingOrBool, { } From 5cb45fc4095befcb8c473eb7bc3dadd552548ce9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:15:47 +0200 Subject: [PATCH 306/633] `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 7c4893688a34ec0f8c3e65698e1708aeabcafca6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 12:41:30 +0200 Subject: [PATCH 307/633] `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 8bad6e85962e3ac42f5e560ec36570169189fcad Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:16:42 +0200 Subject: [PATCH 308/633] `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 3fe220b55536c4a9e389cf2a4fecf394542a7a8c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:22:43 +0200 Subject: [PATCH 309/633] `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 6d1a35a4a61e9eea905e8edb91c0a5cae32e0062 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 11:24:29 +0200 Subject: [PATCH 310/633] `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 c70f8057ba65b8623c7b107f2857268f24022091 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 311/633] `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) } } From a243c119131beffdb2d7a537bc8040cf6e6e17b9 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 22 Sep 2023 23:38:42 +0200 Subject: [PATCH 312/633] `Powerset::fold` Reset between `&mut Combinations::fold` calls. --- src/powerset.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/powerset.rs b/src/powerset.rs index 811cfce7f..d0eae99a3 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -74,6 +74,24 @@ where let (n, combs_count) = self.combs.n_and_count(); combs_count + remaining_for(n, k).unwrap() } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let mut it = self.combs; + if it.k() == 0 { + init = it.by_ref().fold(init, &mut f); + it.reset(1); + } + init = it.by_ref().fold(init, &mut f); + // n is now known for sure because k >= 1 and all k-combinations have been generated. + for k in it.k() + 1..=it.n() { + it.reset(k); + init = it.by_ref().fold(init, &mut f); + } + init + } } impl FusedIterator for Powerset From 1954eebcd763f200fd273d6e6b9fda711b7cc428 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 28 Sep 2023 19:11:27 +0200 Subject: [PATCH 313/633] Bench `Powerset::fold` --- benches/powerset.rs | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/benches/powerset.rs b/benches/powerset.rs index f5862a416..018333d31 100644 --- a/benches/powerset.rs +++ b/benches/powerset.rs @@ -20,6 +20,17 @@ fn powerset_n(c: &mut Criterion, n: usize) { }); } +fn powerset_n_fold(c: &mut Criterion, n: usize) { + let id = format!("powerset {} fold", n); + c.bench_function(id.as_str(), move |b| { + b.iter(|| { + for _ in 0..calc_iters(n) { + (0..n).powerset().fold(0, |s, elt| s + black_box(elt).len()); + } + }) + }); +} + fn powerset_0(c: &mut Criterion) { powerset_n(c, 0); } @@ -44,6 +55,30 @@ fn powerset_12(c: &mut Criterion) { powerset_n(c, 12); } +fn powerset_0_fold(c: &mut Criterion) { + powerset_n_fold(c, 0); +} + +fn powerset_1_fold(c: &mut Criterion) { + powerset_n_fold(c, 1); +} + +fn powerset_2_fold(c: &mut Criterion) { + powerset_n_fold(c, 2); +} + +fn powerset_4_fold(c: &mut Criterion) { + powerset_n_fold(c, 4); +} + +fn powerset_8_fold(c: &mut Criterion) { + powerset_n_fold(c, 8); +} + +fn powerset_12_fold(c: &mut Criterion) { + powerset_n_fold(c, 12); +} + criterion_group!( benches, powerset_0, @@ -52,5 +87,11 @@ criterion_group!( powerset_4, powerset_8, powerset_12, + powerset_0_fold, + powerset_1_fold, + powerset_2_fold, + powerset_4_fold, + powerset_8_fold, + powerset_12_fold, ); criterion_main!(benches); From d6d2a146be391407df89e29cec76aeba837e036b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 28 Sep 2023 21:14:20 +0200 Subject: [PATCH 314/633] Test powerset specializations Truncated to be fast enough. --- tests/specializations.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index efc8ba6ef..e9bfb5ccb 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -75,6 +75,12 @@ quickcheck! { fn intersperse(v: Vec) -> () { test_specializations(&v.into_iter().intersperse(0)); } + + fn powerset(a: Vec) -> () { + let mut a = a; + a.truncate(6); + test_specializations(&a.iter().powerset()) + } } quickcheck! { From 1dbe88629c87f6331cd7d0c60750c2437f132e4e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 15:06:38 +0200 Subject: [PATCH 315/633] Move `count_ident` macro --- src/impl_macros.rs | 5 +++++ src/tuple_impl.rs | 4 ---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/impl_macros.rs b/src/impl_macros.rs index 5fd78cb4d..7c0b0c22e 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -27,3 +27,8 @@ macro_rules! clone_fields { macro_rules! ignore_ident{ ($id:ident, $($t:tt)*) => {$($t)*}; } + +macro_rules! count_ident { + () => {0}; + ($i0:ident, $($i:ident,)*) => {1 + count_ident!($($i,)*)}; +} diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 1ce6a5d15..c01190e98 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -331,10 +331,6 @@ pub trait TupleCollect: Sized { fn left_shift_push(&mut self, item: Self::Item); } -macro_rules! count_ident{ - () => {0}; - ($i0:ident, $($i:ident,)*) => {1 + count_ident!($($i,)*)}; -} macro_rules! rev_for_each_ident{ ($m:ident, ) => {}; ($m:ident, $i0:ident, $($i:ident,)*) => { From ae5ac9fc3871d22f0c52ff79a54d816f719ee1d5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 16:25:17 +0200 Subject: [PATCH 316/633] `TupleCombinations::size_hint` `TupleCombinations` and `Tuple1Combination` merely wrap an iterator so I just call its `size_hint` method. `Tuple2Combination` chains a wrapped `Tuple1Combination` with a smaller `Tuple2Combination` but this one is not initialized yet so it is full and therefore has `binomial(n, k)` elements. Similar for `3..=12`, it's just recursive. --- src/adaptors/mod.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 4bc50eaa0..29aca5e10 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -14,7 +14,8 @@ pub use self::map::{map_into, map_ok, MapInto, MapOk}; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; -use crate::size_hint; +use crate::combinations::checked_binomial; +use crate::size_hint::{self, SizeHint}; use std::fmt; use std::iter::{FromIterator, Fuse, FusedIterator}; use std::marker::PhantomData; @@ -626,6 +627,10 @@ where fn next(&mut self) -> Option { self.iter.next() } + + fn size_hint(&self) -> SizeHint { + self.iter.size_hint() + } } impl FusedIterator for TupleCombinations @@ -652,6 +657,10 @@ impl Iterator for Tuple1Combination { fn next(&mut self) -> Option { self.iter.next().map(|x| (x,)) } + + fn size_hint(&self) -> SizeHint { + self.iter.size_hint() + } } impl HasCombination for (I::Item,) { @@ -701,6 +710,14 @@ macro_rules! impl_tuple_combination { }) } } + + fn size_hint(&self) -> SizeHint { + const K: usize = 1 + count_ident!($($X,)*); + let (mut n_min, mut n_max) = self.iter.size_hint(); + n_min = checked_binomial(n_min, K).unwrap_or(usize::MAX); + n_max = n_max.and_then(|n| checked_binomial(n, K)); + size_hint::add(self.c.size_hint(), (n_min, n_max)) + } } impl HasCombination for (A, $(ignore_ident!($X, A)),*) From e905e1238666e0a07b23cecdfc4dce9e1071c0b2 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 16:34:55 +0200 Subject: [PATCH 317/633] `TupleCombinations::count` Same as `TupleCombinations::size_hint` but simpler. --- src/adaptors/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 29aca5e10..8765c57c1 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -631,6 +631,10 @@ where fn size_hint(&self) -> SizeHint { self.iter.size_hint() } + + fn count(self) -> usize { + self.iter.count() + } } impl FusedIterator for TupleCombinations @@ -661,6 +665,10 @@ impl Iterator for Tuple1Combination { fn size_hint(&self) -> SizeHint { self.iter.size_hint() } + + fn count(self) -> usize { + self.iter.count() + } } impl HasCombination for (I::Item,) { @@ -718,6 +726,12 @@ macro_rules! impl_tuple_combination { n_max = n_max.and_then(|n| checked_binomial(n, K)); size_hint::add(self.c.size_hint(), (n_min, n_max)) } + + fn count(self) -> usize { + const K: usize = 1 + count_ident!($($X,)*); + let n = self.iter.count(); + checked_binomial(n, K).unwrap() + self.c.count() + } } impl HasCombination for (A, $(ignore_ident!($X, A)),*) From 154d15e2fa336dc03d74ce27958cbb6e431434d3 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 18:34:27 +0200 Subject: [PATCH 318/633] Test size of `tuple_combinations` --- tests/quick.rs | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index c7791545b..5729cfaf8 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -901,8 +901,31 @@ quickcheck! { } quickcheck! { - fn size_combinations(it: Iter) -> bool { - correct_size_hint(it.tuple_combinations::<(_, _)>()) + fn size_combinations(a: Iter) -> bool { + let it = a.clone().tuple_combinations::<(_, _)>(); + correct_size_hint(it.clone()) && it.count() == binomial(a.count(), 2) + } + + fn exact_size_combinations_1(a: Vec) -> bool { + let it = a.iter().tuple_combinations::<(_,)>(); + exact_size_for_this(it.clone()) && it.count() == binomial(a.len(), 1) + } + fn exact_size_combinations_2(a: Vec) -> bool { + let it = a.iter().tuple_combinations::<(_, _)>(); + exact_size_for_this(it.clone()) && it.count() == binomial(a.len(), 2) + } + fn exact_size_combinations_3(mut a: Vec) -> bool { + a.truncate(15); + let it = a.iter().tuple_combinations::<(_, _, _)>(); + exact_size_for_this(it.clone()) && it.count() == binomial(a.len(), 3) + } +} + +fn binomial(n: usize, k: usize) -> usize { + if k > n { + 0 + } else { + (n - k + 1..=n).product::() / (1..=k).product::() } } From 99b35f5cfeebec2680c2e409d505d9e364750cb0 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 27 Sep 2023 19:17:39 +0200 Subject: [PATCH 319/633] Move `checked_binomial` Just cut/paste, and fixed imports. It is moved because it is now needed even without heap-allocation. --- src/adaptors/mod.rs | 37 ++++++++++++++++++++++++++- src/combinations.rs | 38 ++-------------------------- src/combinations_with_replacement.rs | 2 +- src/powerset.rs | 3 ++- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 8765c57c1..90cf46eed 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -14,7 +14,6 @@ pub use self::map::{map_into, map_ok, MapInto, MapOk}; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; -use crate::combinations::checked_binomial; use crate::size_hint::{self, SizeHint}; use std::fmt; use std::iter::{FromIterator, Fuse, FusedIterator}; @@ -767,6 +766,42 @@ impl_tuple_combination!(Tuple10Combination Tuple9Combination; a b c d e f g h i) impl_tuple_combination!(Tuple11Combination Tuple10Combination; a b c d e f g h i j); impl_tuple_combination!(Tuple12Combination Tuple11Combination; a b c d e f g h i j k); +// https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages +pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { + if n < k { + return Some(0); + } + // `factorial(n) / factorial(n - k) / factorial(k)` but trying to avoid it overflows: + k = (n - k).min(k); // symmetry + let mut c = 1; + for i in 1..=k { + c = (c / i) + .checked_mul(n)? + .checked_add((c % i).checked_mul(n)? / i)?; + n -= 1; + } + Some(c) +} + +#[test] +fn test_checked_binomial() { + // With the first row: [1, 0, 0, ...] and the first column full of 1s, we check + // row by row the recurrence relation of binomials (which is an equivalent definition). + // For n >= 1 and k >= 1 we have: + // binomial(n, k) == binomial(n - 1, k - 1) + binomial(n - 1, k) + const LIMIT: usize = 500; + let mut row = vec![Some(0); LIMIT + 1]; + row[0] = Some(1); + for n in 0..=LIMIT { + for k in 0..=LIMIT { + assert_eq!(row[k], checked_binomial(n, k)); + } + row = std::iter::once(Some(1)) + .chain((1..=LIMIT).map(|k| row[k - 1]?.checked_add(row[k]?))) + .collect(); + } +} + /// An iterator adapter to filter values within a nested `Result::Ok`. /// /// See [`.filter_ok()`](crate::Itertools::filter_ok) for more information. diff --git a/src/combinations.rs b/src/combinations.rs index 332bcb3e2..1c834a74e 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -4,6 +4,8 @@ use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; use alloc::vec::Vec; +use crate::adaptors::checked_binomial; + /// An iterator to iterate through all the `k`-length combinations in an iterator. /// /// See [`.combinations()`](crate::Itertools::combinations) for more information. @@ -160,42 +162,6 @@ where { } -// https://en.wikipedia.org/wiki/Binomial_coefficient#In_programming_languages -pub(crate) fn checked_binomial(mut n: usize, mut k: usize) -> Option { - if n < k { - return Some(0); - } - // `factorial(n) / factorial(n - k) / factorial(k)` but trying to avoid it overflows: - k = (n - k).min(k); // symmetry - let mut c = 1; - for i in 1..=k { - c = (c / i) - .checked_mul(n)? - .checked_add((c % i).checked_mul(n)? / i)?; - n -= 1; - } - Some(c) -} - -#[test] -fn test_checked_binomial() { - // With the first row: [1, 0, 0, ...] and the first column full of 1s, we check - // row by row the recurrence relation of binomials (which is an equivalent definition). - // For n >= 1 and k >= 1 we have: - // binomial(n, k) == binomial(n - 1, k - 1) + binomial(n - 1, k) - const LIMIT: usize = 500; - let mut row = vec![Some(0); LIMIT + 1]; - row[0] = Some(1); - for n in 0..=LIMIT { - for k in 0..=LIMIT { - assert_eq!(row[k], checked_binomial(n, k)); - } - row = std::iter::once(Some(1)) - .chain((1..=LIMIT).map(|k| row[k - 1]?.checked_add(row[k]?))) - .collect(); - } -} - /// For a given size `n`, return the count of remaining combinations or None if it would overflow. fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index e4f1cda13..f1a0bc07d 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -3,7 +3,7 @@ use std::fmt; use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; -use crate::combinations::checked_binomial; +use crate::adaptors::checked_binomial; /// An iterator to iterate through all the `n`-length combinations in an iterator, with replacement. /// diff --git a/src/powerset.rs b/src/powerset.rs index d0eae99a3..9a7131a3e 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -3,7 +3,8 @@ use std::fmt; use std::iter::FusedIterator; use std::usize; -use super::combinations::{checked_binomial, combinations, Combinations}; +use super::combinations::{combinations, Combinations}; +use crate::adaptors::checked_binomial; use crate::size_hint::{self, SizeHint}; /// An iterator to iterate through the powerset of the elements from an iterator. From a9aaeb4cbacee3f4e83df56b3bb45d35d9167fa9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 28 Sep 2023 21:57:35 +0200 Subject: [PATCH 320/633] Test `tuple_combinations` specializations --- tests/specializations.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index e9bfb5ccb..7c21da66a 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -72,6 +72,12 @@ where } quickcheck! { + fn tuple_combinations(v: Vec) -> () { + let mut v = v; + v.truncate(10); + test_specializations(&v.iter().tuple_combinations::<(_, _, _)>()); + } + fn intersperse(v: Vec) -> () { test_specializations(&v.into_iter().intersperse(0)); } From 3de6d245b09d1c11e3dfd5a280a665e734039b8d Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Sat, 23 Sep 2023 11:08:46 +0200 Subject: [PATCH 321/633] `try_collect` without `use_alloc` We can want to collect to a stack-based structure such as `arrayvec`. --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 458ffe67c..77bc2aad2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,7 +70,6 @@ use std::hash::Hash; use std::iter::{once, IntoIterator}; #[cfg(feature = "use_alloc")] type VecIntoIter = alloc::vec::IntoIter; -#[cfg(feature = "use_alloc")] use std::iter::FromIterator; #[macro_use] @@ -2214,7 +2213,6 @@ pub trait Itertools: Iterator { /// Ok(()) /// } /// ``` - #[cfg(feature = "use_alloc")] fn try_collect(self) -> Result where Self: Sized + Iterator>, From 2ec463c7c6aa8abe159850f89712cc9e650b34a7 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Sat, 23 Sep 2023 12:16:51 +0200 Subject: [PATCH 322/633] `extrema_set` only needs `alloc` --- src/extrema_set.rs | 2 ++ src/lib.rs | 14 +++++++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index ae128364c..d24114c6d 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "use_alloc")] +use alloc::{vec, vec::Vec}; use std::cmp::Ordering; /// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`. diff --git a/src/lib.rs b/src/lib.rs index 77bc2aad2..3d5f05c4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -177,7 +177,7 @@ mod diff; #[cfg(feature = "use_std")] mod duplicates_impl; mod exactly_one_err; -#[cfg(feature = "use_std")] +#[cfg(feature = "use_alloc")] mod extrema_set; mod flatten_ok; mod format; @@ -3169,7 +3169,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn min_set(self) -> Vec where Self: Sized, @@ -3202,7 +3202,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn min_set_by(self, mut compare: F) -> Vec where Self: Sized, @@ -3234,7 +3234,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn min_set_by_key(self, key: F) -> Vec where Self: Sized, @@ -3266,7 +3266,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn max_set(self) -> Vec where Self: Sized, @@ -3299,7 +3299,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn max_set_by(self, mut compare: F) -> Vec where Self: Sized, @@ -3331,7 +3331,7 @@ pub trait Itertools: Iterator { /// /// The elements can be floats but no particular result is guaranteed /// if an element is NaN. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn max_set_by_key(self, key: F) -> Vec where Self: Sized, From 362d203f05214745edf3ef29f1db1eaaec13f4cd Mon Sep 17 00:00:00 2001 From: warren2k <846021+warren2k@users.noreply.github.com> Date: Sun, 30 Jul 2023 10:44:56 -0400 Subject: [PATCH 323/633] Disallow compile warnings in CI Co-authored-by: Shahar "Dawn" Or --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c412fc835..d90274ea4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: cargo check ${{ matrix.features }} + - run: RUSTFLAGS="--deny warnings" cargo check ${{ matrix.features }} msrv: runs-on: ubuntu-latest From f027c36b69a187f26299bf6361c477b8af62ee5b Mon Sep 17 00:00:00 2001 From: "Shahar \"Dawn\" Or" Date: Sat, 30 Sep 2023 21:59:47 +0700 Subject: [PATCH 324/633] build: resolve warnings Co-authored-by: warren2k <846021+warren2k@users.noreply.github.com> --- benches/fold_specialization.rs | 2 ++ benches/tree_fold1.rs | 2 ++ src/size_hint.rs | 1 + src/tuple_impl.rs | 2 +- tests/quick.rs | 2 ++ tests/specializations.rs | 2 ++ tests/test_core.rs | 1 + tests/test_std.rs | 2 ++ 8 files changed, 13 insertions(+), 1 deletion(-) diff --git a/benches/fold_specialization.rs b/benches/fold_specialization.rs index ef7f47c43..3a040305b 100644 --- a/benches/fold_specialization.rs +++ b/benches/fold_specialization.rs @@ -1,3 +1,5 @@ +#![allow(unstable_name_collisions)] + use criterion::{criterion_group, criterion_main, Criterion}; use itertools::Itertools; diff --git a/benches/tree_fold1.rs b/benches/tree_fold1.rs index 77af505f1..5b76d5404 100644 --- a/benches/tree_fold1.rs +++ b/benches/tree_fold1.rs @@ -1,3 +1,5 @@ +#![allow(deprecated)] + use criterion::{criterion_group, criterion_main, Criterion}; use itertools::{cloned, Itertools}; diff --git a/src/size_hint.rs b/src/size_hint.rs index 857e0c4c6..d592ca925 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -29,6 +29,7 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint { } /// Subtract `x` correctly from a `SizeHint`. +#[cfg(feature = "use_alloc")] #[inline] pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index c01190e98..c0b39d79d 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -111,7 +111,7 @@ where // 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(); + let (unbuffered_lo, 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. diff --git a/tests/quick.rs b/tests/quick.rs index 5729cfaf8..267640fe4 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -3,6 +3,8 @@ //! //! In particular we test the tedious size_hint and exact size correctness. +#![allow(deprecated, unstable_name_collisions)] + use itertools::free::{ cloned, enumerate, multipeek, peek_nth, put_back, put_back_n, rciter, zip, zip_eq, }; diff --git a/tests/specializations.rs b/tests/specializations.rs index 7c21da66a..85b46e48b 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -1,3 +1,5 @@ +#![allow(unstable_name_collisions)] + use itertools::Itertools; use quickcheck::quickcheck; use std::fmt::Debug; diff --git a/tests/test_core.rs b/tests/test_core.rs index 729d7c3b8..c624a7e33 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -4,6 +4,7 @@ //! option. This file may not be copied, modified, or distributed //! except according to those terms. #![no_std] +#![allow(deprecated)] use crate::it::chain; use crate::it::free::put_back; diff --git a/tests/test_std.rs b/tests/test_std.rs index 900d7174e..3ef90f39d 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1,3 +1,5 @@ +#![allow(unstable_name_collisions)] + use crate::it::cloned; use crate::it::free::put_back_n; use crate::it::free::rciter; From c4358ab3360c7e0036098a9ab60d05237bf5dac2 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 2 Oct 2023 11:36:06 -0400 Subject: [PATCH 325/633] Configure CI for merge_queue. Fixes #742. See https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue#triggering-merge-group-checks-with-github-actions --- .github/workflows/ci.yml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d90274ea4..83deb9e40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,7 @@ name: CI on: pull_request: - push: - branches: - - staging - - trying + merge_group: jobs: check: @@ -52,12 +49,12 @@ jobs: components: rustfmt - run: cargo fmt --check - # https://github.com/rust-lang/crater/blob/9ab6f9697c901c4a44025cf0a39b73ad5b37d198/.github/workflows/bors.yml#L125-L149 - end-success: - name: bors build finished + # Used to signal to branch protections that all other jobs have succeeded. + all-jobs-succeed: + name: All checks succeeded if: success() runs-on: ubuntu-latest - needs: [check, test] + needs: [check, msrv, test, check-format] steps: - name: Mark the job as successful run: exit 0 From 0897f56ad6a305b9686b0a4447344bc8c7e5c580 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 30 Sep 2023 16:59:18 +0200 Subject: [PATCH 326/633] Fix "bench1" in `debug` mode Benches use the `release` profile so this was silently ignored. But `cargo test --all-targets` uses the `debug` profile so it does not ignore it. --- benches/bench1.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 084567177..4f6e1963e 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -581,7 +581,7 @@ fn kmerge_tenway(c: &mut Criterion) { let mut state = 1729u16; fn rng(state: &mut u16) -> u16 { - let new = state.wrapping_mul(31421) + 6927; + let new = state.wrapping_mul(31421).wrapping_add(6927); *state = new; new } From e01c92920882221b348de0d807d033aebe2aa5c5 Mon Sep 17 00:00:00 2001 From: Mike Hommey Date: Fri, 28 Jul 2023 13:52:50 +0900 Subject: [PATCH 327/633] Allow to use EitherOrBoth to mean the same as EitherOrBoth --- src/either_or_both.rs | 2 +- tests/merge_join.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index d39747eaa..9dbc880d3 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -6,7 +6,7 @@ use either::Either; /// Value that either holds a single A or B, or both. #[derive(Clone, PartialEq, Eq, Hash, Debug)] -pub enum EitherOrBoth { +pub enum EitherOrBoth { /// Both values are present. Both(A, B), /// Only the left value of type `A` is present. diff --git a/tests/merge_join.rs b/tests/merge_join.rs index 870e1faff..776252fc5 100644 --- a/tests/merge_join.rs +++ b/tests/merge_join.rs @@ -5,7 +5,7 @@ use itertools::EitherOrBoth; fn empty() { let left: Vec = vec![]; let right: Vec = vec![]; - let expected_result: Vec> = vec![]; + let expected_result: Vec> = vec![]; let actual_result = merge_join_by(left, right, |l, r| l.cmp(r)).collect::>(); assert_eq!(expected_result, actual_result); } @@ -14,7 +14,7 @@ fn empty() { fn left_only() { let left: Vec = vec![1, 2, 3]; let right: Vec = vec![]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Left(2), EitherOrBoth::Left(3), @@ -27,7 +27,7 @@ fn left_only() { fn right_only() { let left: Vec = vec![]; let right: Vec = vec![1, 2, 3]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Right(1), EitherOrBoth::Right(2), EitherOrBoth::Right(3), @@ -40,7 +40,7 @@ fn right_only() { fn first_left_then_right() { let left: Vec = vec![1, 2, 3]; let right: Vec = vec![4, 5, 6]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Left(2), EitherOrBoth::Left(3), @@ -56,7 +56,7 @@ fn first_left_then_right() { fn first_right_then_left() { let left: Vec = vec![4, 5, 6]; let right: Vec = vec![1, 2, 3]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Right(1), EitherOrBoth::Right(2), EitherOrBoth::Right(3), @@ -72,7 +72,7 @@ fn first_right_then_left() { fn interspersed_left_and_right() { let left: Vec = vec![1, 3, 5]; let right: Vec = vec![2, 4, 6]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Right(2), EitherOrBoth::Left(3), @@ -88,7 +88,7 @@ fn interspersed_left_and_right() { fn overlapping_left_and_right() { let left: Vec = vec![1, 3, 4, 6]; let right: Vec = vec![2, 3, 4, 5]; - let expected_result: Vec> = vec![ + let expected_result: Vec> = vec![ EitherOrBoth::Left(1), EitherOrBoth::Right(2), EitherOrBoth::Both(3, 3), From af9d9328d07374580cb249f44ceac350900366f8 Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Thu, 28 Sep 2023 20:50:10 +0800 Subject: [PATCH 328/633] Implement custom fold for ZipLongest Add custom fold logic and benchmark --- benches/bench1.rs | 20 +++++++++++++++++++- src/zip_longest.rs | 25 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 4f6e1963e..7cef765a1 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,6 +1,6 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use itertools::free::cloned; -use itertools::iproduct; +use itertools::{EitherOrBoth, iproduct}; use itertools::Itertools; use std::cmp; @@ -390,6 +390,23 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) { }); } +fn ziplongest(c: &mut Criterion) { + c.bench_function("ziplongest", move |b| { + b.iter(|| { + let zip = (0..768).zip_longest(0..1024); + let sum = zip.fold(0u32, |mut acc, val| { + match val { + EitherOrBoth::Both(x, y) => acc += x * y, + EitherOrBoth::Left(x) => acc += x, + EitherOrBoth::Right(y) => acc += y, + } + acc + }); + sum + }) + }); +} + fn group_by_lazy_1(c: &mut Criterion) { let mut data = vec![0; 1024]; for (index, elt) in data.iter_mut().enumerate() { @@ -821,6 +838,7 @@ criterion_group!( zipdot_i32_unchecked_counted_loop, zipdot_f32_unchecked_counted_loop, zip_unchecked_counted_loop3, + ziplongest, group_by_lazy_1, group_by_lazy_2, slice_chunks, diff --git a/src/zip_longest.rs b/src/zip_longest.rs index 98ce4e63e..e4e4f90a4 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -52,6 +52,31 @@ where fn size_hint(&self) -> (usize, Option) { size_hint::max(self.a.size_hint(), self.b.size_hint()) } + + #[inline] + fn fold(self, mut acc: B, mut f: F) -> B + where + Self: Sized, F: FnMut(B, Self::Item) -> B + { + let ZipLongest { mut a, mut b } = self; + + loop { + match (a.next(), b.next()) { + (Some(x), Some(y)) => acc = f(acc, EitherOrBoth::Both(x, y)), + (Some(x), None) => { + acc = f(acc, EitherOrBoth::Left(x)); + // b is exhausted, so we can drain a. + return a.fold(acc, |acc, x| f(acc, EitherOrBoth::Left(x))); + } + (None, Some(y)) => { + acc = f(acc, EitherOrBoth::Right(y)); + // a is exhausted, so we can drain b. + return b.fold(acc, |acc, y| f(acc, EitherOrBoth::Right(y))); + } + (None, None) => return acc, // Both iterators are exhausted. + } + } + } } impl DoubleEndedIterator for ZipLongest From 28606e8b0ba66ae54506c1a600c355ae5122aa5d Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Thu, 28 Sep 2023 22:40:44 +0800 Subject: [PATCH 329/633] Run cargo fmt to fix the CI error --- benches/bench1.rs | 2 +- src/zip_longest.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 7cef765a1..4dd1395dc 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,7 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use itertools::free::cloned; -use itertools::{EitherOrBoth, iproduct}; use itertools::Itertools; +use itertools::{iproduct, EitherOrBoth}; use std::cmp; use std::iter::repeat; diff --git a/src/zip_longest.rs b/src/zip_longest.rs index e4e4f90a4..d89e6edef 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -56,7 +56,8 @@ where #[inline] fn fold(self, mut acc: B, mut f: F) -> B where - Self: Sized, F: FnMut(B, Self::Item) -> B + Self: Sized, + F: FnMut(B, Self::Item) -> B, { let ZipLongest { mut a, mut b } = self; From c177dba235f793324151ee8231f4d9227cc73d7e Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Fri, 29 Sep 2023 11:29:29 +0800 Subject: [PATCH 330/633] Add ziplongest to specializations.rs --- tests/specializations.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index 85b46e48b..f4463a1b2 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -89,6 +89,10 @@ quickcheck! { a.truncate(6); test_specializations(&a.iter().powerset()) } + + fn zip_longest(a: Vec, b: Vec) -> () { + test_specializations(&a.into_iter().zip_longest(b)) + } } quickcheck! { From b014b0665a448170b1edcf2517466a7ce462f04b Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Fri, 29 Sep 2023 22:48:26 +0800 Subject: [PATCH 331/633] Revise benchmark test, and optimize fold logic to avoid use of for loop --- benches/bench1.rs | 4 +++- src/zip_longest.rs | 26 +++++++------------------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 4dd1395dc..8a384ee69 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -391,9 +391,11 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) { } fn ziplongest(c: &mut Criterion) { + let v1 = black_box((0..768).collect_vec()); + let v2 = black_box((0..1024).collect_vec()); c.bench_function("ziplongest", move |b| { b.iter(|| { - let zip = (0..768).zip_longest(0..1024); + let zip = v1.iter().zip_longest(v2.iter()); let sum = zip.fold(0u32, |mut acc, val| { match val { EitherOrBoth::Both(x, y) => acc += x * y, diff --git a/src/zip_longest.rs b/src/zip_longest.rs index d89e6edef..27d9f3ab6 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -54,29 +54,17 @@ where } #[inline] - fn fold(self, mut acc: B, mut f: F) -> B + fn fold(self, mut init: B, mut f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { - let ZipLongest { mut a, mut b } = self; - - loop { - match (a.next(), b.next()) { - (Some(x), Some(y)) => acc = f(acc, EitherOrBoth::Both(x, y)), - (Some(x), None) => { - acc = f(acc, EitherOrBoth::Left(x)); - // b is exhausted, so we can drain a. - return a.fold(acc, |acc, x| f(acc, EitherOrBoth::Left(x))); - } - (None, Some(y)) => { - acc = f(acc, EitherOrBoth::Right(y)); - // a is exhausted, so we can drain b. - return b.fold(acc, |acc, y| f(acc, EitherOrBoth::Right(y))); - } - (None, None) => return acc, // Both iterators are exhausted. - } - } + let ZipLongest { a, mut b } = self; + init = a.fold(init, |init, a| match b.next() { + Some(b) => f(init, EitherOrBoth::Both(a, b)), + None => f(init, EitherOrBoth::Left(a)), + }); + b.fold(init, |init, b| f(init, EitherOrBoth::Right(b))) } } From a1a3374faf1d61342879f2024abdd24927ff9b37 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 2 Oct 2023 11:28:34 +0200 Subject: [PATCH 332/633] impl `Eq` for `Position` I do not see any reason not to and I need this for the next commit. --- src/with_position.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/with_position.rs b/src/with_position.rs index 9cdc8319e..58802c5ff 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -37,7 +37,7 @@ where /// Indicates the position of this element in the iterator results. /// /// See [`.with_position()`](crate::Itertools::with_position) for more information. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Position { /// This is the first element. First, From 90b26c18c3dd203ad1dd71dada5114ab2051fdc6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 2 Oct 2023 11:30:35 +0200 Subject: [PATCH 333/633] Test `WithPosition` specializations --- tests/specializations.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index f4463a1b2..4fa6c33df 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -74,6 +74,10 @@ where } quickcheck! { + fn with_position(v: Vec) -> () { + test_specializations(&v.iter().with_position()); + } + fn tuple_combinations(v: Vec) -> () { let mut v = v; v.truncate(10); From 9b2818031a7dee287d0ed898ff51971fd18c7fb7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 2 Oct 2023 12:08:21 +0200 Subject: [PATCH 334/633] Benchmark `WithPosition::fold` --- benches/bench1.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/benches/bench1.rs b/benches/bench1.rs index 8a384ee69..fbcaa05a7 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,6 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use itertools::free::cloned; use itertools::Itertools; +use itertools::Position; use itertools::{iproduct, EitherOrBoth}; use std::cmp; @@ -819,6 +820,22 @@ fn permutations_slice(c: &mut Criterion) { }); } +fn with_position_fold(c: &mut Criterion) { + let v = black_box((0..10240).collect_vec()); + c.bench_function("with_position fold", move |b| { + b.iter(|| { + v.iter() + .with_position() + .fold(0, |acc, (pos, &item)| match pos { + Position::Middle => acc + item, + Position::First => acc - 2 * item, + Position::Last => acc + 2 * item, + Position::Only => acc + 5 * item, + }) + }) + }); +} + criterion_group!( benches, slice_iter, @@ -866,5 +883,6 @@ criterion_group!( permutations_iter, permutations_range, permutations_slice, + with_position_fold, ); criterion_main!(benches); From b8a77c43b3606e3a04174d0a7599e59d5d39c8a4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 2 Oct 2023 11:10:24 +0200 Subject: [PATCH 335/633] `WithPosition::fold` --- src/with_position.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/with_position.rs b/src/with_position.rs index 58802c5ff..89cddeb8a 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -81,6 +81,33 @@ impl Iterator for WithPosition { fn size_hint(&self) -> (usize, Option) { self.peekable.size_hint() } + + fn fold(mut self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + if let Some(mut head) = self.peekable.next() { + if !self.handled_first { + // The current head is `First` or `Only`, + // it depends if there is another item or not. + match self.peekable.next() { + Some(second) => { + let first = std::mem::replace(&mut head, second); + init = f(init, (Position::First, first)); + } + None => return f(init, (Position::Only, head)), + } + } + // Have seen the first item, and there's something left. + init = self.peekable.fold(init, |acc, mut item| { + std::mem::swap(&mut head, &mut item); + f(acc, (Position::Middle, item)) + }); + // The "head" is now the last item. + init = f(init, (Position::Last, head)); + } + init + } } impl ExactSizeIterator for WithPosition where I: ExactSizeIterator {} From a68c17a40858489256ab8398384cff2fbf7d40ab Mon Sep 17 00:00:00 2001 From: aobatact Date: Tue, 8 Feb 2022 00:58:48 +0900 Subject: [PATCH 336/633] Fix tuple_windows as lazy --- src/tuple_impl.rs | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index c0b39d79d..a902f2553 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -167,30 +167,21 @@ where I: Iterator, T: HomogeneousTuple, { - iter: I, + iter: Fuse, last: Option, } /// Create a new tuple windows iterator. -pub fn tuple_windows(mut iter: I) -> TupleWindows +pub fn tuple_windows(iter: I) -> TupleWindows where I: Iterator, T: HomogeneousTuple, T::Item: Clone, { - use std::iter::once; - - let mut last = None; - if T::num_items() != 1 { - // put in a duplicate item in front of the tuple; this simplifies - // .next() function. - if let Some(item) = iter.next() { - let iter = once(item.clone()).chain(once(item)).chain(&mut iter); - last = T::collect_from_iter_no_buf(iter); - } + TupleWindows { + last : None, + iter : iter.fuse(), } - - TupleWindows { iter, last } } impl Iterator for TupleWindows @@ -210,6 +201,13 @@ where last.left_shift_push(new); return Some(last.clone()); } + } else { + use std::iter::once; + if let Some(item) = self.iter.next() { + let iter = once(item).chain(&mut self.iter); + self.last = T::collect_from_iter_no_buf(iter); + return self.last.clone(); + } } None } From 02d5a69c6bf766ee681d1fe67c278504eff436b7 Mon Sep 17 00:00:00 2001 From: aobatact Date: Fri, 11 Feb 2022 11:36:43 +0900 Subject: [PATCH 337/633] Fix remove use of fuse at tuple_windows --- src/tuple_impl.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index a902f2553..df1842065 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -167,7 +167,7 @@ where I: Iterator, T: HomogeneousTuple, { - iter: Fuse, + iter: I, last: Option, } @@ -180,7 +180,7 @@ where { TupleWindows { last : None, - iter : iter.fuse(), + iter, } } @@ -196,20 +196,19 @@ where if T::num_items() == 1 { return T::collect_from_iter_no_buf(&mut self.iter); } - if let Some(ref mut last) = self.last { - if let Some(new) = self.iter.next() { + if let Some(new) = self.iter.next() { + if let Some(ref mut last) = self.last { last.left_shift_push(new); - return Some(last.clone()); - } - } else { - use std::iter::once; - if let Some(item) = self.iter.next() { - let iter = once(item).chain(&mut self.iter); + Some(last.clone()) + } else { + use std::iter::once; + let iter = once(new).chain(&mut self.iter); self.last = T::collect_from_iter_no_buf(iter); - return self.last.clone(); + self.last.clone() } + } else { + None } - None } fn size_hint(&self) -> (usize, Option) { From 255ca84680e34db6b31527a607aff973ccd0af4a Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 3 Oct 2023 19:30:16 +0000 Subject: [PATCH 338/633] rustfmt --- src/tuple_impl.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index df1842065..9769300a7 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -178,10 +178,7 @@ where T: HomogeneousTuple, T::Item: Clone, { - TupleWindows { - last : None, - iter, - } + TupleWindows { last: None, iter } } impl Iterator for TupleWindows From 8db450f927a87c0594da0ca76be7d15104428c08 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 6 Oct 2023 15:46:59 +0200 Subject: [PATCH 339/633] Adjust `TupleWindows::size_hint` Now that `TupleWindows` is lazy, we must adjust the size hint accordingly. --- src/size_hint.rs | 1 - src/tuple_impl.rs | 12 +++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/size_hint.rs b/src/size_hint.rs index d592ca925..857e0c4c6 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -29,7 +29,6 @@ pub fn add_scalar(sh: SizeHint, x: usize) -> SizeHint { } /// Subtract `x` correctly from a `SizeHint`. -#[cfg(feature = "use_alloc")] #[inline] pub fn sub_scalar(sh: SizeHint, x: usize) -> SizeHint { let (mut low, mut hi) = sh; diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 9769300a7..688df448e 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -5,6 +5,8 @@ use std::iter::Fuse; use std::iter::FusedIterator; use std::marker::PhantomData; +use crate::size_hint; + // `HomogeneousTuple` is a public facade for `TupleCollect`, allowing // tuple-related methods to be used by clients in generic contexts, while // hiding the implementation details of `TupleCollect`. @@ -209,9 +211,13 @@ where } fn size_hint(&self) -> (usize, Option) { - // At definition, `T::num_items() - 1` are collected - // so each remaining item in `iter` will lead to an item. - self.iter.size_hint() + let mut sh = self.iter.size_hint(); + // Adjust the size hint at the beginning + // OR when `num_items == 1` (but it does not change the size hint). + if self.last.is_none() { + sh = size_hint::sub_scalar(sh, T::num_items() - 1); + } + sh } } From 4d2bd4e0e98bbc9d506b83f6357787ad037ab12e Mon Sep 17 00:00:00 2001 From: Tristan F Date: Thu, 10 Aug 2023 14:59:09 -0400 Subject: [PATCH 340/633] docs: mention permute with replacement --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3d5f05c4f..c8422f531 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1676,6 +1676,9 @@ pub trait Itertools: Iterator { /// If `k` is greater than the length of the input iterator, the resultant /// iterator adaptor will be empty. /// + /// If you are looking for permutations with replacements, + /// use `repeat_n(iter, k).multi_cartesian_product()` instead. + /// /// ``` /// use itertools::Itertools; /// From 87b41a5d2dafdaefb790cc62cb0b2d57eb960db2 Mon Sep 17 00:00:00 2001 From: David Gilhooley Date: Fri, 6 Oct 2023 11:16:11 -0400 Subject: [PATCH 341/633] Add Clone trait to Unique Using a Unique iterator requires the Clone trait, so require this trait when creating the Unique object. --- src/unique_impl.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index a0b46b785..9b103a075 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -165,14 +165,18 @@ where /// See [`.unique()`](crate::Itertools::unique) for more information. #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct Unique { +pub struct Unique +where + I: Iterator, + I::Item: Eq + Hash + Clone, +{ iter: UniqueBy, } impl fmt::Debug for Unique where I: Iterator + fmt::Debug, - I::Item: Hash + Eq + fmt::Debug, + I::Item: Hash + Eq + fmt::Debug + Clone, { debug_fmt_fields!(Unique, iter); } @@ -180,7 +184,7 @@ where pub fn unique(iter: I) -> Unique where I: Iterator, - I::Item: Eq + Hash, + I::Item: Eq + Hash + Clone, { Unique { iter: UniqueBy { From e541ebd4e3a7ca0078dc117b21088dddbec6cbdc Mon Sep 17 00:00:00 2001 From: Zachary S Date: Tue, 25 Jul 2023 23:11:49 -0500 Subject: [PATCH 342/633] Add `peek_mut` and `peek_nth_mut` to `PeekNth` which mirror `std::iter::Peekable::peek_mut`. --- src/peek_nth.rs | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/peek_nth.rs b/src/peek_nth.rs index bcca45838..9dddb8f01 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -39,6 +39,11 @@ where self.peek_nth(0) } + /// Works exactly like the `peek_mut` method in `std::iter::Peekable` + pub fn peek_mut(&mut self) -> Option<&mut I::Item> { + self.peek_nth_mut(0) + } + /// Returns a reference to the `nth` value without advancing the iterator. /// /// # Examples @@ -69,6 +74,47 @@ where self.buf.get(n) } + + /// Returns a mutable reference to the `nth` value without advancing the iterator. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ``` + /// use itertools::peek_nth; + /// + /// let xs = vec![1, 2, 3, 4, 5]; + /// let mut iter = peek_nth(xs.into_iter()); + /// + /// assert_eq!(iter.peek_nth_mut(0), Some(&mut 1)); + /// assert_eq!(iter.next(), Some(1)); + /// + /// // The iterator does not advance even if we call `peek_nth_mut` multiple times + /// assert_eq!(iter.peek_nth_mut(0), Some(&mut 2)); + /// assert_eq!(iter.peek_nth_mut(1), Some(&mut 3)); + /// assert_eq!(iter.next(), Some(2)); + /// + /// // Peek into the iterator and set the value behind the mutable reference. + /// if let Some(p) = iter.peek_nth_mut(1) { + /// assert_eq!(*p, 4); + /// *p = 9; + /// } + /// + /// // The value we put in reappears as the iterator continues. + /// assert_eq!(iter.next(), Some(3)); + /// assert_eq!(iter.next(), Some(9)); + /// + /// // Calling `peek_nth_mut` past the end of the iterator will return `None` + /// assert_eq!(iter.peek_nth_mut(1), None); + /// ``` + pub fn peek_nth_mut(&mut self, n: usize) -> Option<&mut I::Item> { + let unbuffered_items = (n + 1).saturating_sub(self.buf.len()); + + self.buf.extend(self.iter.by_ref().take(unbuffered_items)); + + self.buf.get_mut(n) + } } impl Iterator for PeekNth From b05cc1f5bd708adbd4c37a32108e05b487b2b117 Mon Sep 17 00:00:00 2001 From: Zachary S Date: Wed, 26 Jul 2023 00:17:34 -0500 Subject: [PATCH 343/633] Add quickcheck tests for PeekNth. --- tests/quick.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 267640fe4..0d7cd9307 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -758,6 +758,32 @@ quickcheck! { } } +quickcheck! { + fn correct_peek_nth(mut a: Vec) -> () { + let mut it = peek_nth(a.clone()); + for start_pos in 0..a.len() + 2 { + for real_idx in start_pos..a.len() + 2 { + let peek_idx = real_idx - start_pos; + assert_eq!(it.peek_nth(peek_idx), a.get(real_idx)); + assert_eq!(it.peek_nth_mut(peek_idx), a.get_mut(real_idx)); + } + assert_eq!(it.next(), a.get(start_pos).copied()); + } + } + + fn peek_nth_mut_replace(a: Vec, b: Vec) -> () { + let mut it = peek_nth(a.iter()); + for i in 0..a.len().min(b.len()) { + *it.peek_nth_mut(i).unwrap() = &b[i]; + } + for i in 0..a.len() { + assert_eq!(it.next().unwrap(), b.get(i).unwrap_or(&a[i])); + } + assert_eq!(it.next(), None); + assert_eq!(it.next(), None); + } +} + quickcheck! { fn dedup_via_coalesce(a: Vec) -> bool { let mut b = a.clone(); From a9480868d7fb4af22edfc0e2f19c0c778296c87b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 9 Oct 2023 12:59:04 +0200 Subject: [PATCH 344/633] Update `PeekNth::peek_nth` doctest Use `into_iter` instead to avoid `&&`. I did the same in `PeekNth::peek_nth_mut` to avoid `&mut &`. --- src/peek_nth.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/peek_nth.rs b/src/peek_nth.rs index 9dddb8f01..1ae428ba0 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -50,19 +50,19 @@ where /// /// Basic usage: /// - /// ```rust + /// ``` /// use itertools::peek_nth; /// - /// let xs = vec![1,2,3]; - /// let mut iter = peek_nth(xs.iter()); + /// let xs = vec![1, 2, 3]; + /// let mut iter = peek_nth(xs.into_iter()); /// - /// assert_eq!(iter.peek_nth(0), Some(&&1)); - /// assert_eq!(iter.next(), Some(&1)); + /// assert_eq!(iter.peek_nth(0), Some(&1)); + /// assert_eq!(iter.next(), Some(1)); /// /// // The iterator does not advance even if we call `peek_nth` multiple times - /// assert_eq!(iter.peek_nth(0), Some(&&2)); - /// assert_eq!(iter.peek_nth(1), Some(&&3)); - /// assert_eq!(iter.next(), Some(&2)); + /// assert_eq!(iter.peek_nth(0), Some(&2)); + /// assert_eq!(iter.peek_nth(1), Some(&3)); + /// assert_eq!(iter.next(), Some(2)); /// /// // Calling `peek_nth` past the end of the iterator will return `None` /// assert_eq!(iter.peek_nth(1), None); From 37207cdb5d10c5eeb4bdf88f09811429f6f88226 Mon Sep 17 00:00:00 2001 From: herlev <> Date: Mon, 21 Aug 2023 15:09:19 +0200 Subject: [PATCH 345/633] add next_if and next_if_eq to PeekNth, such that it matches the functionality found in std::iter::Peekable --- src/peek_nth.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/peek_nth.rs b/src/peek_nth.rs index 1ae428ba0..fcdff384d 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -115,6 +115,23 @@ where self.buf.get_mut(n) } + + /// Works exactly like the `next_if` method in `std::iter::Peekable` + pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option { + match self.peek() { + Some(m) if func(&m) => self.next(), + _ => None, + } + } + + /// Works exactly like the `next_if_eq` method in `std::iter::Peekable` + pub fn next_if_eq(&mut self, expected: &T) -> Option + where + T: ?Sized, + I::Item: PartialEq, + { + self.next_if(|next| next == expected) + } } impl Iterator for PeekNth From d3f4886f80fd1b99eaa33c1340a57c58872395c8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 10 Oct 2023 10:03:03 +0200 Subject: [PATCH 346/633] `PeekNth::next_if`: avoid "peek+next" Similarly, `Peekable::next_if` does not "peek" before calling "next" either. --- src/peek_nth.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/peek_nth.rs b/src/peek_nth.rs index fcdff384d..5f7fb9396 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -118,8 +118,12 @@ where /// Works exactly like the `next_if` method in `std::iter::Peekable` pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option { - match self.peek() { - Some(m) if func(&m) => self.next(), + match self.next() { + Some(item) if func(&item) => Some(item), + Some(item) => { + self.buf.push_front(item); + None + } _ => None, } } From 44b4d71608befe5339432fc16b5c459944376167 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 10 Oct 2023 10:58:36 +0200 Subject: [PATCH 347/633] `test_peek_nth_next_if` Same as `test_peek_nth_peeking_next` (copied/pasted/updated) but with `next_if[_eq]` instead of using `peeking_next` from our `itertools::PeekingNext` trait. --- tests/test_std.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index 3ef90f39d..732790ef4 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -738,6 +738,35 @@ fn test_peek_nth_peeking_next() { assert_eq!(iter.peek(), None); } +#[test] +fn test_peek_nth_next_if() { + let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; + let mut iter = peek_nth(nums.iter().copied()); + + assert_eq!(iter.next_if(|&x| x != 0), Some(1)); + assert_eq!(iter.next(), Some(2)); + + assert_eq!(iter.peek_nth(0), Some(&3)); + assert_eq!(iter.peek_nth(1), Some(&4)); + assert_eq!(iter.next_if_eq(&3), Some(3)); + assert_eq!(iter.peek(), Some(&4)); + + assert_eq!(iter.next_if(|&x| x != 4), None); + assert_eq!(iter.next_if_eq(&4), Some(4)); + assert_eq!(iter.peek_nth(0), Some(&5)); + assert_eq!(iter.peek_nth(1), Some(&6)); + + assert_eq!(iter.next_if(|&x| x != 5), None); + assert_eq!(iter.peek(), Some(&5)); + + assert_eq!(iter.next_if(|&x| x % 2 == 1), Some(5)); + assert_eq!(iter.next_if_eq(&6), Some(6)); + assert_eq!(iter.peek_nth(0), Some(&7)); + assert_eq!(iter.peek_nth(1), None); + assert_eq!(iter.next(), Some(7)); + assert_eq!(iter.peek(), None); +} + #[test] fn pad_using() { it::assert_equal((0..0).pad_using(1, |_| 1), 1..2); From 0951795c0e47d69948a7d98d08e37f73d13321f7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 10 Oct 2023 11:38:25 +0200 Subject: [PATCH 348/633] Add `peek_nth_next_if` quickcheck test --- tests/quick.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index 0d7cd9307..f0c8f2d9e 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -782,6 +782,30 @@ quickcheck! { assert_eq!(it.next(), None); assert_eq!(it.next(), None); } + + fn peek_nth_next_if(a: Vec) -> () { + let mut it = peek_nth(a.clone()); + for (idx, mut value) in a.iter().copied().enumerate() { + let should_be_none = it.next_if(|x| x != &value); + assert_eq!(should_be_none, None); + if value % 5 == 0 { + // Sometimes, peek up to 3 further. + let n = value as usize % 3; + let nth = it.peek_nth(n); + assert_eq!(nth, a.get(idx + n)); + } else if value % 5 == 1 { + // Sometimes, peek next element mutably. + if let Some(v) = it.peek_mut() { + *v = v.wrapping_sub(1); + let should_be_none = it.next_if_eq(&value); + assert_eq!(should_be_none, None); + value = value.wrapping_sub(1); + } + } + let eq = it.next_if_eq(&value); + assert_eq!(eq, Some(value)); + } + } } quickcheck! { From 5f9785c13b139fa9dff078f003bb6f9d050e1498 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 14 Oct 2023 20:11:46 +0200 Subject: [PATCH 349/633] check_specialized only needed within function --- tests/specializations.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 4fa6c33df..47c797291 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -17,23 +17,20 @@ where } } -macro_rules! check_specialized { - ($src:expr, |$it:pat| $closure:expr) => { - let $it = $src.clone(); - let v1 = $closure; - - let $it = Unspecialized($src.clone()); - let v2 = $closure; - - assert_eq!(v1, v2); - }; -} - fn test_specializations(it: &Iter) where IterItem: Eq + Debug + Clone, Iter: Iterator + Clone, { + macro_rules! check_specialized { + ($src:expr, |$it:pat| $closure:expr) => { + let $it = $src.clone(); + let v1 = $closure; + let $it = Unspecialized($src.clone()); + let v2 = $closure; + assert_eq!(v1, v2); + }; + } check_specialized!(it, |i| i.count()); check_specialized!(it, |i| i.last()); check_specialized!(it, |i| i.collect::>()); From ad5e25f2057c938498ff1286c4305c95f237dab5 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 14 Oct 2023 20:20:00 +0200 Subject: [PATCH 350/633] Test specializations for next'ed iterators --- tests/specializations.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 47c797291..ba170c42c 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -24,12 +24,17 @@ where { macro_rules! check_specialized { ($src:expr, |$it:pat| $closure:expr) => { - let $it = $src.clone(); - let v1 = $closure; - let $it = Unspecialized($src.clone()); - let v2 = $closure; - assert_eq!(v1, v2); - }; + // Many iterators special-case the first elements, so we test specializations for iterators that have already been advanced. + let mut src = $src.clone(); + for _ in 0..5 { + let $it = src.clone(); + let v1 = $closure; + let $it = Unspecialized(src.clone()); + let v2 = $closure; + assert_eq!(v1, v2); + src.next(); + } + } } check_specialized!(it, |i| i.count()); check_specialized!(it, |i| i.last()); From f00e3ae859b0512cace2d54d6e1dc7e06fdfd0a5 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 14 Oct 2023 20:27:43 +0200 Subject: [PATCH 351/633] Add TODO process_results is as of now tested separately because the corresponding iterator is not exposed nicely, and because it is not `Clone`. However, the respective tests already diverged. Instead of fixing process_results, we should strive to uniformly use test_specializations for ProcessResults, too. --- tests/specializations.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index ba170c42c..3f5e10685 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -129,6 +129,7 @@ quickcheck! { } quickcheck! { + // TODO Replace this function by a normal call to test_specializations fn process_results(v: Vec>) -> () { helper(v.iter().copied()); helper(v.iter().copied().filter(Result::is_ok)); From 2bf459ccd871df2ccecee9c597da8f87a63c8292 Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 6 Oct 2023 14:36:58 -0400 Subject: [PATCH 352/633] GroupMap: add fold_with This is a generalization of `fold` which takes a function rather than a value, which removes the need for a `Clone` bound. `fold` is implemented in terms of `fold_with`. --- src/grouping_map.rs | 51 ++++++++++++++++++++++++++++++++++++++++----- tests/quick.rs | 29 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index affcb2121..8799c6ecb 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -115,6 +115,50 @@ where destination_map } + /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements + /// of each group sequentially, passing the previously accumulated value, a reference to the key + /// and the current element as arguments, and stores the results in a new map. + /// + /// `init` is called to obtain the initial value of each accumulator. + /// + /// `operation` is a function that is invoked on each element with the following parameters: + /// - the current value of the accumulator of the group; + /// - a reference to the key of the group this element belongs to; + /// - the element from the source being accumulated. + /// + /// Return a `HashMap` associating the key of each group with the result of folding that group's elements. + /// + /// ``` + /// use itertools::Itertools; + /// + /// #[derive(Debug, Default)] + /// struct Accumulator { + /// acc: usize, + /// } + /// + /// let lookup = (1..=7) + /// .into_grouping_map_by(|&n| n % 3) + /// .fold_with(|_key| Default::default(), |Accumulator { acc }, _key, val| { + /// let acc = acc + val; + /// Accumulator { acc } + /// }); + /// + /// assert_eq!(lookup[&0].acc, 3 + 6); + /// assert_eq!(lookup[&1].acc, 1 + 4 + 7); + /// assert_eq!(lookup[&2].acc, 2 + 5); + /// assert_eq!(lookup.len(), 3); + /// ``` + pub fn fold_with(self, mut init: FI, mut operation: FO) -> HashMap + where + FI: FnMut(&K) -> R, + FO: FnMut(R, &K, V) -> R, + { + self.aggregate(|acc, key, val| { + let acc = acc.unwrap_or_else(|| init(key)); + Some(operation(acc, key, val)) + }) + } + /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in a new map. @@ -140,15 +184,12 @@ where /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn fold(self, init: R, mut operation: FO) -> HashMap + pub fn fold(self, init: R, operation: FO) -> HashMap where R: Clone, FO: FnMut(R, &K, V) -> R, { - self.aggregate(|acc, key, val| { - let acc = acc.unwrap_or_else(|| init.clone()); - Some(operation(acc, key, val)) - }) + self.fold_with(|_: &K| init.clone(), operation) } /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements diff --git a/tests/quick.rs b/tests/quick.rs index f0c8f2d9e..d65e274f3 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1472,6 +1472,35 @@ quickcheck! { } } + fn correct_grouping_map_by_fold_with_modulo_key(a: Vec, modulo: u8) -> () { + #[derive(Debug, Default, PartialEq)] + struct Accumulator { + acc: u64, + } + + let modulo = if modulo == 0 { 1 } else { modulo } as u64; // Avoid `% 0` + let lookup = a.iter().map(|&b| b as u64) // Avoid overflows + .into_grouping_map_by(|i| i % modulo) + .fold_with(|_key| Default::default(), |Accumulator { acc }, &key, val| { + assert!(val % modulo == key); + let acc = acc + val; + Accumulator { acc } + }); + + let group_map_lookup = a.iter() + .map(|&b| b as u64) + .map(|i| (i % modulo, i)) + .into_group_map() + .into_iter() + .map(|(key, vals)| (key, vals.into_iter().sum())).map(|(key, acc)| (key,Accumulator { acc })) + .collect::>(); + assert_eq!(lookup, group_map_lookup); + + for (&key, &Accumulator { acc: sum }) in lookup.iter() { + assert_eq!(sum, a.iter().map(|&b| b as u64).filter(|&val| val % modulo == key).sum::()); + } + } + fn correct_grouping_map_by_fold_modulo_key(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo } as u64; // Avoid `% 0` let lookup = a.iter().map(|&b| b as u64) // Avoid overflows From 7a6c1ef3e771b75515a420b5759fc7b8c51248ff Mon Sep 17 00:00:00 2001 From: Tamir Duberstein Date: Fri, 6 Oct 2023 14:36:58 -0400 Subject: [PATCH 353/633] GroupMap: add fold_with (2) more generic init This is a generalization of `fold` which takes a function rather than a value, which removes the need for a `Clone` bound. `fold` is implemented in terms of `fold_with`. --- src/grouping_map.rs | 8 ++++---- tests/quick.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 8799c6ecb..aeb86f1b2 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -138,7 +138,7 @@ where /// /// let lookup = (1..=7) /// .into_grouping_map_by(|&n| n % 3) - /// .fold_with(|_key| Default::default(), |Accumulator { acc }, _key, val| { + /// .fold_with(|_key, _val| Default::default(), |Accumulator { acc }, _key, val| { /// let acc = acc + val; /// Accumulator { acc } /// }); @@ -150,11 +150,11 @@ where /// ``` pub fn fold_with(self, mut init: FI, mut operation: FO) -> HashMap where - FI: FnMut(&K) -> R, + FI: FnMut(&K, &V) -> R, FO: FnMut(R, &K, V) -> R, { self.aggregate(|acc, key, val| { - let acc = acc.unwrap_or_else(|| init(key)); + let acc = acc.unwrap_or_else(|| init(key, &val)); Some(operation(acc, key, val)) }) } @@ -189,7 +189,7 @@ where R: Clone, FO: FnMut(R, &K, V) -> R, { - self.fold_with(|_: &K| init.clone(), operation) + self.fold_with(|_, _| init.clone(), operation) } /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements diff --git a/tests/quick.rs b/tests/quick.rs index d65e274f3..6f45a63d0 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1481,7 +1481,7 @@ quickcheck! { let modulo = if modulo == 0 { 1 } else { modulo } as u64; // Avoid `% 0` let lookup = a.iter().map(|&b| b as u64) // Avoid overflows .into_grouping_map_by(|i| i % modulo) - .fold_with(|_key| Default::default(), |Accumulator { acc }, &key, val| { + .fold_with(|_key, _val| Default::default(), |Accumulator { acc }, &key, val| { assert!(val % modulo == key); let acc = acc + val; Accumulator { acc } From 7d2b3383d7819090cefe99ad21ada69e760eb3af Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Tue, 10 Oct 2023 21:40:15 +0800 Subject: [PATCH 354/633] Implement custom fold for while some Implement custom fold for while some --- benches/bench1.rs | 19 +++++++++++++++++++ src/adaptors/mod.rs | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/benches/bench1.rs b/benches/bench1.rs index fbcaa05a7..46d7953e4 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -448,6 +448,24 @@ fn group_by_lazy_2(c: &mut Criterion) { }); } +fn while_some(c: &mut Criterion) { + c.bench_function("while_some", |b| { + b.iter(|| { + let data = black_box( + (0..) + .fuse() + .map(|i| std::char::from_digit(i, 16)) + .while_some(), + ); + let result: String = data.fold(String::new(), |acc, ch| acc + &ch.to_string()); + assert_eq!( + result.chars().collect::>(), + "0123456789abcdef".chars().collect::>() + ); + }); + }); +} + fn slice_chunks(c: &mut Criterion) { let data = vec![0; 1024]; @@ -884,5 +902,6 @@ criterion_group!( permutations_range, permutations_slice, with_position_fold, + while_some, ); criterion_main!(benches); diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 90cf46eed..6ee2adc5f 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -581,6 +581,17 @@ where fn size_hint(&self) -> (usize, Option) { (0, self.iter.size_hint().1) } + + fn fold(self, acc: B, f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + self.iter + .take_while(|opt| opt.is_some()) + .map(|item| item.unwrap()) + .fold(acc, f) + } } /// An iterator to iterate through all combinations in a `Clone`-able iterator that produces tuples From 12df5ce10280cc9b75e45e26542a7844de5cbe11 Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Wed, 11 Oct 2023 22:13:26 +0800 Subject: [PATCH 355/633] Revise benchmark & use try_fold for fold --- benches/bench1.rs | 11 ++++++----- src/adaptors/mod.rs | 12 +++++++----- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 46d7953e4..664191ab4 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -457,11 +457,12 @@ fn while_some(c: &mut Criterion) { .map(|i| std::char::from_digit(i, 16)) .while_some(), ); - let result: String = data.fold(String::new(), |acc, ch| acc + &ch.to_string()); - assert_eq!( - result.chars().collect::>(), - "0123456789abcdef".chars().collect::>() - ); + // let result: String = data.fold(String::new(), |acc, ch| acc + &ch.to_string()); + let result = data.fold(String::new(), |mut acc, ch| { + acc.push(ch); + acc + }); + assert_eq!(result.as_str(), "0123456789abcdef"); }); }); } diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 6ee2adc5f..c5a0bfccb 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -582,15 +582,17 @@ where (0, self.iter.size_hint().1) } - fn fold(self, acc: B, f: F) -> B + fn fold(mut self, acc: B, mut f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { - self.iter - .take_while(|opt| opt.is_some()) - .map(|item| item.unwrap()) - .fold(acc, f) + let res = self.iter.try_fold(acc, |acc, item| match item { + Some(item) => Ok(f(acc, item)), + None => Err(acc), + }); + let (Err(res) | Ok(res)) = res; + res } } From 6710e71731ae1d3ed3e7689bfa32db8db6f1d05d Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Wed, 11 Oct 2023 22:27:34 +0800 Subject: [PATCH 356/633] fix failing msrv test --- src/adaptors/mod.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index c5a0bfccb..18c864ad1 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -591,7 +591,10 @@ where Some(item) => Ok(f(acc, item)), None => Err(acc), }); - let (Err(res) | Ok(res)) = res; + let res = match res { + Ok(val) => val, + Err(val) => val, + }; res } } From 8ae464e239d3e042ef92bc58f7ff145d0a31ac29 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 3 Oct 2023 14:14:33 +0200 Subject: [PATCH 357/633] Benchmark `TupleCombinations::fold` --- benches/bench1.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/benches/bench1.rs b/benches/bench1.rs index 664191ab4..84caf88bc 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -855,6 +855,17 @@ fn with_position_fold(c: &mut Criterion) { }); } +fn tuple_combinations_fold(c: &mut Criterion) { + let v = black_box((0..64).collect_vec()); + c.bench_function("tuple_combinations fold", move |b| { + b.iter(|| { + v.iter() + .tuple_combinations() + .fold(0, |acc, (a, b, c, d)| acc + *a * *c - *b * *d) + }) + }); +} + criterion_group!( benches, slice_iter, @@ -903,6 +914,7 @@ criterion_group!( permutations_range, permutations_slice, with_position_fold, + tuple_combinations_fold, while_some, ); criterion_main!(benches); From a002b41f05cdf1a815e6ccff26b043b15a8bfbe4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 3 Oct 2023 11:21:12 +0200 Subject: [PATCH 358/633] `TupleCombinations::fold` --- src/adaptors/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 18c864ad1..a47cb6ca6 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -650,6 +650,13 @@ where fn count(self) -> usize { self.iter.count() } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.fold(init, f) + } } impl FusedIterator for TupleCombinations From 84d1f044dd2c7d0da44bb845ba0639f3c86e252f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 3 Oct 2023 11:20:34 +0200 Subject: [PATCH 359/633] `Tuple1Combinations::fold` --- src/adaptors/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a47cb6ca6..6d9013f0c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -691,6 +691,13 @@ impl Iterator for Tuple1Combination { fn count(self) -> usize { self.iter.count() } + + fn fold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.iter.map(|x| (x,)).fold(init, f) + } } impl HasCombination for (I::Item,) { From 220e902b2cf7ed6fa62ab0a779cd6d466804df45 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 3 Oct 2023 11:39:53 +0200 Subject: [PATCH 360/633] `Tuple*Combinations::fold` (macro) `I::Item` and `A` are the same so the "where condition" is not really changed, but it is apparently needed here. We fold `c` then for each item of `iter`, we fold the cloned `iter`. The `while let` loop should probably be converted to `fold` somehow later (if possible) but the core logic is done and it is already really faster. --- src/adaptors/mod.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 6d9013f0c..802256542 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -731,7 +731,7 @@ macro_rules! impl_tuple_combination { impl Iterator for $C where I: Iterator + Clone, - I::Item: Clone + A: Clone, { type Item = (A, $(ignore_ident!($X, A)),*); @@ -761,6 +761,26 @@ macro_rules! impl_tuple_combination { let n = self.iter.count(); checked_binomial(n, K).unwrap() + self.c.count() } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let Self { c, item, mut iter } = self; + init = c + .map(|($($X),*,)| { + let z = item.clone().unwrap(); + (z, $($X),*) + }) + .fold(init, &mut f); + while let Some(z) = iter.next() { + let c: $P = iter.clone().into(); + init = c + .map(|($($X),*,)| (z.clone(), $($X),*)) + .fold(init, &mut f); + } + init + } } impl HasCombination for (A, $(ignore_ident!($X, A)),*) From 52c3b56b9d8fe3814895b543288d05118b56d9f7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 27 Oct 2023 08:51:09 +0200 Subject: [PATCH 361/633] `Tuple*Combination::fold`: avoid `unwrap` --- src/adaptors/mod.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 802256542..b3d307a2a 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -767,12 +767,11 @@ macro_rules! impl_tuple_combination { F: FnMut(B, Self::Item) -> B, { let Self { c, item, mut iter } = self; - init = c - .map(|($($X),*,)| { - let z = item.clone().unwrap(); - (z, $($X),*) - }) - .fold(init, &mut f); + if let Some(z) = item.as_ref() { + init = c + .map(|($($X),*,)| (z.clone(), $($X),*)) + .fold(init, &mut f); + } while let Some(z) = iter.next() { let c: $P = iter.clone().into(); init = c From baaf852919da204b21f0bac2b7e862366b623859 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 27 Oct 2023 09:21:07 +0200 Subject: [PATCH 362/633] Simplify commas in `impl_tuple_combination` macro --- src/adaptors/mod.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index b3d307a2a..3aa49d224 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -736,14 +736,14 @@ macro_rules! impl_tuple_combination { type Item = (A, $(ignore_ident!($X, A)),*); fn next(&mut self) -> Option { - if let Some(($($X),*,)) = self.c.next() { + if let Some(($($X,)*)) = self.c.next() { let z = self.item.clone().unwrap(); Some((z, $($X),*)) } else { self.item = self.iter.next(); self.item.clone().and_then(|z| { self.c = self.iter.clone().into(); - self.c.next().map(|($($X),*,)| (z, $($X),*)) + self.c.next().map(|($($X,)*)| (z, $($X),*)) }) } } @@ -769,13 +769,13 @@ macro_rules! impl_tuple_combination { let Self { c, item, mut iter } = self; if let Some(z) = item.as_ref() { init = c - .map(|($($X),*,)| (z.clone(), $($X),*)) + .map(|($($X,)*)| (z.clone(), $($X),*)) .fold(init, &mut f); } while let Some(z) = iter.next() { let c: $P = iter.clone().into(); init = c - .map(|($($X),*,)| (z.clone(), $($X),*)) + .map(|($($X,)*)| (z.clone(), $($X),*)) .fold(init, &mut f); } init From d188e8edbbb0d4ec178105e3cd713e6543814e31 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 27 Oct 2023 09:39:32 +0200 Subject: [PATCH 363/633] `count_ident` macro without any comma --- src/adaptors/mod.rs | 4 ++-- src/impl_macros.rs | 2 +- src/tuple_impl.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 3aa49d224..a19cd0e04 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -749,7 +749,7 @@ macro_rules! impl_tuple_combination { } fn size_hint(&self) -> SizeHint { - const K: usize = 1 + count_ident!($($X,)*); + const K: usize = 1 + count_ident!($($X)*); let (mut n_min, mut n_max) = self.iter.size_hint(); n_min = checked_binomial(n_min, K).unwrap_or(usize::MAX); n_max = n_max.and_then(|n| checked_binomial(n, K)); @@ -757,7 +757,7 @@ macro_rules! impl_tuple_combination { } fn count(self) -> usize { - const K: usize = 1 + count_ident!($($X,)*); + const K: usize = 1 + count_ident!($($X)*); let n = self.iter.count(); checked_binomial(n, K).unwrap() + self.c.count() } diff --git a/src/impl_macros.rs b/src/impl_macros.rs index 7c0b0c22e..3db5ba021 100644 --- a/src/impl_macros.rs +++ b/src/impl_macros.rs @@ -30,5 +30,5 @@ macro_rules! ignore_ident{ macro_rules! count_ident { () => {0}; - ($i0:ident, $($i:ident,)*) => {1 + count_ident!($($i,)*)}; + ($i0:ident $($i:ident)*) => {1 + count_ident!($($i)*)}; } diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 688df448e..2bd9a0413 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -345,7 +345,7 @@ macro_rules! impl_tuple_collect { impl_tuple_collect!($($Y,)*); impl TupleCollect for ($(ignore_ident!($Y, A),)*) { type Item = A; - type Buffer = [Option; count_ident!($($Y,)*) - 1]; + type Buffer = [Option; count_ident!($($Y)*) - 1]; #[allow(unused_assignments, unused_mut)] fn collect_from_iter(iter: I, buf: &mut Self::Buffer) -> Option @@ -388,7 +388,7 @@ macro_rules! impl_tuple_collect { } fn num_items() -> usize { - count_ident!($($Y,)*) + count_ident!($($Y)*) } fn left_shift_push(&mut self, mut item: A) { From 309917677298a8b0c8adfc2ef1d480cd18d346ad Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 30 Oct 2023 15:03:36 +0100 Subject: [PATCH 364/633] Rename `PermutationState` variants And small documentation of the variants. --- src/permutations.rs | 60 +++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index b6ff5c4fa..19cfa8aec 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -26,10 +26,14 @@ where #[derive(Clone, Debug)] enum PermutationState { - StartUnknownLen { k: usize }, - OngoingUnknownLen { k: usize, min_n: usize }, - Complete(CompleteState), - Empty, + /// No permutation generated yet. + Start { k: usize }, + /// Values from the iterator are not fully loaded yet so `n` is still unknown. + Buffered { k: usize, min_n: usize }, + /// All values from the iterator are known so `n` is known. + Loaded(CompleteState), + /// No permutation left to generate. + End, } #[derive(Clone, Debug)] @@ -57,7 +61,7 @@ pub fn permutations(iter: I, k: usize) -> Permutations { if k == 0 { // Special case, yields single empty vec; `n` is irrelevant - let state = PermutationState::Complete(CompleteState::Start { n: 0, k: 0 }); + let state = PermutationState::Loaded(CompleteState::Start { n: 0, k: 0 }); return Permutations { vals, state }; } @@ -66,9 +70,9 @@ pub fn permutations(iter: I, k: usize) -> Permutations { let enough_vals = vals.len() == k; let state = if enough_vals { - PermutationState::StartUnknownLen { k } + PermutationState::Start { k } } else { - PermutationState::Empty + PermutationState::End }; Permutations { vals, state } @@ -88,12 +92,12 @@ where ref mut state, } = self; match *state { - PermutationState::StartUnknownLen { k } => { - *state = PermutationState::OngoingUnknownLen { k, min_n: k }; + PermutationState::Start { k } => { + *state = PermutationState::Buffered { k, min_n: k }; } - PermutationState::OngoingUnknownLen { k, min_n } => { + PermutationState::Buffered { k, min_n } => { if vals.get_next() { - *state = PermutationState::OngoingUnknownLen { + *state = PermutationState::Buffered { k, min_n: min_n + 1, }; @@ -107,13 +111,13 @@ where complete_state.advance(); } - *state = PermutationState::Complete(complete_state); + *state = PermutationState::Loaded(complete_state); } } - PermutationState::Complete(ref mut state) => { + PermutationState::Loaded(ref mut state) => { state.advance(); } - PermutationState::Empty => {} + PermutationState::End => {} }; } let &mut Permutations { @@ -121,23 +125,21 @@ where ref state, } = self; match *state { - PermutationState::StartUnknownLen { .. } => panic!("unexpected iterator state"), - PermutationState::OngoingUnknownLen { k, min_n } => { + PermutationState::Start { .. } => panic!("unexpected iterator state"), + PermutationState::Buffered { k, min_n } => { let latest_idx = min_n - 1; let indices = (0..(k - 1)).chain(once(latest_idx)); Some(indices.map(|i| vals[i].clone()).collect()) } - PermutationState::Complete(CompleteState::Ongoing { + PermutationState::Loaded(CompleteState::Ongoing { ref indices, ref cycles, }) => { let k = cycles.len(); Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) } - PermutationState::Complete(CompleteState::Start { .. }) | PermutationState::Empty => { - None - } + PermutationState::Loaded(CompleteState::Start { .. }) | PermutationState::End => None, } } @@ -150,21 +152,21 @@ where let Permutations { vals, state } = self; match state { - PermutationState::StartUnknownLen { k } => { + PermutationState::Start { k } => { let n = vals.count(); let complete_state = CompleteState::Start { n, k }; from_complete(complete_state) } - PermutationState::OngoingUnknownLen { k, min_n } => { + PermutationState::Buffered { k, min_n } => { let prev_iteration_count = min_n - k + 1; let n = vals.count(); let complete_state = CompleteState::Start { n, k }; from_complete(complete_state) - prev_iteration_count } - PermutationState::Complete(state) => from_complete(state), - PermutationState::Empty => 0, + PermutationState::Loaded(state) => from_complete(state), + PermutationState::End => 0, } } @@ -179,16 +181,16 @@ where (low, upp) }; match self.state { - PermutationState::StartUnknownLen { k } => at_start(k), - PermutationState::OngoingUnknownLen { k, min_n } => { - // Same as `StartUnknownLen` minus the previously generated items. + PermutationState::Start { k } => at_start(k), + PermutationState::Buffered { k, min_n } => { + // Same as `Start` minus the previously generated items. size_hint::sub_scalar(at_start(k), min_n - k + 1) } - PermutationState::Complete(ref state) => match state.remaining() { + PermutationState::Loaded(ref state) => match state.remaining() { Some(count) => (count, Some(count)), None => (::std::usize::MAX, None), }, - PermutationState::Empty => (0, Some(0)), + PermutationState::End => (0, Some(0)), } } } From 07d913d60562582add2c3bb21fd7a6d740d8e93a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 30 Oct 2023 15:20:15 +0100 Subject: [PATCH 365/633] `permutations`: simplify references --- src/permutations.rs | 69 ++++++++++++++++++--------------------------- 1 file changed, 27 insertions(+), 42 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 19cfa8aec..4d4d667e4 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -87,24 +87,18 @@ where fn next(&mut self) -> Option { { - let &mut Permutations { - ref mut vals, - ref mut state, - } = self; - match *state { - PermutationState::Start { k } => { + let Self { vals, state } = self; + match state { + &mut PermutationState::Start { k } => { *state = PermutationState::Buffered { k, min_n: k }; } - PermutationState::Buffered { k, min_n } => { + PermutationState::Buffered { ref k, min_n } => { if vals.get_next() { - *state = PermutationState::Buffered { - k, - min_n: min_n + 1, - }; + *min_n += 1; } else { - let n = min_n; - let prev_iteration_count = n - k + 1; - let mut complete_state = CompleteState::Start { n, k }; + let n = *min_n; + let prev_iteration_count = n - *k + 1; + let mut complete_state = CompleteState::Start { n, k: *k }; // Advance the complete-state iterator to the correct point for _ in 0..(prev_iteration_count + 1) { @@ -114,21 +108,18 @@ where *state = PermutationState::Loaded(complete_state); } } - PermutationState::Loaded(ref mut state) => { + PermutationState::Loaded(state) => { state.advance(); } PermutationState::End => {} }; } - let &mut Permutations { - ref vals, - ref state, - } = self; - match *state { + let Self { vals, state } = &self; + match state { PermutationState::Start { .. } => panic!("unexpected iterator state"), - PermutationState::Buffered { k, min_n } => { - let latest_idx = min_n - 1; - let indices = (0..(k - 1)).chain(once(latest_idx)); + PermutationState::Buffered { ref k, min_n } => { + let latest_idx = *min_n - 1; + let indices = (0..(*k - 1)).chain(once(latest_idx)); Some(indices.map(|i| vals[i].clone()).collect()) } @@ -197,17 +188,13 @@ where impl CompleteState { fn advance(&mut self) { - *self = match *self { - CompleteState::Start { n, k } => { + match self { + &mut CompleteState::Start { n, k } => { let indices = (0..n).collect(); let cycles = ((n - k)..n).rev().collect(); - - CompleteState::Ongoing { cycles, indices } + *self = CompleteState::Ongoing { cycles, indices }; } - CompleteState::Ongoing { - ref mut indices, - ref mut cycles, - } => { + CompleteState::Ongoing { indices, cycles } => { let n = indices.len(); let k = cycles.len(); @@ -225,28 +212,26 @@ impl CompleteState { return; } } - - CompleteState::Start { n, k } + *self = CompleteState::Start { n, k }; } } } /// Returns the count of remaining permutations, or None if it would overflow. fn remaining(&self) -> Option { - match *self { - CompleteState::Start { n, k } => { + match self { + &CompleteState::Start { n, k } => { if n < k { return Some(0); } (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i)) } - CompleteState::Ongoing { - ref indices, - ref cycles, - } => cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { - acc.checked_mul(indices.len() - i) - .and_then(|count| count.checked_add(c)) - }), + CompleteState::Ongoing { indices, cycles } => { + cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { + acc.checked_mul(indices.len() - i) + .and_then(|count| count.checked_add(c)) + }) + } } } } From 044dc779dade873c8a1cf084fe64abda791e7ce4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 30 Oct 2023 16:06:55 +0100 Subject: [PATCH 366/633] `permutations`: `advance` function --- src/permutations.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 4d4d667e4..76d47c68e 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -186,6 +186,24 @@ where } } +fn advance(indices: &mut [usize], cycles: &mut [usize]) -> bool { + let n = indices.len(); + let k = cycles.len(); + // NOTE: if `cycles` are only zeros, then we reached the last permutation. + for i in (0..k).rev() { + if cycles[i] == 0 { + cycles[i] = n - i - 1; + indices[i..].rotate_left(1); + } else { + let swap_index = n - cycles[i]; + indices.swap(i, swap_index); + cycles[i] -= 1; + return false; + } + } + true +} + impl CompleteState { fn advance(&mut self) { match self { @@ -195,24 +213,12 @@ impl CompleteState { *self = CompleteState::Ongoing { cycles, indices }; } CompleteState::Ongoing { indices, cycles } => { - let n = indices.len(); - let k = cycles.len(); - - for i in (0..k).rev() { - if cycles[i] == 0 { - cycles[i] = n - i - 1; - - let to_push = indices.remove(i); - indices.push(to_push); - } else { - let swap_index = n - cycles[i]; - indices.swap(i, swap_index); - - cycles[i] -= 1; - return; - } + if advance(indices, cycles) { + *self = CompleteState::Start { + n: indices.len(), + k: cycles.len(), + }; } - *self = CompleteState::Start { n, k }; } } } From 80a9c7b725ceec6f98e26e8dcc7e3ed424482d56 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 30 Oct 2023 17:29:13 +0100 Subject: [PATCH 367/633] `PermutationState::size_hint_for` --- src/permutations.rs | 86 ++++++++++++++++----------------------------- 1 file changed, 30 insertions(+), 56 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 76d47c68e..7e368b200 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -135,54 +135,16 @@ where } fn count(self) -> usize { - fn from_complete(complete_state: CompleteState) -> usize { - complete_state - .remaining() - .expect("Iterator count greater than usize::MAX") - } - - let Permutations { vals, state } = self; - match state { - PermutationState::Start { k } => { - let n = vals.count(); - let complete_state = CompleteState::Start { n, k }; - - from_complete(complete_state) - } - PermutationState::Buffered { k, min_n } => { - let prev_iteration_count = min_n - k + 1; - let n = vals.count(); - let complete_state = CompleteState::Start { n, k }; - - from_complete(complete_state) - prev_iteration_count - } - PermutationState::Loaded(state) => from_complete(state), - PermutationState::End => 0, - } + let Self { vals, state } = self; + let n = vals.count(); + state.size_hint_for(n).1.unwrap() } fn size_hint(&self) -> SizeHint { - let at_start = |k| { - // At the beginning, there are `n!/(n-k)!` items to come (see `remaining`) but `n` might be unknown. - let (mut low, mut upp) = self.vals.size_hint(); - low = CompleteState::Start { n: low, k } - .remaining() - .unwrap_or(usize::MAX); - upp = upp.and_then(|n| CompleteState::Start { n, k }.remaining()); - (low, upp) - }; - match self.state { - PermutationState::Start { k } => at_start(k), - PermutationState::Buffered { k, min_n } => { - // Same as `Start` minus the previously generated items. - size_hint::sub_scalar(at_start(k), min_n - k + 1) - } - PermutationState::Loaded(ref state) => match state.remaining() { - Some(count) => (count, Some(count)), - None => (::std::usize::MAX, None), - }, - PermutationState::End => (0, Some(0)), - } + let (mut low, mut upp) = self.vals.size_hint(); + low = self.state.size_hint_for(low).0; + upp = upp.and_then(|n| self.state.size_hint_for(n).1); + (low, upp) } } @@ -222,22 +184,34 @@ impl CompleteState { } } } +} - /// Returns the count of remaining permutations, or None if it would overflow. - fn remaining(&self) -> Option { - match self { - &CompleteState::Start { n, k } => { - if n < k { - return Some(0); - } - (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i)) +impl PermutationState { + fn size_hint_for(&self, n: usize) -> SizeHint { + // At the beginning, there are `n!/(n-k)!` items to come. + let at_start = |n, k| { + debug_assert!(n >= k); + let total = (n - k + 1..=n).try_fold(1usize, |acc, i| acc.checked_mul(i)); + (total.unwrap_or(usize::MAX), total) + }; + match *self { + Self::Start { k } => at_start(n, k), + Self::Buffered { k, min_n } => { + // Same as `Start` minus the previously generated items. + size_hint::sub_scalar(at_start(n, k), min_n - k + 1) } - CompleteState::Ongoing { indices, cycles } => { - cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { + Self::Loaded(CompleteState::Start { n, k }) => at_start(n, k), + Self::Loaded(CompleteState::Ongoing { + ref indices, + ref cycles, + }) => { + let count = cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { acc.checked_mul(indices.len() - i) .and_then(|count| count.checked_add(c)) - }) + }); + (count.unwrap_or(usize::MAX), count) } + Self::End => (0, Some(0)), } } } From 7957fd31110f53ce1ee8ca5e4729430fbe25d002 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 12:36:06 +0100 Subject: [PATCH 368/633] Merge `CompleteState` variants into `PermutationState` --- src/permutations.rs | 86 +++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 46 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 7e368b200..34bce3950 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -30,22 +30,18 @@ enum PermutationState { Start { k: usize }, /// Values from the iterator are not fully loaded yet so `n` is still unknown. Buffered { k: usize, min_n: usize }, - /// All values from the iterator are known so `n` is known. - Loaded(CompleteState), - /// No permutation left to generate. - End, -} - -#[derive(Clone, Debug)] -enum CompleteState { - Start { + // Temporary state that will be discarded soon! + LoadedStart { n: usize, k: usize, }, - Ongoing { + /// All values from the iterator are known so `n` is known. + LoadedOngoing { indices: Vec, cycles: Vec, }, + /// No permutation left to generate. + End, } impl fmt::Debug for Permutations @@ -61,7 +57,7 @@ pub fn permutations(iter: I, k: usize) -> Permutations { if k == 0 { // Special case, yields single empty vec; `n` is irrelevant - let state = PermutationState::Loaded(CompleteState::Start { n: 0, k: 0 }); + let state = PermutationState::LoadedStart { n: 0, k: 0 }; return Permutations { vals, state }; } @@ -98,18 +94,36 @@ where } else { let n = *min_n; let prev_iteration_count = n - *k + 1; - let mut complete_state = CompleteState::Start { n, k: *k }; - - // Advance the complete-state iterator to the correct point - for _ in 0..(prev_iteration_count + 1) { - complete_state.advance(); + let mut indices: Vec<_> = (0..n).collect(); + let mut cycles: Vec<_> = (n - k..n).rev().collect(); + let mut found_something = false; + // Advance the state to the correct point. + for _ in 0..prev_iteration_count { + if advance(&mut indices, &mut cycles) { + *state = PermutationState::LoadedStart { + n: indices.len(), + k: cycles.len(), + }; + found_something = true; + } + } + if !found_something { + *state = PermutationState::LoadedOngoing { indices, cycles }; } - - *state = PermutationState::Loaded(complete_state); } } - PermutationState::Loaded(state) => { - state.advance(); + &mut PermutationState::LoadedStart { n, k } => { + let indices = (0..n).collect(); + let cycles = (n - k..n).rev().collect(); + *state = PermutationState::LoadedOngoing { cycles, indices }; + } + PermutationState::LoadedOngoing { indices, cycles } => { + if advance(indices, cycles) { + *state = PermutationState::LoadedStart { + n: indices.len(), + k: cycles.len(), + }; + } } PermutationState::End => {} }; @@ -123,14 +137,14 @@ where Some(indices.map(|i| vals[i].clone()).collect()) } - PermutationState::Loaded(CompleteState::Ongoing { + PermutationState::LoadedOngoing { ref indices, ref cycles, - }) => { + } => { let k = cycles.len(); Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) } - PermutationState::Loaded(CompleteState::Start { .. }) | PermutationState::End => None, + PermutationState::LoadedStart { .. } | PermutationState::End => None, } } @@ -166,26 +180,6 @@ fn advance(indices: &mut [usize], cycles: &mut [usize]) -> bool { true } -impl CompleteState { - fn advance(&mut self) { - match self { - &mut CompleteState::Start { n, k } => { - let indices = (0..n).collect(); - let cycles = ((n - k)..n).rev().collect(); - *self = CompleteState::Ongoing { cycles, indices }; - } - CompleteState::Ongoing { indices, cycles } => { - if advance(indices, cycles) { - *self = CompleteState::Start { - n: indices.len(), - k: cycles.len(), - }; - } - } - } - } -} - impl PermutationState { fn size_hint_for(&self, n: usize) -> SizeHint { // At the beginning, there are `n!/(n-k)!` items to come. @@ -200,11 +194,11 @@ impl PermutationState { // Same as `Start` minus the previously generated items. size_hint::sub_scalar(at_start(n, k), min_n - k + 1) } - Self::Loaded(CompleteState::Start { n, k }) => at_start(n, k), - Self::Loaded(CompleteState::Ongoing { + Self::LoadedStart { n, k } => at_start(n, k), + Self::LoadedOngoing { ref indices, ref cycles, - }) => { + } => { let count = cycles.iter().enumerate().try_fold(0usize, |acc, (i, &c)| { acc.checked_mul(indices.len() - i) .and_then(|count| count.checked_add(c)) From b1a781779fa70fb5c21c551924ab0ab1e70608fe Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 12:38:45 +0100 Subject: [PATCH 369/633] Unindent block in `Permutations::next` --- src/permutations.rs | 82 ++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 34bce3950..b0d75627e 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -82,52 +82,50 @@ where type Item = Vec; fn next(&mut self) -> Option { - { - let Self { vals, state } = self; - match state { - &mut PermutationState::Start { k } => { - *state = PermutationState::Buffered { k, min_n: k }; - } - PermutationState::Buffered { ref k, min_n } => { - if vals.get_next() { - *min_n += 1; - } else { - let n = *min_n; - let prev_iteration_count = n - *k + 1; - let mut indices: Vec<_> = (0..n).collect(); - let mut cycles: Vec<_> = (n - k..n).rev().collect(); - let mut found_something = false; - // Advance the state to the correct point. - for _ in 0..prev_iteration_count { - if advance(&mut indices, &mut cycles) { - *state = PermutationState::LoadedStart { - n: indices.len(), - k: cycles.len(), - }; - found_something = true; - } - } - if !found_something { - *state = PermutationState::LoadedOngoing { indices, cycles }; + let Self { vals, state } = self; + match state { + &mut PermutationState::Start { k } => { + *state = PermutationState::Buffered { k, min_n: k }; + } + PermutationState::Buffered { ref k, min_n } => { + if vals.get_next() { + *min_n += 1; + } else { + let n = *min_n; + let prev_iteration_count = n - *k + 1; + let mut indices: Vec<_> = (0..n).collect(); + let mut cycles: Vec<_> = (n - k..n).rev().collect(); + let mut found_something = false; + // Advance the state to the correct point. + for _ in 0..prev_iteration_count { + if advance(&mut indices, &mut cycles) { + *state = PermutationState::LoadedStart { + n: indices.len(), + k: cycles.len(), + }; + found_something = true; } } - } - &mut PermutationState::LoadedStart { n, k } => { - let indices = (0..n).collect(); - let cycles = (n - k..n).rev().collect(); - *state = PermutationState::LoadedOngoing { cycles, indices }; - } - PermutationState::LoadedOngoing { indices, cycles } => { - if advance(indices, cycles) { - *state = PermutationState::LoadedStart { - n: indices.len(), - k: cycles.len(), - }; + if !found_something { + *state = PermutationState::LoadedOngoing { indices, cycles }; } } - PermutationState::End => {} - }; - } + } + &mut PermutationState::LoadedStart { n, k } => { + let indices = (0..n).collect(); + let cycles = (n - k..n).rev().collect(); + *state = PermutationState::LoadedOngoing { cycles, indices }; + } + PermutationState::LoadedOngoing { indices, cycles } => { + if advance(indices, cycles) { + *state = PermutationState::LoadedStart { + n: indices.len(), + k: cycles.len(), + }; + } + } + PermutationState::End => {} + }; let Self { vals, state } = &self; match state { PermutationState::Start { .. } => panic!("unexpected iterator state"), From 4e9d888e2f4e04372a8458dd0cbaf9f5bf6fb36e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 13:53:28 +0100 Subject: [PATCH 370/633] Inline second inspection of state --- src/permutations.rs | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index b0d75627e..2ccb0bd13 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -86,16 +86,21 @@ where match state { &mut PermutationState::Start { k } => { *state = PermutationState::Buffered { k, min_n: k }; + let latest_idx = k - 1; + let indices = (0..(k - 1)).chain(once(latest_idx)); + Some(indices.map(|i| vals[i].clone()).collect()) } PermutationState::Buffered { ref k, min_n } => { if vals.get_next() { *min_n += 1; + let latest_idx = *min_n - 1; + let indices = (0..*k - 1).chain(once(latest_idx)); + Some(indices.map(|i| vals[i].clone()).collect()) } else { let n = *min_n; let prev_iteration_count = n - *k + 1; let mut indices: Vec<_> = (0..n).collect(); let mut cycles: Vec<_> = (n - k..n).rev().collect(); - let mut found_something = false; // Advance the state to the correct point. for _ in 0..prev_iteration_count { if advance(&mut indices, &mut cycles) { @@ -103,18 +108,20 @@ where n: indices.len(), k: cycles.len(), }; - found_something = true; + return None; } } - if !found_something { - *state = PermutationState::LoadedOngoing { indices, cycles }; - } + let item = indices[0..*k].iter().map(|&i| vals[i].clone()).collect(); + *state = PermutationState::LoadedOngoing { indices, cycles }; + Some(item) } } &mut PermutationState::LoadedStart { n, k } => { - let indices = (0..n).collect(); + let indices: Vec<_> = (0..n).collect(); let cycles = (n - k..n).rev().collect(); + let item = indices[0..k].iter().map(|&i| vals[i].clone()).collect(); *state = PermutationState::LoadedOngoing { cycles, indices }; + Some(item) } PermutationState::LoadedOngoing { indices, cycles } => { if advance(indices, cycles) { @@ -122,27 +129,12 @@ where n: indices.len(), k: cycles.len(), }; + return None; } - } - PermutationState::End => {} - }; - let Self { vals, state } = &self; - match state { - PermutationState::Start { .. } => panic!("unexpected iterator state"), - PermutationState::Buffered { ref k, min_n } => { - let latest_idx = *min_n - 1; - let indices = (0..(*k - 1)).chain(once(latest_idx)); - - Some(indices.map(|i| vals[i].clone()).collect()) - } - PermutationState::LoadedOngoing { - ref indices, - ref cycles, - } => { let k = cycles.len(); Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) } - PermutationState::LoadedStart { .. } | PermutationState::End => None, + PermutationState::End => None, } } From eaff3d5dfb570eca8186f2bbd2b0bf6dad24fd23 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 13:59:04 +0100 Subject: [PATCH 371/633] Remove `PermutationState::LoadedStart` --- src/permutations.rs | 42 ++++++++++-------------------------------- 1 file changed, 10 insertions(+), 32 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 2ccb0bd13..b43acac8b 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -30,13 +30,8 @@ enum PermutationState { Start { k: usize }, /// Values from the iterator are not fully loaded yet so `n` is still unknown. Buffered { k: usize, min_n: usize }, - // Temporary state that will be discarded soon! - LoadedStart { - n: usize, - k: usize, - }, /// All values from the iterator are known so `n` is known. - LoadedOngoing { + Loaded { indices: Vec, cycles: Vec, }, @@ -55,13 +50,6 @@ where pub fn permutations(iter: I, k: usize) -> Permutations { let mut vals = LazyBuffer::new(iter); - if k == 0 { - // Special case, yields single empty vec; `n` is irrelevant - let state = PermutationState::LoadedStart { n: 0, k: 0 }; - - return Permutations { vals, state }; - } - vals.prefill(k); let enough_vals = vals.len() == k; @@ -84,6 +72,10 @@ where fn next(&mut self) -> Option { let Self { vals, state } = self; match state { + PermutationState::Start { k: 0 } => { + *state = PermutationState::End; + Some(Vec::new()) + } &mut PermutationState::Start { k } => { *state = PermutationState::Buffered { k, min_n: k }; let latest_idx = k - 1; @@ -104,31 +96,18 @@ where // Advance the state to the correct point. for _ in 0..prev_iteration_count { if advance(&mut indices, &mut cycles) { - *state = PermutationState::LoadedStart { - n: indices.len(), - k: cycles.len(), - }; + *state = PermutationState::End; return None; } } let item = indices[0..*k].iter().map(|&i| vals[i].clone()).collect(); - *state = PermutationState::LoadedOngoing { indices, cycles }; + *state = PermutationState::Loaded { indices, cycles }; Some(item) } } - &mut PermutationState::LoadedStart { n, k } => { - let indices: Vec<_> = (0..n).collect(); - let cycles = (n - k..n).rev().collect(); - let item = indices[0..k].iter().map(|&i| vals[i].clone()).collect(); - *state = PermutationState::LoadedOngoing { cycles, indices }; - Some(item) - } - PermutationState::LoadedOngoing { indices, cycles } => { + PermutationState::Loaded { indices, cycles } => { if advance(indices, cycles) { - *state = PermutationState::LoadedStart { - n: indices.len(), - k: cycles.len(), - }; + *state = PermutationState::End; return None; } let k = cycles.len(); @@ -184,8 +163,7 @@ impl PermutationState { // Same as `Start` minus the previously generated items. size_hint::sub_scalar(at_start(n, k), min_n - k + 1) } - Self::LoadedStart { n, k } => at_start(n, k), - Self::LoadedOngoing { + Self::Loaded { ref indices, ref cycles, } => { From c9fdd48b08890a2320b937a6ab3f7700326dfaa1 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 14:02:49 +0100 Subject: [PATCH 372/633] `Permutations::next`: simplify two vectors --- src/permutations.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index b43acac8b..361d04bfe 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -78,16 +78,16 @@ where } &mut PermutationState::Start { k } => { *state = PermutationState::Buffered { k, min_n: k }; - let latest_idx = k - 1; - let indices = (0..(k - 1)).chain(once(latest_idx)); - Some(indices.map(|i| vals[i].clone()).collect()) + Some(vals[0..k].to_vec()) } PermutationState::Buffered { ref k, min_n } => { if vals.get_next() { + let item = (0..*k - 1) + .chain(once(*min_n)) + .map(|i| vals[i].clone()) + .collect(); *min_n += 1; - let latest_idx = *min_n - 1; - let indices = (0..*k - 1).chain(once(latest_idx)); - Some(indices.map(|i| vals[i].clone()).collect()) + Some(item) } else { let n = *min_n; let prev_iteration_count = n - *k + 1; From 14d2fa290ce75e228169ed7b08e45b536019cd5f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 1 Nov 2023 14:19:43 +0100 Subject: [PATCH 373/633] `impl FusedIterator for Permutations` --- src/permutations.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/permutations.rs b/src/permutations.rs index 361d04bfe..5dd3dbf9a 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -1,6 +1,7 @@ use alloc::vec::Vec; use std::fmt; use std::iter::once; +use std::iter::FusedIterator; use super::lazy_buffer::LazyBuffer; use crate::size_hint::{self, SizeHint}; @@ -131,6 +132,13 @@ where } } +impl FusedIterator for Permutations +where + I: Iterator, + I::Item: Clone, +{ +} + fn advance(indices: &mut [usize], cycles: &mut [usize]) -> bool { let n = indices.len(); let k = cycles.len(); From 04baddb4eb36b1d0bfaa45769902ffd95af94531 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 3 Nov 2023 12:44:28 +0100 Subject: [PATCH 374/633] Make `Permutations` lazy To make the `Permutations` adaptor lazy, the operation "prefill the lazy buffer" is now done when handling the initial `Start` state rather than at definition. --- src/permutations.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 5dd3dbf9a..534ca59c9 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -49,18 +49,10 @@ where } pub fn permutations(iter: I, k: usize) -> Permutations { - let mut vals = LazyBuffer::new(iter); - - vals.prefill(k); - let enough_vals = vals.len() == k; - - let state = if enough_vals { - PermutationState::Start { k } - } else { - PermutationState::End - }; - - Permutations { vals, state } + Permutations { + vals: LazyBuffer::new(iter), + state: PermutationState::Start { k }, + } } impl Iterator for Permutations @@ -78,6 +70,11 @@ where Some(Vec::new()) } &mut PermutationState::Start { k } => { + vals.prefill(k); + if vals.len() != k { + *state = PermutationState::End; + return None; + } *state = PermutationState::Buffered { k, min_n: k }; Some(vals[0..k].to_vec()) } @@ -166,6 +163,7 @@ impl PermutationState { (total.unwrap_or(usize::MAX), total) }; match *self { + Self::Start { k } if n < k => (0, Some(0)), Self::Start { k } => at_start(n, k), Self::Buffered { k, min_n } => { // Same as `Start` minus the previously generated items. From 1c8a1e5f8266e3bad31dec38b0ad1fd1ec7e9d3d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 3 Nov 2023 19:12:48 +0100 Subject: [PATCH 375/633] Add missing `must_use` attributes on adaptors --- src/adaptors/mod.rs | 1 + src/combinations_with_replacement.rs | 1 + src/multipeek_impl.rs | 1 + src/peek_nth.rs | 1 + src/put_back_n_impl.rs | 1 + src/rciter_impl.rs | 1 + 6 files changed, 6 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a19cd0e04..7d05a70b3 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -185,6 +185,7 @@ where /// item to the front of the iterator. /// /// Iterator element type is `I::Item`. +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PutBack where I: Iterator, diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index f1a0bc07d..88d858b5f 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -10,6 +10,7 @@ use crate::adaptors::checked_binomial; /// See [`.combinations_with_replacement()`](crate::Itertools::combinations_with_replacement) /// for more information. #[derive(Clone)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct CombinationsWithReplacement where I: Iterator, diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 339cf9781..00c5d4ea7 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -7,6 +7,7 @@ use std::iter::Fuse; /// See [`multipeek()`] for more information. #[derive(Clone, Debug)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MultiPeek where I: Iterator, diff --git a/src/peek_nth.rs b/src/peek_nth.rs index 5f7fb9396..e8546030d 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -5,6 +5,7 @@ use std::iter::Fuse; /// See [`peek_nth()`] for more information. #[derive(Clone, Debug)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PeekNth where I: Iterator, diff --git a/src/put_back_n_impl.rs b/src/put_back_n_impl.rs index 5fdbd5a98..9b23fa7d5 100644 --- a/src/put_back_n_impl.rs +++ b/src/put_back_n_impl.rs @@ -7,6 +7,7 @@ use crate::size_hint; /// /// Iterator element type is `I::Item`. #[derive(Debug, Clone)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct PutBackN { top: Vec, iter: I, diff --git a/src/rciter_impl.rs b/src/rciter_impl.rs index 1282ae865..e3b753206 100644 --- a/src/rciter_impl.rs +++ b/src/rciter_impl.rs @@ -4,6 +4,7 @@ use std::iter::{FusedIterator, IntoIterator}; /// A wrapper for `Rc>`, that implements the `Iterator` trait. #[derive(Debug)] +#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct RcIter { /// The boxed iterator. pub rciter: Rc>, From 63d139d60a1fb6e4219a355e72257fe127ba0a7d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 3 Nov 2023 18:59:26 +0100 Subject: [PATCH 376/633] Make `Combinations` lazy To make the `Combinations` adaptor lazy, the operation "prefill the lazy buffer" is now done when handling the first element rather than at definition. However, the value returned by the `n` method is changed when called on a fresh `it.combinations(k)` since the "prefill" operation was delayed. I'm perplex on why the method `n` was made public in the first place as the value is subject to change (said in its documentation). But it is public and this change is therefore a breaking one. --- src/combinations.rs | 6 ++---- tests/test_std.rs | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 1c834a74e..d8b5351ec 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -37,12 +37,9 @@ pub fn combinations(iter: I, k: usize) -> Combinations where I: Iterator, { - let mut pool = LazyBuffer::new(iter); - pool.prefill(k); - Combinations { indices: (0..k).collect(), - pool, + pool: LazyBuffer::new(iter), first: true, } } @@ -107,6 +104,7 @@ where type Item = Vec; fn next(&mut self) -> Option { if self.first { + self.pool.prefill(self.k()); if self.k() > self.n() { return None; } diff --git a/tests/test_std.rs b/tests/test_std.rs index 732790ef4..732be7b7d 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1044,7 +1044,7 @@ fn combinations_inexact_size_hints() { let len = binomial(real_n, k); assert_eq!(len, it.clone().count()); - let mut nb_loaded = numbers.by_ref().take(k).count(); // because of `LazyBuffer::prefill(k)` + let mut nb_loaded = 0; let sh = numbers.size_hint(); assert_eq!(binomial(sh.0 + nb_loaded, k), it.size_hint().0); assert_eq!(sh.1.map(|n| binomial(n + nb_loaded, k)), it.size_hint().1); @@ -1053,8 +1053,10 @@ fn combinations_inexact_size_hints() { let elem = it.next(); assert!(elem.is_some()); assert_eq!(len - next_count, it.clone().count()); - // It does not load anything more the very first time (it's prefilled). - if next_count > 1 { + if next_count == 1 { + // The very first time, the lazy buffer is prefilled. + nb_loaded = numbers.by_ref().take(k).count(); + } else { // Then it loads one item each time until exhausted. let nb = numbers.next(); if nb.is_some() { From 92aa4cf99d7922fe5614de514cd75103b6dbebcd Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 19 Oct 2023 23:51:45 +0200 Subject: [PATCH 377/633] Add macro `bench_specializations` --- Cargo.toml | 4 ++ benches/specializations.rs | 130 +++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 benches/specializations.rs diff --git a/Cargo.toml b/Cargo.toml index 2a023a1f9..69f653fa8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,3 +71,7 @@ harness = false [[bench]] name = "powerset" harness = false + +[[bench]] +name = "specializations" +harness = false diff --git a/benches/specializations.rs b/benches/specializations.rs new file mode 100644 index 000000000..86b02dadf --- /dev/null +++ b/benches/specializations.rs @@ -0,0 +1,130 @@ +use criterion::black_box; +use itertools::Itertools; + +/// Create multiple functions each defining a benchmark group about iterator methods. +/// +/// Each created group has functions with the following ids: +/// +/// - `next`, `size_hint`, `count`, `last`, `nth`, `collect`, `fold` +/// - and when marked as `DoubleEndedIterator`: `next_back`, `rfold` +/// - and when marked as `ExactSizeIterator`: `len` +/// +/// Note that this macro can be called only once. +macro_rules! bench_specializations { + ( + $( + $name:ident { + $($extra:ident)* + {$( + $init:stmt; + )*} + $iterator:expr + } + )* + ) => { + $( + fn $name(c: &mut ::criterion::Criterion) { + let mut bench_group = c.benchmark_group(stringify!($name)); + $( + $init + )* + let bench_first_its = { + let mut bench_idx = 0; + [0; 1000].map(|_| { + let mut it = $iterator; + if bench_idx != 0 { + it.nth(bench_idx - 1); + } + bench_idx += 1; + it + }) + }; + bench_specializations!(@Iterator bench_group bench_first_its: $iterator); + $( + bench_specializations!(@$extra bench_group bench_first_its: $iterator); + )* + bench_group.finish(); + } + )* + + ::criterion::criterion_group!(benches, $($name, )*); + ::criterion::criterion_main!(benches); + }; + + (@Iterator $group:ident $first_its:ident: $iterator:expr) => { + $group.bench_function("next", |bencher| bencher.iter(|| { + let mut it = $iterator; + while let Some(x) = it.next() { + black_box(x); + } + })); + $group.bench_function("size_hint", |bencher| bencher.iter(|| { + $first_its.iter().for_each(|it| { + black_box(it.size_hint()); + }) + })); + $group.bench_function("count", |bencher| bencher.iter(|| { + $iterator.count() + })); + $group.bench_function("last", |bencher| bencher.iter(|| { + $iterator.last() + })); + $group.bench_function("nth", |bencher| bencher.iter(|| { + for start in 0_usize..10 { + for n in 0..10 { + let mut it = $iterator; + if let Some(s) = start.checked_sub(1) { + black_box(it.nth(s)); + } + while let Some(x) = it.nth(n) { + black_box(x); + } + } + } + })); + $group.bench_function("collect", |bencher| bencher.iter(|| { + $iterator.collect::>() + })); + $group.bench_function("fold", |bencher| bencher.iter(|| { + $iterator.fold((), |(), x| { + black_box(x); + }) + })); + }; + + (@DoubleEndedIterator $group:ident $_first_its:ident: $iterator:expr) => { + $group.bench_function("next_back", |bencher| bencher.iter(|| { + let mut it = $iterator; + while let Some(x) = it.next_back() { + black_box(x); + } + })); + $group.bench_function("rfold", |bencher| bencher.iter(|| { + $iterator.rfold((), |(), x| { + black_box(x); + }) + })); + }; + + (@ExactSizeIterator $group:ident $first_its:ident: $_iterator:expr) => { + $group.bench_function("len", |bencher| bencher.iter(|| { + $first_its.iter().for_each(|it| { + black_box(it.len()); + }) + })); + }; +} + +// Example: To bench only `ZipLongest::fold`, you can do +// cargo bench --bench specializations zip_longest/fold +bench_specializations! { + zip_longest { + DoubleEndedIterator + ExactSizeIterator + { + let xs = black_box(vec![0; 1024]); + let ys = black_box(vec![0; 768]); + } + xs.iter().zip_longest(ys.iter()) + } +} From 55c97cecde6915745364e7a1f453eea6dbaef8e6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 27 Oct 2023 11:27:23 +0200 Subject: [PATCH 378/633] Move `fold` specializations away from "bench1" --- benches/bench1.rs | 106 +------------------------------------ benches/specializations.rs | 33 ++++++++++++ 2 files changed, 34 insertions(+), 105 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 84caf88bc..0946affe5 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,8 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use itertools::free::cloned; +use itertools::iproduct; use itertools::Itertools; -use itertools::Position; -use itertools::{iproduct, EitherOrBoth}; use std::cmp; use std::iter::repeat; @@ -391,25 +390,6 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) { }); } -fn ziplongest(c: &mut Criterion) { - let v1 = black_box((0..768).collect_vec()); - let v2 = black_box((0..1024).collect_vec()); - c.bench_function("ziplongest", move |b| { - b.iter(|| { - let zip = v1.iter().zip_longest(v2.iter()); - let sum = zip.fold(0u32, |mut acc, val| { - match val { - EitherOrBoth::Both(x, y) => acc += x * y, - EitherOrBoth::Left(x) => acc += x, - EitherOrBoth::Right(y) => acc += y, - } - acc - }); - sum - }) - }); -} - fn group_by_lazy_1(c: &mut Criterion) { let mut data = vec![0; 1024]; for (index, elt) in data.iter_mut().enumerate() { @@ -448,25 +428,6 @@ fn group_by_lazy_2(c: &mut Criterion) { }); } -fn while_some(c: &mut Criterion) { - c.bench_function("while_some", |b| { - b.iter(|| { - let data = black_box( - (0..) - .fuse() - .map(|i| std::char::from_digit(i, 16)) - .while_some(), - ); - // let result: String = data.fold(String::new(), |acc, ch| acc + &ch.to_string()); - let result = data.fold(String::new(), |mut acc, ch| { - acc.push(ch); - acc - }); - assert_eq!(result.as_str(), "0123456789abcdef"); - }); - }); -} - fn slice_chunks(c: &mut Criterion) { let data = vec![0; 1024]; @@ -703,22 +664,6 @@ fn cartesian_product_iterator(c: &mut Criterion) { }); } -fn cartesian_product_fold(c: &mut Criterion) { - let xs = vec![0; 16]; - - c.bench_function("cartesian product fold", move |b| { - b.iter(|| { - let mut sum = 0; - iproduct!(&xs, &xs, &xs).fold((), |(), (&x, &y, &z)| { - sum += x; - sum += y; - sum += z; - }); - sum - }) - }); -} - fn multi_cartesian_product_iterator(c: &mut Criterion) { let xs = [vec![0; 16], vec![0; 16], vec![0; 16]]; @@ -735,22 +680,6 @@ fn multi_cartesian_product_iterator(c: &mut Criterion) { }); } -fn multi_cartesian_product_fold(c: &mut Criterion) { - let xs = [vec![0; 16], vec![0; 16], vec![0; 16]]; - - c.bench_function("multi cartesian product fold", move |b| { - b.iter(|| { - let mut sum = 0; - xs.iter().multi_cartesian_product().fold((), |(), x| { - sum += x[0]; - sum += x[1]; - sum += x[2]; - }); - sum - }) - }); -} - fn cartesian_product_nested_for(c: &mut Criterion) { let xs = vec![0; 16]; @@ -839,33 +768,6 @@ fn permutations_slice(c: &mut Criterion) { }); } -fn with_position_fold(c: &mut Criterion) { - let v = black_box((0..10240).collect_vec()); - c.bench_function("with_position fold", move |b| { - b.iter(|| { - v.iter() - .with_position() - .fold(0, |acc, (pos, &item)| match pos { - Position::Middle => acc + item, - Position::First => acc - 2 * item, - Position::Last => acc + 2 * item, - Position::Only => acc + 5 * item, - }) - }) - }); -} - -fn tuple_combinations_fold(c: &mut Criterion) { - let v = black_box((0..64).collect_vec()); - c.bench_function("tuple_combinations fold", move |b| { - b.iter(|| { - v.iter() - .tuple_combinations() - .fold(0, |acc, (a, b, c, d)| acc + *a * *c - *b * *d) - }) - }); -} - criterion_group!( benches, slice_iter, @@ -887,7 +789,6 @@ criterion_group!( zipdot_i32_unchecked_counted_loop, zipdot_f32_unchecked_counted_loop, zip_unchecked_counted_loop3, - ziplongest, group_by_lazy_1, group_by_lazy_2, slice_chunks, @@ -903,9 +804,7 @@ criterion_group!( step_range_2, step_range_10, cartesian_product_iterator, - cartesian_product_fold, multi_cartesian_product_iterator, - multi_cartesian_product_fold, cartesian_product_nested_for, all_equal, all_equal_for, @@ -913,8 +812,5 @@ criterion_group!( permutations_iter, permutations_range, permutations_slice, - with_position_fold, - tuple_combinations_fold, - while_some, ); criterion_main!(benches); diff --git a/benches/specializations.rs b/benches/specializations.rs index 86b02dadf..b98b7e5eb 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -1,4 +1,5 @@ use criterion::black_box; +use itertools::iproduct; use itertools::Itertools; /// Create multiple functions each defining a benchmark group about iterator methods. @@ -118,6 +119,38 @@ macro_rules! bench_specializations { // Example: To bench only `ZipLongest::fold`, you can do // cargo bench --bench specializations zip_longest/fold bench_specializations! { + cartesian_product { + { + let v = black_box(vec![0; 16]); + } + iproduct!(&v, &v, &v) + } + multi_cartesian_product { + { + let vs = black_box([0; 3].map(|_| vec![0; 16])); + } + vs.iter().multi_cartesian_product() + } + tuple_combinations { + { + let v = black_box((0..64).collect_vec()); + } + v.iter().tuple_combinations::<(_, _, _, _)>() + } + while_some { + {} + (0..) + .map(black_box) + .map(|i| char::from_digit(i, 16)) + .while_some() + } + with_position { + ExactSizeIterator + { + let v = black_box((0..10240).collect_vec()); + } + v.iter().with_position() + } zip_longest { DoubleEndedIterator ExactSizeIterator From 9d84fe18afec3e163060689d849dd7b718741ced Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 30 Oct 2023 11:59:38 +0100 Subject: [PATCH 379/633] Benchmark `nth_back` specializations too Similar to `nth` benchmark. With this, all iterator method specializations are considered except for methods that rely on `[r]fold/try_[r]fold` by default. We might want to add others later. --- benches/specializations.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/benches/specializations.rs b/benches/specializations.rs index b98b7e5eb..139270897 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -100,6 +100,19 @@ macro_rules! bench_specializations { black_box(x); } })); + $group.bench_function("nth_back", |bencher| bencher.iter(|| { + for start in 0_usize..10 { + for n in 0..10 { + let mut it = $iterator; + if let Some(s) = start.checked_sub(1) { + black_box(it.nth_back(s)); + } + while let Some(x) = it.nth_back(n) { + black_box(x); + } + } + } + })); $group.bench_function("rfold", |bencher| bencher.iter(|| { $iterator.rfold((), |(), x| { black_box(x); From fc35bceeca67816024368beeeab4ebf1c2439771 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 6 Nov 2023 18:43:29 +0100 Subject: [PATCH 380/633] Clonable `TakeWhileInclusive` --- src/take_while_inclusive.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index 46c7a9ba8..5207d8a01 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -8,6 +8,7 @@ use std::fmt; /// See [`.take_while_inclusive()`](crate::Itertools::take_while_inclusive) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[derive(Clone)] pub struct TakeWhileInclusive { iter: I, predicate: F, From 08264c2573f85eade4820fcf58577f243819cd3f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 6 Nov 2023 18:43:51 +0100 Subject: [PATCH 381/633] Clonable `FilterMapOk` --- src/adaptors/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 7d05a70b3..435593749 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -935,6 +935,7 @@ where /// /// See [`.filter_map_ok()`](crate::Itertools::filter_map_ok) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[derive(Clone)] pub struct FilterMapOk { iter: I, f: F, From 5002f91bb3d5edb0646ddf812351b21a0e0ac9dc Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 6 Nov 2023 18:45:05 +0100 Subject: [PATCH 382/633] Clonable `Duplicates[By]` `private::{DuplicatesBy, Meta}` both derive `Clone` but it's not enough for `DuplicatesBy` and `Duplicates` to derive it as well, because `ById` and `ByFn` do not yet. I think users are do not want to clone it as it would clone a `HashMap` and it would probably be slow. However, I need that to add specialization tests. --- src/duplicates_impl.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index a80316a7c..71ed21841 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -122,7 +122,7 @@ mod private { } /// Apply the identity function to elements before checking them for equality. - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct ById; impl KeyMethod for ById { type Container = JustValue; @@ -133,6 +133,7 @@ mod private { } /// Apply a user-supplied function to elements before checking them for equality. + #[derive(Clone)] pub struct ByFn(pub(crate) F); impl fmt::Debug for ByFn { debug_fmt_fields!(ByFn,); From 0d3e3f34202276a2194e693a0a7e0f54d27e792d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 6 Nov 2023 18:48:55 +0100 Subject: [PATCH 383/633] New specialization tests I do not want to add them one by one in future pull requests so this is done! --- tests/specializations.rs | 241 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 236 insertions(+), 5 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 3f5e10685..1063e80d0 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -1,7 +1,7 @@ #![allow(unstable_name_collisions)] use itertools::Itertools; -use quickcheck::quickcheck; +use quickcheck::{quickcheck, TestResult}; use std::fmt::Debug; struct Unspecialized(I); @@ -76,13 +76,110 @@ where } quickcheck! { + fn interleave(v: Vec, w: Vec) -> () { + test_specializations(&v.iter().interleave(w.iter())); + } + + fn interleave_shortest(v: Vec, w: Vec) -> () { + test_specializations(&v.iter().interleave_shortest(w.iter())); + } + + fn batching(v: Vec) -> () { + test_specializations(&v.iter().batching(Iterator::next)); + } + + fn tuple_windows(v: Vec) -> () { + test_specializations(&v.iter().tuple_windows::<(_,)>()); + test_specializations(&v.iter().tuple_windows::<(_, _)>()); + test_specializations(&v.iter().tuple_windows::<(_, _, _)>()); + } + + fn circular_tuple_windows(v: Vec) -> () { + test_specializations(&v.iter().circular_tuple_windows::<(_,)>()); + test_specializations(&v.iter().circular_tuple_windows::<(_, _)>()); + test_specializations(&v.iter().circular_tuple_windows::<(_, _, _)>()); + } + + fn tuples(v: Vec) -> () { + test_specializations(&v.iter().tuples::<(_,)>()); + test_specializations(&v.iter().tuples::<(_, _)>()); + test_specializations(&v.iter().tuples::<(_, _, _)>()); + } + + fn cartesian_product(a: Vec, b: Vec) -> TestResult { + if a.len() * b.len() > 100 { + return TestResult::discard(); + } + test_specializations(&a.iter().cartesian_product(&b)); + TestResult::passed() + } + + fn coalesce(v: Vec) -> () { + test_specializations(&v.iter().coalesce(|x, y| if x == y { Ok(x) } else { Err((x, y)) })) + } + + fn dedup(v: Vec) -> () { + test_specializations(&v.iter().dedup()) + } + + fn dedup_by(v: Vec) -> () { + test_specializations(&v.iter().dedup_by(PartialOrd::ge)) + } + + fn dedup_with_count(v: Vec) -> () { + test_specializations(&v.iter().dedup_with_count()) + } + + fn dedup_by_with_count(v: Vec) -> () { + test_specializations(&v.iter().dedup_by_with_count(PartialOrd::ge)) + } + + fn duplicates(v: Vec) -> () { + test_specializations(&v.iter().duplicates()); + } + + fn duplicates_by(v: Vec) -> () { + test_specializations(&v.iter().duplicates_by(|x| *x % 10)); + } + + fn unique(v: Vec) -> () { + test_specializations(&v.iter().unique()); + } + + fn unique_by(v: Vec) -> () { + test_specializations(&v.iter().unique_by(|x| *x % 50)); + } + + fn take_while_inclusive(v: Vec) -> () { + test_specializations(&v.iter().copied().take_while_inclusive(|&x| x < 100)); + } + + fn while_some(v: Vec) -> () { + test_specializations(&v.iter().map(|&x| if x < 100 { Some(2 * x) } else { None }).while_some()); + } + + fn pad_using(v: Vec) -> () { + use std::convert::TryFrom; + test_specializations(&v.iter().copied().pad_using(10, |i| u8::try_from(5 * i).unwrap_or(u8::MAX))); + } + fn with_position(v: Vec) -> () { test_specializations(&v.iter().with_position()); } + fn positions(v: Vec) -> () { + test_specializations(&v.iter().positions(|x| x % 5 == 0)); + } + + fn update(v: Vec) -> () { + test_specializations(&v.iter().copied().update(|x| *x = x.wrapping_mul(7))); + } + fn tuple_combinations(v: Vec) -> () { let mut v = v; v.truncate(10); + test_specializations(&v.iter().tuple_combinations::<(_,)>()); + test_specializations(&v.iter().tuple_combinations::<(_, _)>()); test_specializations(&v.iter().tuple_combinations::<(_, _, _)>()); } @@ -90,6 +187,34 @@ quickcheck! { test_specializations(&v.into_iter().intersperse(0)); } + fn intersperse_with(v: Vec) -> () { + test_specializations(&v.into_iter().intersperse_with(|| 0)); + } + + fn combinations(a: Vec, n: u8) -> TestResult { + if n > 3 || a.len() > 8 { + return TestResult::discard(); + } + test_specializations(&a.iter().combinations(n as usize)); + TestResult::passed() + } + + fn combinations_with_replacement(a: Vec, n: u8) -> TestResult { + if n > 3 || a.len() > 7 { + return TestResult::discard(); + } + test_specializations(&a.iter().combinations_with_replacement(n as usize)); + TestResult::passed() + } + + fn permutations(a: Vec, n: u8) -> TestResult { + if n > 3 || a.len() > 8 { + return TestResult::discard(); + } + test_specializations(&a.iter().permutations(n as usize)); + TestResult::passed() + } + fn powerset(a: Vec) -> () { let mut a = a; a.truncate(6); @@ -99,6 +224,27 @@ quickcheck! { fn zip_longest(a: Vec, b: Vec) -> () { test_specializations(&a.into_iter().zip_longest(b)) } + + fn zip_eq(a: Vec) -> () { + test_specializations(&a.iter().zip_eq(a.iter().rev())) + } + + fn multizip(a: Vec) -> () { + let its = (a.iter(), a.iter().rev(), a.iter().take(50)); + test_specializations(&itertools::multizip(its)); + } + + fn izip(a: Vec, b: Vec) -> () { + test_specializations(&itertools::izip!(b.iter(), a, b.iter().rev())); + } + + fn iproduct(a: Vec, b: Vec, c: Vec) -> TestResult { + if a.len() * b.len() * c.len() > 200 { + return TestResult::discard(); + } + test_specializations(&itertools::iproduct!(a, b.iter(), c)); + TestResult::passed() + } } quickcheck! { @@ -108,11 +254,85 @@ quickcheck! { pb.put_back(1); test_specializations(&pb); } + + fn put_back_n(v: Vec, n: u8) -> () { + let mut it = itertools::put_back_n(v); + for k in 0..n { + it.put_back(k); + } + test_specializations(&it); + } + + fn multipeek(v: Vec, n: u8) -> () { + let mut it = v.into_iter().multipeek(); + for _ in 0..n { + it.peek(); + } + test_specializations(&it); + } + + fn peek_nth_with_peek(v: Vec, n: u8) -> () { + let mut it = itertools::peek_nth(v); + for _ in 0..n { + it.peek(); + } + test_specializations(&it); + } + + fn peek_nth_with_peek_nth(v: Vec, n: u8) -> () { + let mut it = itertools::peek_nth(v); + it.peek_nth(n as usize); + test_specializations(&it); + } + + fn peek_nth_with_peek_mut(v: Vec, n: u8) -> () { + let mut it = itertools::peek_nth(v); + for _ in 0..n { + if let Some(x) = it.peek_mut() { + *x = x.wrapping_add(50); + } + } + test_specializations(&it); + } + + fn peek_nth_with_peek_nth_mut(v: Vec, n: u8) -> () { + let mut it = itertools::peek_nth(v); + if let Some(x) = it.peek_nth_mut(n as usize) { + *x = x.wrapping_add(50); + } + test_specializations(&it); + } } quickcheck! { - fn merge_join_by_qc(i1: Vec, i2: Vec) -> () { - test_specializations(&i1.into_iter().merge_join_by(i2.into_iter(), std::cmp::Ord::cmp)); + fn merge(a: Vec, b: Vec) -> () { + test_specializations(&a.into_iter().merge(b)) + } + + fn merge_by(a: Vec, b: Vec) -> () { + test_specializations(&a.into_iter().merge_by(b, PartialOrd::ge)) + } + + fn merge_join_by_ordering(i1: Vec, i2: Vec) -> () { + test_specializations(&i1.into_iter().merge_join_by(i2, Ord::cmp)); + } + + fn merge_join_by_bool(i1: Vec, i2: Vec) -> () { + test_specializations(&i1.into_iter().merge_join_by(i2, PartialOrd::ge)); + } + + fn kmerge(a: Vec, b: Vec, c: Vec) -> () { + test_specializations(&vec![a, b, c] + .into_iter() + .map(|v| v.into_iter().sorted()) + .kmerge()); + } + + fn kmerge_by(a: Vec, b: Vec, c: Vec) -> () { + test_specializations(&vec![a, b, c] + .into_iter() + .map(|v| v.into_iter().sorted_by_key(|a| a.abs())) + .kmerge_by(|a, b| a.abs() < b.abs())); } } @@ -120,12 +340,23 @@ quickcheck! { fn map_into(v: Vec) -> () { test_specializations(&v.into_iter().map_into::()); } -} -quickcheck! { fn map_ok(v: Vec>) -> () { test_specializations(&v.into_iter().map_ok(|u| u.checked_add(1))); } + + fn filter_ok(v: Vec>) -> () { + test_specializations(&v.into_iter().filter_ok(|&i| i < 20)); + } + + fn filter_map_ok(v: Vec>) -> () { + test_specializations(&v.into_iter().filter_map_ok(|i| if i < 20 { Some(i * 2) } else { None })); + } + + // `Option` because `Vec` would be very slow!! And we can't give `[u8; 3]`. + fn flatten_ok(v: Vec, char>>) -> () { + test_specializations(&v.into_iter().flatten_ok()); + } } quickcheck! { From 11863b42153fc6bfc14d1c6de07a671c06bfe38a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 12 Nov 2023 11:34:43 +0100 Subject: [PATCH 384/633] quickcheck: discard inputs rather than truncate them For tests to not be slow, we sometimes need small enough inputs but I should not have truncated inputs previously. Thanks to jswrenn, I now know that the right quickcheck-way is to discard some inputs (it then asks for other inputs). --- tests/specializations.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 1063e80d0..30e2082e4 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -175,12 +175,14 @@ quickcheck! { test_specializations(&v.iter().copied().update(|x| *x = x.wrapping_mul(7))); } - fn tuple_combinations(v: Vec) -> () { - let mut v = v; - v.truncate(10); + fn tuple_combinations(v: Vec) -> TestResult { + if v.len() > 10 { + return TestResult::discard(); + } test_specializations(&v.iter().tuple_combinations::<(_,)>()); test_specializations(&v.iter().tuple_combinations::<(_, _)>()); test_specializations(&v.iter().tuple_combinations::<(_, _, _)>()); + TestResult::passed() } fn intersperse(v: Vec) -> () { @@ -215,10 +217,12 @@ quickcheck! { TestResult::passed() } - fn powerset(a: Vec) -> () { - let mut a = a; - a.truncate(6); - test_specializations(&a.iter().powerset()) + fn powerset(a: Vec) -> TestResult { + if a.len() > 6 { + return TestResult::discard(); + } + test_specializations(&a.iter().powerset()); + TestResult::passed() } fn zip_longest(a: Vec, b: Vec) -> () { From e44dd53ad6d2e2ff1b70aa7ec3c0bb378514883b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 12 Nov 2023 12:24:45 +0100 Subject: [PATCH 385/633] Test `MultiProduct` specializations --- tests/specializations.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index 30e2082e4..fe14234d6 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -114,6 +114,15 @@ quickcheck! { TestResult::passed() } + #[ignore] // It currently fails because `MultiProduct` is not fused. + fn multi_cartesian_product(a: Vec, b: Vec, c: Vec) -> TestResult { + if a.len() * b.len() * c.len() > 100 { + return TestResult::discard(); + } + test_specializations(&vec![a, b, c].into_iter().multi_cartesian_product()); + TestResult::passed() + } + fn coalesce(v: Vec) -> () { test_specializations(&v.iter().coalesce(|x, y| if x == y { Ok(x) } else { Err((x, y)) })) } From 9c302ce411aff70a03e29a4bdbf3587bc141ee2b Mon Sep 17 00:00:00 2001 From: chris-ha458 Date: Mon, 6 Nov 2023 18:40:53 +0900 Subject: [PATCH 386/633] implement a free `sorted_unstable` - based on free `sorted` --- src/free.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/free.rs b/src/free.rs index 6de0a0daf..5ce1b49b3 100644 --- a/src/free.rs +++ b/src/free.rs @@ -295,3 +295,22 @@ where { iterable.into_iter().sorted() } + +/// Sort all iterator elements into a new iterator in ascending order. +/// This sort is unstable (i.e., may reorder equal elements). +/// [`IntoIterator`] enabled version of [`Itertools::sorted_unstable`]. +/// +/// ``` +/// use itertools::sorted_unstable; +/// use itertools::assert_equal; +/// +/// assert_equal(sorted_unstable("rust".chars()), "rstu".chars()); +/// ``` +#[cfg(feature = "use_alloc")] +pub fn sorted_unstable(iterable: I) -> VecIntoIter +where + I: IntoIterator, + I::Item: Ord, +{ + iterable.into_iter().sorted_unstable() +} From 955927f6c424f895ad7519d413bc5718e6ad26bf Mon Sep 17 00:00:00 2001 From: RobWalt Date: Fri, 20 Oct 2023 21:10:32 +0200 Subject: [PATCH 387/633] chore: fixup docs of tree_fold1 --- src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index c8422f531..2a450bd8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2499,7 +2499,8 @@ pub trait Itertools: Iterator { /// └─f─f─f─f─f─f /// ``` /// - /// If `f` is associative, prefer the normal [`Iterator::reduce`] instead. + /// If `f` is non-associative, prefer the normal [`Iterator::reduce`] instead to prevent + /// unexpected behavior unless you know what you're doing. /// /// ``` /// use itertools::Itertools; From b76172b412116356ebef05b884a6e4def63a4d17 Mon Sep 17 00:00:00 2001 From: aviac Date: Wed, 25 Oct 2023 21:02:17 +0200 Subject: [PATCH 388/633] chore: adjust docs to reflect discussion in the PR --- src/lib.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2a450bd8f..11b624d48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2499,8 +2499,18 @@ pub trait Itertools: Iterator { /// └─f─f─f─f─f─f /// ``` /// - /// If `f` is non-associative, prefer the normal [`Iterator::reduce`] instead to prevent - /// unexpected behavior unless you know what you're doing. + /// If `f` is associative you should also decide carefully: + /// + /// - if `f` is a trivial operation like `u32::wrapping_add`, prefer the normal + /// [`Iterator::reduce`] instead since it will most likely result in the generation of simpler + /// code because the compiler is able to optimize it + /// - otherwise if `f` is non-trivial like `format!`, you should use `tree_fold1` since it + /// reduces the number of operations from `O(n)` to `O(ln(n))` + /// + /// Here "non-trivial" means: + /// + /// - any allocating operation + /// - any function that is a composition of many operations /// /// ``` /// use itertools::Itertools; From 4f22173b93a2eb58da16b7da6d08e6c3f1c56544 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 7 Nov 2023 15:41:33 +0100 Subject: [PATCH 389/633] Refactor `IntersperseWith::next` --- src/intersperse.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/intersperse.rs b/src/intersperse.rs index 035a7a3e4..1b44ed484 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -78,15 +78,20 @@ where type Item = I::Item; #[inline] fn next(&mut self) -> Option { - if self.peek.is_some() { - self.peek.take() - } else { - self.peek = self.iter.next(); - if self.peek.is_some() { - Some(self.element.generate()) - } else { - None - } + let Self { + element, + iter, + peek, + } = self; + match peek { + item @ Some(_) => item.take(), + None => match iter.next() { + new @ Some(_) => { + *peek = new; + Some(element.generate()) + } + None => None, + }, } } From 9b01a118919f0d1f7c3327d1a15a5eb660f3912e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 7 Nov 2023 15:44:00 +0100 Subject: [PATCH 390/633] Make `IntersperseWith` lazy Similar to what is done by `core::iter::Peekable`, a nested option is now used. --- src/intersperse.rs | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/intersperse.rs b/src/intersperse.rs index 1b44ed484..9f68a12bf 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -54,7 +54,7 @@ where { element: ElemF, iter: Fuse, - peek: Option, + peek: Option>, } /// Create a new `IntersperseWith` iterator @@ -62,10 +62,9 @@ pub fn intersperse_with(iter: I, elt: ElemF) -> IntersperseWith item.take(), - None => match iter.next() { + Some(item @ Some(_)) => item.take(), + Some(None) => match iter.next() { new @ Some(_) => { - *peek = new; + *peek = Some(new); Some(element.generate()) } None => None, }, + None => { + *peek = Some(None); + iter.next() + } } } fn size_hint(&self) -> (usize, Option) { - // 2 * SH + { 1 or 0 } - let has_peek = self.peek.is_some() as usize; - let sh = self.iter.size_hint(); - size_hint::add_scalar(size_hint::add(sh, sh), has_peek) + let mut sh = self.iter.size_hint(); + sh = size_hint::add(sh, sh); + match self.peek { + Some(Some(_)) => size_hint::add_scalar(sh, 1), + Some(None) => sh, + None => size_hint::sub_scalar(sh, 1), + } } - fn fold(mut self, init: B, mut f: F) -> B + fn fold(self, init: B, mut f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { + let Self { + mut element, + mut iter, + peek, + } = self; let mut accum = init; - if let Some(x) = self.peek.take() { + if let Some(x) = peek.unwrap_or_else(|| iter.next()) { accum = f(accum, x); } - let element = &mut self.element; - - self.iter.fold(accum, |accum, x| { + iter.fold(accum, |accum, x| { let accum = f(accum, element.generate()); f(accum, x) }) From d7e6bab9fd0ad79130692f8e48e21375362a7614 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 13 Nov 2023 18:26:45 +0100 Subject: [PATCH 391/633] Document the field `peek` of `IntersperseWith` --- src/intersperse.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/intersperse.rs b/src/intersperse.rs index 9f68a12bf..5f4f7938a 100644 --- a/src/intersperse.rs +++ b/src/intersperse.rs @@ -54,6 +54,9 @@ where { element: ElemF, iter: Fuse, + /// `peek` is None while no item have been taken out of `iter` (at definition). + /// Then `peek` will alternatively be `Some(None)` and `Some(Some(item))`, + /// where `None` indicates it's time to generate from `element` (unless `iter` is empty). peek: Option>, } From 8d07f6b8566a515118ca8b119f358b73d483152b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 7 Nov 2023 10:40:53 +0100 Subject: [PATCH 392/633] Make `Product` lazy Similar to what is done by `core::iter::Peekable`, a nested option is now used. --- src/adaptors/mod.rs | 46 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 435593749..623272eae 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -308,7 +308,7 @@ where I: Iterator, { a: I, - a_cur: Option, + a_cur: Option>, b: J, b_orig: J, } @@ -316,14 +316,14 @@ where /// Create a new cartesian product iterator /// /// Iterator element type is `(I::Item, J::Item)`. -pub fn cartesian_product(mut i: I, j: J) -> Product +pub fn cartesian_product(i: I, j: J) -> Product where I: Iterator, J: Clone + Iterator, I::Item: Clone, { Product { - a_cur: i.next(), + a_cur: None, a: i, b: j.clone(), b_orig: j, @@ -339,24 +339,33 @@ where type Item = (I::Item, J::Item); fn next(&mut self) -> Option { - let elt_b = match self.b.next() { + let Self { + a, + a_cur, + b, + b_orig, + } = self; + let elt_b = match b.next() { None => { - self.b = self.b_orig.clone(); - match self.b.next() { + *b = b_orig.clone(); + match b.next() { None => return None, Some(x) => { - self.a_cur = self.a.next(); + *a_cur = Some(a.next()); x } } } Some(x) => x, }; - self.a_cur.as_ref().map(|a| (a.clone(), elt_b)) + a_cur + .get_or_insert_with(|| a.next()) + .as_ref() + .map(|a| (a.clone(), elt_b)) } fn size_hint(&self) -> (usize, Option) { - let has_cur = self.a_cur.is_some() as usize; + let has_cur = matches!(self.a_cur, Some(Some(_))) as usize; // Not ExactSizeIterator because size may be larger than usize let (b_min, b_max) = self.b.size_hint(); @@ -367,21 +376,26 @@ where ) } - fn fold(mut self, mut accum: Acc, mut f: G) -> Acc + fn fold(self, mut accum: Acc, mut f: G) -> Acc where G: FnMut(Acc, Self::Item) -> Acc, { // use a split loop to handle the loose a_cur as well as avoiding to // clone b_orig at the end. - if let Some(mut a) = self.a_cur.take() { - let mut b = self.b; + let Self { + mut a, + a_cur, + mut b, + b_orig, + } = self; + if let Some(mut elt_a) = a_cur.unwrap_or_else(|| a.next()) { loop { - accum = b.fold(accum, |acc, elt| f(acc, (a.clone(), elt))); + accum = b.fold(accum, |acc, elt| f(acc, (elt_a.clone(), elt))); // we can only continue iterating a if we had a first element; - if let Some(next_a) = self.a.next() { - b = self.b_orig.clone(); - a = next_a; + if let Some(next_elt_a) = a.next() { + b = b_orig.clone(); + elt_a = next_elt_a; } else { break; } From bf2b0129d1d3cc1ffa733059f3088adb6d745fe6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 7 Nov 2023 10:59:08 +0100 Subject: [PATCH 393/633] Better `Product::size_hint` `b` size hint was computed even when not needed. --- src/adaptors/mod.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 623272eae..e3b6d0f28 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -365,15 +365,13 @@ where } fn size_hint(&self) -> (usize, Option) { - let has_cur = matches!(self.a_cur, Some(Some(_))) as usize; // Not ExactSizeIterator because size may be larger than usize - let (b_min, b_max) = self.b.size_hint(); - // Compute a * b_orig + b for both lower and upper bound - size_hint::add( - size_hint::mul(self.a.size_hint(), self.b_orig.size_hint()), - (b_min * has_cur, b_max.map(move |x| x * has_cur)), - ) + let mut sh = size_hint::mul(self.a.size_hint(), self.b_orig.size_hint()); + if matches!(self.a_cur, Some(Some(_))) { + sh = size_hint::add(sh, self.b.size_hint()); + } + sh } fn fold(self, mut accum: Acc, mut f: G) -> Acc From 6d291786a9c9686a3997d93c513bd18326611fe5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 13 Nov 2023 19:11:59 +0100 Subject: [PATCH 394/633] Document the field `a_cur` of `Product` --- src/adaptors/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index e3b6d0f28..e925db51d 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -308,6 +308,9 @@ where I: Iterator, { a: I, + /// `a_cur` is `None` while no item have been taken out of `a` (at definition). + /// Then `a_cur` will be `Some(Some(item))` until `a` is exhausted, + /// in which case `a_cur` will be `Some(None)`. a_cur: Option>, b: J, b_orig: J, From 22fc427ac5282cbdafccfe38a686ec1d3b720120 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 14 Nov 2023 12:38:11 +0000 Subject: [PATCH 395/633] prepare v0.12.0 release --- CHANGELOG.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7404e75..36fc27459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,54 @@ # Changelog +## 0.12.0 + +### Breaking +- Made `take_while_inclusive` consume iterator by value (#709) +- Added `Clone` bound to `Unique` (#777) + +### Added +- Added `Itertools::try_len` (#723) +- Added free function `sort_unstable` (#796) +- Added `GroupMap::fold_with` (#778, #785) +- Added `PeekNth::{peek_mut, peek_nth_mut}` (#716) +- Added `PeekNth::{next_if, next_if_eq}` (#734) +- Added conversion into `(Option,Option)` to `EitherOrBoth` (#713) +- Added conversion from `Either` to `EitherOrBoth` (#715) +- Implemented `ExactSizeIterator` for `Tuples` (#761) +- Implemented `ExactSizeIterator` for `(Circular)TupleWindows` (#752) +- Made `EitherOrBoth` a shorthand for `EitherOrBoth` (#719) + +### Changed +- Added missing `#[must_use]` annotations on iterator adaptors (#794) +- Made `Combinations` lazy (#795) +- Made `Intersperse(With)` lazy (#797) +- Made `Permutations` lazy (#793) +- Made `Product` lazy (#800) +- Made `TupleWindows` lazy (#602) +- Specialized `Combinations::{count, size_hint}` (#729) +- Specialized `CombinationsWithReplacement::{count, size_hint}` (#737) +- Specialized `Powerset::fold` (#765) +- Specialized `Powerset::count` (#735) +- Specialized `TupleCombinations::{count, size_hint}` (#763) +- Specialized `TupleCombinations::fold` (#775) +- Specialized `WhileSome::fold` (#780) +- Specialized `WithPosition::fold` (#772) +- Specialized `ZipLongest::fold` (#774) +- Changed `{min, max}_set*` operations require `alloc` feature, instead of `std` (#760) +- Improved documentation of `tree_fold1` (#787) +- Improved documentation of `permutations` (#724) +- Fixed typo in documentation of `multiunzip` (#770) + +### Notable Internal Changes +- Improved specialization tests (#799, #786, #782) +- Simplified implementation of `Permutations` (#739, #748, #790) +- Combined `Merge`/`MergeBy`/`MergeJoinBy` implementations (#736) +- Simplified `Permutations::size_hint` (#739) +- Fix wrapping arithmetic in benchmarks (#770) +- Enforced `rustfmt` in CI (#751) +- Disallowed compile warnings in CI (#720) +- Used `cargo hack` to check MSRV (#754) + ## 0.11.0 ### Breaking diff --git a/README.md b/README.md index 626d10d0d..e1c3f721d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.11.0" +itertools = "0.12.0" ``` How to use in your crate: From 98ecabb47d7147dae06fc3fa400ec758947194f3 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 14 Nov 2023 19:25:12 +0000 Subject: [PATCH 396/633] chore: Release itertools version 0.12.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 69f653fa8..ed4e3784d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.11.0" +version = "0.12.0" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" From c116c200f97b39d521285e93e06f00ce9093bf40 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 14 Nov 2023 20:31:05 +0000 Subject: [PATCH 397/633] Remove `html_root_url` setting. As far as I can tell, setting it serves no purpose. Removing it eliminates a place where we need to remember to update teh crate version. --- src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 11b624d48..bdb436e48 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,7 +43,6 @@ //! ## Rust Version //! //! This version of itertools requires Rust 1.43.1 or later. -#![doc(html_root_url = "https://docs.rs/itertools/0.11/")] #[cfg(not(feature = "use_std"))] extern crate core as std; From 779015ea26bd9a06fd7398d84fa87b57798ebe69 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 15 Nov 2023 14:46:37 +0100 Subject: [PATCH 398/633] Delete bors.toml We do not use bors anymore. --- Cargo.toml | 1 - bors.toml | 3 --- 2 files changed, 4 deletions(-) delete mode 100644 bors.toml diff --git a/Cargo.toml b/Cargo.toml index ed4e3784d..f1ffe19cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,6 @@ description = "Extra iterator adaptors, iterator methods, free functions, and ma keywords = ["iterator", "data-structure", "zip", "product", "group-by"] categories = ["algorithms", "rust-patterns"] -exclude = ["/bors.toml"] edition = "2018" diff --git a/bors.toml b/bors.toml deleted file mode 100644 index 8d72225e7..000000000 --- a/bors.toml +++ /dev/null @@ -1,3 +0,0 @@ -status = [ -"bors build finished" -] From 76ebb25ff7b9144a9b87d40b6b7adc9fdacc490e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 15 Nov 2023 14:49:14 +0100 Subject: [PATCH 399/633] No CI if only for markdown changes There is no point in running CI only for markdown changes. Such as #802 (prepare releases) and #767 (Contributing.md) https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 83deb9e40..8f265ae2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,7 +2,11 @@ name: CI on: pull_request: + paths-ignore: + - "**.md" merge_group: + paths-ignore: + - "**.md" jobs: check: From fcc684c92b891efdb37eaf369efaafc7ea9a22fb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 25 Nov 2023 15:24:09 +0100 Subject: [PATCH 400/633] `CircularTupleWindows` does not need phantom data After I randomly took a look at this, I realized this field never was needed because `iter` type owns a `T`. I should have realized that last time I updated this struct. I checked other phantom data fields in the library but that's the only useless one. --- src/tuple_impl.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 2bd9a0413..9cfb484af 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::marker::PhantomData; use crate::size_hint; @@ -252,7 +251,6 @@ where { iter: TupleWindows, T>, len: usize, - phantom_data: PhantomData, } pub fn circular_tuple_windows(iter: I) -> CircularTupleWindows @@ -264,11 +262,7 @@ where let len = iter.len(); let iter = tuple_windows(iter.cycle()); - CircularTupleWindows { - iter, - len, - phantom_data: PhantomData {}, - } + CircularTupleWindows { iter, len } } impl Iterator for CircularTupleWindows From 5d925d2952aa5faed5d16dc95cb6bfe813f349fa Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 9 Nov 2023 15:23:20 +0100 Subject: [PATCH 401/633] `CoalesceBy`: manage counts differently In order to make `DedupByWithCount` lazy soon, I first need to be able to make `|v| (1, v)` more generic. --- src/adaptors/coalesce.rs | 62 +++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index 3df7cc582..316eb6f1d 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -4,26 +4,30 @@ use std::iter::FusedIterator; use crate::size_hint; #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct CoalesceBy +pub struct CoalesceBy where I: Iterator, + C: CountItem, { iter: I, - last: Option, + last: Option, f: F, } -impl Clone for CoalesceBy +impl Clone for CoalesceBy where I: Iterator, + C: CountItem, + C::CItem: Clone, { clone_fields!(last, iter, f); } -impl fmt::Debug for CoalesceBy +impl fmt::Debug for CoalesceBy where I: Iterator + fmt::Debug, - T: fmt::Debug, + C: CountItem, + C::CItem: fmt::Debug, { debug_fmt_fields!(CoalesceBy, iter); } @@ -32,12 +36,13 @@ pub trait CoalescePredicate { fn coalesce_pair(&mut self, t: T, item: Item) -> Result; } -impl Iterator for CoalesceBy +impl Iterator for CoalesceBy where I: Iterator, - F: CoalescePredicate, + F: CoalescePredicate, + C: CountItem, { - type Item = T; + type Item = C::CItem; fn next(&mut self) -> Option { // this fuses the iterator @@ -82,12 +87,43 @@ where } } -impl, T> FusedIterator for CoalesceBy {} +impl FusedIterator for CoalesceBy +where + I: Iterator, + F: CoalescePredicate, + C: CountItem, +{ +} + +pub struct NoCount; + +pub struct WithCount; + +pub trait CountItem { + type CItem; + fn new(t: T) -> Self::CItem; +} + +impl CountItem for NoCount { + type CItem = T; + #[inline(always)] + fn new(t: T) -> T { + t + } +} + +impl CountItem for WithCount { + type CItem = (usize, T); + #[inline(always)] + fn new(t: T) -> (usize, T) { + (1, t) + } +} /// An iterator adaptor that may join together adjacent elements. /// /// See [`.coalesce()`](crate::Itertools::coalesce) for more information. -pub type Coalesce = CoalesceBy::Item>; +pub type Coalesce = CoalesceBy; impl CoalescePredicate for F where @@ -113,7 +149,7 @@ where /// An iterator adaptor that removes repeated duplicates, determining equality using a comparison function. /// /// See [`.dedup_by()`](crate::Itertools::dedup_by) or [`.dedup()`](crate::Itertools::dedup) for more information. -pub type DedupBy = CoalesceBy, ::Item>; +pub type DedupBy = CoalesceBy, NoCount>; #[derive(Clone)] pub struct DedupPred2CoalescePred(DP); @@ -186,7 +222,7 @@ where /// See [`.dedup_by_with_count()`](crate::Itertools::dedup_by_with_count) or /// [`.dedup_with_count()`](crate::Itertools::dedup_with_count) for more information. pub type DedupByWithCount = - CoalesceBy, (usize, ::Item)>; + CoalesceBy, WithCount>; #[derive(Clone, Debug)] pub struct DedupPredWithCount2CoalescePred(DP); @@ -220,7 +256,7 @@ where I: Iterator, { DedupByWithCount { - last: iter.next().map(|v| (1, v)), + last: iter.next().map(WithCount::new), iter, f: DedupPredWithCount2CoalescePred(dedup_pred), } From 0ba7fd753676e9887253e41e1f55026856af520f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 9 Nov 2023 15:43:05 +0100 Subject: [PATCH 402/633] Make `CoalesceBy` lazy Similar to what is done by `core::iter::Peekable`, a nested option is now used. --- src/adaptors/coalesce.rs | 57 ++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index 316eb6f1d..f3abdef73 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -10,7 +10,7 @@ where C: CountItem, { iter: I, - last: Option, + last: Option>, f: F, } @@ -45,26 +45,33 @@ where type Item = C::CItem; fn next(&mut self) -> Option { + let Self { iter, last, f } = self; // this fuses the iterator - let last = self.last.take()?; + let init = match last { + Some(elt) => elt.take(), + None => { + *last = Some(None); + iter.next().map(C::new) + } + }?; - let self_last = &mut self.last; - let self_f = &mut self.f; Some( - self.iter - .try_fold(last, |last, next| match self_f.coalesce_pair(last, next) { - Ok(joined) => Ok(joined), - Err((last_, next_)) => { - *self_last = Some(next_); - Err(last_) - } - }) - .unwrap_or_else(|x| x), + iter.try_fold(init, |accum, next| match f.coalesce_pair(accum, next) { + Ok(joined) => Ok(joined), + Err((last_, next_)) => { + *last = Some(Some(next_)); + Err(last_) + } + }) + .unwrap_or_else(|x| x), ) } fn size_hint(&self) -> (usize, Option) { - let (low, hi) = size_hint::add_scalar(self.iter.size_hint(), self.last.is_some() as usize); + let (low, hi) = size_hint::add_scalar( + self.iter.size_hint(), + matches!(self.last, Some(Some(_))) as usize, + ); ((low > 0) as usize, hi) } @@ -72,9 +79,13 @@ where where FnAcc: FnMut(Acc, Self::Item) -> Acc, { - if let Some(last) = self.last { - let mut f = self.f; - let (last, acc) = self.iter.fold((last, acc), |(last, acc), elt| { + let Self { + mut iter, + last, + mut f, + } = self; + if let Some(last) = last.unwrap_or_else(|| iter.next().map(C::new)) { + let (last, acc) = iter.fold((last, acc), |(last, acc), elt| { match f.coalesce_pair(last, elt) { Ok(joined) => (joined, acc), Err((last_, next_)) => (next_, fn_acc(acc, last_)), @@ -135,12 +146,12 @@ where } /// Create a new `Coalesce`. -pub fn coalesce(mut iter: I, f: F) -> Coalesce +pub fn coalesce(iter: I, f: F) -> Coalesce where I: Iterator, { Coalesce { - last: iter.next(), + last: None, iter, f, } @@ -192,12 +203,12 @@ impl bool> DedupPredicate for F { } /// Create a new `DedupBy`. -pub fn dedup_by(mut iter: I, dedup_pred: Pred) -> DedupBy +pub fn dedup_by(iter: I, dedup_pred: Pred) -> DedupBy where I: Iterator, { DedupBy { - last: iter.next(), + last: None, iter, f: DedupPred2CoalescePred(dedup_pred), } @@ -251,12 +262,12 @@ where pub type DedupWithCount = DedupByWithCount; /// Create a new `DedupByWithCount`. -pub fn dedup_by_with_count(mut iter: I, dedup_pred: Pred) -> DedupByWithCount +pub fn dedup_by_with_count(iter: I, dedup_pred: Pred) -> DedupByWithCount where I: Iterator, { DedupByWithCount { - last: iter.next().map(WithCount::new), + last: None, iter, f: DedupPredWithCount2CoalescePred(dedup_pred), } From cdf12dfa88ab8ba4bf9ef051262bbb17e83205bf Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 13 Nov 2023 22:08:33 +0100 Subject: [PATCH 403/633] Document the field `last` of `CoalesceBy` This behavior is not obvious at first glance of `Iterator::next`. In it, after `let init =`, `last` is `Some(None)` in both cases. With `try_fold`, if `iter` is not exhausted then we have `*last = Some(Some(..))` (otherwise it remains `Some(None)`). --- src/adaptors/coalesce.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index f3abdef73..21eaa016d 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -10,6 +10,9 @@ where C: CountItem, { iter: I, + /// `last` is `None` while no item have been taken out of `iter` (at definition). + /// Then `last` will be `Some(Some(item))` until `iter` is exhausted, + /// in which case `last` will be `Some(None)`. last: Option>, f: F, } From 9fc2af30fed2eb7b8593ac9e77f979adc3e6f1e6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 13:52:05 +0100 Subject: [PATCH 404/633] New specialization benchmarks I do not want to add them one by one in future pull requests so this is (mostly) done! --- benches/specializations.rs | 351 +++++++++++++++++++++++++++++++++++++ 1 file changed, 351 insertions(+) diff --git a/benches/specializations.rs b/benches/specializations.rs index 139270897..75aad9ff2 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -1,3 +1,5 @@ +#![allow(unstable_name_collisions)] + use criterion::black_box; use itertools::iproduct; use itertools::Itertools; @@ -24,6 +26,7 @@ macro_rules! bench_specializations { )* ) => { $( + #[allow(unused_must_use)] fn $name(c: &mut ::criterion::Criterion) { let mut bench_group = c.benchmark_group(stringify!($name)); $( @@ -132,6 +135,59 @@ macro_rules! bench_specializations { // Example: To bench only `ZipLongest::fold`, you can do // cargo bench --bench specializations zip_longest/fold bench_specializations! { + interleave { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().interleave(&v2) + } + interleave_shortest { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().interleave_shortest(&v2) + } + batching { + { + let v = black_box(vec![0; 1024]); + } + v.iter().batching(Iterator::next) + } + tuple_windows { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuple_windows::<(_, _, _, _)>() + } + circular_tuple_windows { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().circular_tuple_windows::<(_, _, _, _)>() + } + tuples { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuples::<(_, _, _, _)>() + } + tuple_buffer { + ExactSizeIterator + { + let v = black_box(vec![0; 11]); + // Short but the buffer can't have 12 or more elements. + } + { + let mut it = v.iter().tuples::<(_, _, _, _, _, _, _, _, _, _, _, _)>(); + it.next(); // No element but it fills the buffer. + it.into_buffer() + } + } cartesian_product { { let v = black_box(vec![0; 16]); @@ -144,12 +200,137 @@ bench_specializations! { } vs.iter().multi_cartesian_product() } + coalesce { + { + let v = black_box(vec![0; 1024]); + } + v.iter().coalesce(|x, y| if x == y { Ok(x) } else { Err((x, y)) }) + } + dedup { + { + let v = black_box((0..32).flat_map(|x| [x; 32]).collect_vec()); + } + v.iter().dedup() + } + dedup_by { + { + let v = black_box((0..32).flat_map(|x| [x; 32]).collect_vec()); + } + v.iter().dedup_by(PartialOrd::ge) + } + dedup_with_count { + { + let v = black_box((0..32).flat_map(|x| [x; 32]).collect_vec()); + } + v.iter().dedup_with_count() + } + dedup_by_with_count { + { + let v = black_box((0..32).flat_map(|x| [x; 32]).collect_vec()); + } + v.iter().dedup_by_with_count(PartialOrd::ge) + } + duplicates { + DoubleEndedIterator + { + let v = black_box((0..32).cycle().take(1024).collect_vec()); + } + v.iter().duplicates() + } + duplicates_by { + DoubleEndedIterator + { + let v = black_box((0..1024).collect_vec()); + } + v.iter().duplicates_by(|x| *x % 10) + } + unique { + DoubleEndedIterator + { + let v = black_box((0..32).cycle().take(1024).collect_vec()); + } + v.iter().unique() + } + unique_by { + DoubleEndedIterator + { + let v = black_box((0..1024).collect_vec()); + } + v.iter().unique_by(|x| *x % 50) + } + take_while_inclusive { + { + let v = black_box((0..1024).collect_vec()); + } + v.iter().take_while_inclusive(|x| **x < 1000) + } + pad_using { + DoubleEndedIterator + ExactSizeIterator + { + let v = black_box((0..1024).collect_vec()); + } + v.iter().copied().pad_using(2048, |i| 5 * i) + } + positions { + DoubleEndedIterator + { + let v = black_box((0..1024).collect_vec()); + } + v.iter().positions(|x| x % 5 == 0) + } + update { + DoubleEndedIterator + ExactSizeIterator + { + let v = black_box((0_i32..1024).collect_vec()); + } + v.iter().copied().update(|x| *x *= 7) + } tuple_combinations { { let v = black_box((0..64).collect_vec()); } v.iter().tuple_combinations::<(_, _, _, _)>() } + intersperse { + { + let v = black_box(vec![0; 1024]); + let n = black_box(0); + } + v.iter().intersperse(&n) + } + intersperse_with { + { + let v = black_box(vec![0; 1024]); + let n = black_box(0); + } + v.iter().intersperse_with(|| &n) + } + combinations { + { + let v = black_box(vec![0; 16]); + } + v.iter().combinations(4) + } + combinations_with_replacement { + { + let v = black_box(vec![0; 16]); + } + v.iter().combinations_with_replacement(4) + } + permutations { + { + let v = black_box(vec![0; 8]); + } + v.iter().permutations(4) + } + powerset { + { + let v = black_box(vec![0; 10]); + } + v.iter().powerset() + } while_some { {} (0..) @@ -173,4 +354,174 @@ bench_specializations! { } xs.iter().zip_longest(ys.iter()) } + zip_eq { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().zip_eq(v.iter().rev()) + } + multizip { + DoubleEndedIterator + ExactSizeIterator + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + let v3 = black_box(vec![0; 2048]); + } + itertools::multizip((&v1, &v2, &v3)) + } + izip { + DoubleEndedIterator + ExactSizeIterator + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + let v3 = black_box(vec![0; 2048]); + } + itertools::izip!(&v1, &v2, &v3) + } + put_back { + { + let v = black_box(vec![0; 1024]); + } + itertools::put_back(&v).with_value(black_box(&0)) + } + put_back_n { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 16]); + } + { + let mut it = itertools::put_back_n(&v1); + for n in &v2 { + it.put_back(n); + } + it + } + } + exactly_one_error { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + // Use `at_most_one` would be similar. + v.iter().exactly_one().unwrap_err() + } + multipeek { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + let n = black_box(16); + } + { + let mut it = v.iter().multipeek(); + for _ in 0..n { + it.peek(); + } + it + } + } + peek_nth { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + let n = black_box(16); + } + { + let mut it = itertools::peek_nth(&v); + it.peek_nth(n); + it + } + } + repeat_n { + DoubleEndedIterator + ExactSizeIterator + {} + itertools::repeat_n(black_box(0), black_box(1024)) + } + merge { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().merge(&v2) + } + merge_by { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().merge_by(&v2, PartialOrd::ge) + } + merge_join_by_ordering { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().merge_join_by(&v2, Ord::cmp) + } + merge_join_by_bool { + { + let v1 = black_box(vec![0; 1024]); + let v2 = black_box(vec![0; 768]); + } + v1.iter().merge_join_by(&v2, PartialOrd::ge) + } + kmerge { + { + let vs = black_box(vec![vec![0; 1024], vec![0; 256], vec![0; 768]]); + } + vs.iter().kmerge() + } + kmerge_by { + { + let vs = black_box(vec![vec![0; 1024], vec![0; 256], vec![0; 768]]); + } + vs.iter().kmerge_by(PartialOrd::ge) + } + map_into { + DoubleEndedIterator + ExactSizeIterator + { + let v = black_box(vec![0_u8; 1024]); + } + v.iter().copied().map_into::() + } + map_ok { + DoubleEndedIterator + ExactSizeIterator + { + let v = black_box((0_u32..1024) + .map(|x| if x % 2 == 1 { Err(x) } else { Ok(x) }) + .collect_vec()); + } + v.iter().copied().map_ok(|x| x + 1) + } + filter_ok { + { + let v = black_box((0_u32..1024) + .map(|x| if x % 2 == 1 { Err(x) } else { Ok(x) }) + .collect_vec()); + } + v.iter().copied().filter_ok(|x| x % 3 == 0) + } + filter_map_ok { + { + let v = black_box((0_u32..1024) + .map(|x| if x % 2 == 1 { Err(x) } else { Ok(x) }) + .collect_vec()); + } + v.iter().copied().filter_map_ok(|x| if x % 3 == 0 { Some(x + 1) } else { None }) + } + flatten_ok { + DoubleEndedIterator + { + let d = black_box(vec![0; 8]); + let v = black_box((0..512) + .map(|x| if x % 2 == 0 { Ok(&d) } else { Err(x) }) + .collect_vec()); + } + v.iter().copied().flatten_ok() + } } From b559bb563cef06390b916760ea2e33358e2e8073 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 23 Nov 2023 15:55:25 +0100 Subject: [PATCH 405/633] Benchmark specializations and item lengths `tuple_windows, circular_tuple_windows, tuples, tuple_combinations, combinations, combinations_with_replacement, permutations` all generate elements of various fixed lengths. Benchmark against those lengths (here 1 2 3 4) might provide more insight. --- benches/specializations.rs | 151 +++++++++++++++++++++++++++++++++++-- 1 file changed, 143 insertions(+), 8 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 75aad9ff2..b3e7d0a9f 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -155,21 +155,84 @@ bench_specializations! { } v.iter().batching(Iterator::next) } - tuple_windows { + tuple_windows1 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuple_windows::<(_,)>() + } + tuple_windows2 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuple_windows::<(_, _)>() + } + tuple_windows3 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuple_windows::<(_, _, _)>() + } + tuple_windows4 { ExactSizeIterator { let v = black_box(vec![0; 1024]); } v.iter().tuple_windows::<(_, _, _, _)>() } - circular_tuple_windows { + circular_tuple_windows1 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().circular_tuple_windows::<(_,)>() + } + circular_tuple_windows2 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().circular_tuple_windows::<(_, _)>() + } + circular_tuple_windows3 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().circular_tuple_windows::<(_, _, _)>() + } + circular_tuple_windows4 { ExactSizeIterator { let v = black_box(vec![0; 1024]); } v.iter().circular_tuple_windows::<(_, _, _, _)>() } - tuples { + tuples1 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuples::<(_,)>() + } + tuples2 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuples::<(_, _)>() + } + tuples3 { + ExactSizeIterator + { + let v = black_box(vec![0; 1024]); + } + v.iter().tuples::<(_, _, _)>() + } + tuples4 { ExactSizeIterator { let v = black_box(vec![0; 1024]); @@ -287,9 +350,27 @@ bench_specializations! { } v.iter().copied().update(|x| *x *= 7) } - tuple_combinations { + tuple_combinations1 { { - let v = black_box((0..64).collect_vec()); + let v = black_box(vec![0; 1024]); + } + v.iter().tuple_combinations::<(_,)>() + } + tuple_combinations2 { + { + let v = black_box(vec![0; 64]); + } + v.iter().tuple_combinations::<(_, _)>() + } + tuple_combinations3 { + { + let v = black_box(vec![0; 64]); + } + v.iter().tuple_combinations::<(_, _, _)>() + } + tuple_combinations4 { + { + let v = black_box(vec![0; 64]); } v.iter().tuple_combinations::<(_, _, _, _)>() } @@ -307,19 +388,73 @@ bench_specializations! { } v.iter().intersperse_with(|| &n) } - combinations { + combinations1 { + { + let v = black_box(vec![0; 1792]); + } + v.iter().combinations(1) + } + combinations2 { + { + let v = black_box(vec![0; 60]); + } + v.iter().combinations(2) + } + combinations3 { + { + let v = black_box(vec![0; 23]); + } + v.iter().combinations(3) + } + combinations4 { { let v = black_box(vec![0; 16]); } v.iter().combinations(4) } - combinations_with_replacement { + combinations_with_replacement1 { + { + let v = black_box(vec![0; 4096]); + } + v.iter().combinations_with_replacement(1) + } + combinations_with_replacement2 { + { + let v = black_box(vec![0; 90]); + } + v.iter().combinations_with_replacement(2) + } + combinations_with_replacement3 { + { + let v = black_box(vec![0; 28]); + } + v.iter().combinations_with_replacement(3) + } + combinations_with_replacement4 { { let v = black_box(vec![0; 16]); } v.iter().combinations_with_replacement(4) } - permutations { + permutations1 { + { + let v = black_box(vec![0; 1024]); + } + v.iter().permutations(1) + } + permutations2 { + { + let v = black_box(vec![0; 36]); + } + v.iter().permutations(2) + } + permutations3 { + { + let v = black_box(vec![0; 12]); + } + v.iter().permutations(3) + } + permutations4 { { let v = black_box(vec![0; 8]); } From f88e3ef86ee6ef7cea6169683552ca16e6ff34f4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 25 Nov 2023 09:22:59 +0100 Subject: [PATCH 406/633] `Unspecialized`: implement `DoubleEndedIterator` --- tests/specializations.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index fe14234d6..24ae03abd 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -5,6 +5,7 @@ use quickcheck::{quickcheck, TestResult}; use std::fmt::Debug; struct Unspecialized(I); + impl Iterator for Unspecialized where I: Iterator, @@ -17,6 +18,16 @@ where } } +impl DoubleEndedIterator for Unspecialized +where + I: DoubleEndedIterator, +{ + #[inline(always)] + fn next_back(&mut self) -> Option { + self.0.next_back() + } +} + fn test_specializations(it: &Iter) where IterItem: Eq + Debug + Clone, From 3d7559822685a30c58d530935eac47e393220d09 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 25 Nov 2023 09:30:48 +0100 Subject: [PATCH 407/633] Simplify `test_specializations` --- tests/specializations.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index 24ae03abd..d9069d8e2 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -28,10 +28,10 @@ where } } -fn test_specializations(it: &Iter) +fn test_specializations(it: &I) where - IterItem: Eq + Debug + Clone, - Iter: Iterator + Clone, + I::Item: Eq + Debug + Clone, + I: Iterator + Clone, { macro_rules! check_specialized { ($src:expr, |$it:pat| $closure:expr) => { @@ -52,7 +52,7 @@ where check_specialized!(it, |i| i.collect::>()); check_specialized!(it, |i| { let mut parameters_from_fold = vec![]; - let fold_result = i.fold(vec![], |mut acc, v: IterItem| { + let fold_result = i.fold(vec![], |mut acc, v: I::Item| { parameters_from_fold.push((acc.clone(), v.clone())); acc.push(v); acc From 996d86ea5cbc9bc42d25cfcdbd1b7eb215867ba2 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 25 Nov 2023 09:42:10 +0100 Subject: [PATCH 408/633] Test `DoubleEndedIterator` specializations Similar to `test_specializations` but for `DoubleEndedIterator::{rfold,nth_back}`. The other difference is that in the macro, I advance the iterator alternatively from both fronts 8 times instead of one front 5 times. Note that we don't have any `rfold/nth_back` specialization yet but it will come as I intend to do `rfold` specializations alongside `fold` ones. --- tests/specializations.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index d9069d8e2..b42827d88 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -86,6 +86,44 @@ where } } +fn test_double_ended_specializations(it: &I) +where + I::Item: Eq + Debug + Clone, + I: DoubleEndedIterator + Clone, +{ + macro_rules! check_specialized { + ($src:expr, |$it:pat| $closure:expr) => { + // Many iterators special-case the first elements, so we test specializations for iterators that have already been advanced. + let mut src = $src.clone(); + for step in 0..8 { + let $it = src.clone(); + let v1 = $closure; + let $it = Unspecialized(src.clone()); + let v2 = $closure; + assert_eq!(v1, v2); + if step % 2 == 0 { + src.next(); + } else { + src.next_back(); + } + } + } + } + check_specialized!(it, |i| { + let mut parameters_from_rfold = vec![]; + let rfold_result = i.rfold(vec![], |mut acc, v: I::Item| { + parameters_from_rfold.push((acc.clone(), v.clone())); + acc.push(v); + acc + }); + (parameters_from_rfold, rfold_result) + }); + let size = it.clone().count(); + for n in 0..size + 2 { + check_specialized!(it, |mut i| i.nth_back(n)); + } +} + quickcheck! { fn interleave(v: Vec, w: Vec) -> () { test_specializations(&v.iter().interleave(w.iter())); From b07b0ad4fc0b1c79d7e4ddfb971172d58bc77b7f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 25 Nov 2023 10:20:50 +0100 Subject: [PATCH 409/633] Use `test_double_ended_specializations` I previously forgot `repeat_n`. --- tests/specializations.rs | 55 ++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index b42827d88..abe41fe92 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -193,19 +193,27 @@ quickcheck! { } fn duplicates(v: Vec) -> () { - test_specializations(&v.iter().duplicates()); + let it = v.iter().duplicates(); + test_specializations(&it); + test_double_ended_specializations(&it); } fn duplicates_by(v: Vec) -> () { - test_specializations(&v.iter().duplicates_by(|x| *x % 10)); + let it = v.iter().duplicates_by(|x| *x % 10); + test_specializations(&it); + test_double_ended_specializations(&it); } fn unique(v: Vec) -> () { - test_specializations(&v.iter().unique()); + let it = v.iter().unique(); + test_specializations(&it); + test_double_ended_specializations(&it); } fn unique_by(v: Vec) -> () { - test_specializations(&v.iter().unique_by(|x| *x % 50)); + let it = v.iter().unique_by(|x| *x % 50); + test_specializations(&it); + test_double_ended_specializations(&it); } fn take_while_inclusive(v: Vec) -> () { @@ -218,7 +226,9 @@ quickcheck! { fn pad_using(v: Vec) -> () { use std::convert::TryFrom; - test_specializations(&v.iter().copied().pad_using(10, |i| u8::try_from(5 * i).unwrap_or(u8::MAX))); + let it = v.iter().copied().pad_using(10, |i| u8::try_from(5 * i).unwrap_or(u8::MAX)); + test_specializations(&it); + test_double_ended_specializations(&it); } fn with_position(v: Vec) -> () { @@ -226,11 +236,15 @@ quickcheck! { } fn positions(v: Vec) -> () { - test_specializations(&v.iter().positions(|x| x % 5 == 0)); + let it = v.iter().positions(|x| x % 5 == 0); + test_specializations(&it); + test_double_ended_specializations(&it); } fn update(v: Vec) -> () { - test_specializations(&v.iter().copied().update(|x| *x = x.wrapping_mul(7))); + let it = v.iter().copied().update(|x| *x = x.wrapping_mul(7)); + test_specializations(&it); + test_double_ended_specializations(&it); } fn tuple_combinations(v: Vec) -> TestResult { @@ -284,7 +298,9 @@ quickcheck! { } fn zip_longest(a: Vec, b: Vec) -> () { - test_specializations(&a.into_iter().zip_longest(b)) + let it = a.into_iter().zip_longest(b); + test_specializations(&it); + test_double_ended_specializations(&it); } fn zip_eq(a: Vec) -> () { @@ -292,8 +308,9 @@ quickcheck! { } fn multizip(a: Vec) -> () { - let its = (a.iter(), a.iter().rev(), a.iter().take(50)); - test_specializations(&itertools::multizip(its)); + let it = itertools::multizip((a.iter(), a.iter().rev(), a.iter().take(50))); + test_specializations(&it); + test_double_ended_specializations(&it); } fn izip(a: Vec, b: Vec) -> () { @@ -307,6 +324,12 @@ quickcheck! { test_specializations(&itertools::iproduct!(a, b.iter(), c)); TestResult::passed() } + + fn repeat_n(element: i8, n: u8) -> () { + let it = itertools::repeat_n(element, n as usize); + test_specializations(&it); + test_double_ended_specializations(&it); + } } quickcheck! { @@ -400,11 +423,15 @@ quickcheck! { quickcheck! { fn map_into(v: Vec) -> () { - test_specializations(&v.into_iter().map_into::()); + let it = v.into_iter().map_into::(); + test_specializations(&it); + test_double_ended_specializations(&it); } fn map_ok(v: Vec>) -> () { - test_specializations(&v.into_iter().map_ok(|u| u.checked_add(1))); + let it = v.into_iter().map_ok(|u| u.checked_add(1)); + test_specializations(&it); + test_double_ended_specializations(&it); } fn filter_ok(v: Vec>) -> () { @@ -417,7 +444,9 @@ quickcheck! { // `Option` because `Vec` would be very slow!! And we can't give `[u8; 3]`. fn flatten_ok(v: Vec, char>>) -> () { - test_specializations(&v.into_iter().flatten_ok()); + let it = v.into_iter().flatten_ok(); + test_specializations(&it); + test_double_ended_specializations(&it); } } From e23d1cba622070941766a7ab9f601db48961b241 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 8 Nov 2023 12:11:06 +0100 Subject: [PATCH 410/633] `Positions::fold` --- src/adaptors/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index e925db51d..7f6ff95a1 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1081,6 +1081,21 @@ where fn size_hint(&self) -> (usize, Option) { (0, self.iter.size_hint().1) } + + fn fold(self, init: B, mut func: G) -> B + where + G: FnMut(B, Self::Item) -> B, + { + let mut count = self.count; + let mut f = self.f; + self.iter.fold(init, |mut acc, val| { + if f(val) { + acc = func(acc, count); + } + count += 1; + acc + }) + } } impl DoubleEndedIterator for Positions From 0fc4675b51ecc95ec49026d2212e3b1225fe8b33 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 24 Nov 2023 10:47:02 +0100 Subject: [PATCH 411/633] `Positions::rfold` --- src/adaptors/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 7f6ff95a1..52ee4800c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1111,6 +1111,21 @@ where } None } + + fn rfold(self, init: B, mut func: G) -> B + where + G: FnMut(B, Self::Item) -> B, + { + let mut count = self.count + self.iter.len(); + let mut f = self.f; + self.iter.rfold(init, |mut acc, val| { + count -= 1; + if f(val) { + acc = func(acc, count); + } + acc + }) + } } impl FusedIterator for Positions From 715b5568ab398b061db3d6c7c86ea8ceb06730eb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 8 Dec 2023 19:15:14 +0100 Subject: [PATCH 412/633] Use `Enumerate` inside `Positions` --- src/adaptors/mod.rs | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 52ee4800c..3528e3b23 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -16,7 +16,7 @@ pub use self::multi_product::*; use crate::size_hint::{self, SizeHint}; use std::fmt; -use std::iter::{FromIterator, Fuse, FusedIterator}; +use std::iter::{Enumerate, FromIterator, Fuse, FusedIterator}; use std::marker::PhantomData; /// An iterator adaptor that alternates elements from two iterators until both @@ -1039,16 +1039,15 @@ where #[derive(Clone)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Positions { - iter: I, + iter: Enumerate, f: F, - count: usize, } impl fmt::Debug for Positions where I: fmt::Debug, { - debug_fmt_fields!(Positions, iter, count); + debug_fmt_fields!(Positions, iter); } /// Create a new `Positions` iterator. @@ -1057,7 +1056,8 @@ where I: Iterator, F: FnMut(I::Item) -> bool, { - Positions { iter, f, count: 0 } + let iter = iter.enumerate(); + Positions { iter, f } } impl Iterator for Positions @@ -1068,14 +1068,10 @@ where type Item = usize; fn next(&mut self) -> Option { - while let Some(v) = self.iter.next() { - let i = self.count; - self.count = i + 1; - if (self.f)(v) { - return Some(i); - } - } - None + let f = &mut self.f; + // TODO: once MSRV >= 1.62, use `then_some`. + self.iter + .find_map(|(count, val)| if f(val) { Some(count) } else { None }) } fn size_hint(&self) -> (usize, Option) { @@ -1086,13 +1082,11 @@ where where G: FnMut(B, Self::Item) -> B, { - let mut count = self.count; let mut f = self.f; - self.iter.fold(init, |mut acc, val| { + self.iter.fold(init, |mut acc, (count, val)| { if f(val) { acc = func(acc, count); } - count += 1; acc }) } @@ -1104,22 +1098,20 @@ where F: FnMut(I::Item) -> bool, { fn next_back(&mut self) -> Option { - while let Some(v) = self.iter.next_back() { - if (self.f)(v) { - return Some(self.count + self.iter.len()); - } - } - None + let f = &mut self.f; + // TODO: once MSRV >= 1.62, use `then_some`. + self.iter + .by_ref() + .rev() + .find_map(|(count, val)| if f(val) { Some(count) } else { None }) } fn rfold(self, init: B, mut func: G) -> B where G: FnMut(B, Self::Item) -> B, { - let mut count = self.count + self.iter.len(); let mut f = self.f; - self.iter.rfold(init, |mut acc, val| { - count -= 1; + self.iter.rfold(init, |mut acc, (count, val)| { if f(val) { acc = func(acc, count); } From cfb2774fb02f61798967e89e1372bb95e625b7e6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 8 Dec 2023 22:29:11 +0100 Subject: [PATCH 413/633] Quicktest `Positions` `Positions` is tested to be fused, for specialized methods and has two doctests. No other test. While I was adding `Enumerate` in `Positions` internals, running `cargo test positions` told me `rfold` was wrong. The silly mistake was on `next_back` instead and this test would have helped me. Plus, I noticed that the documentation was just a bit wrong. --- src/lib.rs | 2 +- tests/quick.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index bdb436e48..9a4043095 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1806,7 +1806,7 @@ pub trait Itertools: Iterator { /// Return an iterator adaptor that yields the indices of all elements /// satisfying a predicate, counted from the start of the iterator. /// - /// Equivalent to `iter.enumerate().filter(|(_, v)| predicate(v)).map(|(i, _)| i)`. + /// Equivalent to `iter.enumerate().filter(|(_, v)| predicate(*v)).map(|(i, _)| i)`. /// /// ``` /// use itertools::Itertools; diff --git a/tests/quick.rs b/tests/quick.rs index 6f45a63d0..92d3f9f8e 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -604,6 +604,12 @@ quickcheck! { let b = &b[..len]; itertools::equal(zip_eq(a, b), zip(a, b)) } + fn equal_positions(a: Vec) -> bool { + let with_pos = a.iter().positions(|v| v % 2 == 0); + let without = a.iter().enumerate().filter(|(_, v)| *v % 2 == 0).map(|(i, _)| i); + itertools::equal(with_pos.clone(), without.clone()) + && itertools::equal(with_pos.rev(), without.rev()) + } fn size_zip_longest(a: Iter, b: Iter) -> bool { let filt = a.clone().dedup(); let filt2 = b.clone().dedup(); From b19eb9eb22abf5081631c29ec6e95f533cfa4cbe Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:05:31 +0100 Subject: [PATCH 414/633] `FilterOk::next`: use `find` --- src/adaptors/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 3528e3b23..887f0db1c 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -901,17 +901,11 @@ where type Item = Result; fn next(&mut self) -> Option { - loop { - match self.iter.next() { - Some(Ok(v)) => { - if (self.f)(&v) { - return Some(Ok(v)); - } - } - Some(Err(e)) => return Some(Err(e)), - None => return None, - } - } + let f = &mut self.f; + self.iter.find(|res| match res { + Ok(t) => f(t), + _ => true, + }) } fn size_hint(&self) -> (usize, Option) { From b2b44aa088323f828724928dd30a93f16071562e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:05:56 +0100 Subject: [PATCH 415/633] `FilterMapOk::next`: use `find_map` --- src/adaptors/mod.rs | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 887f0db1c..f622bceda 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -982,17 +982,11 @@ where type Item = Result; fn next(&mut self) -> Option { - loop { - match self.iter.next() { - Some(Ok(v)) => { - if let Some(v) = (self.f)(v) { - return Some(Ok(v)); - } - } - Some(Err(e)) => return Some(Err(e)), - None => return None, - } - } + let f = &mut self.f; + self.iter.find_map(|res| match res { + Ok(t) => f(t).map(Ok), + Err(e) => Some(Err(e)), + }) } fn size_hint(&self) -> (usize, Option) { From a2aaedd88aaacd5fc9b0475bb7b096e7e3ab4805 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:18:23 +0100 Subject: [PATCH 416/633] `UniqueBy::next`: use `find` --- src/unique_impl.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 9b103a075..eb5c3d64d 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -61,13 +61,8 @@ where type Item = I::Item; fn next(&mut self) -> Option { - while let Some(v) = self.iter.next() { - let key = (self.f)(&v); - if self.used.insert(key, ()).is_none() { - return Some(v); - } - } - None + let Self { iter, used, f } = self; + iter.find(|v| used.insert(f(v), ()).is_none()) } #[inline] From b6584e38d4f24ee1d9ef5c03adffdf07419c3392 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:19:15 +0100 Subject: [PATCH 417/633] `UniqueBy::next_back`: use `rfind` --- src/unique_impl.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index eb5c3d64d..44f6abb17 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -84,13 +84,8 @@ where F: FnMut(&I::Item) -> V, { fn next_back(&mut self) -> Option { - while let Some(v) = self.iter.next_back() { - let key = (self.f)(&v); - if self.used.insert(key, ()).is_none() { - return Some(v); - } - } - None + let Self { iter, used, f } = self; + iter.rfind(|v| used.insert(f(v), ()).is_none()) } } From 1abaf110e3a39ab5d259b7350486b50b62ee73c8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:21:10 +0100 Subject: [PATCH 418/633] `Unique::next`: use `find_map` --- src/unique_impl.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index 44f6abb17..e35392952 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -105,14 +105,15 @@ where type Item = I::Item; fn next(&mut self) -> Option { - while let Some(v) = self.iter.iter.next() { - if let Entry::Vacant(entry) = self.iter.used.entry(v) { + let UniqueBy { iter, used, .. } = &mut self.iter; + iter.find_map(|v| { + if let Entry::Vacant(entry) = used.entry(v) { let elt = entry.key().clone(); entry.insert(()); return Some(elt); } - } - None + None + }) } #[inline] From 7e7fcafce3c689979450d3d6b15ad78ab4601b47 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:21:35 +0100 Subject: [PATCH 419/633] `Unique::next_back`: use `rev().find_map` --- src/unique_impl.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/unique_impl.rs b/src/unique_impl.rs index e35392952..0f6397e48 100644 --- a/src/unique_impl.rs +++ b/src/unique_impl.rs @@ -133,14 +133,15 @@ where I::Item: Eq + Hash + Clone, { fn next_back(&mut self) -> Option { - while let Some(v) = self.iter.iter.next_back() { - if let Entry::Vacant(entry) = self.iter.used.entry(v) { + let UniqueBy { iter, used, .. } = &mut self.iter; + iter.rev().find_map(|v| { + if let Entry::Vacant(entry) = used.entry(v) { let elt = entry.key().clone(); entry.insert(()); return Some(elt); } - } - None + None + }) } } From 85d5077dfbea2ff1c5bc4d41cc93da50bb82f81b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 20 Nov 2023 16:35:45 +0100 Subject: [PATCH 420/633] `partition`: use `rfind` And then no need for a named loop anymore. --- src/lib.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9a4043095..f3b6e807d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4054,18 +4054,11 @@ where { let mut split_index = 0; let mut iter = iter.into_iter(); - 'main: while let Some(front) = iter.next() { + while let Some(front) = iter.next() { if !pred(front) { - loop { - match iter.next_back() { - Some(back) => { - if pred(back) { - std::mem::swap(front, back); - break; - } - } - None => break 'main, - } + match iter.rfind(|back| pred(back)) { + Some(back) => std::mem::swap(front, back), + None => break, } } split_index += 1; From 451c72a566ca122b1c048ee5aeccc140e4cc311a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 13 Dec 2023 16:05:38 +0100 Subject: [PATCH 421/633] Bench `partition` --- benches/bench1.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 0946affe5..b01b8f360 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -1,4 +1,4 @@ -use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use criterion::{black_box, criterion_group, criterion_main, BatchSize, Criterion}; use itertools::free::cloned; use itertools::iproduct; use itertools::Itertools; @@ -648,6 +648,22 @@ fn step_range_10(c: &mut Criterion) { }); } +fn vec_iter_mut_partition(c: &mut Criterion) { + let data = std::iter::repeat(-1024i32..1024) + .take(256) + .flatten() + .collect_vec(); + c.bench_function("vec iter mut partition", move |b| { + b.iter_batched( + || data.clone(), + |mut data| { + black_box(itertools::partition(black_box(&mut data), |n| *n >= 0)); + }, + BatchSize::LargeInput, + ) + }); +} + fn cartesian_product_iterator(c: &mut Criterion) { let xs = vec![0; 16]; @@ -803,6 +819,7 @@ criterion_group!( step_vec_10, step_range_2, step_range_10, + vec_iter_mut_partition, cartesian_product_iterator, multi_cartesian_product_iterator, cartesian_product_nested_for, From 7c5a5c096393ec9500e0b59aa6d9eaf79cf7f672 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 11:27:13 +0100 Subject: [PATCH 422/633] `RepeatN::fold` --- src/repeatn.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/repeatn.rs b/src/repeatn.rs index 512d057f3..15035ba00 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -44,6 +44,20 @@ where fn size_hint(&self) -> (usize, Option) { (self.n, Some(self.n)) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + match self { + Self { elt: Some(elt), n } => { + debug_assert!(n > 0); + init = (1..n).map(|_| elt.clone()).fold(init, &mut f); + f(init, elt) + } + _ => init, + } + } } impl DoubleEndedIterator for RepeatN From db0311c94a910cf7b2c875c58012475d8c79adc6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 11:27:32 +0100 Subject: [PATCH 423/633] `RepeatN::rfold` --- src/repeatn.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/repeatn.rs b/src/repeatn.rs index 15035ba00..539c42615 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -68,6 +68,14 @@ where fn next_back(&mut self) -> Option { self.next() } + + #[inline] + fn rfold(self, init: B, f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + self.fold(init, f) + } } impl ExactSizeIterator for RepeatN where A: Clone {} From fbe30e71784f87b1c99989fb0f15766d971d5fae Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 9 Nov 2023 19:33:00 +0100 Subject: [PATCH 424/633] `Multipeek::fold` --- src/multipeek_impl.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index 00c5d4ea7..a78bc447b 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -102,6 +102,14 @@ where fn size_hint(&self) -> (usize, Option) { size_hint::add_scalar(self.iter.size_hint(), self.buf.len()) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + init = self.buf.into_iter().fold(init, &mut f); + self.iter.fold(init, f) + } } // Same size From 2357feb6347e741759dbe94b05ab2b3b09484b16 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 8 Nov 2023 12:07:26 +0100 Subject: [PATCH 425/633] `PutBackN::fold` --- src/put_back_n_impl.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/put_back_n_impl.rs b/src/put_back_n_impl.rs index 9b23fa7d5..40b55e5f6 100644 --- a/src/put_back_n_impl.rs +++ b/src/put_back_n_impl.rs @@ -59,4 +59,12 @@ impl Iterator for PutBackN { fn size_hint(&self) -> (usize, Option) { size_hint::add_scalar(self.iter.size_hint(), self.top.len()) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + init = self.top.into_iter().rfold(init, &mut f); + self.iter.fold(init, f) + } } From 298eb8bdc00395826f1f56b95b264f107202b014 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 9 Nov 2023 19:42:35 +0100 Subject: [PATCH 426/633] `PeekNth::fold` --- src/peek_nth.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/peek_nth.rs b/src/peek_nth.rs index e8546030d..ff5a55cc0 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -152,6 +152,14 @@ where fn size_hint(&self) -> (usize, Option) { size_hint::add_scalar(self.iter.size_hint(), self.buf.len()) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + init = self.buf.into_iter().fold(init, &mut f); + self.iter.fold(init, f) + } } impl ExactSizeIterator for PeekNth where I: ExactSizeIterator {} From 5f3cb52e222ecb3da4638f85fa5103ca3d458453 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 12:21:47 +0100 Subject: [PATCH 427/633] `ExactlyOneError::fold` --- src/exactly_one_err.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/exactly_one_err.rs b/src/exactly_one_err.rs index e24d33fc5..47c734c1a 100644 --- a/src/exactly_one_err.rs +++ b/src/exactly_one_err.rs @@ -63,6 +63,21 @@ where fn size_hint(&self) -> (usize, Option) { size_hint::add_scalar(self.inner.size_hint(), self.additional_len()) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + match self.first_two { + Some(Either::Left([first, second])) => { + init = f(init, first); + init = f(init, second); + } + Some(Either::Right(second)) => init = f(init, second), + None => {} + } + self.inner.fold(init, f) + } } impl ExactSizeIterator for ExactlyOneError where I: ExactSizeIterator {} From a90f0950f21fb0043b82dcb3547726a1422baf6a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 15 Dec 2023 19:06:04 +0100 Subject: [PATCH 428/633] Test `ExactlyOneError` specializations --- tests/specializations.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index abe41fe92..e14b1b669 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -330,6 +330,17 @@ quickcheck! { test_specializations(&it); test_double_ended_specializations(&it); } + + fn exactly_one_error(v: Vec) -> TestResult { + // Use `at_most_one` would be similar. + match v.iter().exactly_one() { + Ok(_) => TestResult::discard(), + Err(it) => { + test_specializations(&it); + TestResult::passed() + } + } + } } quickcheck! { From e3f8b277c6291a2f20b739e47be8cc5cee00afc5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 9 Nov 2023 19:53:15 +0100 Subject: [PATCH 429/633] `PadUsing::fold` --- src/pad_tail.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 47e62b2cf..4be781242 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -67,6 +67,18 @@ where let tail = self.min.saturating_sub(self.pos); size_hint::max(self.iter.size_hint(), (tail, Some(tail))) } + + 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) + } } impl DoubleEndedIterator for PadUsing From 3589780fd95a01057eddb38757e8ac45a01a4ad9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 14:07:00 +0100 Subject: [PATCH 430/633] `PadUsing::rfold` --- src/pad_tail.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pad_tail.rs b/src/pad_tail.rs index 4be781242..5595b42ba 100644 --- a/src/pad_tail.rs +++ b/src/pad_tail.rs @@ -97,6 +97,16 @@ where Some((self.filler)(self.min)) } } + + 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) + } } impl ExactSizeIterator for PadUsing From 5a5731c03990449ff212b69a91e310e93500ebe3 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Thu, 28 Dec 2023 13:52:35 +0000 Subject: [PATCH 431/633] Fix spelling error in documentation. The primary trait in this crate is called Itertools, not IterTools. Signed-off-by: Simon Tatham --- src/exactly_one_err.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exactly_one_err.rs b/src/exactly_one_err.rs index 47c734c1a..90d898778 100644 --- a/src/exactly_one_err.rs +++ b/src/exactly_one_err.rs @@ -8,7 +8,7 @@ use either::Either; use crate::size_hint; -/// Iterator returned for the error case of `IterTools::exactly_one()` +/// Iterator returned for the error case of `Itertools::exactly_one()` /// This iterator yields exactly the same elements as the input iterator. /// /// During the execution of `exactly_one` the iterator must be mutated. This wrapper From b99a3f3c4f3a2dfd7ed2bd2308c616df5337c8d6 Mon Sep 17 00:00:00 2001 From: "Shahar \"Dawn\" Or" Date: Sun, 8 Oct 2023 20:09:16 +0700 Subject: [PATCH 432/633] build: enforce clippy Co-authored-by: warren2k <846021+warren2k@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f265ae2b..e9e6aef59 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,9 @@ jobs: steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - - run: RUSTFLAGS="--deny warnings" cargo check ${{ matrix.features }} + with: + components: ${{ matrix.rust == 'stable' && 'clippy' || '' }} + - run: RUSTFLAGS="--deny warnings" cargo ${{ matrix.rust == 'stable' && 'clippy' || 'check' }} ${{ matrix.features }} msrv: runs-on: ubuntu-latest From c5a1b16602983ef4b68d452a33b989e594f6ec64 Mon Sep 17 00:00:00 2001 From: "Shahar \"Dawn\" Or" Date: Tue, 9 Jan 2024 07:35:55 +0700 Subject: [PATCH 433/633] refactor: resolve clippy lints Co-authored-by: warren2k <846021+warren2k@users.noreply.github.com> --- .github/workflows/ci.yml | 4 +-- Cargo.toml | 1 + benches/bench1.rs | 10 ++++++- benches/extra/zipslices.rs | 2 ++ benches/fold_specialization.rs | 10 +++++-- examples/iris.rs | 4 +-- src/adaptors/mod.rs | 6 ++--- src/adaptors/multi_product.rs | 6 ++--- src/either_or_both.rs | 11 +++----- src/lib.rs | 5 ++-- src/multipeek_impl.rs | 2 +- src/tuple_impl.rs | 2 +- tests/quick.rs | 19 +++++++------ tests/test_core.rs | 2 +- tests/test_std.rs | 49 +++++++++++++++++----------------- 15 files changed, 73 insertions(+), 60 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9e6aef59..10b9bb9d9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,8 @@ jobs: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable with: - components: ${{ matrix.rust == 'stable' && 'clippy' || '' }} - - run: RUSTFLAGS="--deny warnings" cargo ${{ matrix.rust == 'stable' && 'clippy' || 'check' }} ${{ matrix.features }} + components: clippy + - run: RUSTFLAGS="--deny warnings" cargo clippy ${{ matrix.features }} msrv: runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index f1ffe19cd..9903ed4a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ categories = ["algorithms", "rust-patterns"] edition = "2018" +# When bumping, please resolve all `#[allow(clippy::*)]` that are newly resolvable. rust-version = "1.43.1" [lib] diff --git a/benches/bench1.rs b/benches/bench1.rs index b01b8f360..8d8881676 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -475,6 +475,8 @@ fn merge_default(c: &mut Criterion) { let mut data1 = vec![0; 1024]; let mut data2 = vec![0; 800]; let mut x = 0; + + #[allow(clippy::explicit_counter_loop, clippy::unused_enumerate_index)] for (_, elt) in data1.iter_mut().enumerate() { *elt = x; x += 1; @@ -501,6 +503,8 @@ fn merge_by_cmp(c: &mut Criterion) { let mut data1 = vec![0; 1024]; let mut data2 = vec![0; 800]; let mut x = 0; + + #[allow(clippy::explicit_counter_loop, clippy::unused_enumerate_index)] for (_, elt) in data1.iter_mut().enumerate() { *elt = x; x += 1; @@ -527,6 +531,8 @@ fn merge_by_lt(c: &mut Criterion) { let mut data1 = vec![0; 1024]; let mut data2 = vec![0; 800]; let mut x = 0; + + #[allow(clippy::explicit_counter_loop, clippy::unused_enumerate_index)] for (_, elt) in data1.iter_mut().enumerate() { *elt = x; x += 1; @@ -553,6 +559,8 @@ fn kmerge_default(c: &mut Criterion) { let mut data1 = vec![0; 1024]; let mut data2 = vec![0; 800]; let mut x = 0; + + #[allow(clippy::explicit_counter_loop, clippy::unused_enumerate_index)] for (_, elt) in data1.iter_mut().enumerate() { *elt = x; x += 1; @@ -592,7 +600,7 @@ fn kmerge_tenway(c: &mut Criterion) { let mut chunks = Vec::new(); let mut rest = &mut data[..]; - while rest.len() > 0 { + while !rest.is_empty() { let chunk_len = 1 + rng(&mut state) % 512; let chunk_len = cmp::min(rest.len(), chunk_len as usize); let (fst, tail) = { rest }.split_at_mut(chunk_len); diff --git a/benches/extra/zipslices.rs b/benches/extra/zipslices.rs index 5476c0cb8..a7120e168 100644 --- a/benches/extra/zipslices.rs +++ b/benches/extra/zipslices.rs @@ -138,6 +138,8 @@ where /// A helper trait to let `ZipSlices` accept both `&[T]` and `&mut [T]`. /// +/// # Safety +/// /// Unsafe trait because: /// /// - Implementors must guarantee that `get_unchecked` is valid for all indices `0..len()`. diff --git a/benches/fold_specialization.rs b/benches/fold_specialization.rs index 3a040305b..b44f34721 100644 --- a/benches/fold_specialization.rs +++ b/benches/fold_specialization.rs @@ -46,7 +46,10 @@ mod specialization { let arr = [1; 1024]; c.bench_function("internal specialized", move |b| { - b.iter(|| arr.iter().intersperse(&0).fold(0, |acc, x| acc + x)) + b.iter(|| { + #[allow(clippy::unnecessary_fold)] + arr.iter().intersperse(&0).fold(0, |acc, x| acc + x) + }) }); } @@ -54,7 +57,10 @@ mod specialization { let arr = [1; 1024]; c.bench_function("internal unspecialized", move |b| { - b.iter(|| Unspecialized(arr.iter().intersperse(&0)).fold(0, |acc, x| acc + x)) + b.iter(|| { + #[allow(clippy::unnecessary_fold)] + Unspecialized(arr.iter().intersperse(&0)).fold(0, |acc, x| acc + x) + }) }); } } diff --git a/examples/iris.rs b/examples/iris.rs index af64322d6..798fdb08e 100644 --- a/examples/iris.rs +++ b/examples/iris.rs @@ -9,7 +9,7 @@ use std::iter::repeat; use std::num::ParseFloatError; use std::str::FromStr; -static DATA: &'static str = include_str!("iris.data"); +static DATA: &str = include_str!("iris.data"); #[derive(Clone, Debug)] struct Iris { @@ -38,7 +38,7 @@ impl FromStr for Iris { name: "".into(), data: [0.; 4], }; - let mut parts = s.split(",").map(str::trim); + let mut parts = s.split(',').map(str::trim); // using Iterator::by_ref() for (index, part) in parts.by_ref().take(4).enumerate() { diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index f622bceda..a5b182d78 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -607,11 +607,11 @@ where Some(item) => Ok(f(acc, item)), None => Err(acc), }); - let res = match res { + + match res { Ok(val) => val, Err(val) => val, - }; - res + } } } diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index ef7fadba8..f02f8c740 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -220,9 +220,9 @@ where self.0.iter().fold( (0, Some(0)), |acc, - &MultiProductIter { - ref iter, - ref iter_orig, + MultiProductIter { + iter, + iter_orig, cur: _, }| { let cur_size = iter.size_hint(); diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 9dbc880d3..2232cd68c 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -29,19 +29,13 @@ impl EitherOrBoth { /// If `Left`, return true. Otherwise, return false. /// Exclusive version of [`has_left`](EitherOrBoth::has_left). pub fn is_left(&self) -> bool { - match *self { - Left(_) => true, - _ => false, - } + matches!(self, Left(_)) } /// If `Right`, return true. Otherwise, return false. /// Exclusive version of [`has_right`](EitherOrBoth::has_right). pub fn is_right(&self) -> bool { - match *self { - Right(_) => true, - _ => false, - } + matches!(self, Right(_)) } /// If `Both`, return true. Otherwise, return false. @@ -500,6 +494,7 @@ impl EitherOrBoth { } } +#[allow(clippy::from_over_into)] impl Into>> for EitherOrBoth { fn into(self) -> Option> { match self { diff --git a/src/lib.rs b/src/lib.rs index f3b6e807d..211827ff2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1008,7 +1008,7 @@ pub trait Itertools: Iterator { J: IntoIterator, F: FnMut(&Self::Item, &Self::Item) -> bool, { - merge_join::merge_by_new(self, other.into_iter(), is_first) + merge_join::merge_by_new(self, other, is_first) } /// Create an iterator that merges items from both this and the specified @@ -2047,6 +2047,7 @@ pub trait Itertools: Iterator { /// let data : Option = None; /// assert_eq!(data.into_iter().all_equal_value(), Err(None)); /// ``` + #[allow(clippy::type_complexity)] fn all_equal_value(&mut self) -> Result> where Self: Sized, @@ -4013,7 +4014,7 @@ where (None, None) => return, (a, b) => { let equal = match (&a, &b) { - (&Some(ref a), &Some(ref b)) => a == b, + (Some(a), Some(b)) => a == b, _ => false, }; assert!( diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs index a78bc447b..6f800b6fb 100644 --- a/src/multipeek_impl.rs +++ b/src/multipeek_impl.rs @@ -79,7 +79,7 @@ where return None; } } - } else if let Some(r) = self.buf.get(0) { + } else if let Some(r) = self.buf.front() { if !accept(r) { return None; } diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 9cfb484af..4a7cd4da8 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -62,7 +62,7 @@ where buffer .iter() .position(|x| x.is_none()) - .unwrap_or_else(|| buffer.len()) + .unwrap_or(buffer.len()) }; (len, Some(len)) } diff --git a/tests/quick.rs b/tests/quick.rs index 92d3f9f8e..0ca32f49a 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -779,11 +779,11 @@ quickcheck! { fn peek_nth_mut_replace(a: Vec, b: Vec) -> () { let mut it = peek_nth(a.iter()); - for i in 0..a.len().min(b.len()) { - *it.peek_nth_mut(i).unwrap() = &b[i]; + for (i, m) in b.iter().enumerate().take(a.len().min(b.len())) { + *it.peek_nth_mut(i).unwrap() = m; } - for i in 0..a.len() { - assert_eq!(it.next().unwrap(), b.get(i).unwrap_or(&a[i])); + for (i, m) in a.iter().enumerate() { + assert_eq!(it.next().unwrap(), b.get(i).unwrap_or(m)); } assert_eq!(it.next(), None); assert_eq!(it.next(), None); @@ -875,9 +875,8 @@ quickcheck! { quickcheck! { fn size_put_back(a: Vec, x: Option) -> bool { let mut it = put_back(a.into_iter()); - match x { - Some(t) => it.put_back(t), - None => {} + if let Some(t) = x { + it.put_back(t) } correct_size_hint(it) } @@ -999,7 +998,7 @@ quickcheck! { } } } - cmb.next() == None + cmb.next().is_none() } } @@ -1310,7 +1309,7 @@ struct Val(u32, u32); impl PartialOrd for Val { fn partial_cmp(&self, other: &Val) -> Option { - self.0.partial_cmp(&other.0) + Some(self.cmp(other)) } } @@ -1413,7 +1412,7 @@ quickcheck! { fn at_most_one_i32(a: Vec) -> TestResult { let ret = a.iter().cloned().at_most_one(); match a.len() { - 0 => TestResult::from_bool(ret.unwrap() == None), + 0 => TestResult::from_bool(ret.unwrap().is_none()), 1 => TestResult::from_bool(ret.unwrap() == Some(a[0])), _ => TestResult::from_bool(ret.unwrap_err().eq(a.iter().cloned())), } diff --git a/tests/test_core.rs b/tests/test_core.rs index c624a7e33..fc2c858a3 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -27,7 +27,7 @@ fn product2() { assert!(prod.next() == Some(('α', 1))); assert!(prod.next() == Some(('β', 0))); assert!(prod.next() == Some(('β', 1))); - assert!(prod.next() == None); + assert!(prod.next().is_none()); } #[test] diff --git a/tests/test_std.rs b/tests/test_std.rs index 732be7b7d..24429e919 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -40,19 +40,19 @@ fn product3() { fn interleave_shortest() { let v0: Vec = vec![0, 2, 4]; let v1: Vec = vec![1, 3, 5, 7]; - let it = v0.into_iter().interleave_shortest(v1.into_iter()); + let it = v0.into_iter().interleave_shortest(v1); assert_eq!(it.size_hint(), (6, Some(6))); assert_eq!(it.collect_vec(), vec![0, 1, 2, 3, 4, 5]); let v0: Vec = vec![0, 2, 4, 6, 8]; let v1: Vec = vec![1, 3, 5]; - let it = v0.into_iter().interleave_shortest(v1.into_iter()); + let it = v0.into_iter().interleave_shortest(v1); assert_eq!(it.size_hint(), (7, Some(7))); assert_eq!(it.collect_vec(), vec![0, 1, 2, 3, 4, 5, 6]); let i0 = ::std::iter::repeat(0); let v1: Vec<_> = vec![1, 3, 5]; - let it = i0.interleave_shortest(v1.into_iter()); + let it = i0.interleave_shortest(v1); assert_eq!(it.size_hint(), (7, Some(7))); let v0: Vec<_> = vec![0, 2, 4]; @@ -93,7 +93,7 @@ fn duplicates() { let ys_rev = [1, 0]; it::assert_equal(ys_rev.iter(), xs.iter().duplicates().rev()); - let xs = vec![0, 1, 2, 1, 2]; + let xs = [0, 1, 2, 1, 2]; let ys = vec![1, 2]; assert_eq!(ys, xs.iter().duplicates().cloned().collect_vec()); assert_eq!( @@ -146,7 +146,7 @@ fn intersperse() { let ys = [0, 1, 2, 3]; let mut it = ys[..0].iter().copied().intersperse(1); - assert!(it.next() == None); + assert!(it.next().is_none()); } #[test] @@ -167,7 +167,7 @@ fn dedup() { #[test] fn coalesce() { - let data = vec![-1., -2., -3., 3., 1., 0., -1.]; + let data = [-1., -2., -3., 3., 1., 0., -1.]; let it = data.iter().cloned().coalesce(|x, y| { if (x >= 0.) == (y >= 0.) { Ok(x + y) @@ -395,8 +395,8 @@ fn trait_pointers() { #[test] fn merge_by() { let odd: Vec<(u32, &str)> = vec![(1, "hello"), (3, "world"), (5, "!")]; - let even = vec![(2, "foo"), (4, "bar"), (6, "baz")]; - let expected = vec![ + let even = [(2, "foo"), (4, "bar"), (6, "baz")]; + let expected = [ (1, "hello"), (2, "foo"), (3, "world"), @@ -417,9 +417,9 @@ fn merge_by_btree() { let mut bt2 = BTreeMap::new(); bt2.insert("foo", 2); bt2.insert("bar", 4); - let results = bt1.into_iter().merge_by(bt2.into_iter(), |a, b| a.0 <= b.0); + let results = bt1.into_iter().merge_by(bt2, |a, b| a.0 <= b.0); let expected = vec![("bar", 4), ("foo", 2), ("hello", 1), ("world", 3)]; - it::assert_equal(results, expected.into_iter()); + it::assert_equal(results, expected); } #[allow(deprecated)] @@ -651,7 +651,7 @@ fn test_multipeek_reset() { #[test] fn test_multipeek_peeking_next() { use crate::it::PeekingNext; - let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; + let nums = [1u8, 2, 3, 4, 5, 6, 7]; let mut mp = multipeek(nums.iter().copied()); assert_eq!(mp.peeking_next(|&x| x != 0), Some(1)); @@ -711,7 +711,7 @@ fn test_peek_nth() { #[test] fn test_peek_nth_peeking_next() { use it::PeekingNext; - let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; + let nums = [1u8, 2, 3, 4, 5, 6, 7]; let mut iter = peek_nth(nums.iter().copied()); assert_eq!(iter.peeking_next(|&x| x != 0), Some(1)); @@ -740,7 +740,7 @@ fn test_peek_nth_peeking_next() { #[test] fn test_peek_nth_next_if() { - let nums = vec![1u8, 2, 3, 4, 5, 6, 7]; + let nums = [1u8, 2, 3, 4, 5, 6, 7]; let mut iter = peek_nth(nums.iter().copied()); assert_eq!(iter.next_if(|&x| x != 0), Some(1)); @@ -863,12 +863,12 @@ fn group_by() { #[test] fn group_by_lazy_2() { - let data = vec![0, 1]; + let data = [0, 1]; let groups = data.iter().group_by(|k| *k); let gs = groups.into_iter().collect_vec(); it::assert_equal(data.iter(), gs.into_iter().flat_map(|(_k, g)| g)); - let data = vec![0, 1, 1, 0, 0]; + let data = [0, 1, 1, 0, 0]; let groups = data.iter().group_by(|k| *k); let mut gs = groups.into_iter().collect_vec(); gs[1..].reverse(); @@ -883,7 +883,7 @@ fn group_by_lazy_2() { } it::assert_equal(&mut groups[0], &[1, 1]); - let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; + let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().group_by(|k| *k); let mut groups = Vec::new(); for (i, (_, group)) in grouper.into_iter().enumerate() { @@ -900,7 +900,7 @@ fn group_by_lazy_2() { it::assert_equal(&mut groups[2], &[3, 3]); // use groups as chunks - let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; + let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let mut i = 0; let grouper = data.iter().group_by(move |_| { let k = i / 3; @@ -921,7 +921,7 @@ fn group_by_lazy_2() { #[test] fn group_by_lazy_3() { // test consuming each group on the lap after it was produced - let data = vec![0, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2]; + let data = [0, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2]; let grouper = data.iter().group_by(|elt| *elt); let mut last = None; for (key, group) in &grouper { @@ -936,7 +936,7 @@ fn group_by_lazy_3() { #[test] fn chunks() { - let data = vec![0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; + let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().chunks(3); for (i, chunk) in grouper.into_iter().enumerate() { match i { @@ -1216,7 +1216,7 @@ fn powerset() { #[test] fn diff_mismatch() { - let a = vec![1, 2, 3, 4]; + let a = [1, 2, 3, 4]; let b = vec![1.0, 5.0, 3.0, 4.0]; let b_map = b.into_iter().map(|f| f as i32); let diff = it::diff_with(a.iter(), b_map, |a, b| *a == b); @@ -1230,7 +1230,7 @@ fn diff_mismatch() { #[test] fn diff_longer() { - let a = vec![1, 2, 3, 4]; + let a = [1, 2, 3, 4]; let b = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]; let b_map = b.into_iter().map(|f| f as i32); let diff = it::diff_with(a.iter(), b_map, |a, b| *a == b); @@ -1243,7 +1243,7 @@ fn diff_longer() { #[test] fn diff_shorter() { - let a = vec![1, 2, 3, 4]; + let a = [1, 2, 3, 4]; let b = vec![1.0, 2.0]; let b_map = b.into_iter().map(|f| f as i32); let diff = it::diff_with(a.iter(), b_map, |a, b| *a == b); @@ -1265,7 +1265,7 @@ fn extrema_set() { impl PartialOrd for Val { fn partial_cmp(&self, other: &Val) -> Option { - self.0.partial_cmp(&other.0) + Some(self.cmp(other)) } } @@ -1314,7 +1314,7 @@ fn minmax() { impl PartialOrd for Val { fn partial_cmp(&self, other: &Val) -> Option { - self.0.partial_cmp(&other.0) + Some(self.cmp(other)) } } @@ -1446,6 +1446,7 @@ fn multiunzip() { .multiunzip(); assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); let (): () = [(), (), ()].iter().cloned().multiunzip(); + #[allow(clippy::type_complexity)] let t: ( Vec<_>, Vec<_>, From cd6bd87562e664868c62b8a84d4659cacf2a59e8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 10 Jan 2024 14:40:38 +0100 Subject: [PATCH 434/633] `Itertools::find_position`: use `find` `find` usually rely on `try_fold` so this change should improve performance for iterators specializing `find/try_fold`. Co-Authored-By: Marcel Hellwig Co-Authored-By: phimuemue <157728+phimuemue@users.noreply.github.com> --- src/lib.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 211827ff2..b2641d19d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1910,12 +1910,7 @@ pub trait Itertools: Iterator { where P: FnMut(&Self::Item) -> bool, { - for (index, elt) in self.enumerate() { - if pred(&elt) { - return Some((index, elt)); - } - } - None + self.enumerate().find(|(_, elt)| pred(elt)) } /// Find the value of the first element satisfying a predicate or return the last element, if any. /// From a22553ed5b827a9a1c6692e807ddc2d43b3deb70 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 10 Jan 2024 14:45:29 +0100 Subject: [PATCH 435/633] fix clippy::derive_partial_eq_without_eq I merely did `cargo clippy --fix -- -D clippy::derive_partial_eq_without_eq` but the next link is useful to understand why it should be done https://rust-lang.github.io/rust-clippy/master/index.html#derive_partial_eq_without_eq Co-Authored-By: Marcel Hellwig --- src/minmax.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/minmax.rs b/src/minmax.rs index f04e5adba..9f02a49f9 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -1,7 +1,7 @@ /// `MinMaxResult` is an enum returned by `minmax`. /// /// See [`.minmax()`](crate::Itertools::minmax) for more detail. -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum MinMaxResult { /// Empty iterator NoElements, From 2c487f07552d9c1b1e4ad9a2baba82e33f7b4e3c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 10 Jan 2024 15:35:10 +0100 Subject: [PATCH 436/633] Use `Self` Two simple steps: - run `cargo clippy --fix -- -D clippy::use_self` - In `either_or_both`, remove `Self::` as glob use is quite convenient. https://rust-lang.github.io/rust-clippy/master/index.html#/use_self Co-Authored-By: Marcel Hellwig --- benches/extra/zipslices.rs | 2 +- examples/iris.rs | 4 ++-- src/adaptors/mod.rs | 4 ++-- src/adaptors/multi_product.rs | 6 +++--- src/duplicates_impl.rs | 6 +++--- src/either_or_both.rs | 16 ++++++++-------- src/groupbylazy.rs | 2 +- src/kmerge_impl.rs | 4 ++-- src/lazy_buffer.rs | 4 ++-- src/lib.rs | 6 +++--- src/minmax.rs | 6 +++--- src/tuple_impl.rs | 2 +- src/zip_longest.rs | 2 +- tests/quick.rs | 26 +++++++++++++------------- tests/test_core.rs | 2 +- tests/test_std.rs | 14 +++++++------- 16 files changed, 53 insertions(+), 53 deletions(-) diff --git a/benches/extra/zipslices.rs b/benches/extra/zipslices.rs index a7120e168..2ec7bc0c0 100644 --- a/benches/extra/zipslices.rs +++ b/benches/extra/zipslices.rs @@ -58,7 +58,7 @@ where #[inline(always)] pub fn from_slices(a: T, b: U) -> Self { let minl = cmp::min(a.len(), b.len()); - ZipSlices { + Self { t: a, u: b, len: minl, diff --git a/examples/iris.rs b/examples/iris.rs index 798fdb08e..5f5525154 100644 --- a/examples/iris.rs +++ b/examples/iris.rs @@ -25,7 +25,7 @@ enum ParseError { impl From for ParseError { fn from(err: ParseFloatError) -> Self { - ParseError::Numeric(err) + Self::Numeric(err) } } @@ -34,7 +34,7 @@ impl FromStr for Iris { type Err = ParseError; fn from_str(s: &str) -> Result { - let mut iris = Iris { + let mut iris = Self { name: "".into(), data: [0.; 4], }; diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a5b182d78..08d91e397 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -218,7 +218,7 @@ where /// Split the `PutBack` into its parts. #[inline] pub fn into_parts(self) -> (Option, I) { - let PutBack { top, iter } = self; + let Self { top, iter } = self; (top, iter) } @@ -689,7 +689,7 @@ pub struct Tuple1Combination { impl From for Tuple1Combination { fn from(iter: I) -> Self { - Tuple1Combination { iter } + Self { iter } } } diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index f02f8c740..74cb06d0e 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -95,7 +95,7 @@ where if last.in_progress() { true - } else if MultiProduct::iterate_last(rest, state) { + } else if Self::iterate_last(rest, state) { last.reset(); last.iterate(); // If iterator is None twice consecutively, then iterator is @@ -139,7 +139,7 @@ where I::Item: Clone, { fn new(iter: I) -> Self { - MultiProductIter { + Self { cur: None, iter: iter.clone(), iter_orig: iter, @@ -171,7 +171,7 @@ where type Item = Vec; fn next(&mut self) -> Option { - if MultiProduct::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { + if Self::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { Some(self.curr_iterator()) } else { None diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs index 71ed21841..a0db15432 100644 --- a/src/duplicates_impl.rs +++ b/src/duplicates_impl.rs @@ -22,7 +22,7 @@ mod private { impl DuplicatesBy { pub(crate) fn new(iter: I, key_method: F) -> Self { - DuplicatesBy { + Self { iter, meta: Meta { used: HashMap::new(), @@ -77,7 +77,7 @@ mod private { type Item = I::Item; fn next(&mut self) -> Option { - let DuplicatesBy { iter, meta } = self; + let Self { iter, meta } = self; iter.find_map(|v| meta.filter(v)) } @@ -109,7 +109,7 @@ mod private { F: KeyMethod, { fn next_back(&mut self) -> Option { - let DuplicatesBy { iter, meta } = self; + let Self { iter, meta } = self; iter.rev().find_map(|v| meta.filter(v)) } } diff --git a/src/either_or_both.rs b/src/either_or_both.rs index 2232cd68c..d13763441 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -301,9 +301,9 @@ impl EitherOrBoth { B: Default, { match self { - EitherOrBoth::Left(l) => (l, B::default()), - EitherOrBoth::Right(r) => (A::default(), r), - EitherOrBoth::Both(l, r) => (l, r), + Left(l) => (l, B::default()), + Right(r) => (A::default(), r), + Both(l, r) => (l, r), } } @@ -498,9 +498,9 @@ impl EitherOrBoth { impl Into>> for EitherOrBoth { fn into(self) -> Option> { match self { - EitherOrBoth::Left(l) => Some(Either::Left(l)), - EitherOrBoth::Right(r) => Some(Either::Right(r)), - _ => None, + Left(l) => Some(Either::Left(l)), + Right(r) => Some(Either::Right(r)), + Both(..) => None, } } } @@ -508,8 +508,8 @@ impl Into>> for EitherOrBoth { impl From> for EitherOrBoth { fn from(either: Either) -> Self { match either { - Either::Left(l) => EitherOrBoth::Left(l), - Either::Right(l) => EitherOrBoth::Right(l), + Either::Left(l) => Left(l), + Either::Right(l) => Right(l), } } } diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 6cf33838c..3d1bfab5e 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -29,7 +29,7 @@ struct ChunkIndex { impl ChunkIndex { #[inline(always)] fn new(size: usize) -> Self { - ChunkIndex { + Self { size, index: 0, key: 0, diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index c077cdda1..4660caf07 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -27,9 +27,9 @@ where I: Iterator, { /// Constructs a `HeadTail` from an `Iterator`. Returns `None` if the `Iterator` is empty. - fn new(mut it: I) -> Option> { + fn new(mut it: I) -> Option { let head = it.next(); - head.map(|h| HeadTail { head: h, tail: it }) + head.map(|h| Self { head: h, tail: it }) } /// Get the next element and update `head`, returning the old head in `Some`. diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 38c7d405b..5cb039a63 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -14,8 +14,8 @@ impl LazyBuffer where I: Iterator, { - pub fn new(it: I) -> LazyBuffer { - LazyBuffer { + pub fn new(it: I) -> Self { + Self { it: it.fuse(), buffer: Vec::new(), } diff --git a/src/lib.rs b/src/lib.rs index b2641d19d..c670e07b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4077,15 +4077,15 @@ impl FoldWhile { /// Return the value in the continue or done. pub fn into_inner(self) -> T { match self { - FoldWhile::Continue(x) | FoldWhile::Done(x) => x, + Self::Continue(x) | Self::Done(x) => x, } } /// Return true if `self` is `Done`, false if it is `Continue`. pub fn is_done(&self) -> bool { match *self { - FoldWhile::Continue(_) => false, - FoldWhile::Done(_) => true, + Self::Continue(_) => false, + Self::Done(_) => true, } } } diff --git a/src/minmax.rs b/src/minmax.rs index 9f02a49f9..5c9674e01 100644 --- a/src/minmax.rs +++ b/src/minmax.rs @@ -37,9 +37,9 @@ impl MinMaxResult { /// ``` pub fn into_option(self) -> Option<(T, T)> { match self { - MinMaxResult::NoElements => None, - MinMaxResult::OneElement(x) => Some((x.clone(), x)), - MinMaxResult::MinMax(x, y) => Some((x, y)), + Self::NoElements => None, + Self::OneElement(x) => Some((x.clone(), x)), + Self::MinMax(x, y) => Some((x, y)), } } } diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index 4a7cd4da8..ac04a1952 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -34,7 +34,7 @@ where T: HomogeneousTuple, { fn new(buf: T::Buffer) -> Self { - TupleBuffer { cur: 0, buf } + Self { cur: 0, buf } } } diff --git a/src/zip_longest.rs b/src/zip_longest.rs index 27d9f3ab6..e53733542 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -59,7 +59,7 @@ where Self: Sized, F: FnMut(B, Self::Item) -> B, { - let ZipLongest { a, mut b } = self; + let Self { a, mut b } = self; init = a.fold(init, |init, a| match b.next() { Some(b) => f(init, EitherOrBoth::Both(a, b)), None => f(init, EitherOrBoth::Left(a)), diff --git a/tests/quick.rs b/tests/quick.rs index 0ca32f49a..dffcd22f6 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -38,7 +38,7 @@ impl HintKind for Exact { impl qc::Arbitrary for Exact { fn arbitrary(_: &mut G) -> Self { - Exact {} + Self {} } } @@ -69,7 +69,7 @@ impl qc::Arbitrary for Inexact { // Compensate for quickcheck using extreme values too rarely let ue_choices = &[0, ue_value, usize::max_value()]; let oe_choices = &[0, oe_value, usize::max_value()]; - Inexact { + Self { underestimate: *ue_choices.choose(g).unwrap(), overestimate: *oe_choices.choose(g).unwrap(), } @@ -79,7 +79,7 @@ impl qc::Arbitrary for Inexact { let underestimate_value = self.underestimate; let overestimate_value = self.overestimate; Box::new(underestimate_value.shrink().flat_map(move |ue_value| { - overestimate_value.shrink().map(move |oe_value| Inexact { + overestimate_value.shrink().map(move |oe_value| Self { underestimate: ue_value, overestimate: oe_value, }) @@ -108,7 +108,7 @@ where HK: HintKind, { fn new(it: Range, hint_kind: HK) -> Self { - Iter { + Self { iterator: it, fuse_flag: 0, hint_kind, @@ -166,16 +166,16 @@ where HK: HintKind, { fn arbitrary(g: &mut G) -> Self { - Iter::new(T::arbitrary(g)..T::arbitrary(g), HK::arbitrary(g)) + Self::new(T::arbitrary(g)..T::arbitrary(g), HK::arbitrary(g)) } - fn shrink(&self) -> Box>> { + fn shrink(&self) -> Box> { let r = self.iterator.clone(); let hint_kind = self.hint_kind; Box::new(r.start.shrink().flat_map(move |a| { r.end .shrink() - .map(move |b| Iter::new(a.clone()..b, hint_kind)) + .map(move |b| Self::new(a.clone()..b, hint_kind)) })) } } @@ -231,7 +231,7 @@ where let iter_count = g.gen_range(0, MAX_ITER_COUNT + 1); let hint_kind = qc::Arbitrary::arbitrary(g); - ShiftRange { + Self { range_start, range_end, start_step, @@ -1307,14 +1307,14 @@ quickcheck! { #[derive(Clone, Debug, PartialEq, Eq)] struct Val(u32, u32); -impl PartialOrd for Val { - fn partial_cmp(&self, other: &Val) -> Option { +impl PartialOrd for Val { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Val { - fn cmp(&self, other: &Val) -> Ordering { + fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } @@ -1322,10 +1322,10 @@ impl Ord for Val { impl qc::Arbitrary for Val { fn arbitrary(g: &mut G) -> Self { let (x, y) = <(u32, u32)>::arbitrary(g); - Val(x, y) + Self(x, y) } fn shrink(&self) -> Box> { - Box::new((self.0, self.1).shrink().map(|(x, y)| Val(x, y))) + Box::new((self.0, self.1).shrink().map(|(x, y)| Self(x, y))) } } diff --git a/tests/test_core.rs b/tests/test_core.rs index fc2c858a3..45a8df438 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -237,7 +237,7 @@ fn count_clones() { fn clone(&self) -> Self { let n = self.n.get(); self.n.set(n + 1); - Foo { + Self { n: Cell::new(n + 1), } } diff --git a/tests/test_std.rs b/tests/test_std.rs index 24429e919..063eca049 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -540,7 +540,7 @@ where impl qc::Arbitrary for RandIter { fn arbitrary(g: &mut G) -> Self { - RandIter { + Self { idx: 0, len: g.size(), rng: R::seed_from_u64(g.next_u64()), @@ -1263,14 +1263,14 @@ fn extrema_set() { #[derive(Clone, Debug, PartialEq, Eq)] struct Val(u32, u32); - impl PartialOrd for Val { - fn partial_cmp(&self, other: &Val) -> Option { + impl PartialOrd for Val { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Val { - fn cmp(&self, other: &Val) -> Ordering { + fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } @@ -1312,14 +1312,14 @@ fn minmax() { #[derive(Clone, Debug, PartialEq, Eq)] struct Val(u32, u32); - impl PartialOrd for Val { - fn partial_cmp(&self, other: &Val) -> Option { + impl PartialOrd for Val { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for Val { - fn cmp(&self, other: &Val) -> Ordering { + fn cmp(&self, other: &Self) -> Ordering { self.0.cmp(&other.0) } } From 1fb979b51e24a9f78d719c0f07d14af18b12be3f Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 7 Jul 2022 10:23:37 +0200 Subject: [PATCH 437/633] fix clippy::type_repetition_in_bounds https://rust-lang.github.io/rust-clippy/master/index.html#/type_repetition_in_bounds `CircularTupleWindows` even had a duplicate bound. Co-Authored-By: Marcel Hellwig --- src/adaptors/coalesce.rs | 5 +++-- src/free.rs | 4 ++-- src/groupbylazy.rs | 24 ++++++++++++++---------- src/lib.rs | 5 ++--- src/peeking_take_while.rs | 8 ++++---- src/tuple_impl.rs | 2 +- src/ziptuple.rs | 3 +-- tests/test_std.rs | 4 ++-- 8 files changed, 29 insertions(+), 26 deletions(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index 21eaa016d..ad9df1192 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -17,9 +17,10 @@ where f: F, } -impl Clone for CoalesceBy +impl Clone for CoalesceBy where - I: Iterator, + I: Clone + Iterator, + F: Clone, C: CountItem, C::CItem: Clone, { diff --git a/src/free.rs b/src/free.rs index 5ce1b49b3..617a1977d 100644 --- a/src/free.rs +++ b/src/free.rs @@ -166,10 +166,10 @@ where /// /// assert_eq!(cloned(b"abc").next(), Some(b'a')); /// ``` -pub fn cloned<'a, I, T: 'a>(iterable: I) -> iter::Cloned +pub fn cloned<'a, I, T>(iterable: I) -> iter::Cloned where I: IntoIterator, - T: Clone, + T: Clone + 'a, { iterable.into_iter().cloned() } diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 3d1bfab5e..c318ec7b3 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -7,9 +7,9 @@ trait KeyFunction { fn call_mut(&mut self, arg: A) -> Self::Key; } -impl KeyFunction for F +impl KeyFunction for F where - F: FnMut(A) -> K, + F: FnMut(A) -> K + ?Sized, { type Key = K; #[inline] @@ -370,10 +370,12 @@ where /// /// See [`.group_by()`](crate::Itertools::group_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct Groups<'a, K: 'a, I: 'a, F: 'a> +pub struct Groups<'a, K, I, F> where - I: Iterator, + I: Iterator + 'a, I::Item: 'a, + K: 'a, + F: 'a, { parent: &'a GroupBy, } @@ -409,10 +411,12 @@ where /// An iterator for the elements in a single group. /// /// Iterator element type is `I::Item`. -pub struct Group<'a, K: 'a, I: 'a, F: 'a> +pub struct Group<'a, K, I, F> where - I: Iterator, + I: Iterator + 'a, I::Item: 'a, + K: 'a, + F: 'a, { parent: &'a GroupBy, index: usize, @@ -537,9 +541,9 @@ where /// See [`.chunks()`](crate::Itertools::chunks) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Clone)] -pub struct Chunks<'a, I: 'a> +pub struct Chunks<'a, I> where - I: Iterator, + I: Iterator + 'a, I::Item: 'a, { parent: &'a IntoChunks, @@ -568,9 +572,9 @@ where /// An iterator for the elements in a single chunk. /// /// Iterator element type is `I::Item`. -pub struct Chunk<'a, I: 'a> +pub struct Chunk<'a, I> where - I: Iterator, + I: Iterator + 'a, I::Item: 'a, { parent: &'a IntoChunks, diff --git a/src/lib.rs b/src/lib.rs index c670e07b3..a571f49b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2124,8 +2124,7 @@ pub trait Itertools: Iterator { /// ``` fn dropping_back(mut self, n: usize) -> Self where - Self: Sized, - Self: DoubleEndedIterator, + Self: Sized + DoubleEndedIterator, { if n > 0 { (&mut self).rev().nth(n - 1); @@ -3962,7 +3961,7 @@ pub trait Itertools: Iterator { } } -impl Itertools for T where T: Iterator {} +impl Itertools for T where T: Iterator + ?Sized {} /// Return `true` if both iterables produce equal sequences /// (elements pairwise equal and sequences of the same length), diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index b08794a8d..b4852c1c9 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -96,17 +96,17 @@ where /// See [`.peeking_take_while()`](crate::Itertools::peeking_take_while) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct PeekingTakeWhile<'a, I: 'a, F> +pub struct PeekingTakeWhile<'a, I, F> where - I: Iterator, + I: Iterator + 'a, { iter: &'a mut I, f: F, } -impl<'a, I: 'a, F> std::fmt::Debug for PeekingTakeWhile<'a, I, F> +impl<'a, I, F> std::fmt::Debug for PeekingTakeWhile<'a, I, F> where - I: Iterator + std::fmt::Debug, + I: Iterator + std::fmt::Debug + 'a, { debug_fmt_fields!(PeekingTakeWhile, iter); } diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs index ac04a1952..c0d556fc9 100644 --- a/src/tuple_impl.rs +++ b/src/tuple_impl.rs @@ -244,7 +244,7 @@ where /// information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] #[derive(Debug, Clone)] -pub struct CircularTupleWindows +pub struct CircularTupleWindows where I: Iterator + Clone, T: TupleCollect + Clone, diff --git a/src/ziptuple.rs b/src/ziptuple.rs index 82760ae8f..5ff0fad33 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -39,8 +39,7 @@ pub struct Zip { /// [`izip!()`]: crate::izip pub fn multizip(t: U) -> Zip where - Zip: From, - Zip: Iterator, + Zip: From + Iterator, { Zip::from(t) } diff --git a/tests/test_std.rs b/tests/test_std.rs index 063eca049..be163e98d 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -364,9 +364,9 @@ fn test_rciter() { fn trait_pointers() { struct ByRef<'r, I: ?Sized>(&'r mut I); - impl<'r, X, I: ?Sized> Iterator for ByRef<'r, I> + impl<'r, X, I> Iterator for ByRef<'r, I> where - I: 'r + Iterator, + I: ?Sized + 'r + Iterator, { type Item = X; fn next(&mut self) -> Option { From 18657fa0b3e16f30ce3642f50dc1bae6436550ed Mon Sep 17 00:00:00 2001 From: Marcel Hellwig Date: Thu, 2 Jun 2022 13:57:22 +0200 Subject: [PATCH 438/633] CI: add `cargo doc` --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10b9bb9d9..3fae4a116 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,6 +27,13 @@ jobs: components: clippy - run: RUSTFLAGS="--deny warnings" cargo clippy ${{ matrix.features }} + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - run: RUSTDOCFLAGS="-Dwarnings" cargo doc --all-features + msrv: runs-on: ubuntu-latest env: @@ -60,7 +67,7 @@ jobs: name: All checks succeeded if: success() runs-on: ubuntu-latest - needs: [check, msrv, test, check-format] + needs: [check, msrv, test, check-format, doc] steps: - name: Mark the job as successful run: exit 0 From 2f4739cc5f11c8ddb5da1a0b13f510a5543ea874 Mon Sep 17 00:00:00 2001 From: Ryan Avella Date: Sat, 23 Dec 2023 11:25:16 -0800 Subject: [PATCH 439/633] Document order guarantees for `[Tuple]Combinations` adapters --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index a571f49b1..a9977e1f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1591,6 +1591,11 @@ pub trait Itertools: Iterator { /// let it: TupleCombinations, (u32, u32, u32)> = (1..5).tuple_combinations(); /// itertools::assert_equal(it, vec![(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]); /// ``` + /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. fn tuple_combinations(self) -> TupleCombinations where Self: Sized + Clone, @@ -1629,6 +1634,11 @@ pub trait Itertools: Iterator { /// vec![2, 2], /// ]); /// ``` + /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. #[cfg(feature = "use_alloc")] fn combinations(self, k: usize) -> Combinations where From 8c9bc3c340531270e96bbf8e6b4b7242576e85ad Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 11 Jan 2024 11:08:13 +0100 Subject: [PATCH 440/633] `iterate`: document a possible panic --- src/sources.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sources.rs b/src/sources.rs index bd520c21d..6f4003dec 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -179,6 +179,18 @@ where /// /// itertools::assert_equal(iterate(1, |&i| i * 3).take(5), vec![1, 3, 9, 27, 81]); /// ``` +/// +/// **Panics** if compute the next value does. +/// +/// ```should_panic +/// # use itertools::iterate; +/// let mut it = iterate(25u32, |x| x - 10).take_while(|&x| x > 10); +/// assert_eq!(it.next(), Some(25)); // `Iterate` holds 15. +/// assert_eq!(it.next(), Some(15)); // `Iterate` holds 5. +/// it.next(); // `5 - 10` overflows. +/// ``` +/// +/// You can alternatively use [`core::iter::successors`]. pub fn iterate(initial_value: St, f: F) -> Iterate where F: FnMut(&St) -> St, From 62bbd2f3f342d259344a9fc27085fd47a2154c4f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 11 Jan 2024 15:18:38 +0100 Subject: [PATCH 441/633] `iterate`: better working example --- src/sources.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sources.rs b/src/sources.rs index 6f4003dec..a7f51e52f 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -177,7 +177,7 @@ where /// ``` /// use itertools::iterate; /// -/// itertools::assert_equal(iterate(1, |&i| i * 3).take(5), vec![1, 3, 9, 27, 81]); +/// itertools::assert_equal(iterate(1, |i| i % 3 + 1).take(5), vec![1, 2, 3, 1, 2]); /// ``` /// /// **Panics** if compute the next value does. @@ -190,7 +190,7 @@ where /// it.next(); // `5 - 10` overflows. /// ``` /// -/// You can alternatively use [`core::iter::successors`]. +/// You can alternatively use [`core::iter::successors`] as it better describes a finite iterator. pub fn iterate(initial_value: St, f: F) -> Iterate where F: FnMut(&St) -> St, From 5a321caa5dd355167c4272c2dd080d6d3b1755d8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 12 Jan 2024 09:11:51 +0100 Subject: [PATCH 442/633] Either or both: `impl From` implies `impl Into` I removed the clippy lint, commit, run `clippy --fix`, squash. --- src/either_or_both.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/either_or_both.rs b/src/either_or_both.rs index d13763441..b7a7fc141 100644 --- a/src/either_or_both.rs +++ b/src/either_or_both.rs @@ -494,10 +494,9 @@ impl EitherOrBoth { } } -#[allow(clippy::from_over_into)] -impl Into>> for EitherOrBoth { - fn into(self) -> Option> { - match self { +impl From> for Option> { + fn from(value: EitherOrBoth) -> Self { + match value { Left(l) => Some(Either::Left(l)), Right(r) => Some(Either::Right(r)), Both(..) => None, From d880c3f0ab3fab9668c2ee6c42daf325e95a2ef0 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 10:58:30 +0100 Subject: [PATCH 443/633] `ZipLongest::rfold` --- src/zip_longest.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/zip_longest.rs b/src/zip_longest.rs index e53733542..f390edebd 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -87,6 +87,37 @@ where Less => self.b.next_back().map(EitherOrBoth::Right), } } + + fn rfold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let Self { mut a, mut b } = self; + let a_len = a.len(); + let b_len = b.len(); + match a_len.cmp(&b_len) { + Equal => {} + Greater => { + init = a + .by_ref() + .rev() + .take(a_len - b_len) + .map(EitherOrBoth::Left) + .fold(init, &mut f) + } + Less => { + init = b + .by_ref() + .rev() + .take(b_len - a_len) + .map(EitherOrBoth::Right) + .fold(init, &mut f) + } + } + a.rfold(init, |acc, item_a| { + f(acc, EitherOrBoth::Both(item_a, b.next_back().unwrap())) + }) + } } impl ExactSizeIterator for ZipLongest From 0ed6f7992e21c14ece2f02d0e5b48b1459700923 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sat, 13 Jan 2024 16:50:17 +0100 Subject: [PATCH 444/633] CI: tell dependabot to not update the MSRV Co-Authored-By: danieleades <33452915+danieleades@users.noreply.github.com> --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3fae4a116..2f87f3b79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,7 +41,10 @@ jobs: steps: - uses: actions/checkout@v4 - uses: taiki-e/install-action@cargo-no-dev-deps - - uses: dtolnay/rust-toolchain@1.43.1 + - uses: dtolnay/rust-toolchain@master + with: + # Here, it does not trigger a PR from dependabot. + toolchain: 1.43.1 - run: cargo no-dev-deps check test: From f60fe7e9548ffb047420c3d9ec576aa792d8e9ca Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 26 Nov 2023 11:13:50 +0100 Subject: [PATCH 445/633] `TakeWhileInclusive::fold` --- src/take_while_inclusive.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index 5207d8a01..077b95498 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -64,6 +64,28 @@ where (0, self.iter.size_hint().1) } } + + fn fold(mut self, init: B, mut f: Fold) -> B + where + Fold: FnMut(B, Self::Item) -> B, + { + if self.done { + init + } else { + let predicate = &mut self.predicate; + self.iter + .try_fold(init, |mut acc, item| { + let is_ok = predicate(&item); + acc = f(acc, item); + if is_ok { + Ok(acc) + } else { + Err(acc) + } + }) + .unwrap_or_else(|err| err) + } + } } impl FusedIterator for TakeWhileInclusive From 0c606cc3d7576bc44ca216e8d308239169fd01a8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 12 Jan 2024 19:52:53 +0100 Subject: [PATCH 446/633] Debug `Diff` We can not derive Debug. I checked, an usual debugged enum does not display the name of the type but only the variant name. --- src/diff.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/diff.rs b/src/diff.rs index 0d3d358b5..ecbd20f92 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -5,6 +5,8 @@ //! describes the difference between two non-`Clone` iterators `I` and `J` after breaking ASAP from //! a lock-step comparison. +use std::fmt; + use crate::free::put_back; use crate::structs::PutBack; @@ -26,6 +28,27 @@ where Longer(usize, PutBack), } +impl fmt::Debug for Diff +where + I: Iterator, + J: Iterator, + PutBack: fmt::Debug, + PutBack: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::FirstMismatch(idx, i, j) => f + .debug_tuple("FirstMismatch") + .field(idx) + .field(i) + .field(j) + .finish(), + Self::Shorter(idx, i) => f.debug_tuple("Shorter").field(idx).field(i).finish(), + Self::Longer(idx, j) => f.debug_tuple("Longer").field(idx).field(j).finish(), + } + } +} + /// Compares every element yielded by both `i` and `j` with the given function in lock-step and /// returns a [`Diff`] which describes how `j` differs from `i`. /// From eeda182b9dcc55bb89c6cedba95e04cc0f6bc384 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Fri, 12 Jan 2024 23:19:06 +0100 Subject: [PATCH 447/633] Clone `Diff` Implementation can not be derived either. --- src/diff.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/diff.rs b/src/diff.rs index ecbd20f92..acef16ecc 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -49,6 +49,22 @@ where } } +impl Clone for Diff +where + I: Iterator, + J: Iterator, + PutBack: Clone, + PutBack: Clone, +{ + fn clone(&self) -> Self { + match self { + Self::FirstMismatch(idx, i, j) => Self::FirstMismatch(*idx, i.clone(), j.clone()), + Self::Shorter(idx, i) => Self::Shorter(*idx, i.clone()), + Self::Longer(idx, j) => Self::Longer(*idx, j.clone()), + } + } +} + /// Compares every element yielded by both `i` and `j` with the given function in lock-step and /// returns a [`Diff`] which describes how `j` differs from `i`. /// From 7f5f00d012e00219a12baa5298d56744d58fa077 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 8 Nov 2023 12:09:17 +0100 Subject: [PATCH 448/633] `Interleave::fold` --- src/adaptors/mod.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 08d91e397..36ff1cde6 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -76,6 +76,30 @@ where fn size_hint(&self) -> (usize, Option) { size_hint::add(self.a.size_hint(), self.b.size_hint()) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let Self { mut a, mut b, flag } = self; + if flag { + match b.next() { + Some(y) => init = f(init, y), + None => return a.fold(init, f), + } + } + let res = a.try_fold(init, |mut acc, x| { + acc = f(acc, x); + match b.next() { + Some(y) => Ok(f(acc, y)), + None => Err(acc), + } + }); + match res { + Ok(acc) => b.fold(acc, f), + Err(acc) => a.fold(acc, f), + } + } } impl FusedIterator for Interleave From 98344add23c53b25c14b992b785cb69516a54f32 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 8 Nov 2023 12:10:10 +0100 Subject: [PATCH 449/633] `InterleaveShortest::fold` --- src/adaptors/mod.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 36ff1cde6..a2bfb92ea 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -195,6 +195,34 @@ where }; (lower, upper) } + + fn fold(self, mut init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let Self { + mut it0, + mut it1, + phase, + } = self; + if phase { + match it1.next() { + Some(y) => init = f(init, y), + None => return init, + } + } + let res = it0.try_fold(init, |mut acc, x| { + acc = f(acc, x); + match it1.next() { + Some(y) => Ok(f(acc, y)), + None => Err(acc), + } + }); + match res { + Ok(val) => val, + Err(val) => val, + } + } } impl FusedIterator for InterleaveShortest From 6c588cd4752059fb87a688a3ef3ae15af5aab007 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 17 Jan 2024 09:13:35 +0100 Subject: [PATCH 450/633] `ZipLongest::fold`: use `try_fold` --- src/zip_longest.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/zip_longest.rs b/src/zip_longest.rs index f390edebd..6eb4bfbff 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -54,17 +54,20 @@ where } #[inline] - fn fold(self, mut init: B, mut f: F) -> B + fn fold(self, init: B, mut f: F) -> B where Self: Sized, F: FnMut(B, Self::Item) -> B, { - let Self { a, mut b } = self; - init = a.fold(init, |init, a| match b.next() { - Some(b) => f(init, EitherOrBoth::Both(a, b)), - None => f(init, EitherOrBoth::Left(a)), + let Self { mut a, mut b } = self; + let res = a.try_fold(init, |init, a| match b.next() { + Some(b) => Ok(f(init, EitherOrBoth::Both(a, b))), + None => Err(f(init, EitherOrBoth::Left(a))), }); - b.fold(init, |init, b| f(init, EitherOrBoth::Right(b))) + match res { + Ok(acc) => b.map(EitherOrBoth::Right).fold(acc, f), + Err(acc) => a.map(EitherOrBoth::Left).fold(acc, f), + } } } From 272aa212a937aeddeae0444e0d9270aec742d5da Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 18 Jan 2024 11:53:06 +0100 Subject: [PATCH 451/633] `RepeatN`: implement `PeekingNext` --- src/peeking_take_while.rs | 14 ++++++++++++++ src/repeatn.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index b4852c1c9..b224907aa 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -1,6 +1,7 @@ use crate::PutBack; #[cfg(feature = "use_alloc")] use crate::PutBackN; +use crate::RepeatN; use std::iter::Peekable; /// An iterator that allows peeking at an element before deciding to accept it. @@ -91,6 +92,19 @@ where } } +impl PeekingNext for RepeatN { + fn peeking_next(&mut self, accept: F) -> Option + where + F: FnOnce(&Self::Item) -> bool, + { + let r = self.elt.as_ref()?; + if !accept(r) { + return None; + } + self.next() + } +} + /// An iterator adaptor that takes items while a closure returns `true`. /// /// See [`.peeking_take_while()`](crate::Itertools::peeking_take_while) diff --git a/src/repeatn.rs b/src/repeatn.rs index 539c42615..d86ad9fac 100644 --- a/src/repeatn.rs +++ b/src/repeatn.rs @@ -6,7 +6,7 @@ use std::iter::FusedIterator; #[must_use = "iterators are lazy and do nothing unless consumed"] #[derive(Clone, Debug)] pub struct RepeatN { - elt: Option, + pub(crate) elt: Option, n: usize, } From 1714cc0d480c7a936e74f69cc8804041a31c5c24 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 18 Jan 2024 11:54:02 +0100 Subject: [PATCH 452/633] Test `RepeatN::peeking_next` --- tests/test_std.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index be163e98d..5d2864573 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -8,6 +8,7 @@ use crate::it::izip; use crate::it::multipeek; use crate::it::multizip; use crate::it::peek_nth; +use crate::it::repeat_n; use crate::it::ExactlyOneError; use crate::it::FoldWhile; use crate::it::Itertools; @@ -674,6 +675,21 @@ fn test_multipeek_peeking_next() { assert_eq!(mp.peek(), None); } +#[test] +fn test_repeat_n_peeking_next() { + use crate::it::PeekingNext; + let mut rn = repeat_n(0, 5); + assert_eq!(rn.peeking_next(|&x| x != 0), None); + assert_eq!(rn.peeking_next(|&x| x <= 0), Some(0)); + assert_eq!(rn.next(), Some(0)); + assert_eq!(rn.peeking_next(|&x| x <= 0), Some(0)); + assert_eq!(rn.peeking_next(|&x| x != 0), None); + assert_eq!(rn.peeking_next(|&x| x >= 0), Some(0)); + assert_eq!(rn.next(), Some(0)); + assert_eq!(rn.peeking_next(|&x| x <= 0), None); + assert_eq!(rn.next(), None); +} + #[test] fn test_peek_nth() { let nums = vec![1u8, 2, 3, 4, 5]; From ea6e5a7c47ff3c284b974f82973f21e9c6f95fbd Mon Sep 17 00:00:00 2001 From: Daniel Eades Date: Sat, 13 Jan 2024 04:53:25 +0000 Subject: [PATCH 453/633] add test coverage workflow --- .github/workflows/coverage.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..92f1e04ed --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,30 @@ +on: + push: + branches: [master] + pull_request: + +name: Code Coverage + +jobs: + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - name: checkout source + uses: actions/checkout@v4 + + - name: Install nightly toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: llvm-tools-preview + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Run llvm-cov + run: cargo llvm-cov --all-features --doctests --workspace --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + files: lcov.info From 3ee34d4c03608210a4bd29e15e5673e720341550 Mon Sep 17 00:00:00 2001 From: danieleades <33452915+danieleades@users.noreply.github.com> Date: Thu, 18 Jan 2024 08:52:14 +0000 Subject: [PATCH 454/633] Update coverage.yml --- .github/workflows/coverage.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 92f1e04ed..735e5cd22 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,12 +1,18 @@ on: push: branches: [master] + paths-ignore: + - "**.md" pull_request: + paths-ignore: + - "**.md" name: Code Coverage jobs: coverage: + # Don't run coverage on merge queue CI to avoid duplicating codecov reports + if: github.event_name != 'merge_group' name: coverage runs-on: ubuntu-latest steps: From f4afe797f7aa0a540972aa2650225ebf8fe0fb3a Mon Sep 17 00:00:00 2001 From: danieleades <33452915+danieleades@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:16:48 +0000 Subject: [PATCH 455/633] Update coverage.yml --- .github/workflows/coverage.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 735e5cd22..42c7b300b 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -11,8 +11,6 @@ name: Code Coverage jobs: coverage: - # Don't run coverage on merge queue CI to avoid duplicating codecov reports - if: github.event_name != 'merge_group' name: coverage runs-on: ubuntu-latest steps: From 80cd096fd2cf89d677c42a794411795d4061c336 Mon Sep 17 00:00:00 2001 From: philipp Date: Sat, 20 Jan 2024 18:12:00 +0100 Subject: [PATCH 456/633] Uniform field names for Interleave[Shortest] --- src/adaptors/mod.rs | 92 +++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index a2bfb92ea..84bbf98e0 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -28,9 +28,9 @@ use std::marker::PhantomData; #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Interleave { - a: Fuse, - b: Fuse, - flag: bool, + i: Fuse, + j: Fuse, + next_coming_from_j: bool, } /// Create an iterator that interleaves elements in `i` and `j`. @@ -45,9 +45,9 @@ where J: IntoIterator, { Interleave { - a: i.into_iter().fuse(), - b: j.into_iter().fuse(), - flag: false, + i: i.into_iter().fuse(), + j: j.into_iter().fuse(), + next_coming_from_j: false, } } @@ -59,45 +59,49 @@ where type Item = I::Item; #[inline] fn next(&mut self) -> Option { - self.flag = !self.flag; - if self.flag { - match self.a.next() { - None => self.b.next(), + self.next_coming_from_j = !self.next_coming_from_j; + if self.next_coming_from_j { + match self.i.next() { + None => self.j.next(), r => r, } } else { - match self.b.next() { - None => self.a.next(), + match self.j.next() { + None => self.i.next(), r => r, } } } fn size_hint(&self) -> (usize, Option) { - size_hint::add(self.a.size_hint(), self.b.size_hint()) + size_hint::add(self.i.size_hint(), self.j.size_hint()) } fn fold(self, mut init: B, mut f: F) -> B where F: FnMut(B, Self::Item) -> B, { - let Self { mut a, mut b, flag } = self; - if flag { - match b.next() { + let Self { + mut i, + mut j, + next_coming_from_j, + } = self; + if next_coming_from_j { + match j.next() { Some(y) => init = f(init, y), - None => return a.fold(init, f), + None => return i.fold(init, f), } } - let res = a.try_fold(init, |mut acc, x| { + let res = i.try_fold(init, |mut acc, x| { acc = f(acc, x); - match b.next() { + match j.next() { Some(y) => Ok(f(acc, y)), None => Err(acc), } }); match res { - Ok(acc) => b.fold(acc, f), - Err(acc) => a.fold(acc, f), + Ok(acc) => j.fold(acc, f), + Err(acc) => i.fold(acc, f), } } } @@ -123,21 +127,21 @@ where I: Iterator, J: Iterator, { - it0: I, - it1: J, - phase: bool, // false ==> it0, true ==> it1 + i: I, + j: J, + next_coming_from_j: bool, } /// Create a new `InterleaveShortest` iterator. -pub fn interleave_shortest(a: I, b: J) -> InterleaveShortest +pub fn interleave_shortest(i: I, j: J) -> InterleaveShortest where I: Iterator, J: Iterator, { InterleaveShortest { - it0: a, - it1: b, - phase: false, + i, + j, + next_coming_from_j: false, } } @@ -150,13 +154,13 @@ where #[inline] fn next(&mut self) -> Option { - let e = if self.phase { - self.it1.next() + let e = if self.next_coming_from_j { + self.j.next() } else { - self.it0.next() + self.i.next() }; if e.is_some() { - self.phase = !self.phase; + self.next_coming_from_j = !self.next_coming_from_j; } e } @@ -164,12 +168,12 @@ where #[inline] fn size_hint(&self) -> (usize, Option) { let (curr_hint, next_hint) = { - let it0_hint = self.it0.size_hint(); - let it1_hint = self.it1.size_hint(); - if self.phase { - (it1_hint, it0_hint) + let i_hint = self.i.size_hint(); + let j_hint = self.j.size_hint(); + if self.next_coming_from_j { + (j_hint, i_hint) } else { - (it0_hint, it1_hint) + (i_hint, j_hint) } }; let (curr_lower, curr_upper) = curr_hint; @@ -201,19 +205,19 @@ where F: FnMut(B, Self::Item) -> B, { let Self { - mut it0, - mut it1, - phase, + mut i, + mut j, + next_coming_from_j, } = self; - if phase { - match it1.next() { + if next_coming_from_j { + match j.next() { Some(y) => init = f(init, y), None => return init, } } - let res = it0.try_fold(init, |mut acc, x| { + let res = i.try_fold(init, |mut acc, x| { acc = f(acc, x); - match it1.next() { + match j.next() { Some(y) => Ok(f(acc, y)), None => Err(acc), } From e5593606661e53aab93694b0952f2c40dbf3b25d Mon Sep 17 00:00:00 2001 From: "daniel.eades" Date: Fri, 19 Jan 2024 13:02:18 +0000 Subject: [PATCH 457/633] allow small variation in coverage without returning a failure --- .codecov.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .codecov.yml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 000000000..d06394ae0 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,7 @@ +coverage: + status: + project: + default: + target: auto + # Allow a tiny drop of overall project coverage in PR to reduce spurious failures. + threshold: 0.25% From cafd9758b5f47cba9deced55f7cbf2217c8fd40b Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Wed, 18 Oct 2023 21:36:00 +0800 Subject: [PATCH 458/633] CI: add semver-check --- .github/workflows/ci.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2f87f3b79..205d4862c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,12 +65,21 @@ jobs: components: rustfmt - run: cargo fmt --check + semver-checks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: obi1kenobi/cargo-semver-checks-action@v2.1 + with: + rust-toolchain: stable + feature-group: all-features + # Used to signal to branch protections that all other jobs have succeeded. all-jobs-succeed: name: All checks succeeded if: success() runs-on: ubuntu-latest - needs: [check, msrv, test, check-format, doc] + needs: [check, msrv, test, check-format, doc, semver-checks] steps: - name: Mark the job as successful run: exit 0 From 62d2b65bdadd3e3dbe10bd78a5144edd241c88e9 Mon Sep 17 00:00:00 2001 From: Owen Leung Date: Fri, 19 Jan 2024 17:20:01 +0800 Subject: [PATCH 459/633] remove semver-check from all-jobs-succeed --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 205d4862c..9361ee62b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,7 @@ jobs: name: All checks succeeded if: success() runs-on: ubuntu-latest - needs: [check, msrv, test, check-format, doc, semver-checks] + needs: [check, msrv, test, check-format, doc] steps: - name: Mark the job as successful run: exit 0 From 9958f408be832fa6e3478a79be7291598df5b6ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:40:14 +0000 Subject: [PATCH 460/633] Bump obi1kenobi/cargo-semver-checks-action from 2.1 to 2.2 Bumps [obi1kenobi/cargo-semver-checks-action](https://github.com/obi1kenobi/cargo-semver-checks-action) from 2.1 to 2.2. - [Release notes](https://github.com/obi1kenobi/cargo-semver-checks-action/releases) - [Commits](https://github.com/obi1kenobi/cargo-semver-checks-action/compare/v2.1...v2.2) --- updated-dependencies: - dependency-name: obi1kenobi/cargo-semver-checks-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9361ee62b..e160f87a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: obi1kenobi/cargo-semver-checks-action@v2.1 + - uses: obi1kenobi/cargo-semver-checks-action@v2.2 with: rust-toolchain: stable feature-group: all-features From a48c5b474bd005d184f23146a125ad1c94c54ab6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:25:48 +0100 Subject: [PATCH 461/633] `WithPosition`: implement Debug --- src/with_position.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/with_position.rs b/src/with_position.rs index 89cddeb8a..2d56bb9b2 100644 --- a/src/with_position.rs +++ b/src/with_position.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::iter::{Fuse, FusedIterator, Peekable}; /// An iterator adaptor that wraps each element in an [`Position`]. @@ -14,6 +15,14 @@ where peekable: Peekable>, } +impl fmt::Debug for WithPosition +where + I: Iterator, + Peekable>: fmt::Debug, +{ + debug_fmt_fields!(WithPosition, handled_first, peekable); +} + impl Clone for WithPosition where I: Clone + Iterator, From 2e325a0bd466330c127907b1e0b6f5a7ebff40bf Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:31:13 +0100 Subject: [PATCH 462/633] `TakeWhileInclusive`: missing field in `Debug` --- src/take_while_inclusive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index 077b95498..0d186d76b 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -34,7 +34,7 @@ impl fmt::Debug for TakeWhileInclusive where I: Iterator + fmt::Debug, { - debug_fmt_fields!(TakeWhileInclusive, iter); + debug_fmt_fields!(TakeWhileInclusive, iter, done); } impl Iterator for TakeWhileInclusive From 94452e3eafb7c95fcbbb85c3dd78c274c6e43b62 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:32:50 +0100 Subject: [PATCH 463/633] `GroupingMapBy`: fix Debug implementation Derive it is not the best in this case since `F` is a function. `MapForGrouping` is now Debug and `GroupingMapBy` too as it wraps it. --- src/grouping_map.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index aeb86f1b2..a01968d4a 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -3,14 +3,19 @@ use crate::MinMaxResult; use std::cmp::Ordering; use std::collections::HashMap; +use std::fmt; use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; /// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by) -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct MapForGrouping(I, F); +impl fmt::Debug for MapForGrouping { + debug_fmt_fields!(MapForGrouping, 0); +} + impl MapForGrouping { pub(crate) fn new(iter: I, key_mapper: F) -> Self { Self(iter, key_mapper) From 7a1c22be5efc6be366496d1cfce7ebac2386f02f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:34:47 +0100 Subject: [PATCH 464/633] `FlattenOk`: Debug with macro --- src/flatten_ok.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 21ae1f722..853122295 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -147,13 +147,7 @@ where T: IntoIterator, T::IntoIter: fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FlattenOk") - .field("iter", &self.iter) - .field("inner_front", &self.inner_front) - .field("inner_back", &self.inner_back) - .finish() - } + debug_fmt_fields!(FlattenOk, iter, inner_front, inner_back); } /// Only the iterator being flattened needs to implement [`FusedIterator`]. From b785403f5f96404e1580ddcf3ce22e02d178248b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:48:23 +0100 Subject: [PATCH 465/633] `ExactlyOneError`: implement Debug differently It could be derived. It apparently was decided not to in #484, probably to not leak implementation details that could confuse the user. However, it would not render well for pretty formats such as `"{:#?}"`. I think we should call methods on `f` instead of using `write!`. --- src/exactly_one_err.rs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/exactly_one_err.rs b/src/exactly_one_err.rs index 90d898778..19b9e1918 100644 --- a/src/exactly_one_err.rs +++ b/src/exactly_one_err.rs @@ -102,25 +102,17 @@ where I::Item: Debug, { fn fmt(&self, f: &mut Formatter) -> FmtResult { + let mut dbg = f.debug_struct("ExactlyOneError"); match &self.first_two { Some(Either::Left([first, second])) => { - write!( - f, - "ExactlyOneError[First: {:?}, Second: {:?}, RemainingIter: {:?}]", - first, second, self.inner - ) + dbg.field("first", first).field("second", second); } Some(Either::Right(second)) => { - write!( - f, - "ExactlyOneError[Second: {:?}, RemainingIter: {:?}]", - second, self.inner - ) - } - None => { - write!(f, "ExactlyOneError[RemainingIter: {:?}]", self.inner) + dbg.field("second", second); } + None => {} } + dbg.field("inner", &self.inner).finish() } } From 8dd75f155cd88813e438fc23c373dc6716c1f8d7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:59:09 +0100 Subject: [PATCH 466/633] `Permutations`: use boxed slices internally --- src/permutations.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/permutations.rs b/src/permutations.rs index 534ca59c9..cf5973c8c 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use alloc::vec::Vec; use std::fmt; use std::iter::once; @@ -33,8 +34,8 @@ enum PermutationState { Buffered { k: usize, min_n: usize }, /// All values from the iterator are known so `n` is known. Loaded { - indices: Vec, - cycles: Vec, + indices: Box<[usize]>, + cycles: Box<[usize]>, }, /// No permutation left to generate. End, @@ -89,8 +90,8 @@ where } else { let n = *min_n; let prev_iteration_count = n - *k + 1; - let mut indices: Vec<_> = (0..n).collect(); - let mut cycles: Vec<_> = (n - k..n).rev().collect(); + let mut indices: Box<[_]> = (0..n).collect(); + let mut cycles: Box<[_]> = (n - k..n).rev().collect(); // Advance the state to the correct point. for _ in 0..prev_iteration_count { if advance(&mut indices, &mut cycles) { From a0411d6c6f03057d13e7282cc64071a1b5976beb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Jan 2024 14:03:24 +0100 Subject: [PATCH 467/633] `CombinationsWithReplacement`: use a boxed slice internally --- src/combinations_with_replacement.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 88d858b5f..ee3497849 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -1,3 +1,4 @@ +use alloc::boxed::Box; use alloc::vec::Vec; use std::fmt; use std::iter::FusedIterator; @@ -16,7 +17,7 @@ where I: Iterator, I::Item: Clone, { - indices: Vec, + indices: Box<[usize]>, pool: LazyBuffer, first: bool, } @@ -46,7 +47,7 @@ where I: Iterator, I::Item: Clone, { - let indices: Vec = alloc::vec![0; k]; + let indices = alloc::vec![0; k].into_boxed_slice(); let pool: LazyBuffer = LazyBuffer::new(iter); CombinationsWithReplacement { From 00998a4bbc0fee91ceef380e7c46d8865fa22b95 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Jan 2024 19:59:56 +0100 Subject: [PATCH 468/633] `CoalesceBy`: missing field in `Debug` --- src/adaptors/coalesce.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs index ad9df1192..ab1ab5255 100644 --- a/src/adaptors/coalesce.rs +++ b/src/adaptors/coalesce.rs @@ -33,7 +33,7 @@ where C: CountItem, C::CItem: fmt::Debug, { - debug_fmt_fields!(CoalesceBy, iter); + debug_fmt_fields!(CoalesceBy, iter, last); } pub trait CoalescePredicate { From dffac1fde4f0c1454e3542d1530cce96e78063f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 22:40:21 +0000 Subject: [PATCH 469/633] Bump obi1kenobi/cargo-semver-checks-action from 2.2 to 2.3 Bumps [obi1kenobi/cargo-semver-checks-action](https://github.com/obi1kenobi/cargo-semver-checks-action) from 2.2 to 2.3. - [Release notes](https://github.com/obi1kenobi/cargo-semver-checks-action/releases) - [Commits](https://github.com/obi1kenobi/cargo-semver-checks-action/compare/v2.2...v2.3) --- updated-dependencies: - dependency-name: obi1kenobi/cargo-semver-checks-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e160f87a7..9140274f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: obi1kenobi/cargo-semver-checks-action@v2.2 + - uses: obi1kenobi/cargo-semver-checks-action@v2.3 with: rust-toolchain: stable feature-group: all-features From 98d3978c87d6aa8d35e3f459dc0089130e04b646 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Tue, 23 Jan 2024 17:29:44 -0800 Subject: [PATCH 470/633] Prepare v0.12.1 release --- CHANGELOG.md | 37 +++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36fc27459..3994e5ce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,42 @@ # Changelog +## 0.12.1 + +### Added +- Documented iteration order guarantee for `Itertools::[tuple_]combinations` (#822) +- Documented possible panic in `iterate` (#842) +- Implemented `Clone` and `Debug` for `Diff` (#845) +- Implemented `Debug` for `WithPosition` (#859) +- Implemented `Eq` for `MinMaxResult` (#838) +- Implemented `From>` for `Option>` (#843) +- Implemented `PeekingNext` for `RepeatN` (#855) + +### Changed +- Made `CoalesceBy` lazy (#801) +- Optimized `Filter[Map]Ok::next`, `Itertools::partition`, `Unique[By]::next[_back]` (#818) +- Optimized `Itertools::find_position` (#837) +- Optimized `Positions::next[_back]` (#816) +- Optimized `ZipLongest::fold` (#854) +- Relaxed `Debug` bounds for `GroupingMapBy` (#860) +- Specialized `ExactlyOneError::fold` (#826) +- Specialized `Interleave[Shortest]::fold` (#849) +- Specialized `MultiPeek::fold` (#820) +- Specialized `PadUsing::[r]fold` (#825) +- Specialized `PeekNth::fold` (#824) +- Specialized `Positions::[r]fold` (#813) +- Specialized `PutBackN::fold` (#823) +- Specialized `RepeatN::[r]fold` (#821) +- Specialized `TakeWhileInclusive::fold` (#851) +- Specialized `ZipLongest::rfold` (#848) + +### Notable Internal Changes +- Added test coverage in CI (#847, #856) +- Added semver check in CI (#784) +- Enforced `clippy` in CI (#740) +- Enforced `rustdoc` in CI (#840) +- Improved specialization tests (#807) +- More specialization benchmarks (#806) + ## 0.12.0 ### Breaking diff --git a/Cargo.toml b/Cargo.toml index 9903ed4a9..70af68465 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.12.0" +version = "0.12.1" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" diff --git a/README.md b/README.md index e1c3f721d..381a3e9d9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.12.0" +itertools = "0.12.1" ``` How to use in your crate: From 8f33b9dfd557a4af8d578eac34e37c4ca63e1c77 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Tue, 16 Jan 2024 16:06:39 +0100 Subject: [PATCH 471/633] `ConsTuples`: remove `DoubleEndedIterator` implementation --- src/cons_tuples_impl.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/cons_tuples_impl.rs b/src/cons_tuples_impl.rs index 3cae0b06e..9ab309478 100644 --- a/src/cons_tuples_impl.rs +++ b/src/cons_tuples_impl.rs @@ -21,16 +21,6 @@ macro_rules! impl_cons_iter( self.iter.fold(accum, move |acc, (($($B,)*), x)| f(acc, ($($B,)* x, ))) } } - - #[allow(non_snake_case)] - impl DoubleEndedIterator for ConsTuples - where Iter: DoubleEndedIterator, - { - fn next_back(&mut self) -> Option { - self.iter.next().map(|(($($B,)*), x)| ($($B,)* x, )) - } - } - ); ); From bbb1e2231c372774105c4b2942dd617ccc4c3a48 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 18:18:39 +0100 Subject: [PATCH 472/633] `MultiProduct`: fix documentation --- src/adaptors/multi_product.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 74cb06d0e..eee246119 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -9,7 +9,7 @@ use alloc::vec::Vec; /// An iterator adaptor that iterates over the cartesian product of /// multiple iterators of type `I`. /// -/// An iterator element type is `Vec`. +/// An iterator element type is `Vec`. /// /// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) /// for more information. From b0a768e826d7535a531b6dfb681e3bc23ae79afa Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 18:24:51 +0100 Subject: [PATCH 473/633] `MultiProduct`: fix debug name --- src/adaptors/multi_product.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index eee246119..05c193f51 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -24,7 +24,7 @@ where I: Iterator + Clone + std::fmt::Debug, I::Item: Clone + std::fmt::Debug, { - debug_fmt_fields!(CoalesceBy, 0); + debug_fmt_fields!(MultiProduct, 0); } /// Create a new cartesian product iterator over an arbitrary number From f99a2d57cac56b018712f0ff718b50a125d71a12 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 12:17:49 +0100 Subject: [PATCH 474/633] `MultiProduct`: start over --- src/adaptors/multi_product.rs | 174 +--------------------------------- 1 file changed, 1 insertion(+), 173 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 05c193f51..f9091dd0b 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -1,8 +1,5 @@ #![cfg(feature = "use_alloc")] -use crate::size_hint; -use crate::Itertools; - use alloc::vec::Vec; #[derive(Clone)] @@ -52,87 +49,10 @@ where I: Iterator + Clone, I::Item: Clone, { - cur: Option, iter: I, iter_orig: I, } -/// Holds the current state during an iteration of a `MultiProduct`. -#[derive(Debug)] -enum MultiProductIterState { - StartOfIter, - MidIter { on_first_iter: bool }, -} - -impl MultiProduct -where - I: Iterator + Clone, - I::Item: Clone, -{ - /// Iterates the rightmost iterator, then recursively iterates iterators - /// to the left if necessary. - /// - /// Returns true if the iteration succeeded, else false. - fn iterate_last( - multi_iters: &mut [MultiProductIter], - mut state: MultiProductIterState, - ) -> bool { - use self::MultiProductIterState::*; - - if let Some((last, rest)) = multi_iters.split_last_mut() { - let on_first_iter = match state { - StartOfIter => { - let on_first_iter = !last.in_progress(); - state = MidIter { on_first_iter }; - on_first_iter - } - MidIter { on_first_iter } => on_first_iter, - }; - - if !on_first_iter { - last.iterate(); - } - - if last.in_progress() { - true - } else if Self::iterate_last(rest, state) { - last.reset(); - last.iterate(); - // If iterator is None twice consecutively, then iterator is - // empty; whole product is empty. - last.in_progress() - } else { - false - } - } else { - // Reached end of iterator list. On initialisation, return true. - // At end of iteration (final iterator finishes), finish. - match state { - StartOfIter => false, - MidIter { on_first_iter } => on_first_iter, - } - } - } - - /// Returns the unwrapped value of the next iteration. - fn curr_iterator(&self) -> Vec { - self.0 - .iter() - .map(|multi_iter| multi_iter.cur.clone().unwrap()) - .collect() - } - - /// Returns true if iteration has started and has not yet finished; false - /// otherwise. - fn in_progress(&self) -> bool { - if let Some(last) = self.0.last() { - last.in_progress() - } else { - false - } - } -} - impl MultiProductIter where I: Iterator + Clone, @@ -140,27 +60,10 @@ where { fn new(iter: I) -> Self { Self { - cur: None, iter: iter.clone(), iter_orig: iter, } } - - /// Iterate the managed iterator. - fn iterate(&mut self) { - self.cur = self.iter.next(); - } - - /// Reset the managed iterator. - fn reset(&mut self) { - self.iter = self.iter_orig.clone(); - } - - /// Returns true if the current iterator has been started and has not yet - /// finished; false otherwise. - fn in_progress(&self) -> bool { - self.cur.is_some() - } } impl Iterator for MultiProduct @@ -171,81 +74,6 @@ where type Item = Vec; fn next(&mut self) -> Option { - if Self::iterate_last(&mut self.0, MultiProductIterState::StartOfIter) { - Some(self.curr_iterator()) - } else { - None - } - } - - fn count(self) -> usize { - if self.0.is_empty() { - return 0; - } - - if !self.in_progress() { - return self - .0 - .into_iter() - .fold(1, |acc, multi_iter| acc * multi_iter.iter.count()); - } - - self.0.into_iter().fold( - 0, - |acc, - MultiProductIter { - iter, - iter_orig, - cur: _, - }| { - let total_count = iter_orig.count(); - let cur_count = iter.count(); - acc * total_count + cur_count - }, - ) - } - - fn size_hint(&self) -> (usize, Option) { - // Not ExactSizeIterator because size may be larger than usize - if self.0.is_empty() { - return (0, Some(0)); - } - - if !self.in_progress() { - return self.0.iter().fold((1, Some(1)), |acc, multi_iter| { - size_hint::mul(acc, multi_iter.iter.size_hint()) - }); - } - - self.0.iter().fold( - (0, Some(0)), - |acc, - MultiProductIter { - iter, - iter_orig, - cur: _, - }| { - let cur_size = iter.size_hint(); - let total_size = iter_orig.size_hint(); - size_hint::add(size_hint::mul(acc, total_size), cur_size) - }, - ) - } - - fn last(self) -> Option { - let iter_count = self.0.len(); - - let lasts: Self::Item = self - .0 - .into_iter() - .map(|multi_iter| multi_iter.iter.last()) - .while_some() - .collect(); - - if lasts.len() == iter_count { - Some(lasts) - } else { - None - } + todo!() } } From 6cb82b07663cc605fd9dbc3e791da40f54c19aae Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 12:24:08 +0100 Subject: [PATCH 475/633] `MultiProduct`: add `MultiProductInner` By wrapping "inner" in an option, I'll be able to fuse the iterator. Keep the current value of each iterator in `cur`: none at the beginning, some later. Previously, each `MultiProductIter` remembered its own element. Now, we have a vector of them. That way, we can update `cur` in-place and clone it to generate the item, I think that's simpler (less unwrap) and maybe more efficient. But we have two different vectors instead of a single bigger vector. --- src/adaptors/multi_product.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index f9091dd0b..bdedd5748 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -11,11 +11,21 @@ use alloc::vec::Vec; /// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MultiProduct(Vec>) +pub struct MultiProduct(Option>) where I: Iterator + Clone, I::Item: Clone; +#[derive(Clone)] +struct MultiProductInner +where + I: Iterator + Clone, + I::Item: Clone, +{ + iters: Vec>, + cur: Option>, +} + impl std::fmt::Debug for MultiProduct where I: Iterator + Clone + std::fmt::Debug, @@ -24,6 +34,14 @@ where debug_fmt_fields!(MultiProduct, 0); } +impl std::fmt::Debug for MultiProductInner +where + I: Iterator + Clone + std::fmt::Debug, + I::Item: Clone + std::fmt::Debug, +{ + debug_fmt_fields!(MultiProductInner, iters, cur); +} + /// Create a new cartesian product iterator over an arbitrary number /// of iterators of the same type. /// @@ -35,11 +53,13 @@ where ::IntoIter: Clone, ::Item: Clone, { - MultiProduct( - iters + let inner = MultiProductInner { + iters: iters .map(|i| MultiProductIter::new(i.into_iter())) .collect(), - ) + cur: None, + }; + MultiProduct(Some(inner)) } #[derive(Clone, Debug)] @@ -74,6 +94,7 @@ where type Item = Vec; fn next(&mut self) -> Option { + let inner = self.0.as_mut()?; todo!() } } From caa0abe5ae31719151c512bcd0ce70c44f382568 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 14:36:44 +0100 Subject: [PATCH 476/633] `MultiProduct::next` Co-Authored-By: Jakob Degen --- src/adaptors/multi_product.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index bdedd5748..9e20af0c8 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -95,6 +95,32 @@ where fn next(&mut self) -> Option { let inner = self.0.as_mut()?; - todo!() + match &mut inner.cur { + Some(values) => { + for (iter, item) in inner.iters.iter_mut().zip(values.iter_mut()).rev() { + if let Some(new) = iter.iter.next() { + *item = new; + return Some(values.clone()); + } else { + iter.iter = iter.iter_orig.clone(); + // `cur` is not none so the untouched `iter_orig` can not be empty. + *item = iter.iter.next().unwrap(); + } + } + // The iterator ends. + self.0 = None; + None + } + // Only the first time. + None => { + let next: Option> = inner.iters.iter_mut().map(|i| i.iter.next()).collect(); + if next.is_some() { + inner.cur = next.clone(); + } else { + self.0 = None; + } + next + } + } } } From 31a63ac452a2ad140361debbe056b80670c7ee9e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 15:17:01 +0100 Subject: [PATCH 477/633] `MultiProduct`: test the bugfix An empty product is supposed to generate a single empty vector, test this. Co-Authored-By: Jakob Degen --- tests/quick.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/quick.rs b/tests/quick.rs index dffcd22f6..954655a43 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -450,6 +450,12 @@ quickcheck! { assert_eq!(answer.into_iter().last(), a.multi_cartesian_product().last()); } + fn correct_empty_multi_product() -> () { + let empty = Vec::>::new().into_iter().multi_cartesian_product(); + assert!(correct_size_hint(empty.clone())); + itertools::assert_equal(empty, std::iter::once(Vec::new())) + } + #[allow(deprecated)] fn size_step(a: Iter, s: usize) -> bool { let mut s = s; From 4f3998a570cd615e99f3316c7d1cd9832af117a7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 15:20:19 +0100 Subject: [PATCH 478/633] `MultiProduct` is now fused --- src/adaptors/multi_product.rs | 7 +++++++ src/lib.rs | 3 ++- tests/specializations.rs | 1 - 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 9e20af0c8..5bf7dc334 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -124,3 +124,10 @@ where } } } + +impl std::iter::FusedIterator for MultiProduct +where + I: Iterator + Clone, + I::Item: Clone, +{ +} diff --git a/src/lib.rs b/src/lib.rs index a9977e1f1..126eb2221 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1162,10 +1162,11 @@ pub trait Itertools: Iterator { /// the product of iterators yielding multiple types, use the /// [`iproduct`] macro instead. /// - /// /// The iterator element type is `Vec`, where `T` is the iterator element /// of the subiterators. /// + /// Note that the iterator is fused. + /// /// ``` /// use itertools::Itertools; /// let mut multi_prod = (0..3).map(|i| (i * 2)..(i * 2 + 2)) diff --git a/tests/specializations.rs b/tests/specializations.rs index e14b1b669..fd8801e4e 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -163,7 +163,6 @@ quickcheck! { TestResult::passed() } - #[ignore] // It currently fails because `MultiProduct` is not fused. fn multi_cartesian_product(a: Vec, b: Vec, c: Vec) -> TestResult { if a.len() * b.len() * c.len() > 100 { return TestResult::discard(); From 4b6890c1ef9e9e1a6c366c233eae37c1a22e9040 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 16:46:37 +0100 Subject: [PATCH 479/633] `MultiProduct::last` --- src/adaptors/multi_product.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 5bf7dc334..3b146e812 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -123,6 +123,31 @@ where } } } + + fn last(self) -> Option { + let MultiProductInner { iters, cur } = self.0?; + if let Some(values) = cur { + let mut count = iters.len(); + let last = iters + .into_iter() + .zip(values) + .map(|(i, value)| { + i.iter.last().unwrap_or_else(|| { + count -= 1; + value + }) + }) + .collect(); + if count == 0 { + // `values` was the last item. + None + } else { + Some(last) + } + } else { + iters.into_iter().map(|i| i.iter.last()).collect() + } + } } impl std::iter::FusedIterator for MultiProduct From d9d1dafdc17368ea89612a9ebceedcb9b7e1d415 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 16:03:19 +0100 Subject: [PATCH 480/633] `MultiProduct::count` `count` is generally not a cheap operation so I avoid it when the result would be zero anyway. --- src/adaptors/multi_product.rs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 3b146e812..f95a60039 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -124,6 +124,34 @@ where } } + fn count(self) -> usize { + match self.0 { + None => 0, + Some(MultiProductInner { iters, cur }) => { + if cur.is_none() { + iters + .into_iter() + .map(|iter| iter.iter_orig.count()) + .try_fold(1, |product, count| { + if count == 0 { + None + } else { + Some(product * count) + } + }) + .unwrap_or_default() + } else { + iters.into_iter().fold(0, |mut acc, iter| { + if acc != 0 { + acc *= iter.iter_orig.count(); + } + acc + iter.iter.count() + }) + } + } + } + } + fn last(self) -> Option { let MultiProductInner { iters, cur } = self.0?; if let Some(values) = cur { From 114ae32c323a485f0567d6dd4d011d75e0c4f3e3 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Wed, 3 Jan 2024 16:11:20 +0100 Subject: [PATCH 481/633] `MultiProduct::size_hint` Similar to `count` but with "size hint" operations. --- src/adaptors/multi_product.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index f95a60039..9c8ed1988 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -2,6 +2,8 @@ use alloc::vec::Vec; +use crate::size_hint; + #[derive(Clone)] /// An iterator adaptor that iterates over the cartesian product of /// multiple iterators of type `I`. @@ -152,6 +154,27 @@ where } } + fn size_hint(&self) -> (usize, Option) { + match &self.0 { + None => (0, Some(0)), + Some(MultiProductInner { iters, cur }) => { + if cur.is_none() { + iters + .iter() + .map(|iter| iter.iter_orig.size_hint()) + .fold((1, Some(1)), size_hint::mul) + } else if let [first, tail @ ..] = &iters[..] { + tail.iter().fold(first.iter.size_hint(), |mut sh, iter| { + sh = size_hint::mul(sh, iter.iter_orig.size_hint()); + size_hint::add(sh, iter.iter.size_hint()) + }) + } else { + (0, Some(0)) + } + } + } + } + fn last(self) -> Option { let MultiProductInner { iters, cur } = self.0?; if let Some(values) = cur { From c85b083dac65d498506800d8131ec10e3950d436 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Sun, 7 Jan 2024 16:22:05 +0100 Subject: [PATCH 482/633] `MultiProduct`: add some documentation --- src/adaptors/multi_product.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 9c8ed1988..f0a2e3c8a 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -13,18 +13,24 @@ use crate::size_hint; /// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MultiProduct(Option>) +pub struct MultiProduct( + /// `None` once the iterator has ended. + Option>, +) where I: Iterator + Clone, I::Item: Clone; #[derive(Clone)] +/// Internals for `MultiProduct`. struct MultiProductInner where I: Iterator + Clone, I::Item: Clone, { + /// Holds the iterators. iters: Vec>, + /// It is `None` at the beginning then it holds the current item of each iterator. cur: Option>, } From ec7fb5fd09357958d4949b28d31a81248e982151 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 8 Jan 2024 19:31:50 +0100 Subject: [PATCH 483/633] `MultiProduct`: ends after the nullary product While adding comments, I realized I could set the iterator as finished in the case of the nullary cartesian product. It adds a simple invariant. I thought of making a comment but a debug check is better. --- src/adaptors/multi_product.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index f0a2e3c8a..53ca8f64b 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -105,6 +105,7 @@ where let inner = self.0.as_mut()?; match &mut inner.cur { Some(values) => { + debug_assert!(!inner.iters.is_empty()); for (iter, item) in inner.iters.iter_mut().zip(values.iter_mut()).rev() { if let Some(new) = iter.iter.next() { *item = new; @@ -122,10 +123,11 @@ where // Only the first time. None => { let next: Option> = inner.iters.iter_mut().map(|i| i.iter.next()).collect(); - if next.is_some() { - inner.cur = next.clone(); - } else { + if next.is_none() || inner.iters.is_empty() { + // This cartesian product had at most one item to generate and now ends. self.0 = None; + } else { + inner.cur = next.clone(); } next } @@ -175,7 +177,8 @@ where size_hint::add(sh, iter.iter.size_hint()) }) } else { - (0, Some(0)) + // Since `cur` is some, this cartesian product has started so `iters` is not empty. + unreachable!() } } } From dd1055bdbe32e84303399f8e0c683b6488105ccb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Mon, 8 Jan 2024 19:57:16 +0100 Subject: [PATCH 484/633] `MultiProduct`: add comments --- src/adaptors/multi_product.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 53ca8f64b..55fd0dbf3 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -102,10 +102,13 @@ where type Item = Vec; fn next(&mut self) -> Option { + // This fuses the iterator. let inner = self.0.as_mut()?; match &mut inner.cur { Some(values) => { debug_assert!(!inner.iters.is_empty()); + // Find (from the right) a non-finished iterator and + // reset the finished ones encountered. for (iter, item) in inner.iters.iter_mut().zip(values.iter_mut()).rev() { if let Some(new) = iter.iter.next() { *item = new; @@ -136,9 +139,12 @@ where fn count(self) -> usize { match self.0 { - None => 0, + None => 0, // The cartesian product has ended. Some(MultiProductInner { iters, cur }) => { if cur.is_none() { + // The iterator is fresh so the count is the product of the length of each iterator: + // - If one of them is empty, stop counting. + // - Less `count()` calls than the general case. iters .into_iter() .map(|iter| iter.iter_orig.count()) @@ -151,6 +157,7 @@ where }) .unwrap_or_default() } else { + // The general case. iters.into_iter().fold(0, |mut acc, iter| { if acc != 0 { acc *= iter.iter_orig.count(); @@ -164,7 +171,7 @@ where fn size_hint(&self) -> (usize, Option) { match &self.0 { - None => (0, Some(0)), + None => (0, Some(0)), // The cartesian product has ended. Some(MultiProductInner { iters, cur }) => { if cur.is_none() { iters @@ -186,6 +193,7 @@ where fn last(self) -> Option { let MultiProductInner { iters, cur } = self.0?; + // Collect the last item of each iterator of the product. if let Some(values) = cur { let mut count = iters.len(); let last = iters @@ -193,6 +201,7 @@ where .zip(values) .map(|(i, value)| { i.iter.last().unwrap_or_else(|| { + // The iterator is empty, use its current `value`. count -= 1; value }) From 2cd6f441a810d656a3ad4d055015a6808b5a9d45 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 18 Jan 2024 17:29:56 +0100 Subject: [PATCH 485/633] `MultiProduct` state: comment with code --- src/adaptors/multi_product.rs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 55fd0dbf3..f18eaafb7 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -1,4 +1,5 @@ #![cfg(feature = "use_alloc")] +use Option::{self as State, None as ProductEnded, Some as ProductInProgress}; use alloc::vec::Vec; @@ -13,10 +14,7 @@ use crate::size_hint; /// See [`.multi_cartesian_product()`](crate::Itertools::multi_cartesian_product) /// for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct MultiProduct( - /// `None` once the iterator has ended. - Option>, -) +pub struct MultiProduct(State>) where I: Iterator + Clone, I::Item: Clone; @@ -67,7 +65,7 @@ where .collect(), cur: None, }; - MultiProduct(Some(inner)) + MultiProduct(ProductInProgress(inner)) } #[derive(Clone, Debug)] @@ -119,8 +117,7 @@ where *item = iter.iter.next().unwrap(); } } - // The iterator ends. - self.0 = None; + self.0 = ProductEnded; None } // Only the first time. @@ -128,7 +125,7 @@ where let next: Option> = inner.iters.iter_mut().map(|i| i.iter.next()).collect(); if next.is_none() || inner.iters.is_empty() { // This cartesian product had at most one item to generate and now ends. - self.0 = None; + self.0 = ProductEnded; } else { inner.cur = next.clone(); } @@ -139,8 +136,8 @@ where fn count(self) -> usize { match self.0 { - None => 0, // The cartesian product has ended. - Some(MultiProductInner { iters, cur }) => { + ProductEnded => 0, + ProductInProgress(MultiProductInner { iters, cur }) => { if cur.is_none() { // The iterator is fresh so the count is the product of the length of each iterator: // - If one of them is empty, stop counting. @@ -171,8 +168,8 @@ where fn size_hint(&self) -> (usize, Option) { match &self.0 { - None => (0, Some(0)), // The cartesian product has ended. - Some(MultiProductInner { iters, cur }) => { + ProductEnded => (0, Some(0)), + ProductInProgress(MultiProductInner { iters, cur }) => { if cur.is_none() { iters .iter() From 515a17ba9888132305916a743ef03c3898e9ec84 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 18 Jan 2024 17:41:54 +0100 Subject: [PATCH 486/633] `MultiProduct` values: comment with code (1) --- src/adaptors/multi_product.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index f18eaafb7..724fd4fd0 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -1,5 +1,6 @@ #![cfg(feature = "use_alloc")] use Option::{self as State, None as ProductEnded, Some as ProductInProgress}; +use Option::{self as CurrentItems, None as NotYetPopulated, Some as Populated}; use alloc::vec::Vec; @@ -28,8 +29,8 @@ where { /// Holds the iterators. iters: Vec>, - /// It is `None` at the beginning then it holds the current item of each iterator. - cur: Option>, + /// Not populated at the beginning then it holds the current item of each iterator. + cur: CurrentItems>, } impl std::fmt::Debug for MultiProduct @@ -63,7 +64,7 @@ where iters: iters .map(|i| MultiProductIter::new(i.into_iter())) .collect(), - cur: None, + cur: NotYetPopulated, }; MultiProduct(ProductInProgress(inner)) } @@ -103,7 +104,7 @@ where // This fuses the iterator. let inner = self.0.as_mut()?; match &mut inner.cur { - Some(values) => { + Populated(values) => { debug_assert!(!inner.iters.is_empty()); // Find (from the right) a non-finished iterator and // reset the finished ones encountered. @@ -113,7 +114,7 @@ where return Some(values.clone()); } else { iter.iter = iter.iter_orig.clone(); - // `cur` is not none so the untouched `iter_orig` can not be empty. + // `cur` is populated so the untouched `iter_orig` can not be empty. *item = iter.iter.next().unwrap(); } } @@ -121,7 +122,7 @@ where None } // Only the first time. - None => { + NotYetPopulated => { let next: Option> = inner.iters.iter_mut().map(|i| i.iter.next()).collect(); if next.is_none() || inner.iters.is_empty() { // This cartesian product had at most one item to generate and now ends. @@ -181,7 +182,7 @@ where size_hint::add(sh, iter.iter.size_hint()) }) } else { - // Since `cur` is some, this cartesian product has started so `iters` is not empty. + // Since it is populated, this cartesian product has started so `iters` is not empty. unreachable!() } } @@ -191,7 +192,7 @@ where fn last(self) -> Option { let MultiProductInner { iters, cur } = self.0?; // Collect the last item of each iterator of the product. - if let Some(values) = cur { + if let Populated(values) = cur { let mut count = iters.len(); let last = iters .into_iter() From 33541c422406017a5ff63c11aebe8aa09a30f2f9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet Date: Thu, 18 Jan 2024 17:48:10 +0100 Subject: [PATCH 487/633] `MultiProduct` values: comment with code (2) Separate `Populated/NotYetPopulated` in `match` blocks. Indents change quite a bit. --- src/adaptors/multi_product.rs | 70 +++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index 724fd4fd0..d551c0afc 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -138,45 +138,51 @@ where fn count(self) -> usize { match self.0 { ProductEnded => 0, - ProductInProgress(MultiProductInner { iters, cur }) => { - if cur.is_none() { - // The iterator is fresh so the count is the product of the length of each iterator: - // - If one of them is empty, stop counting. - // - Less `count()` calls than the general case. - iters - .into_iter() - .map(|iter| iter.iter_orig.count()) - .try_fold(1, |product, count| { - if count == 0 { - None - } else { - Some(product * count) - } - }) - .unwrap_or_default() - } else { - // The general case. - iters.into_iter().fold(0, |mut acc, iter| { - if acc != 0 { - acc *= iter.iter_orig.count(); - } - acc + iter.iter.count() - }) + // The iterator is fresh so the count is the product of the length of each iterator: + // - If one of them is empty, stop counting. + // - Less `count()` calls than the general case. + ProductInProgress(MultiProductInner { + iters, + cur: NotYetPopulated, + }) => iters + .into_iter() + .map(|iter| iter.iter_orig.count()) + .try_fold(1, |product, count| { + if count == 0 { + None + } else { + Some(product * count) + } + }) + .unwrap_or_default(), + // The general case. + ProductInProgress(MultiProductInner { + iters, + cur: Populated(_), + }) => iters.into_iter().fold(0, |mut acc, iter| { + if acc != 0 { + acc *= iter.iter_orig.count(); } - } + acc + iter.iter.count() + }), } } fn size_hint(&self) -> (usize, Option) { match &self.0 { ProductEnded => (0, Some(0)), - ProductInProgress(MultiProductInner { iters, cur }) => { - if cur.is_none() { - iters - .iter() - .map(|iter| iter.iter_orig.size_hint()) - .fold((1, Some(1)), size_hint::mul) - } else if let [first, tail @ ..] = &iters[..] { + ProductInProgress(MultiProductInner { + iters, + cur: NotYetPopulated, + }) => iters + .iter() + .map(|iter| iter.iter_orig.size_hint()) + .fold((1, Some(1)), size_hint::mul), + ProductInProgress(MultiProductInner { + iters, + cur: Populated(_), + }) => { + if let [first, tail @ ..] = &iters[..] { tail.iter().fold(first.iter.size_hint(), |mut sh, iter| { sh = size_hint::mul(sh, iter.iter_orig.size_hint()); size_hint::add(sh, iter.iter.size_hint()) From 1f69c5347ec6e353f0ca63f125a144b797450e58 Mon Sep 17 00:00:00 2001 From: YizhePKU Date: Wed, 3 Jan 2024 05:02:35 +0000 Subject: [PATCH 488/633] `multi_cartesian_product`: document the nullary product Regression test for #337 --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 126eb2221..1f39379e2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1181,6 +1181,16 @@ pub trait Itertools: Iterator { /// assert_eq!(multi_prod.next(), Some(vec![1, 3, 5])); /// assert_eq!(multi_prod.next(), None); /// ``` + /// + /// If the adapted iterator is empty, the result is an iterator yielding a single empty vector. + /// This is known as the [nullary cartesian product](https://en.wikipedia.org/wiki/Empty_product#Nullary_Cartesian_product). + /// + /// ``` + /// use itertools::Itertools; + /// let mut nullary_cartesian_product = (0..0).map(|i| (i * 2)..(i * 2 + 2)).multi_cartesian_product(); + /// assert_eq!(nullary_cartesian_product.next(), Some(vec![])); + /// assert_eq!(nullary_cartesian_product.next(), None); + /// ``` #[cfg(feature = "use_alloc")] fn multi_cartesian_product(self) -> MultiProduct<::IntoIter> where From 0d741ab93eb025965dd7372e51f8c82cf9076643 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:38:25 +0100 Subject: [PATCH 489/633] Deprecate `group_by` in favor of `chunk_by` --- src/groupbylazy.rs | 4 ++-- src/lib.rs | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index c318ec7b3..08e9e933b 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -294,7 +294,7 @@ where /// value. It should be stored in a local variable or temporary and /// iterated. /// -/// See [`.group_by()`](crate::Itertools::group_by) for more information. +/// See [`.chunk_by()`](crate::Itertools::chunk_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct GroupBy where @@ -368,7 +368,7 @@ where /// Iterator element type is `(K, Group)`: /// the group's key `K` and the group's iterator. /// -/// See [`.group_by()`](crate::Itertools::group_by) for more information. +/// See [`.chunk_by()`](crate::Itertools::chunk_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct Groups<'a, K, I, F> where diff --git a/src/lib.rs b/src/lib.rs index 1f39379e2..bfe4b95a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,7 @@ //! - `use_std` //! - Enabled by default. //! - Disable to compile itertools using `#![no_std]`. This disables -//! any items that depend on collections (like `group_by`, `unique`, +//! any items that depend on collections (like `chunk_by`, `unique`, //! `kmerge`, `join` and many more). //! //! ## Rust Version @@ -600,13 +600,13 @@ pub trait Itertools: Iterator { /// // Note: The `&` is significant here, `GroupBy` is iterable /// // only by reference. You can also call `.into_iter()` explicitly. /// let mut data_grouped = Vec::new(); - /// for (key, group) in &data.into_iter().group_by(|elt| *elt >= 0) { + /// for (key, group) in &data.into_iter().chunk_by(|elt| *elt >= 0) { /// data_grouped.push((key, group.collect())); /// } /// assert_eq!(data_grouped, vec![(true, vec![1, 3]), (false, vec![-2, -2]), (true, vec![1, 0, 1, 2])]); /// ``` #[cfg(feature = "use_alloc")] - fn group_by(self, key: F) -> GroupBy + fn chunk_by(self, key: F) -> GroupBy where Self: Sized, F: FnMut(&Self::Item) -> K, @@ -615,6 +615,18 @@ pub trait Itertools: Iterator { groupbylazy::new(self, key) } + /// See [`.chunk_by()`](Itertools::chunk_by). + #[deprecated(note = "Use .chunk_by() instead", since = "0.13.0")] + #[cfg(feature = "use_alloc")] + fn group_by(self, key: F) -> GroupBy + where + Self: Sized, + F: FnMut(&Self::Item) -> K, + K: PartialEq, + { + self.chunk_by(key) + } + /// Return an *iterable* that can chunk the iterator. /// /// Yield subiterators (chunks) that each yield a fixed number elements, From e4f4a6edf8bd0396725219976bfb89b6d5299587 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 10:11:04 +0100 Subject: [PATCH 490/633] Update `group_by` doc example --- src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bfe4b95a9..d0043b199 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -593,15 +593,15 @@ pub trait Itertools: Iterator { /// ``` /// use itertools::Itertools; /// - /// // group data into runs of larger than zero or not. + /// // chunk data into runs of larger than zero or not. /// let data = vec![1, 3, -2, -2, 1, 0, 1, 2]; - /// // groups: |---->|------>|--------->| + /// // chunks: |---->|------>|--------->| /// /// // Note: The `&` is significant here, `GroupBy` is iterable /// // only by reference. You can also call `.into_iter()` explicitly. /// let mut data_grouped = Vec::new(); - /// for (key, group) in &data.into_iter().chunk_by(|elt| *elt >= 0) { - /// data_grouped.push((key, group.collect())); + /// for (key, chunk) in &data.into_iter().chunk_by(|elt| *elt >= 0) { + /// data_grouped.push((key, chunk.collect())); /// } /// assert_eq!(data_grouped, vec![(true, vec![1, 3]), (false, vec![-2, -2]), (true, vec![1, 0, 1, 2])]); /// ``` From 1c12851d899d3fe1f729599d1e8d7dd8bcfb4207 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 30 Jan 2024 23:03:21 +0100 Subject: [PATCH 491/633] Deprecate `GroupBy` in favor of `ChunkBy` --- src/groupbylazy.rs | 26 +++++++++++++++----------- src/lib.rs | 17 ++++++++++------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 08e9e933b..007fea6f6 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -1,7 +1,7 @@ use alloc::vec::{self, Vec}; use std::cell::{Cell, RefCell}; -/// A trait to unify `FnMut` for `GroupBy` with the chunk key in `IntoChunks` +/// A trait to unify `FnMut` for `ChunkBy` with the chunk key in `IntoChunks` trait KeyFunction { type Key; fn call_mut(&mut self, arg: A) -> Self::Key; @@ -282,10 +282,14 @@ where } } -/// `GroupBy` is the storage for the lazy grouping operation. +#[deprecated(note = "Use `ChunkBy` instead", since = "0.13.0")] +/// See [`ChunkBy`](crate::structs::ChunkBy). +pub type GroupBy = ChunkBy; + +/// `ChunkBy` is the storage for the lazy grouping operation. /// /// If the groups are consumed in their original order, or if each -/// group is dropped without keeping it around, then `GroupBy` uses +/// group is dropped without keeping it around, then `ChunkBy` uses /// no allocations. It needs allocations only if several group iterators /// are alive at the same time. /// @@ -296,7 +300,7 @@ where /// /// See [`.chunk_by()`](crate::Itertools::chunk_by) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct GroupBy +pub struct ChunkBy where I: Iterator, { @@ -307,12 +311,12 @@ where } /// Create a new -pub fn new(iter: J, f: F) -> GroupBy +pub fn new(iter: J, f: F) -> ChunkBy where J: IntoIterator, F: FnMut(&J::Item) -> K, { - GroupBy { + ChunkBy { inner: RefCell::new(GroupInner { key: f, iter: iter.into_iter(), @@ -329,7 +333,7 @@ where } } -impl GroupBy +impl ChunkBy where I: Iterator, { @@ -348,7 +352,7 @@ where } } -impl<'a, K, I, F> IntoIterator for &'a GroupBy +impl<'a, K, I, F> IntoIterator for &'a ChunkBy where I: Iterator, I::Item: 'a, @@ -377,7 +381,7 @@ where K: 'a, F: 'a, { - parent: &'a GroupBy, + parent: &'a ChunkBy, } impl<'a, K, I, F> Iterator for Groups<'a, K, I, F> @@ -418,7 +422,7 @@ where K: 'a, F: 'a, { - parent: &'a GroupBy, + parent: &'a ChunkBy, index: usize, first: Option, } @@ -476,7 +480,7 @@ where /// `ChunkLazy` is the storage for a lazy chunking operation. /// -/// `IntoChunks` behaves just like `GroupBy`: it is iterable, and +/// `IntoChunks` behaves just like `ChunkBy`: it is iterable, and /// it only buffers if several chunk iterators are alive at the same time. /// /// This type implements [`IntoIterator`] (it is **not** an iterator diff --git a/src/lib.rs b/src/lib.rs index d0043b199..f12d95a1d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,8 +99,11 @@ pub mod structs { pub use crate::exactly_one_err::ExactlyOneError; pub use crate::flatten_ok::FlattenOk; pub use crate::format::{Format, FormatWith}; + #[allow(deprecated)] + #[cfg(feature = "use_alloc")] + pub use crate::groupbylazy::GroupBy; #[cfg(feature = "use_alloc")] - pub use crate::groupbylazy::{Chunk, Chunks, Group, GroupBy, Groups, IntoChunks}; + pub use crate::groupbylazy::{Chunk, ChunkBy, Chunks, Group, Groups, IntoChunks}; #[cfg(feature = "use_std")] pub use crate::grouping_map::{GroupingMap, GroupingMapBy}; pub use crate::intersperse::{Intersperse, IntersperseWith}; @@ -575,10 +578,10 @@ pub trait Itertools: Iterator { /// Consecutive elements that map to the same key (“runs”), are assigned /// to the same group. /// - /// `GroupBy` is the storage for the lazy grouping operation. + /// `ChunkBy` is the storage for the lazy grouping operation. /// /// If the groups are consumed in order, or if each group's iterator is - /// dropped without keeping it around, then `GroupBy` uses no + /// dropped without keeping it around, then `ChunkBy` uses no /// allocations. It needs allocations only if several group iterators /// are alive at the same time. /// @@ -597,7 +600,7 @@ pub trait Itertools: Iterator { /// let data = vec![1, 3, -2, -2, 1, 0, 1, 2]; /// // chunks: |---->|------>|--------->| /// - /// // Note: The `&` is significant here, `GroupBy` is iterable + /// // Note: The `&` is significant here, `ChunkBy` is iterable /// // only by reference. You can also call `.into_iter()` explicitly. /// let mut data_grouped = Vec::new(); /// for (key, chunk) in &data.into_iter().chunk_by(|elt| *elt >= 0) { @@ -606,7 +609,7 @@ pub trait Itertools: Iterator { /// assert_eq!(data_grouped, vec![(true, vec![1, 3]), (false, vec![-2, -2]), (true, vec![1, 0, 1, 2])]); /// ``` #[cfg(feature = "use_alloc")] - fn chunk_by(self, key: F) -> GroupBy + fn chunk_by(self, key: F) -> ChunkBy where Self: Sized, F: FnMut(&Self::Item) -> K, @@ -618,7 +621,7 @@ pub trait Itertools: Iterator { /// See [`.chunk_by()`](Itertools::chunk_by). #[deprecated(note = "Use .chunk_by() instead", since = "0.13.0")] #[cfg(feature = "use_alloc")] - fn group_by(self, key: F) -> GroupBy + fn group_by(self, key: F) -> ChunkBy where Self: Sized, F: FnMut(&Self::Item) -> K, @@ -633,7 +636,7 @@ pub trait Itertools: Iterator { /// determined by `size`. The last chunk will be shorter if there aren't /// enough elements. /// - /// `IntoChunks` is based on `GroupBy`: it is iterable (implements + /// `IntoChunks` is based on `ChunkBy`: it is iterable (implements /// `IntoIterator`, **not** `Iterator`), and it only buffers if several /// chunk iterators are alive at the same time. /// From 3024f952a5dcaef6ef4a7802d186317daef8b26f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:35:56 +0100 Subject: [PATCH 492/633] Fix `iris.rs` --- examples/iris.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/iris.rs b/examples/iris.rs index 5f5525154..a041b2849 100644 --- a/examples/iris.rs +++ b/examples/iris.rs @@ -77,15 +77,15 @@ fn main() { let mut plot_symbols = "+ox".chars().cycle(); let mut symbolmap = HashMap::new(); - // using Itertools::group_by - for (species, species_group) in &irises.iter().group_by(|iris| &iris.name) { + // using Itertools::chunk_by + for (species, species_chunk) in &irises.iter().chunk_by(|iris| &iris.name) { // assign a plot symbol symbolmap .entry(species) .or_insert_with(|| plot_symbols.next().unwrap()); println!("{} (symbol={})", species, symbolmap[species]); - for iris in species_group { + for iris in species_chunk { // using Itertools::format for lazy formatting println!("{:>3.1}", iris.data.iter().format(", ")); } From 6cd500b91b6718a1f93600bce78e73144d983e3f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:38:11 +0100 Subject: [PATCH 493/633] Fix `bench1.rs` --- benches/bench1.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/benches/bench1.rs b/benches/bench1.rs index 8d8881676..f718ce2c9 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -390,7 +390,7 @@ fn zip_unchecked_counted_loop3(c: &mut Criterion) { }); } -fn group_by_lazy_1(c: &mut Criterion) { +fn chunk_by_lazy_1(c: &mut Criterion) { let mut data = vec![0; 1024]; for (index, elt) in data.iter_mut().enumerate() { *elt = index / 10; @@ -398,10 +398,10 @@ fn group_by_lazy_1(c: &mut Criterion) { let data = black_box(data); - c.bench_function("group by lazy 1", move |b| { + c.bench_function("chunk by lazy 1", move |b| { b.iter(|| { - for (_key, group) in &data.iter().group_by(|elt| **elt) { - for elt in group { + for (_key, chunk) in &data.iter().chunk_by(|elt| **elt) { + for elt in chunk { black_box(elt); } } @@ -409,7 +409,7 @@ fn group_by_lazy_1(c: &mut Criterion) { }); } -fn group_by_lazy_2(c: &mut Criterion) { +fn chunk_by_lazy_2(c: &mut Criterion) { let mut data = vec![0; 1024]; for (index, elt) in data.iter_mut().enumerate() { *elt = index / 2; @@ -417,10 +417,10 @@ fn group_by_lazy_2(c: &mut Criterion) { let data = black_box(data); - c.bench_function("group by lazy 2", move |b| { + c.bench_function("chunk by lazy 2", move |b| { b.iter(|| { - for (_key, group) in &data.iter().group_by(|elt| **elt) { - for elt in group { + for (_key, chunk) in &data.iter().chunk_by(|elt| **elt) { + for elt in chunk { black_box(elt); } } @@ -436,8 +436,8 @@ fn slice_chunks(c: &mut Criterion) { c.bench_function("slice chunks", move |b| { b.iter(|| { - for group in data.chunks(sz) { - for elt in group { + for chunk in data.chunks(sz) { + for elt in chunk { black_box(elt); } } @@ -453,8 +453,8 @@ fn chunks_lazy_1(c: &mut Criterion) { c.bench_function("chunks lazy 1", move |b| { b.iter(|| { - for group in &data.iter().chunks(sz) { - for elt in group { + for chunk in &data.iter().chunks(sz) { + for elt in chunk { black_box(elt); } } @@ -813,8 +813,8 @@ criterion_group!( zipdot_i32_unchecked_counted_loop, zipdot_f32_unchecked_counted_loop, zip_unchecked_counted_loop3, - group_by_lazy_1, - group_by_lazy_2, + chunk_by_lazy_1, + chunk_by_lazy_2, slice_chunks, chunks_lazy_1, equal, From 7eacf7c6452eb6aff862fdcdf8df4f9727754334 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:41:43 +0100 Subject: [PATCH 494/633] Fix tests 1: `group_by` to `chunk_by` --- tests/quick.rs | 16 ++++++++-------- tests/test_std.rs | 34 +++++++++++++++++----------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index 954655a43..e7c7c55ce 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1053,24 +1053,24 @@ quickcheck! { } quickcheck! { - fn fuzz_group_by_lazy_1(it: Iter) -> bool { + fn fuzz_chunk_by_lazy_1(it: Iter) -> bool { let jt = it.clone(); - let groups = it.group_by(|k| *k); + let groups = it.chunk_by(|k| *k); itertools::equal(jt, groups.into_iter().flat_map(|(_, x)| x)) } } quickcheck! { - fn fuzz_group_by_lazy_2(data: Vec) -> bool { - let groups = data.iter().group_by(|k| *k / 10); + fn fuzz_chunk_by_lazy_2(data: Vec) -> bool { + let groups = data.iter().chunk_by(|k| *k / 10); let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x)); res } } quickcheck! { - fn fuzz_group_by_lazy_3(data: Vec) -> bool { - let grouper = data.iter().group_by(|k| *k / 10); + fn fuzz_chunk_by_lazy_3(data: Vec) -> bool { + let grouper = data.iter().chunk_by(|k| *k / 10); let groups = grouper.into_iter().collect_vec(); let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x)); res @@ -1078,8 +1078,8 @@ quickcheck! { } quickcheck! { - fn fuzz_group_by_lazy_duo(data: Vec, order: Vec<(bool, bool)>) -> bool { - let grouper = data.iter().group_by(|k| *k / 3); + fn fuzz_chunk_by_lazy_duo(data: Vec, order: Vec<(bool, bool)>) -> bool { + let grouper = data.iter().chunk_by(|k| *k / 3); let mut groups1 = grouper.into_iter(); let mut groups2 = grouper.into_iter(); let mut elts = Vec::<&u8>::new(); diff --git a/tests/test_std.rs b/tests/test_std.rs index 5d2864573..402385488 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -275,7 +275,7 @@ fn all_equal() { assert!("A".chars().all_equal()); assert!(!"AABBCCC".chars().all_equal()); assert!("AAAAAAA".chars().all_equal()); - for (_key, mut sub) in &"AABBCCC".chars().group_by(|&x| x) { + for (_key, mut sub) in &"AABBCCC".chars().chunk_by(|&x| x) { assert!(sub.all_equal()); } } @@ -797,14 +797,14 @@ fn pad_using() { } #[test] -fn group_by() { - for (ch1, sub) in &"AABBCCC".chars().group_by(|&x| x) { +fn chunk_by() { + for (ch1, sub) in &"AABBCCC".chars().chunk_by(|&x| x) { for ch2 in sub { assert_eq!(ch1, ch2); } } - for (ch1, sub) in &"AAABBBCCCCDDDD".chars().group_by(|&x| x) { + for (ch1, sub) in &"AAABBBCCCCDDDD".chars().chunk_by(|&x| x) { for ch2 in sub { assert_eq!(ch1, ch2); if ch1 == 'C' { @@ -817,7 +817,7 @@ fn group_by() { // try all possible orderings for indices in permutohedron::Heap::new(&mut [0, 1, 2, 3]) { - let groups = "AaaBbbccCcDDDD".chars().group_by(&toupper); + let groups = "AaaBbbccCcDDDD".chars().chunk_by(&toupper); let mut subs = groups.into_iter().collect_vec(); for &idx in &indices[..] { @@ -833,7 +833,7 @@ fn group_by() { } } - let groups = "AAABBBCCCCDDDD".chars().group_by(|&x| x); + let groups = "AAABBBCCCCDDDD".chars().chunk_by(|&x| x); let mut subs = groups.into_iter().map(|(_, g)| g).collect_vec(); let sd = subs.pop().unwrap(); @@ -851,7 +851,7 @@ fn group_by() { { let mut ntimes = 0; let text = "AABCCC"; - for (_, sub) in &text.chars().group_by(|&x| { + for (_, sub) in &text.chars().chunk_by(|&x| { ntimes += 1; x }) { @@ -863,7 +863,7 @@ fn group_by() { { let mut ntimes = 0; let text = "AABCCC"; - for _ in &text.chars().group_by(|&x| { + for _ in &text.chars().chunk_by(|&x| { ntimes += 1; x }) {} @@ -872,25 +872,25 @@ fn group_by() { { let text = "ABCCCDEEFGHIJJKK"; - let gr = text.chars().group_by(|&x| x); + let gr = text.chars().chunk_by(|&x| x); it::assert_equal(gr.into_iter().flat_map(|(_, sub)| sub), text.chars()); } } #[test] -fn group_by_lazy_2() { +fn chunk_by_lazy_2() { let data = [0, 1]; - let groups = data.iter().group_by(|k| *k); + let groups = data.iter().chunk_by(|k| *k); let gs = groups.into_iter().collect_vec(); it::assert_equal(data.iter(), gs.into_iter().flat_map(|(_k, g)| g)); let data = [0, 1, 1, 0, 0]; - let groups = data.iter().group_by(|k| *k); + let groups = data.iter().chunk_by(|k| *k); let mut gs = groups.into_iter().collect_vec(); gs[1..].reverse(); it::assert_equal(&[0, 0, 0, 1, 1], gs.into_iter().flat_map(|(_, g)| g)); - let grouper = data.iter().group_by(|k| *k); + let grouper = data.iter().chunk_by(|k| *k); let mut groups = Vec::new(); for (k, group) in &grouper { if *k == 1 { @@ -900,7 +900,7 @@ fn group_by_lazy_2() { it::assert_equal(&mut groups[0], &[1, 1]); let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; - let grouper = data.iter().group_by(|k| *k); + let grouper = data.iter().chunk_by(|k| *k); let mut groups = Vec::new(); for (i, (_, group)) in grouper.into_iter().enumerate() { if i < 2 { @@ -918,7 +918,7 @@ fn group_by_lazy_2() { // use groups as chunks let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let mut i = 0; - let grouper = data.iter().group_by(move |_| { + let grouper = data.iter().chunk_by(move |_| { let k = i / 3; i += 1; k @@ -935,10 +935,10 @@ fn group_by_lazy_2() { } #[test] -fn group_by_lazy_3() { +fn chunk_by_lazy_3() { // test consuming each group on the lap after it was produced let data = [0, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2]; - let grouper = data.iter().group_by(|elt| *elt); + let grouper = data.iter().chunk_by(|elt| *elt); let mut last = None; for (key, group) in &grouper { if let Some(gr) = last.take() { From 49c85f4eaa07a8f4f1296ebac4f2df78727eef28 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:43:19 +0100 Subject: [PATCH 495/633] Fix tests 2: `groups` to `chunks` (variables) --- tests/quick.rs | 30 +++++++++++++++--------------- tests/test_std.rs | 35 +++++++++++++++++------------------ 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index e7c7c55ce..f96418f40 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1055,15 +1055,15 @@ quickcheck! { quickcheck! { fn fuzz_chunk_by_lazy_1(it: Iter) -> bool { let jt = it.clone(); - let groups = it.chunk_by(|k| *k); - itertools::equal(jt, groups.into_iter().flat_map(|(_, x)| x)) + let chunks = it.chunk_by(|k| *k); + itertools::equal(jt, chunks.into_iter().flat_map(|(_, x)| x)) } } quickcheck! { fn fuzz_chunk_by_lazy_2(data: Vec) -> bool { - let groups = data.iter().chunk_by(|k| *k / 10); - let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x)); + let chunks = data.iter().chunk_by(|k| *k / 10); + let res = itertools::equal(data.iter(), chunks.into_iter().flat_map(|(_, x)| x)); res } } @@ -1071,8 +1071,8 @@ quickcheck! { quickcheck! { fn fuzz_chunk_by_lazy_3(data: Vec) -> bool { let grouper = data.iter().chunk_by(|k| *k / 10); - let groups = grouper.into_iter().collect_vec(); - let res = itertools::equal(data.iter(), groups.into_iter().flat_map(|(_, x)| x)); + let chunks = grouper.into_iter().collect_vec(); + let res = itertools::equal(data.iter(), chunks.into_iter().flat_map(|(_, x)| x)); res } } @@ -1080,31 +1080,31 @@ quickcheck! { quickcheck! { fn fuzz_chunk_by_lazy_duo(data: Vec, order: Vec<(bool, bool)>) -> bool { let grouper = data.iter().chunk_by(|k| *k / 3); - let mut groups1 = grouper.into_iter(); - let mut groups2 = grouper.into_iter(); + let mut chunks1 = grouper.into_iter(); + let mut chunks2 = grouper.into_iter(); let mut elts = Vec::<&u8>::new(); - let mut old_groups = Vec::new(); + let mut old_chunks = Vec::new(); let tup1 = |(_, b)| b; for &(ord, consume_now) in &order { - let iter = &mut [&mut groups1, &mut groups2][ord as usize]; + let iter = &mut [&mut chunks1, &mut chunks2][ord as usize]; match iter.next() { Some((_, gr)) => if consume_now { - for og in old_groups.drain(..) { + for og in old_chunks.drain(..) { elts.extend(og); } elts.extend(gr); } else { - old_groups.push(gr); + old_chunks.push(gr); }, None => break, } } - for og in old_groups.drain(..) { + for og in old_chunks.drain(..) { elts.extend(og); } - for gr in groups1.map(&tup1) { elts.extend(gr); } - for gr in groups2.map(&tup1) { elts.extend(gr); } + for gr in chunks1.map(&tup1) { elts.extend(gr); } + for gr in chunks2.map(&tup1) { elts.extend(gr); } itertools::assert_equal(&data, elts); true } diff --git a/tests/test_std.rs b/tests/test_std.rs index 402385488..7852de2ff 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -817,8 +817,8 @@ fn chunk_by() { // try all possible orderings for indices in permutohedron::Heap::new(&mut [0, 1, 2, 3]) { - let groups = "AaaBbbccCcDDDD".chars().chunk_by(&toupper); - let mut subs = groups.into_iter().collect_vec(); + let chunks = "AaaBbbccCcDDDD".chars().chunk_by(&toupper); + let mut subs = chunks.into_iter().collect_vec(); for &idx in &indices[..] { let (key, text) = match idx { @@ -833,8 +833,8 @@ fn chunk_by() { } } - let groups = "AAABBBCCCCDDDD".chars().chunk_by(|&x| x); - let mut subs = groups.into_iter().map(|(_, g)| g).collect_vec(); + let chunks = "AAABBBCCCCDDDD".chars().chunk_by(|&x| x); + let mut subs = chunks.into_iter().map(|(_, g)| g).collect_vec(); let sd = subs.pop().unwrap(); let sc = subs.pop().unwrap(); @@ -880,42 +880,41 @@ fn chunk_by() { #[test] fn chunk_by_lazy_2() { let data = [0, 1]; - let groups = data.iter().chunk_by(|k| *k); - let gs = groups.into_iter().collect_vec(); + let chunks = data.iter().chunk_by(|k| *k); + let gs = chunks.into_iter().collect_vec(); it::assert_equal(data.iter(), gs.into_iter().flat_map(|(_k, g)| g)); let data = [0, 1, 1, 0, 0]; - let groups = data.iter().chunk_by(|k| *k); - let mut gs = groups.into_iter().collect_vec(); + let chunks = data.iter().chunk_by(|k| *k); + let mut gs = chunks.into_iter().collect_vec(); gs[1..].reverse(); it::assert_equal(&[0, 0, 0, 1, 1], gs.into_iter().flat_map(|(_, g)| g)); let grouper = data.iter().chunk_by(|k| *k); - let mut groups = Vec::new(); + let mut chunks = Vec::new(); for (k, group) in &grouper { if *k == 1 { - groups.push(group); + chunks.push(group); } } - it::assert_equal(&mut groups[0], &[1, 1]); + it::assert_equal(&mut chunks[0], &[1, 1]); let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().chunk_by(|k| *k); - let mut groups = Vec::new(); + let mut chunks = Vec::new(); for (i, (_, group)) in grouper.into_iter().enumerate() { if i < 2 { - groups.push(group); + chunks.push(group); } else if i < 4 { for _ in group {} } else { - groups.push(group); + chunks.push(group); } } - it::assert_equal(&mut groups[0], &[0, 0, 0]); - it::assert_equal(&mut groups[1], &[1, 1]); - it::assert_equal(&mut groups[2], &[3, 3]); + it::assert_equal(&mut chunks[0], &[0, 0, 0]); + it::assert_equal(&mut chunks[1], &[1, 1]); + it::assert_equal(&mut chunks[2], &[3, 3]); - // use groups as chunks let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let mut i = 0; let grouper = data.iter().chunk_by(move |_| { From b607fbd729cda44b3f937c5b82b641e9f6a5b719 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 31 Jan 2024 09:43:31 +0100 Subject: [PATCH 496/633] Fix tests 3: `group` to `chunk` (variables) Remain `grouper`, rename it to what? --- tests/test_std.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 7852de2ff..7266fdfef 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -892,9 +892,9 @@ fn chunk_by_lazy_2() { let grouper = data.iter().chunk_by(|k| *k); let mut chunks = Vec::new(); - for (k, group) in &grouper { + for (k, chunk) in &grouper { if *k == 1 { - chunks.push(group); + chunks.push(chunk); } } it::assert_equal(&mut chunks[0], &[1, 1]); @@ -902,13 +902,13 @@ fn chunk_by_lazy_2() { let data = [0, 0, 0, 1, 1, 0, 0, 2, 2, 3, 3]; let grouper = data.iter().chunk_by(|k| *k); let mut chunks = Vec::new(); - for (i, (_, group)) in grouper.into_iter().enumerate() { + for (i, (_, chunk)) in grouper.into_iter().enumerate() { if i < 2 { - chunks.push(group); + chunks.push(chunk); } else if i < 4 { - for _ in group {} + for _ in chunk {} } else { - chunks.push(group); + chunks.push(chunk); } } it::assert_equal(&mut chunks[0], &[0, 0, 0]); @@ -922,12 +922,12 @@ fn chunk_by_lazy_2() { i += 1; k }); - for (i, group) in &grouper { + for (i, chunk) in &grouper { match i { - 0 => it::assert_equal(group, &[0, 0, 0]), - 1 => it::assert_equal(group, &[1, 1, 0]), - 2 => it::assert_equal(group, &[0, 2, 2]), - 3 => it::assert_equal(group, &[3, 3]), + 0 => it::assert_equal(chunk, &[0, 0, 0]), + 1 => it::assert_equal(chunk, &[1, 1, 0]), + 2 => it::assert_equal(chunk, &[0, 2, 2]), + 3 => it::assert_equal(chunk, &[3, 3]), _ => unreachable!(), } } @@ -935,17 +935,17 @@ fn chunk_by_lazy_2() { #[test] fn chunk_by_lazy_3() { - // test consuming each group on the lap after it was produced + // test consuming each chunk on the lap after it was produced let data = [0, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2]; let grouper = data.iter().chunk_by(|elt| *elt); let mut last = None; - for (key, group) in &grouper { + for (key, chunk) in &grouper { if let Some(gr) = last.take() { for elt in gr { assert!(elt != key && i32::abs(elt - key) == 1); } } - last = Some(group); + last = Some(chunk); } } From 81c9cbefadabc1ecdcf85e3ad54ecb546f22aedb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:29:06 +0100 Subject: [PATCH 497/633] `iproduct`: allow commas at the end --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f12d95a1d..4e35761ac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,13 +252,13 @@ macro_rules! iproduct { (@flatten $I:expr, $J:expr, $($K:expr,)*) => ( $crate::iproduct!(@flatten $crate::cons_tuples($crate::iproduct!($I, $J)), $($K,)*) ); - ($I:expr) => ( + ($I:expr $(,)?) => ( $crate::__std_iter::IntoIterator::into_iter($I) ); - ($I:expr, $J:expr) => ( + ($I:expr, $J:expr $(,)?) => ( $crate::Itertools::cartesian_product($crate::iproduct!($I), $crate::iproduct!($J)) ); - ($I:expr, $J:expr, $($K:expr),+) => ( + ($I:expr, $J:expr, $($K:expr),+ $(,)?) => ( $crate::iproduct!(@flatten $crate::iproduct!($I, $J), $($K,)+) ); } From 248ab7d0aa7a571c41e6b2ef739b5659c309a81e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:36:05 +0100 Subject: [PATCH 498/633] `iproduct(it)`: yield 1-tuples instead --- src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4e35761ac..eb9ad2a70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,10 +253,13 @@ macro_rules! iproduct { $crate::iproduct!(@flatten $crate::cons_tuples($crate::iproduct!($I, $J)), $($K,)*) ); ($I:expr $(,)?) => ( - $crate::__std_iter::IntoIterator::into_iter($I) + $crate::__std_iter::IntoIterator::into_iter($I).map(|elt| (elt,)) ); ($I:expr, $J:expr $(,)?) => ( - $crate::Itertools::cartesian_product($crate::iproduct!($I), $crate::iproduct!($J)) + $crate::Itertools::cartesian_product( + $crate::__std_iter::IntoIterator::into_iter($I), + $crate::__std_iter::IntoIterator::into_iter($J), + ) ); ($I:expr, $J:expr, $($K:expr),+ $(,)?) => ( $crate::iproduct!(@flatten $crate::iproduct!($I, $J), $($K,)+) From b32a3b3e7b2a5c54125db3181904414212a5f22c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 1 Feb 2024 23:32:06 +0100 Subject: [PATCH 499/633] `iproduct`: yield one unit for the empty product --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index eb9ad2a70..218b3a8b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -252,6 +252,9 @@ macro_rules! iproduct { (@flatten $I:expr, $J:expr, $($K:expr,)*) => ( $crate::iproduct!(@flatten $crate::cons_tuples($crate::iproduct!($I, $J)), $($K,)*) ); + () => ( + $crate::__std_iter::once(()) + ); ($I:expr $(,)?) => ( $crate::__std_iter::IntoIterator::into_iter($I).map(|elt| (elt,)) ); From b1be1e4c24a226c1a8d8b66944cce44d7e423773 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:15:59 +0100 Subject: [PATCH 500/633] Update `iproduct` tests Test name `iproduct1` starts with `i` because `product1` is for another kind of test. --- tests/macros_hygiene.rs | 1 + tests/test_core.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/tests/macros_hygiene.rs b/tests/macros_hygiene.rs index d1111245d..20b59fba8 100644 --- a/tests/macros_hygiene.rs +++ b/tests/macros_hygiene.rs @@ -1,5 +1,6 @@ #[test] fn iproduct_hygiene() { + let _ = itertools::iproduct!(); let _ = itertools::iproduct!(0..6); let _ = itertools::iproduct!(0..6, 0..9); let _ = itertools::iproduct!(0..6, 0..9, 0..12); diff --git a/tests/test_core.rs b/tests/test_core.rs index 45a8df438..db77a1e3e 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -18,6 +18,23 @@ use crate::it::Itertools; use core::iter; use itertools as it; +#[test] +fn product0() { + let mut prod = iproduct!(); + assert_eq!(prod.next(), Some(())); + assert!(prod.next().is_none()); +} + +#[test] +fn iproduct1() { + let s = "αβ"; + + let mut prod = iproduct!(s.chars()); + assert_eq!(prod.next(), Some(('α',))); + assert_eq!(prod.next(), Some(('β',))); + assert!(prod.next().is_none()); +} + #[test] fn product2() { let s = "αβ"; From 68141809aabec2816d3250820c8d6608f3b2293f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 6 Feb 2024 23:17:06 +0100 Subject: [PATCH 501/633] Test laziness Ignore the 3 failing tests (for now). `ChunkBy` and `IntoChunks` are not iterators themselves but implement `IntoIterator` instead. Then `Groups` and `Chunks` are iterators. All four are lazy and must be used. --- tests/laziness.rs | 274 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 tests/laziness.rs diff --git a/tests/laziness.rs b/tests/laziness.rs new file mode 100644 index 000000000..cf0c75bd8 --- /dev/null +++ b/tests/laziness.rs @@ -0,0 +1,274 @@ +#![allow(unstable_name_collisions)] + +use itertools::Itertools; + +#[derive(Debug, Clone)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +struct Panicking; + +impl Iterator for Panicking { + type Item = u8; + + fn next(&mut self) -> Option { + panic!("iterator adaptor is not lazy") + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(0)) + } +} + +impl ExactSizeIterator for Panicking {} + +/// ## Usage example +/// ```compile_fail +/// must_use_tests! { +/// name { +/// Panicking.name(); // Add `let _ =` only if required (encountered error). +/// } +/// // ... +/// } +/// ``` +/// +/// **TODO:** test missing `must_use` attributes better, maybe with a new lint. +macro_rules! must_use_tests { + ($($(#[$attr:meta])* $name:ident $body:block)*) => { + $( + /// `#[deny(unused_must_use)]` should force us to ignore the resulting iterators + /// by adding `let _ = ...;` on every iterator. + /// If it does not, then a `must_use` attribute is missing on the associated struct. + /// + /// However, it's only helpful if we don't add `let _ =` before seeing if there is an error or not. + /// And it does not protect us against removed `must_use` attributes. + /// There is no simple way to test this yet. + #[deny(unused_must_use)] + #[test] + $(#[$attr])* + fn $name() $body + )* + }; +} + +must_use_tests! { + // Itertools trait: + interleave { + let _ = Panicking.interleave(Panicking); + } + interleave_shortest { + let _ = Panicking.interleave_shortest(Panicking); + } + intersperse { + let _ = Panicking.intersperse(0); + } + intersperse_with { + let _ = Panicking.intersperse_with(|| 0); + } + zip_longest { + let _ = Panicking.zip_longest(Panicking); + } + zip_eq { + let _ = Panicking.zip_eq(Panicking); + } + batching { + let _ = Panicking.batching(Iterator::next); + } + chunk_by { + // ChunkBy + let _ = Panicking.chunk_by(|x| *x); + // Groups + let _ = Panicking.chunk_by(|x| *x).into_iter(); + } + chunks { + // IntoChunks + let _ = Panicking.chunks(1); + let _ = Panicking.chunks(2); + // Chunks + let _ = Panicking.chunks(1).into_iter(); + let _ = Panicking.chunks(2).into_iter(); + } + tuple_windows { + let _ = Panicking.tuple_windows::<(_,)>(); + let _ = Panicking.tuple_windows::<(_, _)>(); + let _ = Panicking.tuple_windows::<(_, _, _)>(); + } + circular_tuple_windows { + let _ = Panicking.circular_tuple_windows::<(_,)>(); + let _ = Panicking.circular_tuple_windows::<(_, _)>(); + let _ = Panicking.circular_tuple_windows::<(_, _, _)>(); + } + tuples { + let _ = Panicking.tuples::<(_,)>(); + let _ = Panicking.tuples::<(_, _)>(); + let _ = Panicking.tuples::<(_, _, _)>(); + } + tee { + let _ = Panicking.tee(); + } + #[allow(deprecated)] + step { + let _ = Panicking.step(2); + } + map_into { + let _ = Panicking.map_into::(); + } + map_ok { + let _ = Panicking.map(Ok::).map_ok(|x| x + 1); + } + filter_ok { + let _ = Panicking.map(Ok::).filter_ok(|x| x % 2 == 0); + } + filter_map_ok { + let _ = Panicking.map(Ok::).filter_map_ok(|x| { + if x % 2 == 0 { + Some(x + 1) + } else { + None + } + }); + } + flatten_ok { + let _ = Panicking.map(|x| Ok::<_, ()>([x])).flatten_ok(); + } + merge { + let _ = Panicking.merge(Panicking); + } + merge_by { + let _ = Panicking.merge_by(Panicking, |_, _| true); + } + merge_join_by { + let _ = Panicking.merge_join_by(Panicking, |_, _| true); + let _ = Panicking.merge_join_by(Panicking, Ord::cmp); + } + #[ignore] + kmerge { + let _ = Panicking.map(|_| Panicking).kmerge(); + } + #[ignore] + kmerge_by { + let _ = Panicking.map(|_| Panicking).kmerge_by(|_, _| true); + } + cartesian_product { + let _ = Panicking.cartesian_product(Panicking); + } + multi_cartesian_product { + let _ = vec![Panicking, Panicking, Panicking].into_iter().multi_cartesian_product(); + } + coalesce { + let _ = Panicking.coalesce(|x, y| if x == y { Ok(x) } else { Err((x, y)) }); + } + dedup { + let _ = Panicking.dedup(); + } + dedup_by { + let _ = Panicking.dedup_by(|_, _| true); + } + dedup_with_count { + let _ = Panicking.dedup_with_count(); + } + dedup_by_with_count { + let _ = Panicking.dedup_by_with_count(|_, _| true); + } + duplicates { + let _ = Panicking.duplicates(); + } + duplicates_by { + let _ = Panicking.duplicates_by(|x| *x); + } + unique { + let _ = Panicking.unique(); + } + unique_by { + let _ = Panicking.unique_by(|x| *x); + } + peeking_take_while { + let _ = Panicking.peekable().peeking_take_while(|x| x % 2 == 0); + } + take_while_ref { + let _ = Panicking.take_while_ref(|x| x % 2 == 0); + } + take_while_inclusive { + let _ = Panicking.take_while_inclusive(|x| x % 2 == 0); + } + while_some { + let _ = Panicking.map(Some).while_some(); + } + #[ignore] + tuple_combinations { + let _ = Panicking.tuple_combinations::<(_,)>(); + let _ = Panicking.tuple_combinations::<(_, _)>(); + let _ = Panicking.tuple_combinations::<(_, _, _)>(); + } + combinations { + let _ = Panicking.combinations(0); + let _ = Panicking.combinations(1); + let _ = Panicking.combinations(2); + } + combinations_with_replacement { + let _ = Panicking.combinations_with_replacement(0); + let _ = Panicking.combinations_with_replacement(1); + let _ = Panicking.combinations_with_replacement(2); + } + permutations { + let _ = Panicking.permutations(0); + let _ = Panicking.permutations(1); + let _ = Panicking.permutations(2); + } + powerset { + let _ = Panicking.powerset(); + } + pad_using { + let _ = Panicking.pad_using(25, |_| 10); + } + with_position { + let _ = Panicking.with_position(); + } + positions { + let _ = Panicking.positions(|v| v % 2 == 0); + } + update { + let _ = Panicking.update(|n| *n += 1); + } + multipeek { + let _ = Panicking.multipeek(); + } + // Not iterator themselves but still lazy. + into_grouping_map { + let _ = Panicking.map(|x| (x, x + 1)).into_grouping_map(); + } + into_grouping_map_by { + let _ = Panicking.into_grouping_map_by(|x| *x); + } + // Macros: + iproduct { + let _ = itertools::iproduct!(Panicking); + let _ = itertools::iproduct!(Panicking, Panicking); + let _ = itertools::iproduct!(Panicking, Panicking, Panicking); + } + izip { + let _ = itertools::izip!(Panicking); + let _ = itertools::izip!(Panicking, Panicking); + let _ = itertools::izip!(Panicking, Panicking, Panicking); + } + chain { + let _ = itertools::chain!(Panicking); + let _ = itertools::chain!(Panicking, Panicking); + let _ = itertools::chain!(Panicking, Panicking, Panicking); + } + // Free functions: + multizip { + let _ = itertools::multizip((Panicking, Panicking)); + } + put_back { + let _ = itertools::put_back(Panicking); + let _ = itertools::put_back(Panicking).with_value(15); + } + peek_nth { + let _ = itertools::peek_nth(Panicking); + } + put_back_n { + let _ = itertools::put_back_n(Panicking); + } + rciter { + let _ = itertools::rciter(Panicking); + } +} From e3c6c0cd024349f95d220d2da76e12beaa5c8fb1 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 7 Feb 2024 21:49:52 +0100 Subject: [PATCH 502/633] Create CONTRIBUTING.md (#767) --- CONTRIBUTING.md | 189 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 12 +-- 2 files changed, 191 insertions(+), 10 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1dbf6f59d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,189 @@ +# Contributing to itertools + +We use stable Rust only. +Please check the minimum version of Rust we use in `Cargo.toml`. + +_If you are proposing a major change to CI or a new iterator adaptor for this crate, +then **please first file an issue** describing your proposal._ +[Usual concerns about new methods](https://github.com/rust-itertools/itertools/issues/413#issuecomment-657670781). + +To pass CI tests successfully, your code must be free of "compiler warnings" and "clippy warnings" and be "rustfmt" formatted. + +Note that small PRs are easier to review and therefore are more easily merged. + +## Write a new method/adaptor for `Itertools` trait +In general, the code logic should be tested with [quickcheck](https://crates.io/crates/quickcheck) tests in `tests/quick.rs` +which allow us to test properties about the code with randomly generated inputs. + +### Behind `use_std`/`use_alloc` feature? +If it needs the "std" (such as using hashes) then it should be behind the `use_std` feature, +or if it requires heap allocation (such as using vectors) then it should be behind the `use_alloc` feature. +Otherwise it should be able to run in `no_std` context. + +This mostly applies to your new module, each import from it, and to your new `Itertools` method. + +### Pick the right receiver +`self`, `&mut self` or `&self`? From [#710](https://github.com/rust-itertools/itertools/pull/710): + +- Take by value when: + - It transfers ownership to another iterator type, such as `filter`, `map`... + - It consumes the iterator completely, such as `count`, `last`, `max`... +- Mutably borrow when it consumes only part of the iterator, such as `find`, `all`, `try_collect`... +- Immutably borrow when there is no change, such as `size_hint`. + +### Laziness +Iterators are [lazy](https://doc.rust-lang.org/std/iter/index.html#laziness): + +- structs of iterator adaptors should have `#[must_use = "iterator adaptors are lazy and do nothing unless consumed"]` ; +- structs of iterators should have `#[must_use = "iterators are lazy and do nothing unless consumed"]`. + +Those behaviors are **tested** in `tests/laziness.rs`. + +## Specialize `Iterator` methods +It might be more performant to specialize some methods. +However, each specialization should be thoroughly tested. + +Correctly specializing methods can be difficult, and _we do not require that you do it on your initial PR_. + +Most of the time, we want specializations of: + +- [`size_hint`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.size_hint): + It mostly allows allocation optimizations. + When always exact, it also enables to implement `ExactSizeIterator`. + See our private module `src/size_hint.rs` for helpers. +- [`fold`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.fold) + might make iteration faster than calling `next` repeatedly. +- [`count`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.count), + [`last`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.last), + [`nth`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.nth) + as we might be able to avoid iterating on every item with `next`. + +Additionally, + +- `for_each`, `reduce`, `max/min[_by[_key]]` and `partition` all rely on `fold` so you should specialize it instead. +- `all`, `any`, `find`, `find_map`, `cmp`, `partial_cmp`, `eq`, `ne`, `lt`, `le`, `gt`, `ge` and `position` all rely (by default) on `try_fold` + which we can not specialize on stable rust, so you might want to wait it stabilizes + or specialize each of them. +- `DoubleEndedIterator::{nth_back, rfold, rfind}`: similar reasoning. + +An adaptor might use the inner iterator specializations for its own specializations. + +They are **tested** in `tests/specializations.rs` and **benchmarked** in `benches/specializations.rs` +(build those benchmarks is slow so you might want to temporarily remove the ones you do not want to measure). + +## Additional implementations +### The [`Debug`](https://doc.rust-lang.org/std/fmt/trait.Debug.html) implementation +All our iterators should implement `Debug`. + +When one of the field is not debuggable (such as _functions_), you must not derive `Debug`. +Instead, manually implement it and _ignore this field_ in our helper macro `debug_fmt_fields`. + +

+4 examples (click to expand) + +```rust +use std::fmt; + +/* ===== Simple derive. ===== */ +#[derive(Debug)] +struct Name1 { + iter: I, +} + +/* ===== With an unclonable field. ===== */ +struct Name2 { + iter: I, + func: F, +} + +// No `F: Debug` bound and the field `func` is ignored. +impl fmt::Debug for Name2 { + // it defines the `fmt` function from a struct name and the fields you want to debug. + debug_fmt_fields!(Name2, iter); +} + +/* ===== With an unclonable field, but another bound to add. ===== */ +struct Name3 { + iter: I, + item: Option, + func: F, +} + +// Same about `F` and `func`, similar about `I` but we must add the `I::Item: Debug` bound. +impl fmt::Debug for Name3 +where + I::Item: fmt::Debug, +{ + debug_fmt_fields!(Name3, iter, item); +} + +/* ===== With an unclonable field for which we can provide some information. ===== */ +struct Name4 { + iter: I, + func: Option, +} + +// If ignore a field is not good enough, implement Debug fully manually. +impl fmt::Debug for Name4 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let func = if self.func.is_some() { "Some(_)" } else { "None" }; + f.debug_struct("Name4") + .field("iter", &self.iter) + .field("func", &func) + .finish() + } +} +``` +
+ +### When/How to implement [`Clone`](https://doc.rust-lang.org/std/clone/trait.Clone.html) +All our iterators should implement `Clone` when possible. + +Note that a mutable reference is never clonable so `struct Name<'a, I: 'a> { iter: &'a mut I }` can not implement `Clone`. + +Derive `Clone` on a generic struct adds the bound `Clone` on each generic parameter. +It might be an issue in which case you should manually implement it with our helper macro `clone_fields` (it defines the `clone` function calling `clone` on each field) and be careful about the bounds. + +### When to implement [`std::iter::FusedIterator`](https://doc.rust-lang.org/std/iter/trait.FusedIterator.html) +This trait should be implemented _by all iterators that always return `None` after returning `None` once_, because it allows to optimize `Iterator::fuse()`. + +The conditions on which it should be implemented are usually the ones from the `Iterator` implementation, eventually refined to ensure it behaves in a fused way. + +### When to implement [`ExactSizeIterator`](https://doc.rust-lang.org/std/iter/trait.ExactSizeIterator.html) +_When we are always able to return an exact non-overflowing length._ + +Therefore, we do not implement it on adaptors that makes the iterator longer as the resulting length could overflow. + +One should not override `ExactSizeIterator::len` method but rely on an exact `Iterator::size_hint` implementation, meaning it returns `(length, Some(length))` (unless you could make `len` more performant than the default). + +The conditions on which it should be implemented are usually the ones from the `Iterator` implementation, probably refined to ensure the size hint is exact. + +### When to implement [`DoubleEndedIterator`](https://doc.rust-lang.org/std/iter/trait.DoubleEndedIterator.html) +When the iterator structure allows to handle _iterating on both fronts simultaneously_. +The iteration might stop in the middle when both fronts meet. + +The conditions on which it should be implemented are usually the ones from the `Iterator` implementation, probably refined to ensure we can iterate on both fronts simultaneously. + +### When to implement [`itertools::PeekingNext`](https://docs.rs/itertools/latest/itertools/trait.PeekingNext.html) +TODO + +This is currently **tested** in `tests/test_std.rs`. + +## About lending iterators +TODO + + +## Other notes +No guideline about using `#[inline]` yet. + +### `.fold` / `.for_each` / `.try_fold` / `.try_for_each` +In the Rust standard library, it's quite common for `fold` to be implemented in terms of `try_fold`. But it's not something we do yet because we can not specialize `try_fold` methods yet (it uses the unstable `Try`). + +From [#781](https://github.com/rust-itertools/itertools/pull/781), the general rule to follow is something like this: + +- If you need to completely consume an iterator: + - Use `fold` if you need an _owned_ access to an accumulator. + - Use `for_each` otherwise. +- If you need to partly consume an iterator, the same applies with `try_` versions: + - Use `try_fold` if you need an _owned_ access to an accumulator. + - Use `try_for_each` otherwise. diff --git a/README.md b/README.md index 381a3e9d9..9acac2d37 100644 --- a/README.md +++ b/README.md @@ -21,17 +21,9 @@ use itertools::Itertools; ``` ## How to contribute +If you're not sure what to work on, try checking the [help wanted](https://github.com/rust-itertools/itertools/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22) label. -- Fix a bug or implement a new thing -- Include tests for your new feature, preferably a QuickCheck test -- Make a Pull Request - -For new features, please first consider filing a PR to [rust-lang/rust](https://github.com/rust-lang/rust), -adding your new feature to the `Iterator` trait of the standard library, if you believe it is reasonable. -If it isn't accepted there, proposing it for inclusion in ``itertools`` is a good idea. -The reason for doing is this is so that we avoid future breakage as with ``.flatten()``. -However, if your feature involves heap allocation, such as storing elements in a ``Vec``, -then it can't be accepted into ``libcore``, and you should propose it for ``itertools`` directly instead. +See our [CONTRIBUTING.md](https://github.com/rust-itertools/itertools/blob/master/CONTRIBUTING.md) for a detailed guide. ## License From d5709c7e5cd3d426f2fcc43cd1403bfd46aa9b1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 31 Jan 2024 22:32:56 +0000 Subject: [PATCH 503/633] Bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/coverage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 42c7b300b..5c0845659 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -29,6 +29,6 @@ jobs: run: cargo llvm-cov --all-features --doctests --workspace --lcov --output-path lcov.info - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: files: lcov.info From 630bbddc46f6eec9bc833104c68a10c0b3d54abb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:48:15 +0100 Subject: [PATCH 504/633] Deprecate `unfold` for `std::iter::from_fn` --- src/sources.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/sources.rs b/src/sources.rs index a7f51e52f..0cfd79d5f 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -98,6 +98,7 @@ where /// vec![1, 1, 2, 3, 5, 8, 13, 21]); /// assert_eq!(fibonacci.last(), Some(2_971_215_073)) /// ``` +#[deprecated(note = "Use std from_fn() instead", since = "0.13.0")] pub fn unfold(initial_state: St, f: F) -> Unfold where F: FnMut(&mut St) -> Option, @@ -118,6 +119,7 @@ where /// See [`unfold`](crate::unfold) for more information. #[derive(Clone)] #[must_use = "iterators are lazy and do nothing unless consumed"] +#[deprecated(note = "Use std from_fn() instead", since = "0.13.0")] pub struct Unfold { f: F, /// Internal state that will be passed to the closure on the next iteration From 3e47a021c790bbb6a42edd4b8dec4b69e86f7cb5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 7 Feb 2024 08:54:25 +0100 Subject: [PATCH 505/633] Bump version --- Cargo.toml | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 70af68465..e3bfdabd7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.12.1" +version = "0.13.0" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" diff --git a/README.md b/README.md index 9acac2d37..24059e514 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.12.1" +itertools = "0.13.0" ``` How to use in your crate: From c3f6cf95183aba602954afda30fef6015d79975f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 9 Feb 2024 10:22:54 +0100 Subject: [PATCH 506/633] Specialize `MapForGrouping::fold` `MapForGrouping` is only internally used by `GroupingMapBy` and it only calls `for_each` on it (which in turn rely on `fold`). So I allow the clippy lint `missing_trait_methods` because specialize other methods is simply useless. I could replace `next` body by `unreachable!()` and it would still work fine. Anyway, it will disappear from test coverage now that we have one. I previously wandered how to test and benchmark this. The current tests will test this. And I guess I could benchmark this `fold` specialization through some `into_grouping_map_by` benchmark but I simply don't think it's worth the time: no doubt `adapted_iterator.map.fold` is faster than the default `fold` calling `next` repeatedly. Note that all `GroupingMapBy` methods ultimately rely on this `fold` so this specialization will improve performance for all its methods. --- src/grouping_map.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index a01968d4a..f165723cc 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -22,6 +22,7 @@ impl MapForGrouping { } } +#[allow(clippy::missing_trait_methods)] impl Iterator for MapForGrouping where I: Iterator, @@ -32,6 +33,14 @@ where fn next(&mut self) -> Option { self.0.next().map(|val| ((self.1)(&val), val)) } + + fn fold(self, init: B, f: G) -> B + where + G: FnMut(B, Self::Item) -> B, + { + let mut key_mapper = self.1; + self.0.map(|val| (key_mapper(&val), val)).fold(init, f) + } } /// Creates a new `GroupingMap` from `iter` From 21be768fedb89794eb96098c51fc895d37d22186 Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 12 Feb 2024 13:40:47 +0100 Subject: [PATCH 507/633] type MapForGrouping<...> = MapSpecialCase<...> --- src/adaptors/map.rs | 4 ++-- src/adaptors/mod.rs | 2 +- src/grouping_map.rs | 44 ++++++++++++++++---------------------------- 3 files changed, 19 insertions(+), 31 deletions(-) diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index cf5e5a00d..a26ece871 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -4,8 +4,8 @@ use std::marker::PhantomData; #[derive(Clone, Debug)] #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] pub struct MapSpecialCase { - iter: I, - f: F, + pub(crate) iter: I, + pub(crate) f: F, } pub trait MapSpecialCaseFn { diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 84bbf98e0..cf9642bc5 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -5,7 +5,7 @@ //! except according to those terms. mod coalesce; -mod map; +pub(crate) mod map; mod multi_product; pub use self::coalesce::*; #[allow(deprecated)] diff --git a/src/grouping_map.rs b/src/grouping_map.rs index f165723cc..3e450978a 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -1,45 +1,33 @@ #![cfg(feature = "use_std")] -use crate::MinMaxResult; +use crate::{ + adaptors::map::{MapSpecialCase, MapSpecialCaseFn}, + MinMaxResult, +}; use std::cmp::Ordering; use std::collections::HashMap; -use std::fmt; use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; /// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by) -#[derive(Clone)] -pub struct MapForGrouping(I, F); +pub type MapForGrouping = MapSpecialCase>; -impl fmt::Debug for MapForGrouping { - debug_fmt_fields!(MapForGrouping, 0); -} +pub struct GroupingMapFn(F); -impl MapForGrouping { - pub(crate) fn new(iter: I, key_mapper: F) -> Self { - Self(iter, key_mapper) +impl K> MapSpecialCaseFn for GroupingMapFn { + type Out = (K, V); + fn call(&mut self, v: V) -> Self::Out { + ((self.0)(&v), v) } } -#[allow(clippy::missing_trait_methods)] -impl Iterator for MapForGrouping -where - I: Iterator, - K: Hash + Eq, - F: FnMut(&V) -> K, -{ - type Item = (K, V); - fn next(&mut self) -> Option { - self.0.next().map(|val| ((self.1)(&val), val)) - } - - fn fold(self, init: B, f: G) -> B - where - G: FnMut(B, Self::Item) -> B, - { - let mut key_mapper = self.1; - self.0.map(|val| (key_mapper(&val), val)).fold(init, f) +impl K> MapForGrouping { + pub(crate) fn new(iter: I, key_mapper: F) -> Self { + MapSpecialCase { + iter, + f: GroupingMapFn(key_mapper), + } } } From 5c6edf3117a28f5b827866e4b374042b34b80c57 Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 12 Feb 2024 22:38:08 +0100 Subject: [PATCH 508/633] impl Debug, Clone for GroupingMapFn --- src/grouping_map.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 3e450978a..33ae065d2 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -13,8 +13,13 @@ use std::ops::{Add, Mul}; /// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by) pub type MapForGrouping = MapSpecialCase>; +#[derive(Clone)] pub struct GroupingMapFn(F); +impl std::fmt::Debug for GroupingMapFn { + debug_fmt_fields!(GroupingMapFn,); +} + impl K> MapSpecialCaseFn for GroupingMapFn { type Out = (K, V); fn call(&mut self, v: V) -> Self::Out { From 235f2472e40c604782f6757da8bedcb7125b2ac4 Mon Sep 17 00:00:00 2001 From: philipp Date: Mon, 12 Feb 2024 22:52:10 +0100 Subject: [PATCH 509/633] fn new_map_for_grouping as free function --- src/grouping_map.rs | 13 +++++++------ src/lib.rs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 33ae065d2..47a7c096e 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -27,12 +27,13 @@ impl K> MapSpecialCaseFn for GroupingMapFn { } } -impl K> MapForGrouping { - pub(crate) fn new(iter: I, key_mapper: F) -> Self { - MapSpecialCase { - iter, - f: GroupingMapFn(key_mapper), - } +pub(crate) fn new_map_for_grouping K>( + iter: I, + key_mapper: F, +) -> MapForGrouping { + MapSpecialCase { + iter, + f: GroupingMapFn(key_mapper), } } diff --git a/src/lib.rs b/src/lib.rs index 218b3a8b9..b7973248d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3194,7 +3194,7 @@ pub trait Itertools: Iterator { K: Hash + Eq, F: FnMut(&V) -> K, { - grouping_map::new(grouping_map::MapForGrouping::new(self, key_mapper)) + grouping_map::new(grouping_map::new_map_for_grouping(self, key_mapper)) } /// Return all minimum elements of an iterator. From 4efffc1583e29a179bd9a38bb63e5dd545c0f027 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:33:25 +0100 Subject: [PATCH 510/633] `usize::max_value()` -> `usize::MAX` --- src/lib.rs | 2 +- src/sources.rs | 4 ++-- tests/quick.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b7973248d..7a071abaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2623,7 +2623,7 @@ pub trait Itertools: Iterator { Ok(x) } - match inner(usize::max_value(), &mut self, &mut f) { + match inner(usize::MAX, &mut self, &mut f) { Err(x) => x, _ => unreachable!(), } diff --git a/src/sources.rs b/src/sources.rs index 0cfd79d5f..7f458b30b 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -58,7 +58,7 @@ where } fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) + (usize::MAX, None) } } @@ -170,7 +170,7 @@ where #[inline] fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) + (usize::MAX, None) } } diff --git a/tests/quick.rs b/tests/quick.rs index f96418f40..b2982a6ef 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -67,8 +67,8 @@ impl qc::Arbitrary for Inexact { let ue_value = usize::arbitrary(g); let oe_value = usize::arbitrary(g); // Compensate for quickcheck using extreme values too rarely - let ue_choices = &[0, ue_value, usize::max_value()]; - let oe_choices = &[0, oe_value, usize::max_value()]; + let ue_choices = &[0, ue_value, usize::MAX]; + let oe_choices = &[0, oe_value, usize::MAX]; Self { underestimate: *ue_choices.choose(g).unwrap(), overestimate: *oe_choices.choose(g).unwrap(), From 762643f1be2217140a972745cf4d6ed69435f722 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:36:43 +0100 Subject: [PATCH 511/633] Do not use `std::usize` module but `usize` type --- src/powerset.rs | 1 - src/size_hint.rs | 1 - src/ziptuple.rs | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/powerset.rs b/src/powerset.rs index 9a7131a3e..1b2b38540 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -1,7 +1,6 @@ use alloc::vec::Vec; use std::fmt; use std::iter::FusedIterator; -use std::usize; use super::combinations::{combinations, Combinations}; use crate::adaptors::checked_binomial; diff --git a/src/size_hint.rs b/src/size_hint.rs index 857e0c4c6..6cfead7f2 100644 --- a/src/size_hint.rs +++ b/src/size_hint.rs @@ -2,7 +2,6 @@ //! use std::cmp; -use std::usize; /// `SizeHint` is the return type of `Iterator::size_hint()`. pub type SizeHint = (usize, Option); diff --git a/src/ziptuple.rs b/src/ziptuple.rs index 5ff0fad33..e299c4013 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -82,7 +82,7 @@ macro_rules! impl_zip_iter { fn size_hint(&self) -> (usize, Option) { - let sh = (::std::usize::MAX, None); + let sh = (usize::MAX, None); let ($(ref $B,)*) = self.t; $( let sh = size_hint::min($B.size_hint(), sh); From 047ad5d120edd8efbe26350f2338faa498d8cabc Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Sun, 18 Feb 2024 11:38:04 +0100 Subject: [PATCH 512/633] Delete the "group-by" keyword --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e3bfdabd7..e5b541c46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ readme = "README.md" description = "Extra iterator adaptors, iterator methods, free functions, and macros." -keywords = ["iterator", "data-structure", "zip", "product", "group-by"] +keywords = ["iterator", "data-structure", "zip", "product"] categories = ["algorithms", "rust-patterns"] edition = "2018" From b54c4c396ad10e7d664f886919f3e118384879a6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:31:13 +0100 Subject: [PATCH 513/633] `PutBack::put_back` now returns the old value Because it does not return `()` anymore, some changes can be necessary to ignore the returned value like here in "tests/quick.rs". --- src/adaptors/mod.rs | 6 +++--- tests/quick.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index cf9642bc5..191d551da 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -280,10 +280,10 @@ where /// Put back a single value to the front of the iterator. /// - /// If a value is already in the put back slot, it is overwritten. + /// If a value is already in the put back slot, it is returned. #[inline] - pub fn put_back(&mut self, x: I::Item) { - self.top = Some(x); + pub fn put_back(&mut self, x: I::Item) -> Option { + self.top.replace(x) } } diff --git a/tests/quick.rs b/tests/quick.rs index b2982a6ef..e345c9813 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -882,7 +882,7 @@ quickcheck! { fn size_put_back(a: Vec, x: Option) -> bool { let mut it = put_back(a.into_iter()); if let Some(t) = x { - it.put_back(t) + it.put_back(t); } correct_size_hint(it) } From 30de5a8886cad24d487513b0d4edee6bf2a0cdca Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:12:50 +0100 Subject: [PATCH 514/633] Remove deprecated `Itertools::step` --- src/adaptors/mod.rs | 63 --------------------------------------------- src/lib.rs | 29 ++------------------- tests/laziness.rs | 4 --- tests/quick.rs | 42 +----------------------------- tests/test_core.rs | 11 +------- tests/test_std.rs | 6 ++--- 6 files changed, 6 insertions(+), 149 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 191d551da..6c44e5b93 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -507,69 +507,6 @@ where } } -/// An iterator adaptor that steps a number elements in the base iterator -/// for each iteration. -/// -/// The iterator steps by yielding the next element from the base iterator, -/// then skipping forward *n-1* elements. -/// -/// See [`.step()`](crate::Itertools::step) for more information. -#[deprecated(note = "Use std .step_by() instead", since = "0.8.0")] -#[allow(deprecated)] -#[derive(Clone, Debug)] -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct Step { - iter: Fuse, - skip: usize, -} - -/// Create a `Step` iterator. -/// -/// **Panics** if the step is 0. -#[allow(deprecated)] -pub fn step(iter: I, step: usize) -> Step -where - I: Iterator, -{ - assert!(step != 0); - Step { - iter: iter.fuse(), - skip: step - 1, - } -} - -#[allow(deprecated)] -impl Iterator for Step -where - I: Iterator, -{ - type Item = I::Item; - #[inline] - fn next(&mut self) -> Option { - let elt = self.iter.next(); - if self.skip > 0 { - self.iter.nth(self.skip - 1); - } - elt - } - - fn size_hint(&self) -> (usize, Option) { - let (low, high) = self.iter.size_hint(); - let div = |x: usize| { - if x == 0 { - 0 - } else { - 1 + (x - 1) / (self.skip + 1) - } - }; - (div(low), high.map(div)) - } -} - -// known size -#[allow(deprecated)] -impl ExactSizeIterator for Step where I: ExactSizeIterator {} - /// An iterator adaptor that borrows from a `Clone`-able iterator /// to only pick off elements while the predicate returns `true`. /// diff --git a/src/lib.rs b/src/lib.rs index 7a071abaf..ca2cd2796 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,6 +80,8 @@ pub use std::iter as __std_iter; /// The concrete iterator types. pub mod structs { + #[allow(deprecated)] + pub use crate::adaptors::MapResults; #[cfg(feature = "use_alloc")] pub use crate::adaptors::MultiProduct; pub use crate::adaptors::{ @@ -87,8 +89,6 @@ pub mod structs { FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, TakeWhileRef, TupleCombinations, Update, WhileSome, }; - #[allow(deprecated)] - pub use crate::adaptors::{MapResults, Step}; #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; #[cfg(feature = "use_alloc")] @@ -814,31 +814,6 @@ pub trait Itertools: Iterator { tee::new(self) } - /// Return an iterator adaptor that steps `n` elements in the base iterator - /// for each iteration. - /// - /// The iterator steps by yielding the next element from the base iterator, - /// then skipping forward `n - 1` elements. - /// - /// Iterator element type is `Self::Item`. - /// - /// **Panics** if the step is 0. - /// - /// ``` - /// use itertools::Itertools; - /// - /// let it = (0..8).step(3); - /// itertools::assert_equal(it, vec![0, 3, 6]); - /// ``` - #[deprecated(note = "Use std .step_by() instead", since = "0.8.0")] - #[allow(deprecated)] - fn step(self, n: usize) -> Step - where - Self: Sized, - { - adaptors::step(self, n) - } - /// Convert each item of the iterator using the [`Into`] trait. /// /// ```rust diff --git a/tests/laziness.rs b/tests/laziness.rs index cf0c75bd8..2ce29cb8a 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -104,10 +104,6 @@ must_use_tests! { tee { let _ = Panicking.tee(); } - #[allow(deprecated)] - step { - let _ = Panicking.step(2); - } map_into { let _ = Panicking.map_into::(); } diff --git a/tests/quick.rs b/tests/quick.rs index e345c9813..7b163d8dc 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -456,45 +456,6 @@ quickcheck! { itertools::assert_equal(empty, std::iter::once(Vec::new())) } - #[allow(deprecated)] - fn size_step(a: Iter, s: usize) -> bool { - let mut s = s; - if s == 0 { - s += 1; // never zero - } - let filt = a.clone().dedup(); - correct_size_hint(filt.step(s)) && - exact_size(a.step(s)) - } - - #[allow(deprecated)] - fn equal_step(a: Iter, s: usize) -> bool { - let mut s = s; - if s == 0 { - s += 1; // never zero - } - let mut i = 0; - itertools::equal(a.clone().step(s), a.filter(|_| { - let keep = i % s == 0; - i += 1; - keep - })) - } - - #[allow(deprecated)] - fn equal_step_vec(a: Vec, s: usize) -> bool { - let mut s = s; - if s == 0 { - s += 1; // never zero - } - let mut i = 0; - itertools::equal(a.iter().step(s), a.iter().filter(|_| { - let keep = i % s == 0; - i += 1; - keep - })) - } - fn size_multipeek(a: Iter, s: u8) -> bool { let mut it = multipeek(a); // peek a few times @@ -1373,13 +1334,12 @@ quickcheck! { } quickcheck! { - #[allow(deprecated)] fn tree_fold1_f64(mut a: Vec) -> TestResult { fn collapse_adjacent(x: Vec, mut f: F) -> Vec where F: FnMut(f64, f64) -> f64 { let mut out = Vec::new(); - for i in (0..x.len()).step(2) { + for i in (0..x.len()).step_by(2) { if i == x.len()-1 { out.push(x[i]) } else { diff --git a/tests/test_core.rs b/tests/test_core.rs index db77a1e3e..3c5ca70db 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -214,18 +214,9 @@ fn test_put_back() { it::assert_equal(pb, xs.iter().cloned()); } -#[allow(deprecated)] -#[test] -fn step() { - it::assert_equal((0..10).step(1), 0..10); - it::assert_equal((0..10).step(2), (0..10).filter(|x: &i32| *x % 2 == 0)); - it::assert_equal((0..10).step(10), 0..1); -} - -#[allow(deprecated)] #[test] fn merge() { - it::assert_equal((0..10).step(2).merge((1..10).step(2)), 0..10); + it::assert_equal((0..10).step_by(2).merge((1..10).step_by(2)), 0..10); } #[test] diff --git a/tests/test_std.rs b/tests/test_std.rs index 7266fdfef..3f40a6c9b 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -423,18 +423,16 @@ fn merge_by_btree() { it::assert_equal(results, expected); } -#[allow(deprecated)] #[test] fn kmerge() { - let its = (0..4).map(|s| (s..10).step(4)); + let its = (0..4).map(|s| (s..10).step_by(4)); it::assert_equal(its.kmerge(), 0..10); } -#[allow(deprecated)] #[test] fn kmerge_2() { - let its = vec![3, 2, 1, 0].into_iter().map(|s| (s..10).step(4)); + let its = vec![3, 2, 1, 0].into_iter().map(|s| (s..10).step_by(4)); it::assert_equal(its.kmerge(), 0..10); } From fe8efaeacf5e938e213e6001df0e32eba3260cd8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:26:32 +0100 Subject: [PATCH 515/633] Remove deprecated `Itertools::foreach` --- src/lib.rs | 26 -------------------------- tests/test_core.rs | 9 --------- tests/test_std.rs | 4 +--- 3 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ca2cd2796..4c1d4f22e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2149,32 +2149,6 @@ pub trait Itertools: Iterator { self } - /// Run the closure `f` eagerly on each element of the iterator. - /// - /// Consumes the iterator until its end. - /// - /// ``` - /// use std::sync::mpsc::channel; - /// use itertools::Itertools; - /// - /// let (tx, rx) = channel(); - /// - /// // use .foreach() to apply a function to each value -- sending it - /// (0..5).map(|x| x * 2 + 1).foreach(|x| { tx.send(x).unwrap(); } ); - /// - /// drop(tx); - /// - /// itertools::assert_equal(rx.iter(), vec![1, 3, 5, 7, 9]); - /// ``` - #[deprecated(note = "Use .for_each() instead", since = "0.8.0")] - fn foreach(self, f: F) - where - F: FnMut(Self::Item), - Self: Sized, - { - self.for_each(f); - } - /// Combine all an iterator's elements into one element by using [`Extend`]. /// /// This combinator will extend the first item with each of the rest of the diff --git a/tests/test_core.rs b/tests/test_core.rs index 3c5ca70db..448f39652 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -172,15 +172,6 @@ fn test_intersperse_with() { it::assert_equal(it, ys.iter()); } -#[allow(deprecated)] -#[test] -fn foreach() { - let xs = [1i32, 2, 3]; - let mut sum = 0; - xs.iter().foreach(|elt| sum += *elt); - assert!(sum == 6); -} - #[test] fn dropping() { let xs = [1, 2, 3]; diff --git a/tests/test_std.rs b/tests/test_std.rs index 3f40a6c9b..c26384ae2 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -360,7 +360,6 @@ fn test_rciter() { assert_eq!(z.next(), Some((0, 1))); } -#[allow(deprecated)] #[test] fn trait_pointers() { struct ByRef<'r, I: ?Sized>(&'r mut I); @@ -379,7 +378,6 @@ fn trait_pointers() { assert_eq!(it.next(), Some(0)); { - /* make sure foreach works on non-Sized */ let jt: &mut dyn Iterator = &mut *it; assert_eq!(jt.next(), Some(1)); @@ -389,7 +387,7 @@ fn trait_pointers() { } assert_eq!(jt.find_position(|x| *x == 4), Some((1, 4))); - jt.foreach(|_| ()); + jt.for_each(|_| ()); } } From 6f8b2c03355eb62a359bbface6987e5db6908f1a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:30:05 +0100 Subject: [PATCH 516/633] Remove deprecated `repeat_call` --- src/lib.rs | 4 ++-- src/sources.rs | 57 -------------------------------------------------- 2 files changed, 2 insertions(+), 59 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 4c1d4f22e..1f8ad792a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -127,7 +127,7 @@ pub mod structs { pub use crate::rciter_impl::RcIter; pub use crate::repeatn::RepeatN; #[allow(deprecated)] - pub use crate::sources::{Iterate, RepeatCall, Unfold}; + pub use crate::sources::{Iterate, Unfold}; pub use crate::take_while_inclusive::TakeWhileInclusive; #[cfg(feature = "use_alloc")] pub use crate::tee::Tee; @@ -156,7 +156,7 @@ pub use crate::peeking_take_while::PeekingNext; pub use crate::process_results_impl::process_results; pub use crate::repeatn::repeat_n; #[allow(deprecated)] -pub use crate::sources::{iterate, repeat_call, unfold}; +pub use crate::sources::{iterate, unfold}; #[allow(deprecated)] pub use crate::structs::*; pub use crate::unziptuple::{multiunzip, MultiUnzip}; diff --git a/src/sources.rs b/src/sources.rs index 7f458b30b..425324fac 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -5,63 +5,6 @@ use std::fmt; use std::mem; -/// See [`repeat_call`](crate::repeat_call) for more information. -#[derive(Clone)] -#[deprecated(note = "Use std repeat_with() instead", since = "0.8.0")] -pub struct RepeatCall { - f: F, -} - -impl fmt::Debug for RepeatCall { - debug_fmt_fields!(RepeatCall,); -} - -/// An iterator source that produces elements indefinitely by calling -/// a given closure. -/// -/// Iterator element type is the return type of the closure. -/// -/// ``` -/// use itertools::repeat_call; -/// use itertools::Itertools; -/// use std::collections::BinaryHeap; -/// -/// let mut heap = BinaryHeap::from(vec![2, 5, 3, 7, 8]); -/// -/// // extract each element in sorted order -/// for element in repeat_call(|| heap.pop()).while_some() { -/// print!("{}", element); -/// } -/// -/// itertools::assert_equal( -/// repeat_call(|| 1).take(5), -/// vec![1, 1, 1, 1, 1] -/// ); -/// ``` -#[deprecated(note = "Use std repeat_with() instead", since = "0.8.0")] -pub fn repeat_call(function: F) -> RepeatCall -where - F: FnMut() -> A, -{ - RepeatCall { f: function } -} - -impl Iterator for RepeatCall -where - F: FnMut() -> A, -{ - type Item = A; - - #[inline] - fn next(&mut self) -> Option { - Some((self.f)()) - } - - fn size_hint(&self) -> (usize, Option) { - (usize::MAX, None) - } -} - /// Creates a new unfold source with the specified closure as the "iterator /// function" and an initial state to eventually pass to the closure /// From 9598760e70c2fc3709046debc5c1fb88b773e8d9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:02:42 +0100 Subject: [PATCH 517/633] Remove deprecated `Itertools::map_results` --- src/adaptors/map.rs | 4 ---- src/adaptors/mod.rs | 2 -- src/lib.rs | 12 ------------ 3 files changed, 18 deletions(-) diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index a26ece871..d57b11dc7 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -67,10 +67,6 @@ where /// See [`.map_ok()`](crate::Itertools::map_ok) for more information. pub type MapOk = MapSpecialCase>; -/// See [`MapOk`]. -#[deprecated(note = "Use MapOk instead", since = "0.10.0")] -pub type MapResults = MapOk; - impl MapSpecialCaseFn> for MapSpecialCaseFnOk where F: FnMut(T) -> U, diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 6c44e5b93..57b604908 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -8,8 +8,6 @@ mod coalesce; pub(crate) mod map; mod multi_product; pub use self::coalesce::*; -#[allow(deprecated)] -pub use self::map::MapResults; pub use self::map::{map_into, map_ok, MapInto, MapOk}; #[cfg(feature = "use_alloc")] pub use self::multi_product::*; diff --git a/src/lib.rs b/src/lib.rs index 1f8ad792a..bd5aad461 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -80,8 +80,6 @@ pub use std::iter as __std_iter; /// The concrete iterator types. pub mod structs { - #[allow(deprecated)] - pub use crate::adaptors::MapResults; #[cfg(feature = "use_alloc")] pub use crate::adaptors::MultiProduct; pub use crate::adaptors::{ @@ -829,16 +827,6 @@ pub trait Itertools: Iterator { adaptors::map_into(self) } - /// See [`.map_ok()`](Itertools::map_ok). - #[deprecated(note = "Use .map_ok() instead", since = "0.10.0")] - fn map_results(self, f: F) -> MapOk - where - Self: Iterator> + Sized, - F: FnMut(T) -> U, - { - self.map_ok(f) - } - /// Return an iterator adaptor that applies the provided closure /// to every `Result::Ok` value. `Result::Err` values are /// unchanged. From 8e1b832d5e0d11e3a442a2a0c2692d4e8b504d09 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:03:14 +0100 Subject: [PATCH 518/633] Remove deprecated `Itertools::fold_results` --- src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bd5aad461..d3dd39cc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2322,16 +2322,6 @@ pub trait Itertools: Iterator { format::new_format(self, sep, format) } - /// See [`.fold_ok()`](Itertools::fold_ok). - #[deprecated(note = "Use .fold_ok() instead", since = "0.10.0")] - fn fold_results(&mut self, start: B, f: F) -> Result - where - Self: Iterator>, - F: FnMut(B, A) -> B, - { - self.fold_ok(start, f) - } - /// Fold `Result` values from an iterator. /// /// Only `Ok` values are folded. If no error is encountered, the folded From c68e6b47ab3b7eccfae74ed9f7f091f0aee71dd4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:42:15 +0100 Subject: [PATCH 519/633] Useless attribute --- tests/test_std.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index c26384ae2..793018f1c 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1384,7 +1384,6 @@ fn while_some() { it::assert_equal(ns, vec![1, 2, 3, 4]); } -#[allow(deprecated)] #[test] fn fold_while() { let mut iterations = 0; From 04e13c1bb0572e4f328dacfded0eba785e65125a Mon Sep 17 00:00:00 2001 From: Basheer Subei <6508762+bsubei@users.noreply.github.com> Date: Sat, 24 Feb 2024 19:23:13 -0500 Subject: [PATCH 520/633] minor update to multizip documentation The docs for `multizip()` refer to it as a macro, even though it's a function. I'm guessing this typo originated from the copy-paste from `izip!()`, which has a similar section noting the result. --- src/ziptuple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ziptuple.rs b/src/ziptuple.rs index e299c4013..03bf1ad65 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -16,7 +16,7 @@ pub struct Zip { /// The iterator element type is a tuple like like `(A, B, ..., E)` where `A` to `E` are the /// element types of the subiterator. /// -/// **Note:** The result of this macro is a value of a named type (`Zip<(I, J, +/// **Note:** The result of this function is a value of a named type (`Zip<(I, J, /// ..)>` of each component iterator `I, J, ...`) if each component iterator is /// nameable. /// From 16ce601c747a8850306ec8b9a9ebefa562a2858d Mon Sep 17 00:00:00 2001 From: Ewan Mount Date: Mon, 10 Oct 2022 19:19:22 +0100 Subject: [PATCH 521/633] Adding _by, by_key, largest variants of k_smallest --- src/k_smallest.rs | 104 +++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 102 +++++++++++++++++++++++++++++++++++++++++++-- tests/test_std.rs | 51 +++++++++++++++++------ 3 files changed, 226 insertions(+), 31 deletions(-) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 6af66cfaf..766021b65 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -1,22 +1,96 @@ -use alloc::collections::BinaryHeap; -use core::cmp::Ord; +use alloc::vec::Vec; +use core::cmp::Ordering; + +/// Consumes a given iterator, returning the minimum elements in **ascending** order. +pub(crate) fn k_smallest_general(mut iter: I, k: usize, mut comparator: F) -> Vec +where + I: Iterator, + F: FnMut(&I::Item, &I::Item) -> Ordering, +{ + /// Sift the element currently at `origin` away from the root until it is properly ordered. + /// + /// This will leave **larger** elements closer to the root of the heap. + fn sift_down(heap: &mut [T], is_less_than: &mut F, mut origin: usize) + where + F: FnMut(&T, &T) -> bool, + { + #[inline] + fn children_of(n: usize) -> (usize, usize) { + (2 * n + 1, 2 * n + 2) + } + + while origin < heap.len() { + let (left_idx, right_idx) = children_of(origin); + if left_idx >= heap.len() { + return; + } + + let replacement_idx = + if right_idx < heap.len() && is_less_than(&heap[left_idx], &heap[right_idx]) { + right_idx + } else { + left_idx + }; + + if is_less_than(&heap[origin], &heap[replacement_idx]) { + heap.swap(origin, replacement_idx); + origin = replacement_idx; + } else { + return; + } + } + } -pub(crate) fn k_smallest>(mut iter: I, k: usize) -> BinaryHeap { if k == 0 { - return BinaryHeap::new(); + return Vec::new(); } + let mut storage: Vec = iter.by_ref().take(k).collect(); - let mut heap = iter.by_ref().take(k).collect::>(); + let mut is_less_than = move |a: &_, b: &_| comparator(a, b) == Ordering::Less; - iter.for_each(|i| { - debug_assert_eq!(heap.len(), k); - // Equivalent to heap.push(min(i, heap.pop())) but more efficient. - // This should be done with a single `.peek_mut().unwrap()` but - // `PeekMut` sifts-down unconditionally on Rust 1.46.0 and prior. - if *heap.peek().unwrap() > i { - *heap.peek_mut().unwrap() = i; - } - }); + // Rearrange the storage into a valid heap by reordering from the second-bottom-most layer up to the root. + // Slightly faster than ordering on each insert, but only by a factor of lg(k). + // The resulting heap has the **largest** item on top. + for i in (0..=(storage.len() / 2)).rev() { + sift_down(&mut storage, &mut is_less_than, i); + } + + if k == storage.len() { + // If we fill the storage, there may still be iterator elements left so feed them into the heap. + // Also avoids unexpected behaviour with restartable iterators. + iter.for_each(|val| { + if is_less_than(&val, &storage[0]) { + // Treating this as an push-and-pop saves having to write a sift-up implementation. + // https://en.wikipedia.org/wiki/Binary_heap#Insert_then_extract + storage[0] = val; + // We retain the smallest items we've seen so far, but ordered largest first so we can drop the largest efficiently. + sift_down(&mut storage, &mut is_less_than, 0); + } + }); + } + + // Ultimately the items need to be in least-first, strict order, but the heap is currently largest-first. + // To achieve this, repeatedly, + // 1) "pop" the largest item off the heap into the tail slot of the underlying storage, + // 2) shrink the logical size of the heap by 1, + // 3) restore the heap property over the remaining items. + let mut heap = &mut storage[..]; + while heap.len() > 1 { + let last_idx = heap.len() - 1; + heap.swap(0, last_idx); + // Sifting over a truncated slice means that the sifting will not disturb already popped elements. + heap = &mut heap[..last_idx]; + sift_down(heap, &mut is_less_than, 0); + } + + storage +} - heap +#[inline] +pub(crate) fn key_to_cmp(key: F) -> impl Fn(&T, &T) -> Ordering +where + F: Fn(&T) -> K, + K: Ord, +{ + move |a, b| key(a).cmp(&key(b)) } diff --git a/src/lib.rs b/src/lib.rs index d3dd39cc0..71c8234f5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2950,14 +2950,108 @@ pub trait Itertools: Iterator { /// itertools::assert_equal(five_smallest, 0..5); /// ``` #[cfg(feature = "use_alloc")] - fn k_smallest(self, k: usize) -> VecIntoIter + fn k_smallest(mut self, k: usize) -> VecIntoIter where Self: Sized, Self::Item: Ord, { - crate::k_smallest::k_smallest(self, k) - .into_sorted_vec() - .into_iter() + // The stdlib heap has optimised handling of "holes", which is not included in our heap implementation in k_smallest_general. + // While the difference is unlikely to have practical impact unless `Self::Item` is very large, this method uses the stdlib structure + // to maintain performance compared to previous versions of the crate. + use alloc::collections::BinaryHeap; + + if k == 0 { + return Vec::new().into_iter(); + } + + let mut heap = self.by_ref().take(k).collect::>(); + + self.for_each(|i| { + debug_assert_eq!(heap.len(), k); + // Equivalent to heap.push(min(i, heap.pop())) but more efficient. + // This should be done with a single `.peek_mut().unwrap()` but + // `PeekMut` sifts-down unconditionally on Rust 1.46.0 and prior. + if *heap.peek().unwrap() > i { + *heap.peek_mut().unwrap() = i; + } + }); + + heap.into_sorted_vec().into_iter() + } + + /// Sort the k smallest elements into a new iterator using the provided comparison. + /// + /// This corresponds to `self.sorted_by(cmp).take(k)` in the same way that + /// [Itertools::k_smallest] corresponds to `self.sorted().take(k)`, in both semantics and complexity. + /// Particularly, a custom heap implementation ensures the comparison is not cloned. + #[cfg(feature = "use_alloc")] + fn k_smallest_by(self, k: usize, cmp: F) -> VecIntoIter + where + Self: Sized, + F: Fn(&Self::Item, &Self::Item) -> Ordering, + { + k_smallest::k_smallest_general(self, k, cmp).into_iter() + } + + /// Return the elements producing the k smallest outputs of the provided function + /// + /// This corresponds to `self.sorted_by_key(cmp).take(k)` in the same way that + /// [Itertools::k_smallest] corresponds to `self.sorted().take(k)`, in both semantics and time complexity. + #[cfg(feature = "use_alloc")] + fn k_smallest_by_key(self, k: usize, key: F) -> VecIntoIter + where + Self: Sized, + F: Fn(&Self::Item) -> K, + K: Ord, + { + self.k_smallest_by(k, k_smallest::key_to_cmp(key)) + } + + /// Sort the k largest elements into a new iterator, in descending order. + /// Semantically equivalent to `k_smallest` with a reversed `Ord` + /// However, this is implemented by way of a custom binary heap + /// which does not have the same performance characteristics for very large `Self::Item` + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest(5); + /// + /// itertools::assert_equal(five_largest, vec![14,13,12,11,10]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_largest(self, k: usize) -> VecIntoIter + where + Self: Sized, + Self::Item: Ord, + { + self.k_largest_by(k, Self::Item::cmp) + } + + /// Sort the k largest elements into a new iterator using the provided comparison. + /// Functionally equivalent to `k_smallest_by` with a reversed `Ord` + #[cfg(feature = "use_alloc")] + fn k_largest_by(self, k: usize, cmp: F) -> VecIntoIter + where + Self: Sized, + F: Fn(&Self::Item, &Self::Item) -> Ordering, + { + self.k_smallest_by(k, move |a, b| cmp(b, a)) + } + + /// Return the elements producing the k largest outputs of the provided function + #[cfg(feature = "use_alloc")] + fn k_largest_by_key(self, k: usize, key: F) -> VecIntoIter + where + Self: Sized, + F: Fn(&Self::Item) -> K, + K: Ord, + { + self.k_largest_by(k, k_smallest::key_to_cmp(key)) } /// Collect all iterator elements into one of two diff --git a/tests/test_std.rs b/tests/test_std.rs index 793018f1c..412986dd0 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -492,23 +492,50 @@ fn sorted_by() { } qc::quickcheck! { - fn k_smallest_range(n: u64, m: u16, k: u16) -> () { + fn k_smallest_range(n: i64, m: u16, k: u16) -> () { // u16 is used to constrain k and m to 0..2¹⁶, // otherwise the test could use too much memory. - let (k, m) = (k as u64, m as u64); + let (k, m) = (k as usize, m as u64); + let mut v: Vec<_> = (n..n.saturating_add(m as _)).collect(); // Generate a random permutation of n..n+m - let i = { - let mut v: Vec = (n..n.saturating_add(m)).collect(); - v.shuffle(&mut thread_rng()); - v.into_iter() - }; + v.shuffle(&mut thread_rng()); + + // Construct the right answers for the top and bottom elements + let mut sorted = v.clone(); + sorted.sort(); + // how many elements are we checking + let num_elements = min(k, m as _); + + // Compute the top and bottom k in various combinations + let smallest = v.iter().cloned().k_smallest(k); + let smallest_by = v.iter().cloned().k_smallest_by(k, Ord::cmp); + let smallest_by_key = v.iter().cloned().k_smallest_by_key(k, |&x| x); + + let largest = v.iter().cloned().k_largest(k); + let largest_by = v.iter().cloned().k_largest_by(k, Ord::cmp); + let largest_by_key = v.iter().cloned().k_largest_by_key(k, |&x| x); + + // Check the variations produce the same answers and that they're right + for (a,b,c,d) in izip!( + sorted[..num_elements].iter().cloned(), + smallest, + smallest_by, + smallest_by_key) { + assert_eq!(a,b); + assert_eq!(a,c); + assert_eq!(a,d); + } - // Check that taking the k smallest elements yields n..n+min(k, m) - it::assert_equal( - i.k_smallest(k as usize), - n..n.saturating_add(min(k, m)) - ); + for (a,b,c,d) in izip!( + sorted[sorted.len()-num_elements..].iter().rev().cloned(), + largest, + largest_by, + largest_by_key) { + assert_eq!(a,b); + assert_eq!(a,c); + assert_eq!(a,d); + } } } From 5eb29cfc905fe5b357d1203b176d8de9af487916 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:10:48 +0100 Subject: [PATCH 522/633] `k_smallest` variants: update documentation --- src/lib.rs | 100 +++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 71c8234f5..3b13e6a37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2981,9 +2981,27 @@ pub trait Itertools: Iterator { /// Sort the k smallest elements into a new iterator using the provided comparison. /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// /// This corresponds to `self.sorted_by(cmp).take(k)` in the same way that - /// [Itertools::k_smallest] corresponds to `self.sorted().take(k)`, in both semantics and complexity. + /// [`k_smallest`](Itertools::k_smallest) corresponds to `self.sorted().take(k)`, + /// in both semantics and complexity. + /// /// Particularly, a custom heap implementation ensures the comparison is not cloned. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_smallest = numbers + /// .into_iter() + /// .k_smallest_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b))); + /// + /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]); + /// ``` #[cfg(feature = "use_alloc")] fn k_smallest_by(self, k: usize, cmp: F) -> VecIntoIter where @@ -2993,10 +3011,29 @@ pub trait Itertools: Iterator { k_smallest::k_smallest_general(self, k, cmp).into_iter() } - /// Return the elements producing the k smallest outputs of the provided function + /// Return the elements producing the k smallest outputs of the provided function. /// - /// This corresponds to `self.sorted_by_key(cmp).take(k)` in the same way that - /// [Itertools::k_smallest] corresponds to `self.sorted().take(k)`, in both semantics and time complexity. + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// This corresponds to `self.sorted_by_key(key).take(k)` in the same way that + /// [`k_smallest`](Itertools::k_smallest) corresponds to `self.sorted().take(k)`, + /// in both semantics and complexity. + /// + /// Particularly, a custom heap implementation ensures the comparison is not cloned. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_smallest = numbers + /// .into_iter() + /// .k_smallest_by_key(5, |n| (n % 7, *n)); + /// + /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]); + /// ``` #[cfg(feature = "use_alloc")] fn k_smallest_by_key(self, k: usize, key: F) -> VecIntoIter where @@ -3008,9 +3045,15 @@ pub trait Itertools: Iterator { } /// Sort the k largest elements into a new iterator, in descending order. - /// Semantically equivalent to `k_smallest` with a reversed `Ord` - /// However, this is implemented by way of a custom binary heap - /// which does not have the same performance characteristics for very large `Self::Item` + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// It is semantically equivalent to [`k_smallest`](Itertools::k_smallest) + /// with a reversed `Ord`. + /// However, this is implemented with a custom binary heap which does not + /// have the same performance characteristics for very large `Self::Item`. + /// /// ``` /// use itertools::Itertools; /// @@ -3021,7 +3064,7 @@ pub trait Itertools: Iterator { /// .into_iter() /// .k_largest(5); /// - /// itertools::assert_equal(five_largest, vec![14,13,12,11,10]); + /// itertools::assert_equal(five_largest, vec![14, 13, 12, 11, 10]); /// ``` #[cfg(feature = "use_alloc")] fn k_largest(self, k: usize) -> VecIntoIter @@ -3033,7 +3076,25 @@ pub trait Itertools: Iterator { } /// Sort the k largest elements into a new iterator using the provided comparison. - /// Functionally equivalent to `k_smallest_by` with a reversed `Ord` + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// Functionally equivalent to [`k_smallest_by`](Itertools::k_smallest_by) + /// with a reversed `Ord`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b))); + /// + /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]); + /// ``` #[cfg(feature = "use_alloc")] fn k_largest_by(self, k: usize, cmp: F) -> VecIntoIter where @@ -3043,7 +3104,26 @@ pub trait Itertools: Iterator { self.k_smallest_by(k, move |a, b| cmp(b, a)) } - /// Return the elements producing the k largest outputs of the provided function + /// Return the elements producing the k largest outputs of the provided function. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// Functionally equivalent to [`k_smallest_by_key`](Itertools::k_smallest_by_key) + /// with a reversed `Ord`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest_by_key(5, |n| (n % 7, *n)); + /// + /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]); + /// ``` #[cfg(feature = "use_alloc")] fn k_largest_by_key(self, k: usize, key: F) -> VecIntoIter where From 651602bf3ed9b8058bec9b7f57ffb4ffc447cdf9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:07:40 +0100 Subject: [PATCH 523/633] `k_smallest` variants with `FnMut` functions I totally missed this in my review earlier. We usually allow `FnMut` functions rather than only `Fn` ones. --- src/k_smallest.rs | 4 ++-- src/lib.rs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 766021b65..4de1b25e0 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -87,9 +87,9 @@ where } #[inline] -pub(crate) fn key_to_cmp(key: F) -> impl Fn(&T, &T) -> Ordering +pub(crate) fn key_to_cmp(mut key: F) -> impl FnMut(&T, &T) -> Ordering where - F: Fn(&T) -> K, + F: FnMut(&T) -> K, K: Ord, { move |a, b| key(a).cmp(&key(b)) diff --git a/src/lib.rs b/src/lib.rs index 3b13e6a37..64f2b5ac4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3006,7 +3006,7 @@ pub trait Itertools: Iterator { fn k_smallest_by(self, k: usize, cmp: F) -> VecIntoIter where Self: Sized, - F: Fn(&Self::Item, &Self::Item) -> Ordering, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { k_smallest::k_smallest_general(self, k, cmp).into_iter() } @@ -3038,7 +3038,7 @@ pub trait Itertools: Iterator { fn k_smallest_by_key(self, k: usize, key: F) -> VecIntoIter where Self: Sized, - F: Fn(&Self::Item) -> K, + F: FnMut(&Self::Item) -> K, K: Ord, { self.k_smallest_by(k, k_smallest::key_to_cmp(key)) @@ -3096,10 +3096,10 @@ pub trait Itertools: Iterator { /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]); /// ``` #[cfg(feature = "use_alloc")] - fn k_largest_by(self, k: usize, cmp: F) -> VecIntoIter + fn k_largest_by(self, k: usize, mut cmp: F) -> VecIntoIter where Self: Sized, - F: Fn(&Self::Item, &Self::Item) -> Ordering, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, { self.k_smallest_by(k, move |a, b| cmp(b, a)) } @@ -3128,7 +3128,7 @@ pub trait Itertools: Iterator { fn k_largest_by_key(self, k: usize, key: F) -> VecIntoIter where Self: Sized, - F: Fn(&Self::Item) -> K, + F: FnMut(&Self::Item) -> K, K: Ord, { self.k_largest_by(k, k_smallest::key_to_cmp(key)) From 334621397482754b68093e8cb69c2107ab49bdad Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:21:50 +0100 Subject: [PATCH 524/633] `k_smallest_range` test: no `izip` `izip` could truncate an iterator without warning us while `assert_equal` would. --- tests/test_std.rs | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/tests/test_std.rs b/tests/test_std.rs index 412986dd0..d02f252d0 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -508,34 +508,24 @@ qc::quickcheck! { let num_elements = min(k, m as _); // Compute the top and bottom k in various combinations + let sorted_smallest = sorted[..num_elements].iter().cloned(); let smallest = v.iter().cloned().k_smallest(k); let smallest_by = v.iter().cloned().k_smallest_by(k, Ord::cmp); let smallest_by_key = v.iter().cloned().k_smallest_by_key(k, |&x| x); + let sorted_largest = sorted[sorted.len() - num_elements..].iter().rev().cloned(); let largest = v.iter().cloned().k_largest(k); let largest_by = v.iter().cloned().k_largest_by(k, Ord::cmp); let largest_by_key = v.iter().cloned().k_largest_by_key(k, |&x| x); // Check the variations produce the same answers and that they're right - for (a,b,c,d) in izip!( - sorted[..num_elements].iter().cloned(), - smallest, - smallest_by, - smallest_by_key) { - assert_eq!(a,b); - assert_eq!(a,c); - assert_eq!(a,d); - } + it::assert_equal(smallest, sorted_smallest.clone()); + it::assert_equal(smallest_by, sorted_smallest.clone()); + it::assert_equal(smallest_by_key, sorted_smallest); - for (a,b,c,d) in izip!( - sorted[sorted.len()-num_elements..].iter().rev().cloned(), - largest, - largest_by, - largest_by_key) { - assert_eq!(a,b); - assert_eq!(a,c); - assert_eq!(a,d); - } + it::assert_equal(largest, sorted_largest.clone()); + it::assert_equal(largest_by, sorted_largest.clone()); + it::assert_equal(largest_by_key, sorted_largest); } } From 7af0cac4e081842bc79e628149dc30a1e4f6842f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:46:57 +0100 Subject: [PATCH 525/633] Add `generic_test` for `k_smallest_by` --- tests/test_std.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/test_std.rs b/tests/test_std.rs index d02f252d0..50110533d 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -575,6 +575,17 @@ where it::assert_equal(i.k_smallest(k), j.sorted().take(k)) } +// Similar to `k_smallest_sort` but for our custom heap implementation. +fn k_smallest_by_sort(i: I, k: u16) +where + I: Iterator + Clone, + I::Item: Ord + Debug, +{ + let j = i.clone(); + let k = k as usize; + it::assert_equal(i.k_smallest_by(k, Ord::cmp), j.sorted().take(k)) +} + macro_rules! generic_test { ($f:ident, $($t:ty),+) => { $(paste::item! { @@ -588,6 +599,7 @@ macro_rules! generic_test { } generic_test!(k_smallest_sort, u8, u16, u32, u64, i8, i16, i32, i64); +generic_test!(k_smallest_by_sort, u8, u16, u32, u64, i8, i16, i32, i64); #[test] fn sorted_by_key() { From de00c7a071baa69bc3a5575733c10dce69b9c160 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:29:19 +0100 Subject: [PATCH 526/633] `diff_with` with `FnMut` --- src/diff.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index acef16ecc..c6d99657e 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -77,11 +77,11 @@ where /// /// If `i` becomes exhausted before `j` becomes exhausted, the number of elements in `i` along with /// the remaining `j` elements will be returned as `Diff::Longer`. -pub fn diff_with(i: I, j: J, is_equal: F) -> Option> +pub fn diff_with(i: I, j: J, mut is_equal: F) -> Option> where I: IntoIterator, J: IntoIterator, - F: Fn(&I::Item, &J::Item) -> bool, + F: FnMut(&I::Item, &J::Item) -> bool, { let mut i = i.into_iter(); let mut j = j.into_iter(); From 3918da235ede43a2e44f517b0bedd4711cb00109 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:31:06 +0100 Subject: [PATCH 527/633] `into_group_map_by` with `FnMut` --- src/group_map.rs | 3 ++- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/group_map.rs b/src/group_map.rs index 8891f95ac..3dcee83af 100644 --- a/src/group_map.rs +++ b/src/group_map.rs @@ -22,10 +22,11 @@ where lookup } -pub fn into_group_map_by(iter: I, f: impl Fn(&V) -> K) -> HashMap> +pub fn into_group_map_by(iter: I, mut f: F) -> HashMap> where I: Iterator, K: Hash + Eq, + F: FnMut(&V) -> K, { into_group_map(iter.map(|v| (f(&v), v))) } diff --git a/src/lib.rs b/src/lib.rs index 64f2b5ac4..7171b1dc0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3257,7 +3257,7 @@ pub trait Itertools: Iterator { where Self: Iterator + Sized, K: Hash + Eq, - F: Fn(&V) -> K, + F: FnMut(&V) -> K, { group_map::into_group_map_by(self, f) } From 6b931e557b3eede35c635f9b03141004e523e20c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 14 Feb 2024 09:09:15 +0100 Subject: [PATCH 528/633] `MapInto`: relax `Debug/Clone` bounds Derive those adds a bound on `U` which is not needed since it's only `PhantomData`. Note that `PhantomData` is `Debug` for any `T`. --- src/adaptors/map.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs index d57b11dc7..c78b9be69 100644 --- a/src/adaptors/map.rs +++ b/src/adaptors/map.rs @@ -108,9 +108,19 @@ impl, U> MapSpecialCaseFn for MapSpecialCaseFnInto { } } -#[derive(Clone, Debug)] pub struct MapSpecialCaseFnInto(PhantomData); +impl std::fmt::Debug for MapSpecialCaseFnInto { + debug_fmt_fields!(MapSpecialCaseFnInto, 0); +} + +impl Clone for MapSpecialCaseFnInto { + #[inline] + fn clone(&self) -> Self { + Self(PhantomData) + } +} + /// Create a new [`MapInto`] iterator. pub fn map_into(iter: I) -> MapInto { MapSpecialCase { From 11318999cb4145a757ebad9157afe9265083ffaa Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 15 Feb 2024 10:45:37 +0100 Subject: [PATCH 529/633] Document the `use_alloc` feature --- src/lib.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7171b1dc0..2956d302c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,7 +37,11 @@ //! - `use_std` //! - Enabled by default. //! - Disable to compile itertools using `#![no_std]`. This disables -//! any items that depend on collections (like `chunk_by`, `unique`, +//! any item that depend on allocations (see the `use_alloc` feature) +//! and hash maps (like `unique`, `counts`, `into_grouping_map` and more). +//! - `use_alloc` +//! - Enabled by default. +//! - Enables any item that depend on allocations (like `chunk_by`, //! `kmerge`, `join` and many more). //! //! ## Rust Version From 01e20623cc9caaf64a21de5f920ad7cb9b730ace Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 28 Feb 2024 08:29:44 +0100 Subject: [PATCH 530/633] `set_from`: use `zip` Co-Authored-By: Jack Wrenn --- src/lib.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2956d302c..0506a3d84 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2222,15 +2222,10 @@ pub trait Itertools: Iterator { Self: Iterator, J: IntoIterator, { - let mut count = 0; - for elt in from { - match self.next() { - None => break, - Some(ptr) => *ptr = elt, - } - count += 1; - } - count + from.into_iter() + .zip(self) + .map(|(new, old)| *old = new) + .count() } /// Combine all iterator elements into one String, separated by `sep`. From b513c986d1839bf10d00d42234d2bb4f00a78965 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:02:10 +0100 Subject: [PATCH 531/633] Remove badges in README.md (#890) Co-authored-by: Jack Wrenn --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index 24059e514..982ef5dbe 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,6 @@ Extra iterator adaptors, functions and macros. Please read the [API documentation here](https://docs.rs/itertools/). -[![build_status](https://github.com/rust-itertools/itertools/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-itertools/itertools/actions) -[![crates.io](https://img.shields.io/crates/v/itertools.svg)](https://crates.io/crates/itertools) - How to use with Cargo: ```toml From 8ed734b403725a840a8f567415f18b164874b089 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:09:38 +0100 Subject: [PATCH 532/633] Add two "no-std" categories --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index e5b541c46..d6dc3fa24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ readme = "README.md" description = "Extra iterator adaptors, iterator methods, free functions, and macros." keywords = ["iterator", "data-structure", "zip", "product"] -categories = ["algorithms", "rust-patterns"] +categories = ["algorithms", "rust-patterns", "no-std", "no-std::no-alloc"] edition = "2018" From 1af6c66b39bece17f4c858032cd251ee3e45864b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:08:36 +0100 Subject: [PATCH 533/633] Fuse the iterator in `k_smallest` --- src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 0506a3d84..925cd9c6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2949,7 +2949,7 @@ pub trait Itertools: Iterator { /// itertools::assert_equal(five_smallest, 0..5); /// ``` #[cfg(feature = "use_alloc")] - fn k_smallest(mut self, k: usize) -> VecIntoIter + fn k_smallest(self, k: usize) -> VecIntoIter where Self: Sized, Self::Item: Ord, @@ -2963,9 +2963,10 @@ pub trait Itertools: Iterator { return Vec::new().into_iter(); } - let mut heap = self.by_ref().take(k).collect::>(); + let mut iter = self.fuse(); + let mut heap: BinaryHeap<_> = iter.by_ref().take(k).collect(); - self.for_each(|i| { + iter.for_each(|i| { debug_assert_eq!(heap.len(), k); // Equivalent to heap.push(min(i, heap.pop())) but more efficient. // This should be done with a single `.peek_mut().unwrap()` but From 492fa53d07aafd45699ba800146a8fe18a874d15 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 14 Mar 2024 14:09:10 +0100 Subject: [PATCH 534/633] Fuse the iterator in `k_smallest_general` --- src/k_smallest.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 4de1b25e0..fe699fbd4 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -2,7 +2,7 @@ use alloc::vec::Vec; use core::cmp::Ordering; /// Consumes a given iterator, returning the minimum elements in **ascending** order. -pub(crate) fn k_smallest_general(mut iter: I, k: usize, mut comparator: F) -> Vec +pub(crate) fn k_smallest_general(iter: I, k: usize, mut comparator: F) -> Vec where I: Iterator, F: FnMut(&I::Item, &I::Item) -> Ordering, @@ -44,6 +44,7 @@ where if k == 0 { return Vec::new(); } + let mut iter = iter.fuse(); let mut storage: Vec = iter.by_ref().take(k).collect(); let mut is_less_than = move |a: &_, b: &_| comparator(a, b) == Ordering::Less; @@ -55,19 +56,15 @@ where sift_down(&mut storage, &mut is_less_than, i); } - if k == storage.len() { - // If we fill the storage, there may still be iterator elements left so feed them into the heap. - // Also avoids unexpected behaviour with restartable iterators. - iter.for_each(|val| { - if is_less_than(&val, &storage[0]) { - // Treating this as an push-and-pop saves having to write a sift-up implementation. - // https://en.wikipedia.org/wiki/Binary_heap#Insert_then_extract - storage[0] = val; - // We retain the smallest items we've seen so far, but ordered largest first so we can drop the largest efficiently. - sift_down(&mut storage, &mut is_less_than, 0); - } - }); - } + iter.for_each(|val| { + if is_less_than(&val, &storage[0]) { + // Treating this as an push-and-pop saves having to write a sift-up implementation. + // https://en.wikipedia.org/wiki/Binary_heap#Insert_then_extract + storage[0] = val; + // We retain the smallest items we've seen so far, but ordered largest first so we can drop the largest efficiently. + sift_down(&mut storage, &mut is_less_than, 0); + } + }); // Ultimately the items need to be in least-first, strict order, but the heap is currently largest-first. // To achieve this, repeatedly, From 78a9e7e452e1a72f5162fdaa081a7c09819e9fd2 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:39:47 +0100 Subject: [PATCH 535/633] Rename "benches/tree_fold1.rs" --- Cargo.toml | 2 +- benches/{tree_fold1.rs => tree_reduce.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename benches/{tree_fold1.rs => tree_reduce.rs} (100%) diff --git a/Cargo.toml b/Cargo.toml index d6dc3fa24..7fb6a3980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -57,7 +57,7 @@ name = "combinations_with_replacement" harness = false [[bench]] -name = "tree_fold1" +name = "tree_reduce" harness = false [[bench]] diff --git a/benches/tree_fold1.rs b/benches/tree_reduce.rs similarity index 100% rename from benches/tree_fold1.rs rename to benches/tree_reduce.rs From 45c5dec58b4461baab4a4e660ea93a09c66885e0 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:49:13 +0100 Subject: [PATCH 536/633] Deprecate `tree_fold1` for `tree_reduce` And rename various identifiers. --- benches/tree_reduce.rs | 38 +++++++++++++++++++------------------- src/lib.rs | 26 ++++++++++++++++++-------- tests/quick.rs | 4 ++-- tests/test_core.rs | 4 ++-- tests/test_std.rs | 4 ++-- 5 files changed, 43 insertions(+), 33 deletions(-) diff --git a/benches/tree_reduce.rs b/benches/tree_reduce.rs index 5b76d5404..051b14883 100644 --- a/benches/tree_reduce.rs +++ b/benches/tree_reduce.rs @@ -6,7 +6,7 @@ use itertools::{cloned, Itertools}; trait IterEx: Iterator { // Another efficient implementation against which to compare, // but needs `std` so is less desirable. - fn tree_fold1_vec(self, mut f: F) -> Option + fn tree_reduce_vec(self, mut f: F) -> Option where F: FnMut(Self::Item, Self::Item) -> Self::Item, Self: Sized, @@ -91,14 +91,14 @@ def_benchs! { def_benchs! { 10_000, - tree_fold1, - tree_fold1_stack_10k, + tree_reduce, + tree_reduce_stack_10k, } def_benchs! { 10_000, - tree_fold1_vec, - tree_fold1_vec_10k, + tree_reduce_vec, + tree_reduce_vec_10k, } def_benchs! { @@ -109,14 +109,14 @@ def_benchs! { def_benchs! { 100, - tree_fold1, - tree_fold1_stack_100, + tree_reduce, + tree_reduce_stack_100, } def_benchs! { 100, - tree_fold1_vec, - tree_fold1_vec_100, + tree_reduce_vec, + tree_reduce_vec_100, } def_benchs! { @@ -127,24 +127,24 @@ def_benchs! { def_benchs! { 8, - tree_fold1, - tree_fold1_stack_08, + tree_reduce, + tree_reduce_stack_08, } def_benchs! { 8, - tree_fold1_vec, - tree_fold1_vec_08, + tree_reduce_vec, + tree_reduce_vec_08, } criterion_main!( fold1_10k, - tree_fold1_stack_10k, - tree_fold1_vec_10k, + tree_reduce_stack_10k, + tree_reduce_vec_10k, fold1_100, - tree_fold1_stack_100, - tree_fold1_vec_100, + tree_reduce_stack_100, + tree_reduce_vec_100, fold1_08, - tree_fold1_stack_08, - tree_fold1_vec_08, + tree_reduce_stack_08, + tree_reduce_vec_08, ); diff --git a/src/lib.rs b/src/lib.rs index 925cd9c6e..ec374c469 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2466,7 +2466,7 @@ pub trait Itertools: Iterator { /// - if `f` is a trivial operation like `u32::wrapping_add`, prefer the normal /// [`Iterator::reduce`] instead since it will most likely result in the generation of simpler /// code because the compiler is able to optimize it - /// - otherwise if `f` is non-trivial like `format!`, you should use `tree_fold1` since it + /// - otherwise if `f` is non-trivial like `format!`, you should use `tree_reduce` since it /// reduces the number of operations from `O(n)` to `O(ln(n))` /// /// Here "non-trivial" means: @@ -2479,20 +2479,20 @@ pub trait Itertools: Iterator { /// /// // The same tree as above /// let num_strings = (1..8).map(|x| x.to_string()); - /// assert_eq!(num_strings.tree_fold1(|x, y| format!("f({}, {})", x, y)), + /// assert_eq!(num_strings.tree_reduce(|x, y| format!("f({}, {})", x, y)), /// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))"))); /// /// // Like fold1, an empty iterator produces None - /// assert_eq!((0..0).tree_fold1(|x, y| x * y), None); + /// assert_eq!((0..0).tree_reduce(|x, y| x * y), None); /// - /// // tree_fold1 matches fold1 for associative operations... - /// assert_eq!((0..10).tree_fold1(|x, y| x + y), + /// // tree_reduce matches fold1 for associative operations... + /// assert_eq!((0..10).tree_reduce(|x, y| x + y), /// (0..10).fold1(|x, y| x + y)); /// // ...but not for non-associative ones - /// assert_ne!((0..10).tree_fold1(|x, y| x - y), + /// assert_ne!((0..10).tree_reduce(|x, y| x - y), /// (0..10).fold1(|x, y| x - y)); /// ``` - fn tree_fold1(mut self, mut f: F) -> Option + fn tree_reduce(mut self, mut f: F) -> Option where F: FnMut(Self::Item, Self::Item) -> Self::Item, Self: Sized, @@ -2505,7 +2505,7 @@ pub trait Itertools: Iterator { FF: FnMut(T, T) -> T, { // This function could be replaced with `it.next().ok_or(None)`, - // but half the useful tree_fold1 work is combining adjacent items, + // but half the useful tree_reduce work is combining adjacent items, // so put that in a form that LLVM is more likely to optimize well. let a = if let Some(v) = it.next() { @@ -2555,6 +2555,16 @@ pub trait Itertools: Iterator { } } + /// See [`.tree_reduce()`](Itertools::tree_reduce). + #[deprecated(note = "Use .tree_reduce() instead", since = "0.13.0")] + fn tree_fold1(self, f: F) -> Option + where + F: FnMut(Self::Item, Self::Item) -> Self::Item, + Self: Sized, + { + self.tree_reduce(f) + } + /// An iterator method that applies a function, producing a single, final value. /// /// `fold_while()` is basically equivalent to [`Iterator::fold`] but with additional support for diff --git a/tests/quick.rs b/tests/quick.rs index 7b163d8dc..217ccbdc2 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1334,7 +1334,7 @@ quickcheck! { } quickcheck! { - fn tree_fold1_f64(mut a: Vec) -> TestResult { + fn tree_reduce_f64(mut a: Vec) -> TestResult { fn collapse_adjacent(x: Vec, mut f: F) -> Vec where F: FnMut(f64, f64) -> f64 { @@ -1353,7 +1353,7 @@ quickcheck! { return TestResult::discard(); } - let actual = a.iter().cloned().tree_fold1(f64::atan2); + let actual = a.iter().cloned().tree_reduce(f64::atan2); while a.len() > 1 { a = collapse_adjacent(a, f64::atan2); diff --git a/tests/test_core.rs b/tests/test_core.rs index 448f39652..b5826ef39 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -278,9 +278,9 @@ fn part() { } #[test] -fn tree_fold1() { +fn tree_reduce() { for i in 0..100 { - assert_eq!((0..i).tree_fold1(|x, y| x + y), (0..i).fold1(|x, y| x + y)); + assert_eq!((0..i).tree_reduce(|x, y| x + y), (0..i).fold1(|x, y| x + y)); } } diff --git a/tests/test_std.rs b/tests/test_std.rs index 50110533d..1f319d8d3 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1434,7 +1434,7 @@ fn fold_while() { } #[test] -fn tree_fold1() { +fn tree_reduce() { let x = [ "", "0", @@ -1461,7 +1461,7 @@ fn tree_fold1() { Some(s.to_string()) }; let num_strings = (0..i).map(|x| x.to_string()); - let actual = num_strings.tree_fold1(|a, b| format!("{} {} x", a, b)); + let actual = num_strings.tree_reduce(|a, b| format!("{} {} x", a, b)); assert_eq!(actual, expected); } } From 6a69bd9f11c3937c3997deb6d30196061762f0f4 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 14 Mar 2024 11:34:39 +0100 Subject: [PATCH 537/633] Deprecate `GroupingMap::fold_first` for `reduce` --- src/grouping_map.rs | 25 +++++++++++++++++-------- tests/quick.rs | 6 +++--- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 47a7c096e..f910e5fd3 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -220,14 +220,14 @@ where /// /// let lookup = (1..=7) /// .into_grouping_map_by(|&n| n % 3) - /// .fold_first(|acc, _key, val| acc + val); + /// .reduce(|acc, _key, val| acc + val); /// /// assert_eq!(lookup[&0], 3 + 6); /// assert_eq!(lookup[&1], 1 + 4 + 7); /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn fold_first(self, mut operation: FO) -> HashMap + pub fn reduce(self, mut operation: FO) -> HashMap where FO: FnMut(V, &K, V) -> V, { @@ -239,6 +239,15 @@ where }) } + /// See [`.reduce()`](GroupingMap::reduce). + #[deprecated(note = "Use .reduce() instead", since = "0.13.0")] + pub fn fold_first(self, operation: FO) -> HashMap + where + FO: FnMut(V, &K, V) -> V, + { + self.reduce(operation) + } + /// Groups elements from the `GroupingMap` source by key and collects the elements of each group in /// an instance of `C`. The iteration order is preserved when inserting elements. /// @@ -321,7 +330,7 @@ where where F: FnMut(&K, &V, &V) -> Ordering, { - self.fold_first(|acc, key, val| match compare(key, &acc, &val) { + self.reduce(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => val, Ordering::Greater => acc, }) @@ -402,7 +411,7 @@ where where F: FnMut(&K, &V, &V) -> Ordering, { - self.fold_first(|acc, key, val| match compare(key, &acc, &val) { + self.reduce(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => acc, Ordering::Greater => val, }) @@ -553,7 +562,7 @@ where /// Groups elements from the `GroupingMap` source by key and sums them. /// - /// This is just a shorthand for `self.fold_first(|acc, _, val| acc + val)`. + /// This is just a shorthand for `self.reduce(|acc, _, val| acc + val)`. /// It is more limited than `Iterator::sum` since it doesn't use the `Sum` trait. /// /// Returns a `HashMap` associating the key of each group with the sum of that group's elements. @@ -574,12 +583,12 @@ where where V: Add, { - self.fold_first(|acc, _, val| acc + val) + self.reduce(|acc, _, val| acc + val) } /// Groups elements from the `GroupingMap` source by key and multiply them. /// - /// This is just a shorthand for `self.fold_first(|acc, _, val| acc * val)`. + /// This is just a shorthand for `self.reduce(|acc, _, val| acc * val)`. /// It is more limited than `Iterator::product` since it doesn't use the `Product` trait. /// /// Returns a `HashMap` associating the key of each group with the product of that group's elements. @@ -600,6 +609,6 @@ where where V: Mul, { - self.fold_first(|acc, _, val| acc * val) + self.reduce(|acc, _, val| acc * val) } } diff --git a/tests/quick.rs b/tests/quick.rs index 217ccbdc2..bb08e21c6 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1495,16 +1495,16 @@ quickcheck! { } } - fn correct_grouping_map_by_fold_first_modulo_key(a: Vec, modulo: u8) -> () { + fn correct_grouping_map_by_reduce_modulo_key(a: Vec, modulo: u8) -> () { let modulo = if modulo == 0 { 1 } else { modulo } as u64; // Avoid `% 0` let lookup = a.iter().map(|&b| b as u64) // Avoid overflows .into_grouping_map_by(|i| i % modulo) - .fold_first(|acc, &key, val| { + .reduce(|acc, &key, val| { assert!(val % modulo == key); acc + val }); - // TODO: Swap `fold1` with stdlib's `fold_first` when it's stabilized + // TODO: Swap `fold1` with stdlib's `reduce` when it's stabilized let group_map_lookup = a.iter() .map(|&b| b as u64) .map(|i| (i % modulo, i)) From 0d4930efd5c447e3667a33b091649efe5a12b526 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:22:40 +0100 Subject: [PATCH 538/633] New `Itertools::tail` The `.tail(1)` test is for code coverage. --- src/lib.rs | 51 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/quick.rs | 5 +++++ 2 files changed, 56 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index ec374c469..528a1cfa8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3144,6 +3144,57 @@ pub trait Itertools: Iterator { self.k_largest_by(k, k_smallest::key_to_cmp(key)) } + /// Consumes the iterator and return an iterator of the last `n` elements. + /// + /// The iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// ``` + /// use itertools::{assert_equal, Itertools}; + /// + /// let v = vec![5, 9, 8, 4, 2, 12, 0]; + /// assert_equal(v.iter().tail(3), &[2, 12, 0]); + /// assert_equal(v.iter().tail(10), &v); + /// + /// assert_equal(v.iter().tail(1), v.iter().last()); + /// + /// assert_equal((0..100).tail(10), 90..100); + /// ``` + /// + /// For double ended iterators without side-effects, you might prefer + /// `.rev().take(n).rev()` to have a similar result (lazy and non-allocating) + /// without consuming the entire iterator. + #[cfg(feature = "use_alloc")] + fn tail(self, n: usize) -> VecIntoIter + where + Self: Sized, + { + match n { + 0 => { + self.last(); + Vec::new() + } + 1 => self.last().into_iter().collect(), + _ => { + let mut iter = self.fuse(); + let mut data: Vec<_> = iter.by_ref().take(n).collect(); + // Update `data` cyclically. + let idx = iter.fold(0, |i, val| { + data[i] = val; + if i + 1 == n { + 0 + } else { + i + 1 + } + }); + // Respect the insertion order. + data.rotate_left(idx); + data + } + } + .into_iter() + } + /// Collect all iterator elements into one of two /// partitions. Unlike [`Iterator::partition`], each partition may /// have a distinct type. diff --git a/tests/quick.rs b/tests/quick.rs index bb08e21c6..8590303c7 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1949,4 +1949,9 @@ quickcheck! { result_set.is_empty() } } + + fn tail(v: Vec, n: u8) -> bool { + let n = n as usize; + itertools::equal(v.iter().tail(n), &v[v.len().saturating_sub(n)..]) + } } From 5e41c5a4c0a027f0cb090d1842c6ad846ab372eb Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 15 Mar 2024 22:27:39 +0100 Subject: [PATCH 539/633] `Itertools::tail`: skip the starting part of the iterator If the iterator is exact sized, then `.collect()` finishes the work. More generally, if the size hint knows enough and `nth` is efficient, this might skip most of the iterator efficiently. In the tests, `.filter(..)` is there to ensure that `tail` can't leverage a precise `size_hint` to entirely skip the iteration after initial `.collect()`. Co-Authored-By: scottmcm --- src/lib.rs | 6 +++++- tests/quick.rs | 4 +++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 528a1cfa8..c3d41bab3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3159,6 +3159,8 @@ pub trait Itertools: Iterator { /// assert_equal(v.iter().tail(1), v.iter().last()); /// /// assert_equal((0..100).tail(10), 90..100); + /// + /// assert_equal((0..100).filter(|x| x % 3 == 0).tail(10), (72..100).step_by(3)); /// ``` /// /// For double ended iterators without side-effects, you might prefer @@ -3176,7 +3178,9 @@ pub trait Itertools: Iterator { } 1 => self.last().into_iter().collect(), _ => { - let mut iter = self.fuse(); + // Skip the starting part of the iterator if possible. + let (low, _) = self.size_hint(); + let mut iter = self.fuse().skip(low.saturating_sub(n)); let mut data: Vec<_> = iter.by_ref().take(n).collect(); // Update `data` cyclically. let idx = iter.fold(0, |i, val| { diff --git a/tests/quick.rs b/tests/quick.rs index 8590303c7..aa61e3c1e 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1952,6 +1952,8 @@ quickcheck! { fn tail(v: Vec, n: u8) -> bool { let n = n as usize; - itertools::equal(v.iter().tail(n), &v[v.len().saturating_sub(n)..]) + let result = &v[v.len().saturating_sub(n)..]; + itertools::equal(v.iter().tail(n), result) + && itertools::equal(v.iter().filter(|_| true).tail(n), result) } } From 3ec557367168d52a43cbd69955ecfffe2053eeb7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 14 Mar 2024 19:45:38 +0100 Subject: [PATCH 540/633] `Itertools::tail`: return `VecDeque` rather than `Vec` `rotate_left` is more efficient on `VecDeque` than on a slice. `VecDeque::from(vec)` is O(1) on recent rust. Co-Authored-By: scottmcm --- src/lib.rs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c3d41bab3..73f841aa6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -55,7 +55,7 @@ extern crate core as std; extern crate alloc; #[cfg(feature = "use_alloc")] -use alloc::{string::String, vec::Vec}; +use alloc::{collections::VecDeque, string::String, vec::Vec}; pub use either::Either; @@ -72,6 +72,8 @@ use std::fmt::Write; use std::hash::Hash; use std::iter::{once, IntoIterator}; #[cfg(feature = "use_alloc")] +type VecDequeIntoIter = alloc::collections::vec_deque::IntoIter; +#[cfg(feature = "use_alloc")] type VecIntoIter = alloc::vec::IntoIter; use std::iter::FromIterator; @@ -3146,8 +3148,10 @@ pub trait Itertools: Iterator { /// Consumes the iterator and return an iterator of the last `n` elements. /// - /// The iterator, if directly collected to a `Vec`, is converted + /// The iterator, if directly collected to a `VecDeque`, is converted /// without any extra copying or allocation cost. + /// If directly collected to a `Vec`, it may need some data movement + /// but no re-allocation. /// /// ``` /// use itertools::{assert_equal, Itertools}; @@ -3167,20 +3171,22 @@ pub trait Itertools: Iterator { /// `.rev().take(n).rev()` to have a similar result (lazy and non-allocating) /// without consuming the entire iterator. #[cfg(feature = "use_alloc")] - fn tail(self, n: usize) -> VecIntoIter + fn tail(self, n: usize) -> VecDequeIntoIter where Self: Sized, { match n { 0 => { self.last(); - Vec::new() + VecDeque::new() } 1 => self.last().into_iter().collect(), _ => { // Skip the starting part of the iterator if possible. let (low, _) = self.size_hint(); let mut iter = self.fuse().skip(low.saturating_sub(n)); + // TODO: If VecDeque has a more efficient method than + // `.pop_front();.push_back(val)` in the future then maybe revisit this. let mut data: Vec<_> = iter.by_ref().take(n).collect(); // Update `data` cyclically. let idx = iter.fold(0, |i, val| { @@ -3191,7 +3197,8 @@ pub trait Itertools: Iterator { i + 1 } }); - // Respect the insertion order. + // Respect the insertion order, efficiently. + let mut data = VecDeque::from(data); data.rotate_left(idx); data } From 4d96da1684b11e30f537311703139348e3ee31d7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Sun, 17 Mar 2024 09:37:44 +0100 Subject: [PATCH 541/633] Check two invariants --- src/k_smallest.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index fe699fbd4..b909887f5 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -57,6 +57,7 @@ where } iter.for_each(|val| { + debug_assert_eq!(storage.len(), k); if is_less_than(&val, &storage[0]) { // Treating this as an push-and-pop saves having to write a sift-up implementation. // https://en.wikipedia.org/wiki/Binary_heap#Insert_then_extract diff --git a/src/lib.rs b/src/lib.rs index 73f841aa6..a297a6d80 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3190,6 +3190,7 @@ pub trait Itertools: Iterator { let mut data: Vec<_> = iter.by_ref().take(n).collect(); // Update `data` cyclically. let idx = iter.fold(0, |i, val| { + debug_assert_eq!(data.len(), n); data[i] = val; if i + 1 == n { 0 From 72d73e28ad9a1ed4df07a8ff15bd76243f154cda Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 23:04:04 +0000 Subject: [PATCH 542/633] Bump obi1kenobi/cargo-semver-checks-action from 2.3 to 2.4 Bumps [obi1kenobi/cargo-semver-checks-action](https://github.com/obi1kenobi/cargo-semver-checks-action) from 2.3 to 2.4. - [Release notes](https://github.com/obi1kenobi/cargo-semver-checks-action/releases) - [Commits](https://github.com/obi1kenobi/cargo-semver-checks-action/compare/v2.3...v2.4) --- updated-dependencies: - dependency-name: obi1kenobi/cargo-semver-checks-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9140274f7..239ce2405 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: obi1kenobi/cargo-semver-checks-action@v2.3 + - uses: obi1kenobi/cargo-semver-checks-action@v2.4 with: rust-toolchain: stable feature-group: all-features From 5cf292fd98f2ce450152b3d9399ee5ff6541d021 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:37:24 +0100 Subject: [PATCH 543/633] Fix warnings from clippy 1.77 --- examples/iris.rs | 1 + tests/test_std.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/iris.rs b/examples/iris.rs index a041b2849..63f9c4832 100644 --- a/examples/iris.rs +++ b/examples/iris.rs @@ -17,6 +17,7 @@ struct Iris { data: [f32; 4], } +#[allow(dead_code)] // fields are currently ignored #[derive(Clone, Debug)] enum ParseError { Numeric(ParseFloatError), diff --git a/tests/test_std.rs b/tests/test_std.rs index 1f319d8d3..00246d506 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1321,7 +1321,7 @@ fn extrema_set() { assert_eq!(Some(1u32).iter().min_set(), vec![&1]); assert_eq!(Some(1u32).iter().max_set(), vec![&1]); - let data = vec![Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; + let data = [Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; let min_set = data.iter().min_set(); assert_eq!(min_set, vec![&Val(0, 1), &Val(0, 2)]); @@ -1371,7 +1371,7 @@ fn minmax() { assert_eq!(Some(1u32).iter().minmax(), MinMaxResult::OneElement(&1)); - let data = vec![Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; + let data = [Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)]; let minmax = data.iter().minmax(); assert_eq!(minmax, MinMaxResult::MinMax(&Val(0, 1), &Val(2, 1))); From e6c941193d43af0ea34ed9c577ac76db7c8bbcfa Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 29 Mar 2024 09:40:05 +0100 Subject: [PATCH 544/633] k_smallest(0) and variants: consume the iterator The iterator can have side effects so I think it should be consumed in all cases, even k=0. If there are no side-effect then maybe the compiler will optimize it away. --- src/k_smallest.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index b909887f5..97f0ee02a 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -42,6 +42,7 @@ where } if k == 0 { + iter.last(); return Vec::new(); } let mut iter = iter.fuse(); diff --git a/src/lib.rs b/src/lib.rs index a297a6d80..65909fcdd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2972,6 +2972,7 @@ pub trait Itertools: Iterator { use alloc::collections::BinaryHeap; if k == 0 { + self.last(); return Vec::new().into_iter(); } From 1ef54539467b86c12eb6e4d8006906d43f5312f3 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 29 Mar 2024 09:44:28 +0100 Subject: [PATCH 545/633] k_smallest(1) and variants: use `min[_by]` Like with `tail(1)`, I think we should leverage a possible faster specialization. --- src/k_smallest.rs | 3 +++ src/lib.rs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 97f0ee02a..7b2f62ea1 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -45,6 +45,9 @@ where iter.last(); return Vec::new(); } + if k == 1 { + return iter.min_by(comparator).into_iter().collect(); + } let mut iter = iter.fuse(); let mut storage: Vec = iter.by_ref().take(k).collect(); diff --git a/src/lib.rs b/src/lib.rs index 65909fcdd..ff14cf94e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2975,6 +2975,9 @@ pub trait Itertools: Iterator { self.last(); return Vec::new().into_iter(); } + if k == 1 { + return self.min().into_iter().collect_vec().into_iter(); + } let mut iter = self.fuse(); let mut heap: BinaryHeap<_> = iter.by_ref().take(k).collect(); From 1c5d5cdfca89f3dd2ac818be153c40aef605b288 Mon Sep 17 00:00:00 2001 From: Jake Goulding Date: Tue, 2 Apr 2024 21:19:09 -0400 Subject: [PATCH 546/633] Implement DoubleEndedIterator for ProcessResults --- src/process_results_impl.rs | 51 +++++++++++++++++++++++++++++++------ tests/specializations.rs | 15 ++++++++++- 2 files changed, 57 insertions(+), 9 deletions(-) diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index 7b40cc015..ad6c60d3c 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -13,14 +13,10 @@ pub struct ProcessResults<'a, I, E: 'a> { iter: I, } -impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> -where - I: Iterator>, -{ - type Item = T; - - fn next(&mut self) -> Option { - match self.iter.next() { +impl<'a, I, E> ProcessResults<'a, I, E> { + #[inline(always)] + fn next_body(&mut self, item: Option>) -> Option { + match item { Some(Ok(x)) => Some(x), Some(Err(e)) => { *self.error = Err(e); @@ -29,6 +25,18 @@ where None => None, } } +} + +impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> +where + I: Iterator>, +{ + type Item = T; + + fn next(&mut self) -> Option { + let item = self.iter.next(); + self.next_body(item) + } fn size_hint(&self) -> (usize, Option) { (0, self.iter.size_hint().1) @@ -52,6 +60,33 @@ where } } +impl<'a, I, T, E> DoubleEndedIterator for ProcessResults<'a, I, E> +where + I: Iterator>, + I: DoubleEndedIterator, +{ + fn next_back(&mut self) -> Option { + let item = self.iter.next_back(); + self.next_body(item) + } + + fn rfold(mut self, init: B, mut f: F) -> B + where + F: FnMut(B, Self::Item) -> B, + { + let error = self.error; + self.iter + .try_rfold(init, |acc, opt| match opt { + Ok(x) => Ok(f(acc, x)), + Err(e) => { + *error = Err(e); + Err(acc) + } + }) + .unwrap_or_else(|e| e) + } +} + /// “Lift” a function of the values of an iterator so that it can process /// an iterator of `Result` values instead. /// diff --git a/tests/specializations.rs b/tests/specializations.rs index fd8801e4e..949b2a7de 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -466,7 +466,7 @@ quickcheck! { helper(v.iter().copied()); helper(v.iter().copied().filter(Result::is_ok)); - fn helper(it: impl Iterator> + Clone) { + fn helper(it: impl Iterator> + DoubleEndedIterator + Clone) { macro_rules! check_results_specialized { ($src:expr, |$it:pat| $closure:expr) => { assert_eq!( @@ -482,6 +482,7 @@ quickcheck! { check_results_specialized!(it, |i| i.count()); check_results_specialized!(it, |i| i.last()); check_results_specialized!(it, |i| i.collect::>()); + check_results_specialized!(it, |i| i.rev().collect::>()); check_results_specialized!(it, |i| { let mut parameters_from_fold = vec![]; let fold_result = i.fold(vec![], |mut acc, v| { @@ -491,6 +492,15 @@ quickcheck! { }); (parameters_from_fold, fold_result) }); + check_results_specialized!(it, |i| { + let mut parameters_from_rfold = vec![]; + let rfold_result = i.rfold(vec![], |mut acc, v| { + parameters_from_rfold.push((acc.clone(), v)); + acc.push(v); + acc + }); + (parameters_from_rfold, rfold_result) + }); check_results_specialized!(it, |mut i| { let mut parameters_from_all = vec![]; let first = i.next(); @@ -504,6 +514,9 @@ quickcheck! { for n in 0..size + 2 { check_results_specialized!(it, |mut i| i.nth(n)); } + for n in 0..size + 2 { + check_results_specialized!(it, |mut i| i.nth_back(n)); + } } } } From 6de2f5c2ef63691ad69f1fe5ee36bbd908dee180 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:40:12 +0200 Subject: [PATCH 547/633] Remove `ZipSlices` from benchmarks --- benches/bench1.rs | 74 -------------- benches/extra/mod.rs | 2 - benches/extra/zipslices.rs | 194 ------------------------------------- 3 files changed, 270 deletions(-) delete mode 100644 benches/extra/mod.rs delete mode 100644 benches/extra/zipslices.rs diff --git a/benches/bench1.rs b/benches/bench1.rs index f718ce2c9..53e77b0da 100644 --- a/benches/bench1.rs +++ b/benches/bench1.rs @@ -7,10 +7,6 @@ use std::cmp; use std::iter::repeat; use std::ops::{Add, Range}; -mod extra; - -use crate::extra::ZipSlices; - fn slice_iter(c: &mut Criterion) { let xs: Vec<_> = repeat(1i32).take(20).collect(); @@ -120,72 +116,6 @@ fn zip_slices_ziptuple(c: &mut Criterion) { }); } -fn zipslices(c: &mut Criterion) { - let xs = vec![0; 1024]; - let ys = vec![0; 768]; - let xs = black_box(xs); - let ys = black_box(ys); - - c.bench_function("zipslices", move |b| { - b.iter(|| { - for (&x, &y) in ZipSlices::new(&xs, &ys) { - black_box(x); - black_box(y); - } - }) - }); -} - -fn zipslices_mut(c: &mut Criterion) { - let xs = vec![0; 1024]; - let ys = vec![0; 768]; - let xs = black_box(xs); - let mut ys = black_box(ys); - - c.bench_function("zipslices mut", move |b| { - b.iter(|| { - for (&x, &mut y) in ZipSlices::from_slices(&xs[..], &mut ys[..]) { - black_box(x); - black_box(y); - } - }) - }); -} - -fn zipdot_i32_zipslices(c: &mut Criterion) { - let xs = vec![2; 1024]; - let ys = vec![2; 768]; - let xs = black_box(xs); - let ys = black_box(ys); - - c.bench_function("zipdot i32 zipslices", move |b| { - b.iter(|| { - let mut s = 0i32; - for (&x, &y) in ZipSlices::new(&xs, &ys) { - s += x * y; - } - s - }) - }); -} - -fn zipdot_f32_zipslices(c: &mut Criterion) { - let xs = vec![2f32; 1024]; - let ys = vec![2f32; 768]; - let xs = black_box(xs); - let ys = black_box(ys); - - c.bench_function("zipdot f32 zipslices", move |b| { - b.iter(|| { - let mut s = 0.; - for (&x, &y) in ZipSlices::new(&xs, &ys) { - s += x * y; - } - s - }) - }); -} - fn zip_checked_counted_loop(c: &mut Criterion) { let xs = vec![0; 1024]; let ys = vec![0; 768]; @@ -801,10 +731,6 @@ criterion_group!( zipdot_f32_default_zip, zip_default_zip3, zip_slices_ziptuple, - zipslices, - zipslices_mut, - zipdot_i32_zipslices, - zipdot_f32_zipslices, zip_checked_counted_loop, zipdot_i32_checked_counted_loop, zipdot_f32_checked_counted_loop, diff --git a/benches/extra/mod.rs b/benches/extra/mod.rs deleted file mode 100644 index 52fe5cc3f..000000000 --- a/benches/extra/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub use self::zipslices::ZipSlices; -mod zipslices; diff --git a/benches/extra/zipslices.rs b/benches/extra/zipslices.rs deleted file mode 100644 index 2ec7bc0c0..000000000 --- a/benches/extra/zipslices.rs +++ /dev/null @@ -1,194 +0,0 @@ -use std::cmp; - -// Note: There are different ways to implement ZipSlices. -// This version performed the best in benchmarks. -// -// I also implemented a version with three pointers (tptr, tend, uptr), -// that mimiced slice::Iter and only checked bounds by using tptr == tend, -// but that was inferior to this solution. - -/// An iterator which iterates two slices simultaneously. -/// -/// `ZipSlices` acts like a double-ended `.zip()` iterator. -/// -/// It was intended to be more efficient than `.zip()`, and it was, then -/// rustc changed how it optimizes so it can not promise improved performance -/// at this time. -/// -/// Note that elements past the end of the shortest of the two slices are ignored. -/// -/// Iterator element type for `ZipSlices` is `(T::Item, U::Item)`. For example, -/// for a `ZipSlices<&'a [A], &'b mut [B]>`, the element type is `(&'a A, &'b mut B)`. -#[derive(Clone)] -pub struct ZipSlices { - t: T, - u: U, - len: usize, - index: usize, -} - -impl<'a, 'b, A, B> ZipSlices<&'a [A], &'b [B]> { - /// Create a new `ZipSlices` from slices `a` and `b`. - /// - /// Act like a double-ended `.zip()` iterator, but more efficiently. - /// - /// Note that elements past the end of the shortest of the two slices are ignored. - #[inline(always)] - pub fn new(a: &'a [A], b: &'b [B]) -> Self { - let minl = cmp::min(a.len(), b.len()); - ZipSlices { - t: a, - u: b, - len: minl, - index: 0, - } - } -} - -impl ZipSlices -where - T: Slice, - U: Slice, -{ - /// Create a new `ZipSlices` from slices `a` and `b`. - /// - /// Act like a double-ended `.zip()` iterator, but more efficiently. - /// - /// Note that elements past the end of the shortest of the two slices are ignored. - #[inline(always)] - pub fn from_slices(a: T, b: U) -> Self { - let minl = cmp::min(a.len(), b.len()); - Self { - t: a, - u: b, - len: minl, - index: 0, - } - } -} - -impl Iterator for ZipSlices -where - T: Slice, - U: Slice, -{ - type Item = (T::Item, U::Item); - - #[inline(always)] - fn next(&mut self) -> Option { - unsafe { - if self.index >= self.len { - None - } else { - let i = self.index; - self.index += 1; - Some((self.t.get_unchecked(i), self.u.get_unchecked(i))) - } - } - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - let len = self.len - self.index; - (len, Some(len)) - } -} - -impl DoubleEndedIterator for ZipSlices -where - T: Slice, - U: Slice, -{ - #[inline(always)] - fn next_back(&mut self) -> Option { - unsafe { - if self.index >= self.len { - None - } else { - self.len -= 1; - let i = self.len; - Some((self.t.get_unchecked(i), self.u.get_unchecked(i))) - } - } - } -} - -impl ExactSizeIterator for ZipSlices -where - T: Slice, - U: Slice, -{ -} - -unsafe impl Slice for ZipSlices -where - T: Slice, - U: Slice, -{ - type Item = (T::Item, U::Item); - - fn len(&self) -> usize { - self.len - self.index - } - - unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item { - (self.t.get_unchecked(i), self.u.get_unchecked(i)) - } -} - -/// A helper trait to let `ZipSlices` accept both `&[T]` and `&mut [T]`. -/// -/// # Safety -/// -/// Unsafe trait because: -/// -/// - Implementors must guarantee that `get_unchecked` is valid for all indices `0..len()`. -pub unsafe trait Slice { - /// The type of a reference to the slice's elements - type Item; - #[doc(hidden)] - fn len(&self) -> usize; - #[doc(hidden)] - unsafe fn get_unchecked(&mut self, i: usize) -> Self::Item; -} - -unsafe impl<'a, T> Slice for &'a [T] { - type Item = &'a T; - #[inline(always)] - fn len(&self) -> usize { - (**self).len() - } - #[inline(always)] - unsafe fn get_unchecked(&mut self, i: usize) -> &'a T { - debug_assert!(i < self.len()); - (**self).get_unchecked(i) - } -} - -unsafe impl<'a, T> Slice for &'a mut [T] { - type Item = &'a mut T; - #[inline(always)] - fn len(&self) -> usize { - (**self).len() - } - #[inline(always)] - unsafe fn get_unchecked(&mut self, i: usize) -> &'a mut T { - debug_assert!(i < self.len()); - // override the lifetime constraints of &mut &'a mut [T] - (*(*self as *mut [T])).get_unchecked_mut(i) - } -} - -#[test] -fn zipslices() { - let xs = [1, 2, 3, 4, 5, 6]; - let ys = [1, 2, 3, 7]; - ::itertools::assert_equal(ZipSlices::new(&xs, &ys), xs.iter().zip(&ys)); - - let xs = [1, 2, 3, 4, 5, 6]; - let mut ys = [0; 6]; - for (x, y) in ZipSlices::from_slices(&xs[..], &mut ys[..]) { - *y = *x; - } - ::itertools::assert_equal(&xs, &ys); -} From 37c54d1513e6cdd7aae58d8e646bee6df02f56fd Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 10 Apr 2024 11:45:29 +0200 Subject: [PATCH 548/633] `TakeWhileInclusive::new` should not have been public --- src/take_while_inclusive.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/take_while_inclusive.rs b/src/take_while_inclusive.rs index 0d186d76b..420da9847 100644 --- a/src/take_while_inclusive.rs +++ b/src/take_while_inclusive.rs @@ -21,7 +21,7 @@ where F: FnMut(&I::Item) -> bool, { /// Create a new [`TakeWhileInclusive`] from an iterator and a predicate. - pub fn new(iter: I, predicate: F) -> Self { + pub(crate) fn new(iter: I, predicate: F) -> Self { Self { iter, predicate, From 5be2e8d4edd46eed9204d96adb79f921511f8016 Mon Sep 17 00:00:00 2001 From: kinto-b Date: Mon, 15 Apr 2024 16:38:24 +1000 Subject: [PATCH 549/633] Replace custom zip_eq test with quick test --- tests/quick.rs | 8 ++++++++ tests/zip.rs | 19 ------------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/tests/quick.rs b/tests/quick.rs index aa61e3c1e..5b8fd6a21 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -571,6 +571,14 @@ quickcheck! { let b = &b[..len]; itertools::equal(zip_eq(a, b), zip(a, b)) } + + #[should_panic] + fn zip_eq_panics(a: Vec, b: Vec) -> TestResult { + if a.len() == b.len() { return TestResult::discard(); } + zip_eq(a.iter(), b.iter()).for_each(|_| {}); + TestResult::passed() // won't come here + } + fn equal_positions(a: Vec) -> bool { let with_pos = a.iter().positions(|v| v % 2 == 0); let without = a.iter().enumerate().filter(|(_, v)| *v % 2 == 0).map(|(i, _)| i); diff --git a/tests/zip.rs b/tests/zip.rs index f2554d7a4..716ac20b3 100644 --- a/tests/zip.rs +++ b/tests/zip.rs @@ -1,4 +1,3 @@ -use itertools::free::zip_eq; use itertools::multizip; use itertools::EitherOrBoth::{Both, Left, Right}; use itertools::Itertools; @@ -55,21 +54,3 @@ fn test_double_ended_zip() { assert_eq!(it.next_back(), Some((1, 1))); assert_eq!(it.next_back(), None); } - -#[should_panic] -#[test] -fn zip_eq_panic1() { - let a = [1, 2]; - let b = [1, 2, 3]; - - zip_eq(&a, &b).count(); -} - -#[should_panic] -#[test] -fn zip_eq_panic2() { - let a: [i32; 0] = []; - let b = [1, 2, 3]; - - zip_eq(&a, &b).count(); -} From 4cbb6d3cf63f5ba016ab4663e9ceca42e79a47a4 Mon Sep 17 00:00:00 2001 From: kinto-b Date: Sat, 13 Apr 2024 11:48:58 +1000 Subject: [PATCH 550/633] Implement `Combinations::nth` --- src/combinations.rs | 101 +++++++++++++++++++++++++++++++------------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index d8b5351ec..9e5a661c2 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -14,6 +14,7 @@ pub struct Combinations { indices: Vec, pool: LazyBuffer, first: bool, + done: bool, } impl Clone for Combinations @@ -21,7 +22,7 @@ where I: Clone + Iterator, I::Item: Clone, { - clone_fields!(indices, pool, first); + clone_fields!(indices, pool, first, done); } impl fmt::Debug for Combinations @@ -29,7 +30,7 @@ where I: Iterator + fmt::Debug, I::Item: fmt::Debug, { - debug_fmt_fields!(Combinations, indices, pool, first); + debug_fmt_fields!(Combinations, indices, pool, first, done); } /// Create a new `Combinations` from a clonable iterator. @@ -41,6 +42,7 @@ where indices: (0..k).collect(), pool: LazyBuffer::new(iter), first: true, + done: false, } } @@ -70,6 +72,7 @@ impl Combinations { /// elements. pub(crate) fn reset(&mut self, k: usize) { self.first = true; + self.done = false; if k < self.indices.len() { self.indices.truncate(k); @@ -90,10 +93,56 @@ impl Combinations { indices, pool, first, + done: _, } = self; let n = pool.count(); (n, remaining_for(n, first, &indices).unwrap()) } + + /// Initialises the iterator by filling a buffer with elements from the + /// iterator. + fn init(&mut self) { + self.pool.prefill(self.k()); + if self.k() > self.n() { + self.done = true; + } else { + self.first = false; + } + } + + /// Increments indices representing the combination to advance to the next + /// (in lexicographic order by increasing sequence) combination. For example + /// if we have n=3 & k=2 then [0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ... + fn increment_indices(&mut self) { + if self.indices.is_empty() { + self.done = true; + return; + } + + // Scan from the end, looking for an index to increment + let mut i: usize = self.indices.len() - 1; + + // Check if we need to consume more from the iterator + if self.indices[i] == self.pool.len() - 1 { + self.pool.get_next(); // may change pool size + } + + while self.indices[i] == i + self.pool.len() - self.indices.len() { + if i > 0 { + i -= 1; + } else { + // Reached the last combination + self.done = true; + return; + } + } + + // Increment index, and reset the ones to its right + self.indices[i] += 1; + for j in i + 1..self.indices.len() { + self.indices[j] = self.indices[j - 1] + 1; + } + } } impl Iterator for Combinations @@ -104,40 +153,34 @@ where type Item = Vec; fn next(&mut self) -> Option { if self.first { - self.pool.prefill(self.k()); - if self.k() > self.n() { - return None; - } - self.first = false; - } else if self.indices.is_empty() { - return None; + self.init() } else { - // Scan from the end, looking for an index to increment - let mut i: usize = self.indices.len() - 1; + self.increment_indices() + } - // Check if we need to consume more from the iterator - if self.indices[i] == self.pool.len() - 1 { - self.pool.get_next(); // may change pool size - } + if self.done { + return None; + } - while self.indices[i] == i + self.pool.len() - self.indices.len() { - if i > 0 { - i -= 1; - } else { - // Reached the last combination - return None; - } - } + Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) + } + + fn nth(&mut self, n: usize) -> Option { + // Delegate initialisation work to next() + let first = self.next(); - // Increment index, and reset the ones to its right - self.indices[i] += 1; - for j in i + 1..self.indices.len() { - self.indices[j] = self.indices[j - 1] + 1; + if n == 0 { + return first; + } + + for _ in 0..(n - 1) { + self.increment_indices(); + if self.done { + return None; } } - // Create result vector based on the indices - Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) + self.next() } fn size_hint(&self) -> (usize, Option) { From 3c198b79e3a05761b8c8876df63ed1e997c71c3e Mon Sep 17 00:00:00 2001 From: kinto-b Date: Mon, 15 Apr 2024 09:46:17 +1000 Subject: [PATCH 551/633] Drop `done` and needless heap alocation --- src/combinations.rs | 58 ++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 9e5a661c2..fe76ad131 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -14,7 +14,6 @@ pub struct Combinations { indices: Vec, pool: LazyBuffer, first: bool, - done: bool, } impl Clone for Combinations @@ -22,7 +21,7 @@ where I: Clone + Iterator, I::Item: Clone, { - clone_fields!(indices, pool, first, done); + clone_fields!(indices, pool, first); } impl fmt::Debug for Combinations @@ -30,7 +29,7 @@ where I: Iterator + fmt::Debug, I::Item: fmt::Debug, { - debug_fmt_fields!(Combinations, indices, pool, first, done); + debug_fmt_fields!(Combinations, indices, pool, first); } /// Create a new `Combinations` from a clonable iterator. @@ -42,7 +41,6 @@ where indices: (0..k).collect(), pool: LazyBuffer::new(iter), first: true, - done: false, } } @@ -72,7 +70,6 @@ impl Combinations { /// elements. pub(crate) fn reset(&mut self, k: usize) { self.first = true; - self.done = false; if k < self.indices.len() { self.indices.truncate(k); @@ -93,30 +90,32 @@ impl Combinations { indices, pool, first, - done: _, } = self; let n = pool.count(); (n, remaining_for(n, first, &indices).unwrap()) } /// Initialises the iterator by filling a buffer with elements from the - /// iterator. - fn init(&mut self) { + /// iterator, return a boolean indicating whether or not we've run out of + /// combinations. + fn init(&mut self) -> bool { self.pool.prefill(self.k()); - if self.k() > self.n() { - self.done = true; - } else { + let done = self.k() > self.n(); + if !done { self.first = false; } + + done } /// Increments indices representing the combination to advance to the next /// (in lexicographic order by increasing sequence) combination. For example /// if we have n=3 & k=2 then [0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ... - fn increment_indices(&mut self) { + /// + /// Returns a boolean indicating whether or not we've run out of combinations. + fn increment_indices(&mut self) -> bool { if self.indices.is_empty() { - self.done = true; - return; + return true; // Done } // Scan from the end, looking for an index to increment @@ -132,8 +131,7 @@ impl Combinations { i -= 1; } else { // Reached the last combination - self.done = true; - return; + return true; } } @@ -142,6 +140,9 @@ impl Combinations { for j in i + 1..self.indices.len() { self.indices[j] = self.indices[j - 1] + 1; } + + // If we've made it this far, we haven't run out of combos + false } } @@ -152,13 +153,13 @@ where { type Item = Vec; fn next(&mut self) -> Option { - if self.first { + let done = if self.first { self.init() } else { self.increment_indices() - } + }; - if self.done { + if done { return None; } @@ -166,16 +167,23 @@ where } fn nth(&mut self, n: usize) -> Option { - // Delegate initialisation work to next() - let first = self.next(); - if n == 0 { - return first; + return self.next(); + } + + let mut done = if self.first { + self.init() + } else { + self.increment_indices() + }; + + if done { + return None; } for _ in 0..(n - 1) { - self.increment_indices(); - if self.done { + done = self.increment_indices(); + if done { return None; } } From c4b8fd47a5216539a6f796fcbc38827254c1753b Mon Sep 17 00:00:00 2001 From: kinto-b Date: Mon, 15 Apr 2024 19:41:18 +1000 Subject: [PATCH 552/633] Return directly from `nth()` Do not go via `next()` --- src/combinations.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index fe76ad131..b8cae5e97 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -96,8 +96,7 @@ impl Combinations { } /// Initialises the iterator by filling a buffer with elements from the - /// iterator, return a boolean indicating whether or not we've run out of - /// combinations. + /// iterator. Returns true if there are no combinations, false otherwise. fn init(&mut self) -> bool { self.pool.prefill(self.k()); let done = self.k() > self.n(); @@ -110,9 +109,9 @@ impl Combinations { /// Increments indices representing the combination to advance to the next /// (in lexicographic order by increasing sequence) combination. For example - /// if we have n=3 & k=2 then [0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ... + /// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...` /// - /// Returns a boolean indicating whether or not we've run out of combinations. + /// Returns true if we've run out of combinations, false otherwise. fn increment_indices(&mut self) -> bool { if self.indices.is_empty() { return true; // Done @@ -171,7 +170,7 @@ where return self.next(); } - let mut done = if self.first { + let done = if self.first { self.init() } else { self.increment_indices() @@ -181,14 +180,13 @@ where return None; } - for _ in 0..(n - 1) { - done = self.increment_indices(); - if done { + for _ in 0..n { + if self.increment_indices() { return None; } } - self.next() + Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) } fn size_hint(&self) -> (usize, Option) { From 40e24dcf22913b0a85d8a425c774399dda893359 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 16 Apr 2024 10:49:38 +0200 Subject: [PATCH 553/633] Benchmark `.nth[_back](n)` with inputs `n` --- benches/specializations.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index b3e7d0a9f..7e39fc1c8 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -1,9 +1,12 @@ #![allow(unstable_name_collisions)] use criterion::black_box; +use criterion::BenchmarkId; use itertools::iproduct; use itertools::Itertools; +const NTH_INPUTS: &[usize] = &[0, 1, 2, 4, 8]; + /// Create multiple functions each defining a benchmark group about iterator methods. /// /// Each created group has functions with the following ids: @@ -73,19 +76,19 @@ macro_rules! bench_specializations { $group.bench_function("last", |bencher| bencher.iter(|| { $iterator.last() })); - $group.bench_function("nth", |bencher| bencher.iter(|| { - for start in 0_usize..10 { - for n in 0..10 { + for n in NTH_INPUTS { + $group.bench_with_input(BenchmarkId::new("nth", n), n, |bencher, n| bencher.iter(|| { + for start in 0_usize..10 { let mut it = $iterator; if let Some(s) = start.checked_sub(1) { black_box(it.nth(s)); } - while let Some(x) = it.nth(n) { + while let Some(x) = it.nth(*n) { black_box(x); } } - } - })); + })); + } $group.bench_function("collect", |bencher| bencher.iter(|| { $iterator.collect::>() })); @@ -103,19 +106,19 @@ macro_rules! bench_specializations { black_box(x); } })); - $group.bench_function("nth_back", |bencher| bencher.iter(|| { - for start in 0_usize..10 { - for n in 0..10 { + for n in NTH_INPUTS { + $group.bench_with_input(BenchmarkId::new("nth_back", n), n, |bencher, n| bencher.iter(|| { + for start in 0_usize..10 { let mut it = $iterator; if let Some(s) = start.checked_sub(1) { black_box(it.nth_back(s)); } - while let Some(x) = it.nth_back(n) { + while let Some(x) = it.nth_back(*n) { black_box(x); } } - } - })); + })); + } $group.bench_function("rfold", |bencher| bencher.iter(|| { $iterator.rfold((), |(), x| { black_box(x); From 5f401bbbe83c792dea389c4002d0a592ca1b1e36 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 16 Apr 2024 11:33:30 +0200 Subject: [PATCH 554/633] Benchmark specializations: minor doc/usage changes --- benches/specializations.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 7e39fc1c8..22cf426d4 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -12,7 +12,7 @@ const NTH_INPUTS: &[usize] = &[0, 1, 2, 4, 8]; /// Each created group has functions with the following ids: /// /// - `next`, `size_hint`, `count`, `last`, `nth`, `collect`, `fold` -/// - and when marked as `DoubleEndedIterator`: `next_back`, `rfold` +/// - and when marked as `DoubleEndedIterator`: `next_back`, `nth_back`, `rfold` /// - and when marked as `ExactSizeIterator`: `len` /// /// Note that this macro can be called only once. @@ -135,8 +135,11 @@ macro_rules! bench_specializations { }; } -// Example: To bench only `ZipLongest::fold`, you can do +// Usage examples: +// - For `ZipLongest::fold` only: // cargo bench --bench specializations zip_longest/fold +// - For `.combinations(k).nth(8)`: +// cargo bench --bench specializations combinations./nth/8 bench_specializations! { interleave { { From ed695afbbc8a8184267581d053c43d163b299b65 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:23:29 +0200 Subject: [PATCH 555/633] Update specializations.rs When I want to benchmark only a few iterators, I don't use iproduct and end up with an annoying and recurring warning that this simple fix solves. --- benches/specializations.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 22cf426d4..28b01cfbf 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -2,7 +2,6 @@ use criterion::black_box; use criterion::BenchmarkId; -use itertools::iproduct; use itertools::Itertools; const NTH_INPUTS: &[usize] = &[0, 1, 2, 4, 8]; @@ -261,7 +260,7 @@ bench_specializations! { { let v = black_box(vec![0; 16]); } - iproduct!(&v, &v, &v) + itertools::iproduct!(&v, &v, &v) } multi_cartesian_product { { From f7d557d5c78c7bd8f65d43ffeaf65452d2da3f53 Mon Sep 17 00:00:00 2001 From: kinto-b Date: Wed, 17 Apr 2024 16:37:38 +1000 Subject: [PATCH 556/633] Implement helper on LazyBuffer for getting multiple elements by index --- src/combinations.rs | 8 ++------ src/combinations_with_replacement.rs | 15 ++------------- src/lazy_buffer.rs | 10 ++++++++++ src/permutations.rs | 4 ++-- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index b8cae5e97..e254fee7e 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -162,14 +162,10 @@ where return None; } - Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) + Some(self.pool.get_at(&self.indices)) } fn nth(&mut self, n: usize) -> Option { - if n == 0 { - return self.next(); - } - let done = if self.first { self.init() } else { @@ -186,7 +182,7 @@ where } } - Some(self.indices.iter().map(|i| self.pool[*i].clone()).collect()) + Some(self.pool.get_at(&self.indices)) } fn size_hint(&self) -> (usize, Option) { diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index ee3497849..4043678c1 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -30,17 +30,6 @@ where debug_fmt_fields!(CombinationsWithReplacement, indices, pool, first); } -impl CombinationsWithReplacement -where - I: Iterator, - I::Item: Clone, -{ - /// Map the current mask over the pool to get an output combination - fn current(&self) -> Vec { - self.indices.iter().map(|i| self.pool[*i].clone()).collect() - } -} - /// Create a new `CombinationsWithReplacement` from a clonable iterator. pub fn combinations_with_replacement(iter: I, k: usize) -> CombinationsWithReplacement where @@ -72,7 +61,7 @@ where // Otherwise, yield the initial state } else { self.first = false; - Some(self.current()) + Some(self.pool.get_at(&self.indices)) }; } @@ -97,7 +86,7 @@ where for indices_index in increment_from..self.indices.len() { self.indices[indices_index] = increment_value; } - Some(self.current()) + Some(self.pool.get_at(&self.indices)) } // Otherwise, we're done None => None, diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index 5cb039a63..fefcff8f5 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -51,6 +51,16 @@ where } } +impl LazyBuffer +where + I: Iterator, + I::Item: Clone, +{ + pub fn get_at(&self, indices: &[usize]) -> Vec { + indices.iter().map(|i| self.buffer[*i].clone()).collect() + } +} + impl Index for LazyBuffer where I: Iterator, diff --git a/src/permutations.rs b/src/permutations.rs index cf5973c8c..91389a73a 100644 --- a/src/permutations.rs +++ b/src/permutations.rs @@ -99,7 +99,7 @@ where return None; } } - let item = indices[0..*k].iter().map(|&i| vals[i].clone()).collect(); + let item = vals.get_at(&indices[0..*k]); *state = PermutationState::Loaded { indices, cycles }; Some(item) } @@ -110,7 +110,7 @@ where return None; } let k = cycles.len(); - Some(indices[0..k].iter().map(|&i| vals[i].clone()).collect()) + Some(vals.get_at(&indices[0..k])) } PermutationState::End => None, } From fb48ff55556cdf0dcde6ab49f554d1081f174b35 Mon Sep 17 00:00:00 2001 From: kinto-b Date: Thu, 18 Apr 2024 14:28:56 +1000 Subject: [PATCH 557/633] Implement `MergeBy::fold` --- src/merge_join.rs | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/merge_join.rs b/src/merge_join.rs index c83159186..c8f8f9f6d 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -256,6 +256,54 @@ where } } + fn fold(mut self, init: B, mut f: G) -> B + where + Self: Sized, + G: FnMut(B, Self::Item) -> B, + { + let mut acc = init; + let mut left = self.left.next(); + let mut right = self.right.next(); + + loop { + match (left, right) { + (Some(l), Some(r)) => match self.cmp_fn.merge(l, r) { + (None, Some(r), x) => { + acc = f(acc, x); + left = self.left.next(); + right = Some(r); + } + (Some(l), None, x) => { + acc = f(acc, x); + left = Some(l); + right = self.right.next(); + } + (None, None, x) => { + acc = f(acc, x); + left = self.left.next(); + right = self.right.next(); + } + (Some(_), Some(_), _) => unreachable!(), + }, + (Some(l), None) => { + self.left.put_back(l); + acc = self.left.fold(acc, |acc, x| f(acc, F::left(x))); + break; + } + (None, Some(r)) => { + self.right.put_back(r); + acc = self.right.fold(acc, |acc, x| f(acc, F::right(x))); + break; + } + (None, None) => { + break; + } + } + } + + acc + } + fn size_hint(&self) -> SizeHint { F::size_hint(self.left.size_hint(), self.right.size_hint()) } From 8583689e9f4aa546979a8cdf5a15ce8ef42c43c2 Mon Sep 17 00:00:00 2001 From: kinto-b Date: Wed, 24 Apr 2024 09:59:43 +1000 Subject: [PATCH 558/633] Make merge specialization benchmark more realistic --- benches/specializations.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 28b01cfbf..b5230a06a 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -582,29 +582,29 @@ bench_specializations! { } merge { { - let v1 = black_box(vec![0; 1024]); - let v2 = black_box(vec![0; 768]); + let v1 = black_box((0..1024).collect_vec()); + let v2 = black_box((0..768).collect_vec()); } v1.iter().merge(&v2) } merge_by { { - let v1 = black_box(vec![0; 1024]); - let v2 = black_box(vec![0; 768]); + let v1 = black_box((0..1024).collect_vec()); + let v2 = black_box((0..768).collect_vec()); } v1.iter().merge_by(&v2, PartialOrd::ge) } merge_join_by_ordering { { - let v1 = black_box(vec![0; 1024]); - let v2 = black_box(vec![0; 768]); + let v1 = black_box((0..1024).collect_vec()); + let v2 = black_box((0..768).collect_vec()); } v1.iter().merge_join_by(&v2, Ord::cmp) } merge_join_by_bool { { - let v1 = black_box(vec![0; 1024]); - let v2 = black_box(vec![0; 768]); + let v1 = black_box((0..1024).collect_vec()); + let v2 = black_box((0..768).collect_vec()); } v1.iter().merge_join_by(&v2, PartialOrd::ge) } From 232e0c2d9c54d8adbb396bde596cb9dc128272cb Mon Sep 17 00:00:00 2001 From: kinto-b Date: Wed, 24 Apr 2024 10:01:39 +1000 Subject: [PATCH 559/633] Remove `MergeBy::last` and `MergeBy::count` It is faster to rely on new `MergeBy::fold` implementation --- src/merge_join.rs | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index c8f8f9f6d..7ae21e3b8 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -308,52 +308,6 @@ where F::size_hint(self.left.size_hint(), self.right.size_hint()) } - fn count(mut self) -> usize { - let mut count = 0; - loop { - match (self.left.next(), self.right.next()) { - (None, None) => break count, - (Some(_left), None) => break count + 1 + self.left.into_parts().1.count(), - (None, Some(_right)) => break count + 1 + self.right.into_parts().1.count(), - (Some(left), Some(right)) => { - count += 1; - let (left, right, _) = self.cmp_fn.merge(left, right); - if let Some(left) = left { - self.left.put_back(left); - } - if let Some(right) = right { - self.right.put_back(right); - } - } - } - } - } - - fn last(mut self) -> Option { - let mut previous_element = None; - loop { - match (self.left.next(), self.right.next()) { - (None, None) => break previous_element, - (Some(left), None) => { - break Some(F::left(self.left.into_parts().1.last().unwrap_or(left))) - } - (None, Some(right)) => { - break Some(F::right(self.right.into_parts().1.last().unwrap_or(right))) - } - (Some(left), Some(right)) => { - let (left, right, elem) = self.cmp_fn.merge(left, right); - if let Some(left) = left { - self.left.put_back(left); - } - if let Some(right) = right { - self.right.put_back(right); - } - previous_element = Some(elem); - } - } - } - } - fn nth(&mut self, mut n: usize) -> Option { loop { if n == 0 { From 2f53aeafda0a0d3670b980ff2ce5bcb991e5306d Mon Sep 17 00:00:00 2001 From: kinto-b Date: Wed, 24 Apr 2024 10:15:56 +1000 Subject: [PATCH 560/633] Simplify `OrderingOrBool::merge` Return `(Option>, MergeResult)` instead of `(Option, Option, MergeResult)` --- src/merge_join.rs | 66 ++++++++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/src/merge_join.rs b/src/merge_join.rs index 7ae21e3b8..c0de35f90 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -115,7 +115,7 @@ pub trait OrderingOrBool { // "merge" never returns (Some(...), Some(...), ...) so Option> // is appealing but it is always followed by two put_backs, so we think the compiler is // smart enough to optimize it. Or we could move put_backs into "merge". - fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult); + fn merge(&mut self, left: L, right: R) -> (Option>, Self::MergeResult); fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint; } @@ -127,11 +127,11 @@ impl Ordering> OrderingOrBool for MergeFuncLR Self::MergeResult { EitherOrBoth::Right(right) } - fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + fn merge(&mut self, left: L, right: R) -> (Option>, Self::MergeResult) { match self.0(&left, &right) { - Ordering::Equal => (None, None, EitherOrBoth::Both(left, right)), - Ordering::Less => (None, Some(right), EitherOrBoth::Left(left)), - Ordering::Greater => (Some(left), None, EitherOrBoth::Right(right)), + Ordering::Equal => (None, EitherOrBoth::Both(left, right)), + Ordering::Less => (Some(Either::Right(right)), EitherOrBoth::Left(left)), + Ordering::Greater => (Some(Either::Left(left)), EitherOrBoth::Right(right)), } } fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { @@ -154,11 +154,11 @@ impl bool> OrderingOrBool for MergeFuncLR Self::MergeResult { Either::Right(right) } - fn merge(&mut self, left: L, right: R) -> (Option, Option, Self::MergeResult) { + fn merge(&mut self, left: L, right: R) -> (Option>, Self::MergeResult) { if self.0(&left, &right) { - (None, Some(right), Either::Left(left)) + (Some(Either::Right(right)), Either::Left(left)) } else { - (Some(left), None, Either::Right(right)) + (Some(Either::Left(left)), Either::Right(right)) } } fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { @@ -175,11 +175,11 @@ impl bool> OrderingOrBool for F { fn right(right: T) -> Self::MergeResult { right } - fn merge(&mut self, left: T, right: T) -> (Option, Option, Self::MergeResult) { + fn merge(&mut self, left: T, right: T) -> (Option>, Self::MergeResult) { if self(&left, &right) { - (None, Some(right), left) + (Some(Either::Right(right)), left) } else { - (Some(left), None, right) + (Some(Either::Left(left)), right) } } fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { @@ -196,11 +196,11 @@ impl OrderingOrBool for MergeLte { fn right(right: T) -> Self::MergeResult { right } - fn merge(&mut self, left: T, right: T) -> (Option, Option, Self::MergeResult) { + fn merge(&mut self, left: T, right: T) -> (Option>, Self::MergeResult) { if left <= right { - (None, Some(right), left) + (Some(Either::Right(right)), left) } else { - (Some(left), None, right) + (Some(Either::Left(left)), right) } } fn size_hint(left: SizeHint, right: SizeHint) -> SizeHint { @@ -244,13 +244,17 @@ where (Some(left), None) => Some(F::left(left)), (None, Some(right)) => Some(F::right(right)), (Some(left), Some(right)) => { - let (left, right, next) = self.cmp_fn.merge(left, right); - if let Some(left) = left { - self.left.put_back(left); - } - if let Some(right) = right { - self.right.put_back(right); + let (not_next, next) = self.cmp_fn.merge(left, right); + match not_next { + Some(Either::Left(l)) => { + self.left.put_back(l); + } + Some(Either::Right(r)) => { + self.right.put_back(r); + } + None => (), } + Some(next) } } @@ -268,22 +272,21 @@ where loop { match (left, right) { (Some(l), Some(r)) => match self.cmp_fn.merge(l, r) { - (None, Some(r), x) => { + (Some(Either::Right(r)), x) => { acc = f(acc, x); left = self.left.next(); right = Some(r); } - (Some(l), None, x) => { + (Some(Either::Left(l)), x) => { acc = f(acc, x); left = Some(l); right = self.right.next(); } - (None, None, x) => { + (None, x) => { acc = f(acc, x); left = self.left.next(); right = self.right.next(); } - (Some(_), Some(_), _) => unreachable!(), }, (Some(l), None) => { self.left.put_back(l); @@ -319,12 +322,15 @@ where (Some(_left), None) => break self.left.nth(n).map(F::left), (None, Some(_right)) => break self.right.nth(n).map(F::right), (Some(left), Some(right)) => { - let (left, right, _) = self.cmp_fn.merge(left, right); - if let Some(left) = left { - self.left.put_back(left); - } - if let Some(right) = right { - self.right.put_back(right); + let (not_next, _) = self.cmp_fn.merge(left, right); + match not_next { + Some(Either::Left(l)) => { + self.left.put_back(l); + } + Some(Either::Right(r)) => { + self.right.put_back(r); + } + None => (), } } } From 21907bd7d209af8872170610f0f8c74df3b43320 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:11:26 +0200 Subject: [PATCH 561/633] `CombinationsWithReplacement::increment_indices` Steal how `next` increment indices, a bit improved. It will soon be used in `nth` (and more later). --- src/combinations_with_replacement.rs | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 4043678c1..9cb658169 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -46,6 +46,46 @@ where } } +impl CombinationsWithReplacement +where + I: Iterator, + I::Item: Clone, +{ + /// Increments indices representing the combination to advance to the next + /// (in lexicographic order by increasing sequence) combination. + /// + /// Returns true if we've run out of combinations, false otherwise. + fn increment_indices(&mut self) -> bool { + // Check if we need to consume more from the iterator + // This will run while we increment our first index digit + self.pool.get_next(); + + // Work out where we need to update our indices + let mut increment = None; + for (i, indices_int) in self.indices.iter().enumerate().rev() { + if *indices_int < self.pool.len() - 1 { + increment = Some((i, indices_int + 1)); + break; + } + } + match increment { + // If we can update the indices further + Some((increment_from, increment_value)) => { + // We need to update the rightmost non-max value + // and all those to the right + for i in &mut self.indices[increment_from..] { + *i = increment_value; + } + // TODO: once MSRV >= 1.50, use `fill` instead: + // self.indices[increment_from..].fill(increment_value); + false + } + // Otherwise, we're done + None => true, + } + } +} + impl Iterator for CombinationsWithReplacement where I: Iterator, From cd1ed154d419d660f67faec951deefddacde5fec Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:13:25 +0200 Subject: [PATCH 562/633] Update `CombinationsWithReplacement::next` (1) Use the new `increment_indices`. This is done in a different commit because the git difference was difficult to read. --- src/combinations_with_replacement.rs | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 9cb658169..1e2126c1a 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -92,6 +92,7 @@ where I::Item: Clone, { type Item = Vec; + fn next(&mut self) -> Option { // If this is the first iteration, return early if self.first { @@ -105,32 +106,11 @@ where }; } - // Check if we need to consume more from the iterator - // This will run while we increment our first index digit - self.pool.get_next(); - - // Work out where we need to update our indices - let mut increment: Option<(usize, usize)> = None; - for (i, indices_int) in self.indices.iter().enumerate().rev() { - if *indices_int < self.pool.len() - 1 { - increment = Some((i, indices_int + 1)); - break; - } + if self.increment_indices() { + return None; } - match increment { - // If we can update the indices further - Some((increment_from, increment_value)) => { - // We need to update the rightmost non-max value - // and all those to the right - for indices_index in increment_from..self.indices.len() { - self.indices[indices_index] = increment_value; - } - Some(self.pool.get_at(&self.indices)) - } - // Otherwise, we're done - None => None, - } + Some(self.pool.get_at(&self.indices)) } fn size_hint(&self) -> (usize, Option) { From cb066915a34b1baca3e2cf1ffa629c02c32e0911 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:15:10 +0200 Subject: [PATCH 563/633] Update `CombinationsWithReplacement::next` (2) Use `pool.get_at` once! That way, `next` and `nth` will ressemble each other. --- src/combinations_with_replacement.rs | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 1e2126c1a..7acfee898 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -94,22 +94,15 @@ where type Item = Vec; fn next(&mut self) -> Option { - // If this is the first iteration, return early if self.first { // In empty edge cases, stop iterating immediately - return if !(self.indices.is_empty() || self.pool.get_next()) { - None - // Otherwise, yield the initial state - } else { - self.first = false; - Some(self.pool.get_at(&self.indices)) - }; - } - - if self.increment_indices() { + if !(self.indices.is_empty() || self.pool.get_next()) { + return None; + } + self.first = false; + } else if self.increment_indices() { return None; } - Some(self.pool.get_at(&self.indices)) } From dd6a56932141c8b18b9d479eba30134c5afbfb88 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:15:56 +0200 Subject: [PATCH 564/633] Specialize `CombinationsWithReplacement::nth` Similar to `next` except we increment indices n times before generating the vector item. --- src/combinations_with_replacement.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index 7acfee898..f363f9ba2 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -106,6 +106,24 @@ where Some(self.pool.get_at(&self.indices)) } + fn nth(&mut self, n: usize) -> Option { + if self.first { + // In empty edge cases, stop iterating immediately + if !(self.indices.is_empty() || self.pool.get_next()) { + return None; + } + self.first = false; + } else if self.increment_indices() { + return None; + } + for _ in 0..n { + if self.increment_indices() { + return None; + } + } + Some(self.pool.get_at(&self.indices)) + } + fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); From d045e9d99732fd8273366629ff8856681931b230 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 2 May 2024 16:20:13 +0200 Subject: [PATCH 565/633] Fix warnings from clippy 1.78 --- benches/specializations.rs | 2 +- src/adaptors/multi_product.rs | 2 +- tests/specializations.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index b5230a06a..18039fc4e 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -1,4 +1,4 @@ -#![allow(unstable_name_collisions)] +#![allow(unstable_name_collisions, clippy::incompatible_msrv)] use criterion::black_box; use criterion::BenchmarkId; diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs index d551c0afc..314d4a46e 100644 --- a/src/adaptors/multi_product.rs +++ b/src/adaptors/multi_product.rs @@ -128,7 +128,7 @@ where // This cartesian product had at most one item to generate and now ends. self.0 = ProductEnded; } else { - inner.cur = next.clone(); + inner.cur.clone_from(&next); } next } diff --git a/tests/specializations.rs b/tests/specializations.rs index 949b2a7de..cb2141053 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -466,7 +466,7 @@ quickcheck! { helper(v.iter().copied()); helper(v.iter().copied().filter(Result::is_ok)); - fn helper(it: impl Iterator> + DoubleEndedIterator + Clone) { + fn helper(it: impl DoubleEndedIterator> + Clone) { macro_rules! check_results_specialized { ($src:expr, |$it:pat| $closure:expr) => { assert_eq!( From ce2701e091f14e7e59351e552c442381dab33c78 Mon Sep 17 00:00:00 2001 From: Kinto Date: Wed, 1 May 2024 21:26:23 +1000 Subject: [PATCH 566/633] Implement `FlattenOk::fold` --- src/flatten_ok.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 853122295..257ef70ae 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -72,6 +72,29 @@ where } } + fn fold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + // Front + let mut acc = match self.inner_front { + Some(x) => x.fold(init, |a, o| f(a, Ok(o))), + None => init, + }; + + acc = self.iter.fold(acc, |acc, x| match x { + Ok(it) => it.into_iter().fold(acc, |a, o| f(a, Ok(o))), + Err(e) => f(acc, Err(e)), + }); + + // Back + match self.inner_back { + Some(x) => x.fold(acc, |a, o| f(a, Ok(o))), + None => acc, + } + } + fn size_hint(&self) -> (usize, Option) { let inner_hint = |inner: &Option| { inner From b80c578f8f981a80b140adc84593f1a631fc5ba7 Mon Sep 17 00:00:00 2001 From: Kinto Date: Wed, 1 May 2024 21:26:35 +1000 Subject: [PATCH 567/633] Implement `FlattenOk::rfold` --- src/flatten_ok.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/flatten_ok.rs b/src/flatten_ok.rs index 257ef70ae..48f1e90a6 100644 --- a/src/flatten_ok.rs +++ b/src/flatten_ok.rs @@ -153,6 +153,29 @@ where } } } + + fn rfold(self, init: B, mut f: F) -> B + where + Self: Sized, + F: FnMut(B, Self::Item) -> B, + { + // Back + let mut acc = match self.inner_back { + Some(x) => x.rfold(init, |a, o| f(a, Ok(o))), + None => init, + }; + + acc = self.iter.rfold(acc, |acc, x| match x { + Ok(it) => it.into_iter().rfold(acc, |a, o| f(a, Ok(o))), + Err(e) => f(acc, Err(e)), + }); + + // Front + match self.inner_front { + Some(x) => x.rfold(acc, |a, o| f(a, Ok(o))), + None => acc, + } + } } impl Clone for FlattenOk From e53f6352081533c621415507447484cd8076d556 Mon Sep 17 00:00:00 2001 From: Kinto Date: Sun, 5 May 2024 00:22:47 +1000 Subject: [PATCH 568/633] Implement two-element 'vector' for `flatten_ok` test Previously the test used Option but the coverage was bad. We cannot use Vec because it is too slow. --- tests/specializations.rs | 64 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/tests/specializations.rs b/tests/specializations.rs index cb2141053..712311472 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -1,7 +1,9 @@ #![allow(unstable_name_collisions)] use itertools::Itertools; +use quickcheck::Arbitrary; use quickcheck::{quickcheck, TestResult}; +use rand::Rng; use std::fmt::Debug; struct Unspecialized(I); @@ -452,8 +454,8 @@ quickcheck! { test_specializations(&v.into_iter().filter_map_ok(|i| if i < 20 { Some(i * 2) } else { None })); } - // `Option` because `Vec` would be very slow!! And we can't give `[u8; 3]`. - fn flatten_ok(v: Vec, char>>) -> () { + // `SmallIter2` because `Vec` is too slow and we get bad coverage from a singleton like Option + fn flatten_ok(v: Vec, char>>) -> () { let it = v.into_iter().flatten_ok(); test_specializations(&it); test_double_ended_specializations(&it); @@ -520,3 +522,61 @@ quickcheck! { } } } + +/// Like `VecIntoIter` with maximum 2 elements. +#[derive(Debug, Clone, Default)] +enum SmallIter2 { + #[default] + Zero, + One(T), + Two(T, T), +} + +impl Arbitrary for SmallIter2 { + fn arbitrary(g: &mut G) -> Self { + match g.gen_range(0u8, 3) { + 0 => Self::Zero, + 1 => Self::One(T::arbitrary(g)), + 2 => Self::Two(T::arbitrary(g), T::arbitrary(g)), + _ => unreachable!(), + } + } + // maybe implement shrink too, maybe not +} + +impl Iterator for SmallIter2 { + type Item = T; + + fn next(&mut self) -> Option { + match std::mem::take(self) { + Self::Zero => None, + Self::One(val) => Some(val), + Self::Two(val, second) => { + *self = Self::One(second); + Some(val) + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let len = match self { + Self::Zero => 0, + Self::One(_) => 1, + Self::Two(_, _) => 2, + }; + (len, Some(len)) + } +} + +impl DoubleEndedIterator for SmallIter2 { + fn next_back(&mut self) -> Option { + match std::mem::take(self) { + Self::Zero => None, + Self::One(val) => Some(val), + Self::Two(first, val) => { + *self = Self::One(first); + Some(val) + } + } + } +} From 732208f3e51ee3ccc212cb70f36c6f93feba6bfd Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:18:25 +0200 Subject: [PATCH 569/633] New private method `Combinations::try_nth` --- src/combinations.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index e254fee7e..6bb2f3ec6 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -143,6 +143,27 @@ impl Combinations { // If we've made it this far, we haven't run out of combos false } + + /// Returns the n-th item or the number of successful steps. + pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> + where + I::Item: Clone, + { + let done = if self.first { + self.init() + } else { + self.increment_indices() + }; + if done { + return Err(0); + } + for i in 0..n { + if self.increment_indices() { + return Err(i + 1); + } + } + Ok(self.pool.get_at(&self.indices)) + } } impl Iterator for Combinations @@ -166,23 +187,7 @@ where } fn nth(&mut self, n: usize) -> Option { - let done = if self.first { - self.init() - } else { - self.increment_indices() - }; - - if done { - return None; - } - - for _ in 0..n { - if self.increment_indices() { - return None; - } - } - - Some(self.pool.get_at(&self.indices)) + self.try_nth(n).ok() } fn size_hint(&self) -> (usize, Option) { From 54429a0c866ab3c58ff3b9449603baa3396993c7 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:19:25 +0200 Subject: [PATCH 570/633] New private method `Powerset::increment_k` --- src/powerset.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/powerset.rs b/src/powerset.rs index 1b2b38540..019e2711f 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -42,6 +42,18 @@ where } } +impl Powerset { + /// Returns true if `k` has been incremented, false otherwise. + fn increment_k(&mut self) -> bool { + if self.combs.k() < self.combs.n() || self.combs.k() == 0 { + self.combs.reset(self.combs.k() + 1); + true + } else { + false + } + } +} + impl Iterator for Powerset where I: Iterator, @@ -52,8 +64,7 @@ where fn next(&mut self) -> Option { if let Some(elt) = self.combs.next() { Some(elt) - } else if self.combs.k() < self.combs.n() || self.combs.k() == 0 { - self.combs.reset(self.combs.k() + 1); + } else if self.increment_k() { self.combs.next() } else { None From 0f3cbcc5a5823cf046ea67735827b77911b01369 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 26 Apr 2024 10:19:57 +0200 Subject: [PATCH 571/633] Specialize `Powerset::nth` --- src/powerset.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/powerset.rs b/src/powerset.rs index 019e2711f..734eaf614 100644 --- a/src/powerset.rs +++ b/src/powerset.rs @@ -71,6 +71,20 @@ where } } + fn nth(&mut self, mut n: usize) -> Option { + loop { + match self.combs.try_nth(n) { + Ok(item) => return Some(item), + Err(steps) => { + if !self.increment_k() { + return None; + } + n -= steps; + } + } + } + } + fn size_hint(&self) -> SizeHint { let k = self.combs.k(); // Total bounds for source iterator. From 0fcc6d190f16601352e901173fd429eccbe9556b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 3 May 2024 14:52:35 +0200 Subject: [PATCH 572/633] Implement `Debug` for `FormatWith`, same as `Display` The type `std::fmt::Arguments` (returned by `format_args!`) similarly delegates `Debug` to `Display`. --- src/format.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/format.rs b/src/format.rs index c4cb65dcb..83382c5a5 100644 --- a/src/format.rs +++ b/src/format.rs @@ -71,6 +71,16 @@ where } } +impl<'a, I, F> fmt::Debug for FormatWith<'a, I, F> +where + I: Iterator, + F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + impl<'a, I> Format<'a, I> where I: Iterator, From 5a8ab88449d53d7674e22d2a736246bb5993a28f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 09:20:55 +0200 Subject: [PATCH 573/633] Fix `clippy::doc_markdown` lint Only two changes are in public documentation. --- src/format.rs | 4 ++-- src/groupbylazy.rs | 5 +++-- src/grouping_map.rs | 2 +- src/ziptuple.rs | 5 ++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/format.rs b/src/format.rs index 83382c5a5..15cee34d6 100644 --- a/src/format.rs +++ b/src/format.rs @@ -9,7 +9,7 @@ use std::fmt; /// See [`.format_with()`](crate::Itertools::format_with) for more information. pub struct FormatWith<'a, I, F> { sep: &'a str, - /// FormatWith uses interior mutability because Display::fmt takes &self. + /// `FormatWith` uses interior mutability because `Display::fmt` takes `&self`. inner: Cell>, } @@ -22,7 +22,7 @@ pub struct FormatWith<'a, I, F> { /// for more information. pub struct Format<'a, I> { sep: &'a str, - /// Format uses interior mutability because Display::fmt takes &self. + /// `Format` uses interior mutability because `Display::fmt` takes `&self`. inner: Cell>, } diff --git a/src/groupbylazy.rs b/src/groupbylazy.rs index 007fea6f6..5847c8f7d 100644 --- a/src/groupbylazy.rs +++ b/src/groupbylazy.rs @@ -66,12 +66,13 @@ where /// Least index for which we still have elements buffered oldest_buffered_group: usize, /// Group index for `buffer[0]` -- the slots - /// bottom_group..oldest_buffered_group are unused and will be erased when + /// `bottom_group..oldest_buffered_group` are unused and will be erased when /// that range is large enough. bottom_group: usize, /// Buffered groups, from `bottom_group` (index 0) to `top_group`. buffer: Vec>, - /// index of last group iter that was dropped, usize::MAX == none + /// index of last group iter that was dropped, + /// `usize::MAX` initially when no group was dropped dropped_group: usize, } diff --git a/src/grouping_map.rs b/src/grouping_map.rs index f910e5fd3..b4aae9ecf 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -450,7 +450,7 @@ where /// If several elements are equally maximum, the last element is picked. /// If several elements are equally minimum, the first element is picked. /// - /// See [.minmax()](crate::Itertools::minmax) for the non-grouping version. + /// See [`Itertools::minmax`](crate::Itertools::minmax) for the non-grouping version. /// /// Differences from the non grouping version: /// - It never produces a `MinMaxResult::NoElements` diff --git a/src/ziptuple.rs b/src/ziptuple.rs index 03bf1ad65..3ada0296c 100644 --- a/src/ziptuple.rs +++ b/src/ziptuple.rs @@ -7,7 +7,7 @@ pub struct Zip { t: T, } -/// An iterator that generalizes *.zip()* and allows running multiple iterators in lockstep. +/// An iterator that generalizes `.zip()` and allows running multiple iterators in lockstep. /// /// The iterator `Zip<(I, J, ..., M)>` is formed from a tuple of iterators (or values that /// implement [`IntoIterator`]) and yields elements @@ -20,7 +20,7 @@ pub struct Zip { /// ..)>` of each component iterator `I, J, ...`) if each component iterator is /// nameable. /// -/// Prefer [`izip!()`] over `multizip` for the performance benefits of using the +/// Prefer [`izip!()`](crate::izip) over `multizip` for the performance benefits of using the /// standard library `.zip()`. Prefer `multizip` if a nameable type is needed. /// /// ``` @@ -36,7 +36,6 @@ pub struct Zip { /// /// assert_eq!(results, [0 + 3, 10 + 7, 29, 36]); /// ``` -/// [`izip!()`]: crate::izip pub fn multizip(t: U) -> Zip where Zip: From + Iterator, From 623a0b44b15eef9b11d956c78da98e5da07b536e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 14:36:39 +0200 Subject: [PATCH 574/633] Fix doc: backticks, links, empty lines --- src/adaptors/mod.rs | 4 ++-- src/free.rs | 5 +++-- src/lib.rs | 12 ++++++------ src/peek_nth.rs | 8 ++++---- src/peeking_take_while.rs | 6 +++--- src/put_back_n_impl.rs | 3 ++- 6 files changed, 20 insertions(+), 18 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 57b604908..22f7aa363 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -33,7 +33,7 @@ pub struct Interleave { /// Create an iterator that interleaves elements in `i` and `j`. /// -/// [`IntoIterator`] enabled version of `[Itertools::interleave]`. +/// [`IntoIterator`] enabled version of [`Itertools::interleave`](crate::Itertools::interleave). pub fn interleave( i: I, j: J, @@ -471,7 +471,7 @@ where /// A “meta iterator adaptor”. Its closure receives a reference to the iterator /// and may pick off as many elements as it likes, to produce the next iterator element. /// -/// Iterator element type is *X*, if the return type of `F` is *Option\*. +/// Iterator element type is `X` if the return type of `F` is `Option`. /// /// See [`.batching()`](crate::Itertools::batching) for more information. #[derive(Clone)] diff --git a/src/free.rs b/src/free.rs index 617a1977d..8d0bcf3ea 100644 --- a/src/free.rs +++ b/src/free.rs @@ -157,7 +157,7 @@ where i.into_iter().chain(j) } -/// Create an iterator that clones each element from &T to T +/// Create an iterator that clones each element from `&T` to `T`. /// /// [`IntoIterator`] enabled version of [`Iterator::cloned`]. /// @@ -259,7 +259,7 @@ where iterable.into_iter().min() } -/// Combine all iterator elements into one String, separated by `sep`. +/// Combine all iterator elements into one `String`, separated by `sep`. /// /// [`IntoIterator`] enabled version of [`Itertools::join`]. /// @@ -298,6 +298,7 @@ where /// Sort all iterator elements into a new iterator in ascending order. /// This sort is unstable (i.e., may reorder equal elements). +/// /// [`IntoIterator`] enabled version of [`Itertools::sorted_unstable`]. /// /// ``` diff --git a/src/lib.rs b/src/lib.rs index ff14cf94e..e1e0ebf87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1609,7 +1609,7 @@ pub trait Itertools: Iterator { /// Return an iterator adaptor that iterates over the `k`-length combinations of /// the elements from an iterator. /// - /// Iterator element type is `Vec`. The iterator produces a new Vec per iteration, + /// Iterator element type is `Vec`. The iterator produces a new `Vec` per iteration, /// and clones the iterator elements. /// /// ``` @@ -1652,7 +1652,7 @@ pub trait Itertools: Iterator { /// Return an iterator that iterates over the `k`-length combinations of /// the elements from an iterator, with replacement. /// - /// Iterator element type is `Vec`. The iterator produces a new Vec per iteration, + /// Iterator element type is `Vec`. The iterator produces a new `Vec` per iteration, /// and clones the iterator elements. /// /// ``` @@ -1681,7 +1681,7 @@ pub trait Itertools: Iterator { /// elements from an iterator. /// /// Iterator element type is `Vec` with length `k`. The iterator - /// produces a new Vec per iteration, and clones the iterator elements. + /// produces a new `Vec` per iteration, and clones the iterator elements. /// /// If `k` is greater than the length of the input iterator, the resultant /// iterator adaptor will be empty. @@ -2096,7 +2096,7 @@ pub trait Itertools: Iterator { /// Consume the first `n` elements from the iterator eagerly, /// and return the same iterator again. /// - /// It works similarly to *.skip(* `n` *)* except it is eager and + /// It works similarly to `.skip(n)` except it is eager and /// preserves the iterator type. /// /// ``` @@ -2230,7 +2230,7 @@ pub trait Itertools: Iterator { .count() } - /// Combine all iterator elements into one String, separated by `sep`. + /// Combine all iterator elements into one `String`, separated by `sep`. /// /// Use the `Display` implementation of each element. /// @@ -4008,7 +4008,7 @@ pub trait Itertools: Iterator { } } - /// If the iterator yields no elements, Ok(None) will be returned. If the iterator yields + /// If the iterator yields no elements, `Ok(None)` will be returned. If the iterator yields /// exactly one element, that element will be returned, otherwise an error will be returned /// containing an iterator that has the same output as the input iterator. /// diff --git a/src/peek_nth.rs b/src/peek_nth.rs index ff5a55cc0..b03a3ef5f 100644 --- a/src/peek_nth.rs +++ b/src/peek_nth.rs @@ -35,12 +35,12 @@ impl PeekNth where I: Iterator, { - /// Works exactly like the `peek` method in `std::iter::Peekable` + /// Works exactly like the `peek` method in [`std::iter::Peekable`]. pub fn peek(&mut self) -> Option<&I::Item> { self.peek_nth(0) } - /// Works exactly like the `peek_mut` method in `std::iter::Peekable` + /// Works exactly like the `peek_mut` method in [`std::iter::Peekable`]. pub fn peek_mut(&mut self) -> Option<&mut I::Item> { self.peek_nth_mut(0) } @@ -117,7 +117,7 @@ where self.buf.get_mut(n) } - /// Works exactly like the `next_if` method in `std::iter::Peekable` + /// Works exactly like the `next_if` method in [`std::iter::Peekable`]. pub fn next_if(&mut self, func: impl FnOnce(&I::Item) -> bool) -> Option { match self.next() { Some(item) if func(&item) => Some(item), @@ -129,7 +129,7 @@ where } } - /// Works exactly like the `next_if_eq` method in `std::iter::Peekable` + /// Works exactly like the `next_if_eq` method in [`std::iter::Peekable`]. pub fn next_if_eq(&mut self, expected: &T) -> Option where T: ?Sized, diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index b224907aa..19872a964 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -11,11 +11,11 @@ use std::iter::Peekable; /// /// This is implemented by peeking adaptors like peekable and put back, /// but also by a few iterators that can be peeked natively, like the slice’s -/// by reference iterator (`std::slice::Iter`). +/// by reference iterator ([`std::slice::Iter`]). pub trait PeekingNext: Iterator { /// Pass a reference to the next iterator element to the closure `accept`; - /// if `accept` returns true, return it as the next element, - /// else None. + /// if `accept` returns `true`, return it as the next element, + /// else `None`. fn peeking_next(&mut self, accept: F) -> Option where Self: Sized, diff --git a/src/put_back_n_impl.rs b/src/put_back_n_impl.rs index 40b55e5f6..a9eb4179c 100644 --- a/src/put_back_n_impl.rs +++ b/src/put_back_n_impl.rs @@ -28,7 +28,8 @@ where } impl PutBackN { - /// Puts x in front of the iterator. + /// Puts `x` in front of the iterator. + /// /// The values are yielded in order of the most recently put back /// values first. /// From 6cc4281868165d05c64c78b822ff34edf6185d9f Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 14:29:22 +0200 Subject: [PATCH 575/633] Structs `ZipEq` & `ZipLongest` had the same short doc --- src/zip_eq_impl.rs | 1 + src/zip_longest.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 09bc6988c..62d7d5832 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -1,6 +1,7 @@ use super::size_hint; /// An iterator which iterates two other iterators simultaneously +/// and panic if they have different lengths. /// /// See [`.zip_eq()`](crate::Itertools::zip_eq) for more information. #[derive(Clone, Debug)] diff --git a/src/zip_longest.rs b/src/zip_longest.rs index 6eb4bfbff..d4eb9a882 100644 --- a/src/zip_longest.rs +++ b/src/zip_longest.rs @@ -8,6 +8,7 @@ use crate::either_or_both::EitherOrBoth; // and dedicated to itertools https://github.com/rust-lang/rust/pull/19283 /// An iterator which iterates two other iterators simultaneously +/// and wraps the elements in [`EitherOrBoth`]. /// /// This iterator is *fused*. /// From 163fd5199480bbb6397d029c7c5274b59290e235 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 14:59:51 +0200 Subject: [PATCH 576/633] `zip_eq`: clearer short doc --- src/zip_eq_impl.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 62d7d5832..6d3b68296 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -11,9 +11,7 @@ pub struct ZipEq { b: J, } -/// Iterate `i` and `j` in lock step. -/// -/// **Panics** if the iterators are not of the same length. +/// Zips two iterators but **panics** if they are not of the same length. /// /// [`IntoIterator`] enabled version of [`Itertools::zip_eq`](crate::Itertools::zip_eq). /// From af6b8eb616fa4d712ca553577a2b947928dd7922 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 14:48:11 +0200 Subject: [PATCH 577/633] `assert_equal`: fix the doc of the panic message --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index e1e0ebf87..0c8633aef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4206,7 +4206,7 @@ where /// semantics as [`equal(a, b)`](equal). /// /// **Panics** on assertion failure with a message that shows the -/// two iteration elements. +/// two different elements and the iteration index. /// /// ```should_panic /// # use itertools::assert_equal; From a4de4dc6a2aad7fe85ad2bfcf99f5f9e0cfeb68b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 15:27:36 +0200 Subject: [PATCH 578/633] `multiunzip`: wrong name in doc --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0c8633aef..489c0aa65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4131,7 +4131,7 @@ pub trait Itertools: Iterator { /// Converts an iterator of tuples into a tuple of containers. /// - /// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each + /// It consumes an entire iterator of n-ary tuples, producing `n` collections, one for each /// column. /// /// This function is, in some sense, the opposite of [`multizip`]. From 49ad0272e3fe897e7bef2e7e9a297edab58cf424 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 15:09:53 +0200 Subject: [PATCH 579/633] Move guarantees before doctests More visible that way. --- src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 489c0aa65..5a1248d5a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1565,6 +1565,11 @@ pub trait Itertools: Iterator { /// Iterator element can be any homogeneous tuple of type `Self::Item` with /// size up to 12. /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. + /// /// ``` /// use itertools::Itertools; /// @@ -1592,11 +1597,6 @@ pub trait Itertools: Iterator { /// let it: TupleCombinations, (u32, u32, u32)> = (1..5).tuple_combinations(); /// itertools::assert_equal(it, vec![(1, 2, 3), (1, 2, 4), (1, 3, 4), (2, 3, 4)]); /// ``` - /// - /// # Guarantees - /// - /// If the adapted iterator is deterministic, - /// this iterator adapter yields items in a reliable order. fn tuple_combinations(self) -> TupleCombinations where Self: Sized + Clone, @@ -1612,6 +1612,11 @@ pub trait Itertools: Iterator { /// Iterator element type is `Vec`. The iterator produces a new `Vec` per iteration, /// and clones the iterator elements. /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. + /// /// ``` /// use itertools::Itertools; /// @@ -1635,11 +1640,6 @@ pub trait Itertools: Iterator { /// vec![2, 2], /// ]); /// ``` - /// - /// # Guarantees - /// - /// If the adapted iterator is deterministic, - /// this iterator adapter yields items in a reliable order. #[cfg(feature = "use_alloc")] fn combinations(self, k: usize) -> Combinations where From d1ce07db021a95a217e8b84146d9d623d50d56ff Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 13 May 2024 16:49:16 +0200 Subject: [PATCH 580/633] Deprecated for the std: link to the documentation. Strictly similar to what is done for our free function `zip`. --- src/lib.rs | 5 ++++- src/sources.rs | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5a1248d5a..931a28f92 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2426,7 +2426,10 @@ pub trait Itertools: Iterator { /// assert_eq!((0..10).fold1(|x, y| x + y).unwrap_or(0), 45); /// assert_eq!((0..0).fold1(|x, y| x * y), None); /// ``` - #[deprecated(since = "0.10.2", note = "Use `Iterator::reduce` instead")] + #[deprecated( + note = "Use [`Iterator::reduce`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.reduce) instead", + since = "0.10.2" + )] fn fold1(mut self, f: F) -> Option where F: FnMut(Self::Item, Self::Item) -> Self::Item, diff --git a/src/sources.rs b/src/sources.rs index 425324fac..c405ffdc7 100644 --- a/src/sources.rs +++ b/src/sources.rs @@ -41,7 +41,10 @@ use std::mem; /// vec![1, 1, 2, 3, 5, 8, 13, 21]); /// assert_eq!(fibonacci.last(), Some(2_971_215_073)) /// ``` -#[deprecated(note = "Use std from_fn() instead", since = "0.13.0")] +#[deprecated( + note = "Use [std::iter::from_fn](https://doc.rust-lang.org/std/iter/fn.from_fn.html) instead", + since = "0.13.0" +)] pub fn unfold(initial_state: St, f: F) -> Unfold where F: FnMut(&mut St) -> Option, @@ -62,7 +65,10 @@ where /// See [`unfold`](crate::unfold) for more information. #[derive(Clone)] #[must_use = "iterators are lazy and do nothing unless consumed"] -#[deprecated(note = "Use std from_fn() instead", since = "0.13.0")] +#[deprecated( + note = "Use [std::iter::FromFn](https://doc.rust-lang.org/std/iter/struct.FromFn.html) instead", + since = "0.13.0" +)] pub struct Unfold { f: F, /// Internal state that will be passed to the closure on the next iteration From 352851c5941f139845c8c30e0aa7a7bee34fc575 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:33:38 +0100 Subject: [PATCH 581/633] Add `Itertools::get` with `IteratorIndex` Co-Authored-By: wod3 <17980899+wod3@users.noreply.github.com> --- src/iter_index.rs | 126 ++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 50 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 src/iter_index.rs diff --git a/src/iter_index.rs b/src/iter_index.rs new file mode 100644 index 000000000..204dd6297 --- /dev/null +++ b/src/iter_index.rs @@ -0,0 +1,126 @@ +use core::iter::{Skip, Take}; +use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; + +use crate::Itertools; + +mod private_iter_index { + use core::ops; + + pub trait Sealed {} + + impl Sealed for ops::Range {} + impl Sealed for ops::RangeInclusive {} + impl Sealed for ops::RangeTo {} + impl Sealed for ops::RangeToInclusive {} + impl Sealed for ops::RangeFrom {} + impl Sealed for ops::RangeFull {} + impl Sealed for usize {} +} + +/// Used by the ``range`` function to know which iterator +/// to turn different ranges into. +pub trait IteratorIndex: private_iter_index::Sealed +where + I: Iterator, +{ + /// The type that [`get`] or [`Itertools::get`] + /// returns when called with this type of index. + type Output: Iterator; + + /// Returns an iterator(or value) in the specified range. + /// + /// Prefer calling [`get`] or [`Itertools::get`] instead + /// of calling this directly. + fn index(self, from: I) -> Self::Output; +} + +impl IteratorIndex for Range +where + I: Iterator, +{ + type Output = Take>; + + fn index(self, iter: I) -> Self::Output { + iter.skip(self.start) + .take(self.end.saturating_sub(self.start)) + } +} + +impl IteratorIndex for RangeInclusive +where + I: Iterator, +{ + type Output = Take>; + + fn index(self, iter: I) -> Self::Output { + iter.skip(*self.start()) + .take((1 + *self.end()).saturating_sub(*self.start())) + } +} + +impl IteratorIndex for RangeTo +where + I: Iterator, +{ + type Output = Take; + + fn index(self, iter: I) -> Self::Output { + iter.take(self.end) + } +} + +impl IteratorIndex for RangeToInclusive +where + I: Iterator, +{ + type Output = Take; + + fn index(self, iter: I) -> Self::Output { + iter.take(self.end + 1) + } +} + +impl IteratorIndex for RangeFrom +where + I: Iterator, +{ + type Output = Skip; + + fn index(self, iter: I) -> Self::Output { + iter.skip(self.start) + } +} + +impl IteratorIndex for RangeFull +where + I: Iterator, +{ + type Output = I; + + fn index(self, iter: I) -> Self::Output { + iter + } +} + +impl IteratorIndex for usize +where + I: Iterator, +{ + type Output = as IntoIterator>::IntoIter; + + fn index(self, mut iter: I) -> Self::Output { + iter.nth(self).into_iter() + } +} + +/// Returns an element of the iterator or an iterator +/// over a subsection of the iterator. +/// +/// See [`Itertools::get`] for more information. +pub fn get(iter: I, index: R) -> R::Output +where + I: IntoIterator, + R: IteratorIndex, +{ + index.index(iter.into_iter()) +} diff --git a/src/lib.rs b/src/lib.rs index 931a28f92..1dac2f938 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -146,6 +146,7 @@ pub mod structs { /// Traits helpful for using certain `Itertools` methods in generic contexts. pub mod traits { + pub use crate::iter_index::IteratorIndex; pub use crate::tuple_impl::HomogeneousTuple; } @@ -153,6 +154,7 @@ pub use crate::concat_impl::concat; pub use crate::cons_tuples_impl::cons_tuples; pub use crate::diff::diff_with; pub use crate::diff::Diff; +pub use crate::iter_index::get; #[cfg(feature = "use_alloc")] pub use crate::kmerge_impl::kmerge_by; pub use crate::minmax::MinMaxResult; @@ -194,6 +196,7 @@ mod groupbylazy; #[cfg(feature = "use_std")] mod grouping_map; mod intersperse; +mod iter_index; #[cfg(feature = "use_alloc")] mod k_smallest; #[cfg(feature = "use_alloc")] @@ -504,6 +507,53 @@ pub trait Itertools: Iterator { intersperse::intersperse_with(self, element) } + /// Returns an element at a specific location, or returns an iterator + /// over a subsection of the iterator. + /// + /// Works similarly to [`slice::get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get). + /// + /// It's a generalisation of [`take`], [`skip`] and [`nth`], and uses these + /// under the hood. + /// + /// # Examples + /// + /// ``` + /// use itertools::Itertools; + /// + /// let vec = vec![3, 1, 4, 1, 5]; + /// + /// let mut range: Vec<_> = + /// vec.iter().get(1..=3).copied().collect(); + /// assert_eq!(&range, &[1, 4, 1]); + /// + /// // It works with other types of ranges, too + /// range = vec.iter().get(..2).copied().collect(); + /// assert_eq!(&range, &[3, 1]); + /// + /// range = vec.iter().get(0..1).copied().collect(); + /// assert_eq!(&range, &[3]); + /// + /// range = vec.iter().get(2..).copied().collect(); + /// assert_eq!(&range, &[4, 1, 5]); + /// + /// range = vec.iter().get(..).copied().collect(); + /// assert_eq!(range, vec); + /// + /// range = vec.iter().get(3).copied().collect(); + /// assert_eq!(&range, &[1]); + /// ``` + /// + /// [`take`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.take + /// [`skip`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.skip + /// [`nth`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.nth + fn get(self, index: R) -> R::Output + where + Self: Sized, + R: iter_index::IteratorIndex, + { + iter_index::get(self, index) + } + /// Create an iterator which iterates over both this and the specified /// iterator simultaneously, yielding pairs of two optional elements. /// From 24f31de17a1ba865b35b8ba585e92400679ab573 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:37:20 +0100 Subject: [PATCH 582/633] Suggestions Co-Authored-By: TrolledWoods <26680270+trolledwoods@users.noreply.github.com> --- src/iter_index.rs | 13 +------------ src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index 204dd6297..266c5ed53 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -14,7 +14,6 @@ mod private_iter_index { impl Sealed for ops::RangeToInclusive {} impl Sealed for ops::RangeFrom {} impl Sealed for ops::RangeFull {} - impl Sealed for usize {} } /// Used by the ``range`` function to know which iterator @@ -53,6 +52,7 @@ where type Output = Take>; fn index(self, iter: I) -> Self::Output { + debug_assert!(!self.is_empty(), "The given `RangeInclusive` is exhausted. The result of indexing with an exhausted `RangeInclusive` is unspecified."); iter.skip(*self.start()) .take((1 + *self.end()).saturating_sub(*self.start())) } @@ -102,17 +102,6 @@ where } } -impl IteratorIndex for usize -where - I: Iterator, -{ - type Output = as IntoIterator>::IntoIter; - - fn index(self, mut iter: I) -> Self::Output { - iter.nth(self).into_iter() - } -} - /// Returns an element of the iterator or an iterator /// over a subsection of the iterator. /// diff --git a/src/lib.rs b/src/lib.rs index 1dac2f938..d947533b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -515,6 +515,9 @@ pub trait Itertools: Iterator { /// It's a generalisation of [`take`], [`skip`] and [`nth`], and uses these /// under the hood. /// + /// # Unspecified Behavior + /// The result of indexing with an exhausted [`core::ops::RangeInclusive`] is unspecified. + /// /// # Examples /// /// ``` @@ -538,9 +541,6 @@ pub trait Itertools: Iterator { /// /// range = vec.iter().get(..).copied().collect(); /// assert_eq!(range, vec); - /// - /// range = vec.iter().get(3).copied().collect(); - /// assert_eq!(&range, &[1]); /// ``` /// /// [`take`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.take From 38ec02068bdee45eaab7c4f61cd4d1e81a14c615 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 1 Mar 2024 09:43:02 +0100 Subject: [PATCH 583/633] Small fixes Fix my own review. Update docs. Fix a warning with `#[cfg(doc)]`. Use public path `traits::IteratorIndex` instead of private path `iter_index::IteratorIndex`. --- src/iter_index.rs | 11 +++++------ src/lib.rs | 20 ++++++++++---------- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index 266c5ed53..be55457ae 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -1,6 +1,7 @@ use core::iter::{Skip, Take}; use core::ops::{Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, RangeToInclusive}; +#[cfg(doc)] use crate::Itertools; mod private_iter_index { @@ -16,17 +17,16 @@ mod private_iter_index { impl Sealed for ops::RangeFull {} } -/// Used by the ``range`` function to know which iterator +/// Used by [`get`] and [`Itertools::get`] to know which iterator /// to turn different ranges into. pub trait IteratorIndex: private_iter_index::Sealed where I: Iterator, { - /// The type that [`get`] or [`Itertools::get`] - /// returns when called with this type of index. + /// The type returned for this type of index. type Output: Iterator; - /// Returns an iterator(or value) in the specified range. + /// Returns an adapted iterator for the current index. /// /// Prefer calling [`get`] or [`Itertools::get`] instead /// of calling this directly. @@ -102,8 +102,7 @@ where } } -/// Returns an element of the iterator or an iterator -/// over a subsection of the iterator. +/// Returns an iterator over a subsection of the iterator. /// /// See [`Itertools::get`] for more information. pub fn get(iter: I, index: R) -> R::Output diff --git a/src/lib.rs b/src/lib.rs index d947533b9..80e1c47f4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -507,13 +507,14 @@ pub trait Itertools: Iterator { intersperse::intersperse_with(self, element) } - /// Returns an element at a specific location, or returns an iterator - /// over a subsection of the iterator. + /// Returns an iterator over a subsection of the iterator. /// /// Works similarly to [`slice::get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get). /// - /// It's a generalisation of [`take`], [`skip`] and [`nth`], and uses these - /// under the hood. + /// It's a generalisation of [`Iterator::take`] and [`Iterator::skip`], + /// and uses these under the hood. + /// Therefore, the resulting iterator is [`DoubleEndedIterator`] + /// and/or [`ExactSizeIterator`] if the adapted iterator is. /// /// # Unspecified Behavior /// The result of indexing with an exhausted [`core::ops::RangeInclusive`] is unspecified. @@ -526,7 +527,7 @@ pub trait Itertools: Iterator { /// let vec = vec![3, 1, 4, 1, 5]; /// /// let mut range: Vec<_> = - /// vec.iter().get(1..=3).copied().collect(); + /// vec.iter().get(1..=3).copied().collect(); /// assert_eq!(&range, &[1, 4, 1]); /// /// // It works with other types of ranges, too @@ -539,17 +540,16 @@ pub trait Itertools: Iterator { /// range = vec.iter().get(2..).copied().collect(); /// assert_eq!(&range, &[4, 1, 5]); /// + /// range = vec.iter().get(..=2).copied().collect(); + /// assert_eq!(&range, &[3, 1, 4]); + /// /// range = vec.iter().get(..).copied().collect(); /// assert_eq!(range, vec); /// ``` - /// - /// [`take`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.take - /// [`skip`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.skip - /// [`nth`]: https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.nth fn get(self, index: R) -> R::Output where Self: Sized, - R: iter_index::IteratorIndex, + R: traits::IteratorIndex, { iter_index::get(self, index) } From 734742ce350988271d9264204b8347f82ce92cd5 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:24:41 +0100 Subject: [PATCH 584/633] Test `Itertools::get` laziness As we should, I checked it can't pass without `let _ =`. --- tests/laziness.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/laziness.rs b/tests/laziness.rs index 2ce29cb8a..f936decde 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -63,6 +63,14 @@ must_use_tests! { intersperse_with { let _ = Panicking.intersperse_with(|| 0); } + get { + let _ = Panicking.get(1..4); + let _ = Panicking.get(1..=4); + let _ = Panicking.get(1..); + let _ = Panicking.get(..4); + let _ = Panicking.get(..=4); + let _ = Panicking.get(..); + } zip_longest { let _ = Panicking.zip_longest(Panicking); } From f676f2f96451220c827c62f714d79ce6454d0184 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:30:58 +0100 Subject: [PATCH 585/633] Remove the unspecified check about `.get(exhausted_range_inclusive)` `RangeInclusive::is_empty` (rust 1.47+) is in conflict with our MSRV (1.43.1) so we can't use it yet. The unspecified behavior is documented in `Itertools::get` so we simply remove this. --- src/iter_index.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index be55457ae..23b7863d0 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -52,7 +52,6 @@ where type Output = Take>; fn index(self, iter: I) -> Self::Output { - debug_assert!(!self.is_empty(), "The given `RangeInclusive` is exhausted. The result of indexing with an exhausted `RangeInclusive` is unspecified."); iter.skip(*self.start()) .take((1 + *self.end()).saturating_sub(*self.start())) } From 7a9ce56fc59489668178d696db76afb3580a359c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 2 May 2024 11:18:58 +0200 Subject: [PATCH 586/633] `get(r: Range)` as `Skip` Unless there is a reason to prefer `Take` over `Skip`?! --- src/iter_index.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index 23b7863d0..fd945d652 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -37,11 +37,10 @@ impl IteratorIndex for Range where I: Iterator, { - type Output = Take>; + type Output = Skip>; fn index(self, iter: I) -> Self::Output { - iter.skip(self.start) - .take(self.end.saturating_sub(self.start)) + iter.take(self.end).skip(self.start) } } From 4dd6ba0e7c44bb287dff1098d8fb6ab77c32bf87 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 2 May 2024 11:17:04 +0200 Subject: [PATCH 587/633] `get`: panics if the range includes `usize::MAX` --- src/iter_index.rs | 7 ++++--- src/lib.rs | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index fd945d652..679a88075 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -48,11 +48,11 @@ impl IteratorIndex for RangeInclusive where I: Iterator, { - type Output = Take>; + type Output = Skip>; fn index(self, iter: I) -> Self::Output { - iter.skip(*self.start()) - .take((1 + *self.end()).saturating_sub(*self.start())) + assert_ne!(*self.end(), usize::MAX); + iter.take(self.end() + 1).skip(*self.start()) } } @@ -74,6 +74,7 @@ where type Output = Take; fn index(self, iter: I) -> Self::Output { + assert_ne!(self.end, usize::MAX); iter.take(self.end + 1) } } diff --git a/src/lib.rs b/src/lib.rs index 80e1c47f4..b5fc258e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -511,6 +511,8 @@ pub trait Itertools: Iterator { /// /// Works similarly to [`slice::get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get). /// + /// **Panics** if the range includes `usize::MAX`. + /// /// It's a generalisation of [`Iterator::take`] and [`Iterator::skip`], /// and uses these under the hood. /// Therefore, the resulting iterator is [`DoubleEndedIterator`] From 3c16f14baa5515376adcd8c530f6d3d275b14f44 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 2 May 2024 11:11:26 +0200 Subject: [PATCH 588/633] `get`: when is it ESI and/or DEI --- src/lib.rs | 5 +++-- tests/test_core.rs | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index b5fc258e0..29b55f550 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -515,8 +515,9 @@ pub trait Itertools: Iterator { /// /// It's a generalisation of [`Iterator::take`] and [`Iterator::skip`], /// and uses these under the hood. - /// Therefore, the resulting iterator is [`DoubleEndedIterator`] - /// and/or [`ExactSizeIterator`] if the adapted iterator is. + /// Therefore, the resulting iterator is: + /// - [`ExactSizeIterator`] if the adapted iterator is [`ExactSizeIterator`]. + /// - [`DoubleEndedIterator`] if the adapted iterator is [`DoubleEndedIterator`] and [`ExactSizeIterator`]. /// /// # Unspecified Behavior /// The result of indexing with an exhausted [`core::ops::RangeInclusive`] is unspecified. diff --git a/tests/test_core.rs b/tests/test_core.rs index b5826ef39..6f7dc1fda 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -18,6 +18,28 @@ use crate::it::Itertools; use core::iter; use itertools as it; +#[allow(dead_code)] +fn get_esi_then_esi(it: I) { + fn is_esi(_: impl ExactSizeIterator) {} + is_esi(it.clone().get(1..4)); + is_esi(it.clone().get(1..=4)); + is_esi(it.clone().get(1..)); + is_esi(it.clone().get(..4)); + is_esi(it.clone().get(..=4)); + is_esi(it.get(..)); +} + +#[allow(dead_code)] +fn get_dei_esi_then_dei_esi(it: I) { + fn is_dei_esi(_: impl DoubleEndedIterator + ExactSizeIterator) {} + is_dei_esi(it.clone().get(1..4)); + is_dei_esi(it.clone().get(1..=4)); + is_dei_esi(it.clone().get(1..)); + is_dei_esi(it.clone().get(..4)); + is_dei_esi(it.clone().get(..=4)); + is_dei_esi(it.get(..)); +} + #[test] fn product0() { let mut prod = iproduct!(); From 05cc0ee256e84d665e34209053ebc62ef7e4463d Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 2 May 2024 15:59:54 +0200 Subject: [PATCH 589/633] `get(s..=usize::MAX)` should be fine when `s != 0` Test for coverage --- src/iter_index.rs | 12 +++++++++--- src/lib.rs | 2 +- tests/test_core.rs | 13 +++++++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index 679a88075..cf3222c9d 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -48,11 +48,17 @@ impl IteratorIndex for RangeInclusive where I: Iterator, { - type Output = Skip>; + type Output = Take>; fn index(self, iter: I) -> Self::Output { - assert_ne!(*self.end(), usize::MAX); - iter.take(self.end() + 1).skip(*self.start()) + // end - start + 1 without overflowing if possible + let length = if *self.end() == usize::MAX { + assert_ne!(*self.start(), 0); + self.end() - self.start() + 1 + } else { + (self.end() + 1).saturating_sub(*self.start()) + }; + iter.skip(*self.start()).take(length) } } diff --git a/src/lib.rs b/src/lib.rs index 29b55f550..786c0cc4d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -511,7 +511,7 @@ pub trait Itertools: Iterator { /// /// Works similarly to [`slice::get`](https://doc.rust-lang.org/std/primitive.slice.html#method.get). /// - /// **Panics** if the range includes `usize::MAX`. + /// **Panics** for ranges `..=usize::MAX` and `0..=usize::MAX`. /// /// It's a generalisation of [`Iterator::take`] and [`Iterator::skip`], /// and uses these under the hood. diff --git a/tests/test_core.rs b/tests/test_core.rs index 6f7dc1fda..32af246c0 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -40,6 +40,19 @@ fn get_dei_esi_then_dei_esi( is_dei_esi(it.get(..)); } +#[test] +fn get_1_max() { + let mut it = (0..5).get(1..=usize::MAX); + assert_eq!(it.next(), Some(1)); + assert_eq!(it.next_back(), Some(4)); +} + +#[test] +#[should_panic] +fn get_full_range_inclusive() { + let _it = (0..5).get(0..=usize::MAX); +} + #[test] fn product0() { let mut prod = iproduct!(); From 0d4efc84323399b47b09ae9da1ff3fdfc2cf95e1 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 3 May 2024 07:50:15 +0200 Subject: [PATCH 590/633] Remove free function `get` A `use itertools::*;` might conflict with such a general function name. --- src/iter_index.rs | 7 ++----- src/lib.rs | 1 - 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/iter_index.rs b/src/iter_index.rs index cf3222c9d..aadaa72a7 100644 --- a/src/iter_index.rs +++ b/src/iter_index.rs @@ -17,7 +17,7 @@ mod private_iter_index { impl Sealed for ops::RangeFull {} } -/// Used by [`get`] and [`Itertools::get`] to know which iterator +/// Used by [`Itertools::get`] to know which iterator /// to turn different ranges into. pub trait IteratorIndex: private_iter_index::Sealed where @@ -28,7 +28,7 @@ where /// Returns an adapted iterator for the current index. /// - /// Prefer calling [`get`] or [`Itertools::get`] instead + /// Prefer calling [`Itertools::get`] instead /// of calling this directly. fn index(self, from: I) -> Self::Output; } @@ -107,9 +107,6 @@ where } } -/// Returns an iterator over a subsection of the iterator. -/// -/// See [`Itertools::get`] for more information. pub fn get(iter: I, index: R) -> R::Output where I: IntoIterator, diff --git a/src/lib.rs b/src/lib.rs index 786c0cc4d..c439fcfef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -154,7 +154,6 @@ pub use crate::concat_impl::concat; pub use crate::cons_tuples_impl::cons_tuples; pub use crate::diff::diff_with; pub use crate::diff::Diff; -pub use crate::iter_index::get; #[cfg(feature = "use_alloc")] pub use crate::kmerge_impl::kmerge_by; pub use crate::minmax::MinMaxResult; From 2ad9e07ae860bb891e48b35edfea5b3286dcb4ab Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 8 May 2024 23:39:48 +0200 Subject: [PATCH 591/633] `assert_equal`: fix `clippy::default_numeric_fallback` Type of `i` defaults to `i32` when `usize` should be used. It's only a problem if this function is called with two iterators longer than `i32::MAX`. It seems unlikely to me but who knows? `i32` should not be the default integer type in the library. `usize` would have more sense so I add the lint as warning (for the library only, it's okay in tests and benchmarks). --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c439fcfef..f4de79c50 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -#![warn(missing_docs)] +#![warn(missing_docs, clippy::default_numeric_fallback)] #![crate_name = "itertools"] #![cfg_attr(not(feature = "use_std"), no_std)] @@ -4277,7 +4277,7 @@ where { let mut ia = a.into_iter(); let mut ib = b.into_iter(); - let mut i = 0; + let mut i: usize = 0; loop { match (ia.next(), ib.next()) { (None, None) => return, From 074c7fcc07c2bfd60f238585c05134ea3eb43f77 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 16 May 2024 12:13:59 +0200 Subject: [PATCH 592/633] `KMergeBy` is not lazy but must be used nonetheless --- src/kmerge_impl.rs | 2 +- tests/laziness.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 4660caf07..0be3840a1 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -153,7 +153,7 @@ where /// /// See [`.kmerge_by()`](crate::Itertools::kmerge_by) for more /// information. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] pub struct KMergeBy where I: Iterator, diff --git a/tests/laziness.rs b/tests/laziness.rs index f936decde..7575e3786 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -143,11 +143,11 @@ must_use_tests! { let _ = Panicking.merge_join_by(Panicking, |_, _| true); let _ = Panicking.merge_join_by(Panicking, Ord::cmp); } - #[ignore] + #[should_panic] kmerge { let _ = Panicking.map(|_| Panicking).kmerge(); } - #[ignore] + #[should_panic] kmerge_by { let _ = Panicking.map(|_| Panicking).kmerge_by(|_, _| true); } From d7c99d55daeaa76f482444e95beb99f5744ced4e Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 16 May 2024 12:29:44 +0200 Subject: [PATCH 593/633] `TupleCombinations` is not lazy but must be used nonetheless --- src/adaptors/mod.rs | 2 +- tests/laziness.rs | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 22f7aa363..52e36c48b 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -612,7 +612,7 @@ where /// See [`.tuple_combinations()`](crate::Itertools::tuple_combinations) for more /// information. #[derive(Clone, Debug)] -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] +#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] pub struct TupleCombinations where I: Iterator, diff --git a/tests/laziness.rs b/tests/laziness.rs index 7575e3786..c559d33ad 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -196,10 +196,15 @@ must_use_tests! { while_some { let _ = Panicking.map(Some).while_some(); } - #[ignore] - tuple_combinations { + tuple_combinations1 { let _ = Panicking.tuple_combinations::<(_,)>(); + } + #[should_panic] + tuple_combinations2 { let _ = Panicking.tuple_combinations::<(_, _)>(); + } + #[should_panic] + tuple_combinations3 { let _ = Panicking.tuple_combinations::<(_, _, _)>(); } combinations { From d5084d15e959b85d89a49e5cd33ad6267bc541a3 Mon Sep 17 00:00:00 2001 From: Philippe Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Thu, 16 May 2024 17:30:31 +0200 Subject: [PATCH 594/633] Prepare v0.13.0 release (#937) --- CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3994e5ce7..de9564c6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 0.13.0 + +### Breaking +- Removed implementation of `DoubleEndedIterator` for `ConsTuples` (#853) +- Made `MultiProduct` fused and fixed on an empty iterator (#835, #834) +- Changed `iproduct!` to return tuples for maxi one iterator too (#870) +- Changed `PutBack::put_back` to return the old value (#880) +- Removed deprecated `repeat_call, Itertools::{foreach, step, map_results, fold_results}` (#878) +- Removed `TakeWhileInclusive::new` (#912) + +### Added +- Added `Itertools::{smallest_by, smallest_by_key, largest, largest_by, largest_by_key}` (#654, #885) +- Added `Itertools::tail` (#899) +- Implemented `DoubleEndedIterator` for `ProcessResults` (#910) +- Implemented `Debug` for `FormatWith` (#931) +- Added `Itertools::get` (#891) + +### Changed +- Deprecated `Itertools::group_by` (renamed `chunk_by`) (#866, #879) +- Deprecated `unfold` (use `std::iter::from_fn` instead) (#871) +- Optimized `GroupingMapBy` (#873, #876) +- Relaxed `Fn` bounds to `FnMut` in `diff_with, Itertools::into_group_map_by` (#886) +- Relaxed `Debug/Clone` bounds for `MapInto` (#889) +- Documented the `use_alloc` feature (#887) +- Optimized `Itertools::set_from` (#888) +- Removed badges in `README.md` (#890) +- Added "no-std" categories in `Cargo.toml` (#894) +- Fixed `Itertools::k_smallest` on short unfused iterators (#900) +- Deprecated `Itertools::tree_fold1` (renamed `tree_reduce`) (#895) +- Deprecated `GroupingMap::fold_first` (renamed `reduce`) (#902) +- Fixed `Itertools::k_smallest(0)` to consume the iterator, optimized `Itertools::k_smallest(1)` (#909) +- Specialized `Combinations::nth` (#914) +- Specialized `MergeBy::fold` (#920) +- Specialized `CombinationsWithReplacement::nth` (#923) +- Specialized `FlattenOk::{fold, rfold}` (#927) +- Specialized `Powerset::nth` (#924) +- Documentation fixes (#882, #936) +- Fixed `assert_equal` for iterators longer than `i32::MAX` (#932) +- Updated the `must_use` message of non-lazy `KMergeBy` and `TupleCombinations` (#939) + +### Notable Internal Changes +- Tested iterator laziness (#792) +- Created `CONTRIBUTING.md` (#767) + ## 0.12.1 ### Added From 4777762a6bed98210428308b1f762032db8bf255 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 15 May 2024 16:52:14 +0200 Subject: [PATCH 595/633] Faster cargo-semver-checks-action with "manual" toolchain installation --- .github/workflows/ci.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 239ce2405..df6bf412c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,9 +69,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable - uses: obi1kenobi/cargo-semver-checks-action@v2.4 with: - rust-toolchain: stable + rust-toolchain: manual feature-group: all-features # Used to signal to branch protections that all other jobs have succeeded. From 26e35d096735b7651d018ec7f9f8cfaf947d26b6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 21 May 2024 11:07:13 +0200 Subject: [PATCH 596/633] Macro hygiene of `chain!` If someone has a module named `core` and uses `itertools::chain!` then there is a conflict! I improve our macro hygiene test by adding (empty) modules with all the names we might be tempted to use. Without changing `core` in `chain!`, the test failed! --- src/lib.rs | 8 ++++---- tests/macros_hygiene.rs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f4de79c50..1a847d42b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -393,16 +393,16 @@ macro_rules! izip { /// ``` macro_rules! chain { () => { - core::iter::empty() + $crate::__std_iter::empty() }; ($first:expr $(, $rest:expr )* $(,)?) => { { - let iter = core::iter::IntoIterator::into_iter($first); + let iter = $crate::__std_iter::IntoIterator::into_iter($first); $( let iter = - core::iter::Iterator::chain( + $crate::__std_iter::Iterator::chain( iter, - core::iter::IntoIterator::into_iter($rest)); + $crate::__std_iter::IntoIterator::into_iter($rest)); )* iter } diff --git a/tests/macros_hygiene.rs b/tests/macros_hygiene.rs index 20b59fba8..e6e895555 100644 --- a/tests/macros_hygiene.rs +++ b/tests/macros_hygiene.rs @@ -1,3 +1,8 @@ +mod alloc {} +mod core {} +mod either {} +mod std {} + #[test] fn iproduct_hygiene() { let _ = itertools::iproduct!(); @@ -12,3 +17,11 @@ fn izip_hygiene() { let _ = itertools::izip!(0..6, 0..9); let _ = itertools::izip!(0..6, 0..9, 0..12); } + +#[test] +fn chain_hygiene() { + let _: ::std::iter::Empty = itertools::chain!(); + let _ = itertools::chain!(0..6); + let _ = itertools::chain!(0..6, 0..9); + let _ = itertools::chain!(0..6, 0..9, 0..12); +} From 2ca3c5ac72100a2579a942d4a51cf0a31df851bc Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 21 May 2024 11:13:44 +0200 Subject: [PATCH 597/633] Macro hygiene: qualified paths only Is conflict with `.map` and `.zip` absolutely impossible? --- src/lib.rs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1a847d42b..018f877e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,7 +262,10 @@ macro_rules! iproduct { $crate::__std_iter::once(()) ); ($I:expr $(,)?) => ( - $crate::__std_iter::IntoIterator::into_iter($I).map(|elt| (elt,)) + $crate::__std_iter::Iterator::map( + $crate::__std_iter::IntoIterator::into_iter($I), + |elt| (elt,) + ) ); ($I:expr, $J:expr $(,)?) => ( $crate::Itertools::cartesian_product( @@ -330,19 +333,24 @@ macro_rules! izip { // binary ($first:expr, $second:expr $(,)*) => { - $crate::izip!($first) - .zip($second) + $crate::__std_iter::Iterator::zip( + $crate::__std_iter::IntoIterator::into_iter($first), + $second, + ) }; // n-ary where n > 2 ( $first:expr $( , $rest:expr )* $(,)* ) => { - $crate::izip!($first) + { + let iter = $crate::__std_iter::IntoIterator::into_iter($first); $( - .zip($rest) + let iter = $crate::__std_iter::Iterator::zip(iter, $rest); )* - .map( + $crate::__std_iter::Iterator::map( + iter, $crate::izip!(@closure a => (a) $( , $rest )*) ) + } }; } From ac8fb03a9050372ae868c849d015776b080c89ff Mon Sep 17 00:00:00 2001 From: lcnr Date: Thu, 23 May 2024 19:23:03 +0000 Subject: [PATCH 598/633] manually outline closure this avoids capturing unused generic parameters, slightly improving compile times. The performance cost is a lot higher with the next-generation trait solver which pretty much hangs without this change. --- src/adaptors/mod.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 52e36c48b..6f5e0260e 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -773,16 +773,28 @@ macro_rules! impl_tuple_combination { where F: FnMut(B, Self::Item) -> B, { + // We outline this closure to prevent it from unnecessarily + // capturing the type parameters `I`, `B`, and `F`. Not doing + // so ended up causing exponentially big types during MIR + // inlining when building itertools with optimizations enabled. + // + // This change causes a small improvement to compile times in + // release mode. + type CurrTuple = (A, $(ignore_ident!($X, A)),*); + type PrevTuple = ($(ignore_ident!($X, A),)*); + fn map_fn(z: &A) -> impl FnMut(PrevTuple) -> CurrTuple + '_ { + move |($($X,)*)| (z.clone(), $($X),*) + } let Self { c, item, mut iter } = self; if let Some(z) = item.as_ref() { init = c - .map(|($($X,)*)| (z.clone(), $($X),*)) + .map(map_fn::(z)) .fold(init, &mut f); } while let Some(z) = iter.next() { let c: $P = iter.clone().into(); init = c - .map(|($($X,)*)| (z.clone(), $($X),*)) + .map(map_fn::(&z)) .fold(init, &mut f); } init From 23bf81ac55eaf3f5b3e5a777cde3a2a1b2265b96 Mon Sep 17 00:00:00 2001 From: Xenira <1288524+Xenira@users.noreply.github.com> Date: Mon, 27 May 2024 20:00:38 +0200 Subject: [PATCH 599/633] feat(`FilterOk`): implement `DoubleEndedIterator` Refs: #947 --- benches/specializations.rs | 1 + src/adaptors/mod.rs | 24 ++++++++++++++++++++++++ tests/specializations.rs | 4 +++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 18039fc4e..958bcc124 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -639,6 +639,7 @@ bench_specializations! { v.iter().copied().map_ok(|x| x + 1) } filter_ok { + DoubleEndedIterator { let v = black_box((0_u32..1024) .map(|x| if x % 2 == 1 { Err(x) } else { Ok(x) }) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 6f5e0260e..e757837b7 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -936,6 +936,30 @@ where } } +impl DoubleEndedIterator for FilterOk +where + I: DoubleEndedIterator>, + F: FnMut(&T) -> bool, +{ + fn next_back(&mut self) -> Option { + let f = &mut self.f; + self.iter.rfind(|res| match res { + Ok(t) => f(t), + _ => true, + }) + } + + fn rfold(self, init: Acc, fold_f: Fold) -> Acc + where + Fold: FnMut(Acc, Self::Item) -> Acc, + { + let mut f = self.f; + self.iter + .filter(|v| v.as_ref().map(&mut f).unwrap_or(true)) + .rfold(init, fold_f) + } +} + impl FusedIterator for FilterOk where I: FusedIterator>, diff --git a/tests/specializations.rs b/tests/specializations.rs index 712311472..cdc51fb23 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -447,7 +447,9 @@ quickcheck! { } fn filter_ok(v: Vec>) -> () { - test_specializations(&v.into_iter().filter_ok(|&i| i < 20)); + let it = v.into_iter().filter_ok(|&i| i < 20); + test_specializations(&it); + test_double_ended_specializations(&it); } fn filter_map_ok(v: Vec>) -> () { From ad5cc9645a5c3de2811eed1b2c6643f27f06764a Mon Sep 17 00:00:00 2001 From: Xenira <1288524+Xenira@users.noreply.github.com> Date: Mon, 27 May 2024 22:41:20 +0200 Subject: [PATCH 600/633] feat(`FilterMapOk`): implement `DoubleEndedIterator` Refs: #947 --- benches/specializations.rs | 1 + src/adaptors/mod.rs | 24 ++++++++++++++++++++++++ tests/specializations.rs | 4 +++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 958bcc124..3a5d80326 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -648,6 +648,7 @@ bench_specializations! { v.iter().copied().filter_ok(|x| x % 3 == 0) } filter_map_ok { + DoubleEndedIterator { let v = black_box((0_u32..1024) .map(|x| if x % 2 == 1 { Err(x) } else { Ok(x) }) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index e757837b7..095b08918 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1041,6 +1041,30 @@ where } } +impl DoubleEndedIterator for FilterMapOk +where + I: DoubleEndedIterator>, + F: FnMut(T) -> Option, +{ + fn next_back(&mut self) -> Option { + let f = &mut self.f; + self.iter.by_ref().rev().find_map(|res| match res { + Ok(t) => f(t).map(Ok), + Err(e) => Some(Err(e)), + }) + } + + fn rfold(self, init: Acc, fold_f: Fold) -> Acc + where + Fold: FnMut(Acc, Self::Item) -> Acc, + { + let mut f = self.f; + self.iter + .filter_map(|v| transpose_result(v.map(&mut f))) + .rfold(init, fold_f) + } +} + impl FusedIterator for FilterMapOk where I: FusedIterator>, diff --git a/tests/specializations.rs b/tests/specializations.rs index cdc51fb23..ff3d5c19b 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -453,7 +453,9 @@ quickcheck! { } fn filter_map_ok(v: Vec>) -> () { - test_specializations(&v.into_iter().filter_map_ok(|i| if i < 20 { Some(i * 2) } else { None })); + let it = v.into_iter().filter_map_ok(|i| if i < 20 { Some(i * 2) } else { None }); + test_specializations(&it); + test_double_ended_specializations(&it); } // `SmallIter2` because `Vec` is too slow and we get bad coverage from a singleton like Option From b86960ac793da03e49dc60f2d546603d9fad4356 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:34:37 +0200 Subject: [PATCH 601/633] Fix warnings from clippy 1.79 --- src/extrema_set.rs | 1 - src/grouping_map.rs | 2 -- tests/zip.rs | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/extrema_set.rs b/src/extrema_set.rs index d24114c6d..7d353821f 100644 --- a/src/extrema_set.rs +++ b/src/extrema_set.rs @@ -1,4 +1,3 @@ -#![cfg(feature = "use_alloc")] use alloc::{vec, vec::Vec}; use std::cmp::Ordering; diff --git a/src/grouping_map.rs b/src/grouping_map.rs index b4aae9ecf..86cb55dc0 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -1,5 +1,3 @@ -#![cfg(feature = "use_std")] - use crate::{ adaptors::map::{MapSpecialCase, MapSpecialCaseFn}, MinMaxResult, diff --git a/tests/zip.rs b/tests/zip.rs index 716ac20b3..daed31e32 100644 --- a/tests/zip.rs +++ b/tests/zip.rs @@ -20,7 +20,7 @@ fn test_zip_longest_size_hint() { let v: &[_] = &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; let v2 = &[10, 11, 12]; - assert_eq!(c.zip_longest(v.iter()).size_hint(), (std::usize::MAX, None)); + assert_eq!(c.zip_longest(v.iter()).size_hint(), (usize::MAX, None)); assert_eq!(v.iter().zip_longest(v2.iter()).size_hint(), (10, Some(10))); } From 4e13d041afdc81d1feb85155e1a0f25a1d36d355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Br=C3=BCnig?= Date: Sun, 9 Jun 2024 12:38:27 +0200 Subject: [PATCH 602/633] improve documentation of tree_reduce --- src/lib.rs | 53 +++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 018f877e5..1861360bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2528,24 +2528,26 @@ pub trait Itertools: Iterator { /// /// If `f` is associative you should also decide carefully: /// - /// - if `f` is a trivial operation like `u32::wrapping_add`, prefer the normal - /// [`Iterator::reduce`] instead since it will most likely result in the generation of simpler - /// code because the compiler is able to optimize it - /// - otherwise if `f` is non-trivial like `format!`, you should use `tree_reduce` since it - /// reduces the number of operations from `O(n)` to `O(ln(n))` + /// For an iterator producing `n` elements, both [`Iterator::reduce`] and `tree_reduce` will + /// call `f` `n - 1` times. However, `tree_reduce` will call `f` on earlier intermediate + /// results, which is beneficial for `f` that allocate and produce longer results for longer + /// arguments. For example if `f` combines arguments using `format!`, then `tree_reduce` will + /// operate on average on shorter arguments resulting in less bytes being allocated overall. /// - /// Here "non-trivial" means: - /// - /// - any allocating operation - /// - any function that is a composition of many operations + /// If 'f' does not benefit from such a reordering, like `u32::wrapping_add`, prefer the + /// normal [`Iterator::reduce`] instead since it will most likely result in the generation of + /// simpler code because the compiler is able to optimize it. /// /// ``` /// use itertools::Itertools; /// + /// let f = |a: String, b: String| { + /// format!("f({a}, {b})") + /// }; + /// /// // The same tree as above - /// let num_strings = (1..8).map(|x| x.to_string()); - /// assert_eq!(num_strings.tree_reduce(|x, y| format!("f({}, {})", x, y)), - /// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))"))); + /// assert_eq!((1..8).map(|x| x.to_string()).tree_reduce(f), + /// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))"))); /// /// // Like fold1, an empty iterator produces None /// assert_eq!((0..0).tree_reduce(|x, y| x * y), None); @@ -2553,9 +2555,36 @@ pub trait Itertools: Iterator { /// // tree_reduce matches fold1 for associative operations... /// assert_eq!((0..10).tree_reduce(|x, y| x + y), /// (0..10).fold1(|x, y| x + y)); + /// /// // ...but not for non-associative ones /// assert_ne!((0..10).tree_reduce(|x, y| x - y), /// (0..10).fold1(|x, y| x - y)); + /// + /// + /// let mut total_capacity_reduce = 0; + /// let reduce_res = (1..100).map(|x| x.to_string()) + /// .reduce(|a, b| { + /// let r = f(a, b); + /// total_capacity_reduce += r.capacity(); + /// r + /// }) + /// .unwrap(); + /// + /// let mut total_capacity_tree_reduce = 0; + /// let tree_reduce_res = (1..100).map(|x| x.to_string()) + /// .tree_reduce(|a, b| { + /// let r = f(a, b); + /// total_capacity_tree_reduce += r.capacity(); + /// r + /// }) + /// .unwrap(); + /// + /// dbg!(total_capacity_reduce, total_capacity_tree_reduce, + /// reduce_res.len(), tree_reduce_res.len()); + /// // total_capacity_reduce = 65630 + /// // total_capacity_tree_reduce = 7284 + /// // reduce_res.len() = 679 + /// // tree_reduce_res.len() = 679 /// ``` fn tree_reduce(mut self, mut f: F) -> Option where From 46c68744d0a02db1a32e5c1d82c78d358b6e1765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Br=C3=BCnig?= Date: Sun, 16 Jun 2024 11:37:27 +0200 Subject: [PATCH 603/633] tree_reduce doc: use len() instead of capacity() --- src/lib.rs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 1861360bf..7ca6e58a3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2560,31 +2560,27 @@ pub trait Itertools: Iterator { /// assert_ne!((0..10).tree_reduce(|x, y| x - y), /// (0..10).fold1(|x, y| x - y)); /// - /// - /// let mut total_capacity_reduce = 0; + /// let mut total_len_reduce = 0; /// let reduce_res = (1..100).map(|x| x.to_string()) /// .reduce(|a, b| { /// let r = f(a, b); - /// total_capacity_reduce += r.capacity(); + /// total_len_reduce += r.len(); /// r /// }) /// .unwrap(); /// - /// let mut total_capacity_tree_reduce = 0; + /// let mut total_len_tree_reduce = 0; /// let tree_reduce_res = (1..100).map(|x| x.to_string()) /// .tree_reduce(|a, b| { /// let r = f(a, b); - /// total_capacity_tree_reduce += r.capacity(); + /// total_len_tree_reduce += r.len(); /// r /// }) /// .unwrap(); /// - /// dbg!(total_capacity_reduce, total_capacity_tree_reduce, - /// reduce_res.len(), tree_reduce_res.len()); - /// // total_capacity_reduce = 65630 - /// // total_capacity_tree_reduce = 7284 - /// // reduce_res.len() = 679 - /// // tree_reduce_res.len() = 679 + /// assert_eq!(total_len_reduce, 33299); + /// assert_eq!(total_len_tree_reduce, 4228); + /// assert_eq!(reduce_res.len(), tree_reduce_res.len()); /// ``` fn tree_reduce(mut self, mut f: F) -> Option where From 1091f24a39d9c18c9c9d5e826c9cff21f32a3cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Br=C3=BCnig?= Date: Sun, 16 Jun 2024 11:45:06 +0200 Subject: [PATCH 604/633] tree_reduce doc: explain benefit when building binary search tree --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 7ca6e58a3..c05e28266 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2534,6 +2534,11 @@ pub trait Itertools: Iterator { /// arguments. For example if `f` combines arguments using `format!`, then `tree_reduce` will /// operate on average on shorter arguments resulting in less bytes being allocated overall. /// + /// Moreover, the output of `tree_reduce` is preferable to that of [`Iterator::reduce`] in + /// certain cases. For example, building a binary search tree using `tree_reduce` will result in + /// a balanced tree with height O(ln(n)), while [`Iterator::reduce`] will output a tree with + /// height O(n), essentially a linked list. + /// /// If 'f' does not benefit from such a reordering, like `u32::wrapping_add`, prefer the /// normal [`Iterator::reduce`] instead since it will most likely result in the generation of /// simpler code because the compiler is able to optimize it. From a0225ceee50cafe9e974699e1cbf7fa9a1dcf901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Br=C3=BCnig?= Date: Mon, 17 Jun 2024 12:39:12 +0200 Subject: [PATCH 605/633] tree_reduce doc: fix formatting --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c05e28266..0e7e68fb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2536,10 +2536,10 @@ pub trait Itertools: Iterator { /// /// Moreover, the output of `tree_reduce` is preferable to that of [`Iterator::reduce`] in /// certain cases. For example, building a binary search tree using `tree_reduce` will result in - /// a balanced tree with height O(ln(n)), while [`Iterator::reduce`] will output a tree with - /// height O(n), essentially a linked list. + /// a balanced tree with height `O(ln(n))`, while [`Iterator::reduce`] will output a tree with + /// height `O(n)`, essentially a linked list. /// - /// If 'f' does not benefit from such a reordering, like `u32::wrapping_add`, prefer the + /// If `f` does not benefit from such a reordering, like `u32::wrapping_add`, prefer the /// normal [`Iterator::reduce`] instead since it will most likely result in the generation of /// simpler code because the compiler is able to optimize it. /// From 502c816b2159065362960616864462463b94c2e2 Mon Sep 17 00:00:00 2001 From: Kinto Date: Mon, 17 Jun 2024 20:37:54 +0200 Subject: [PATCH 606/633] Increase MSRV to 1.63.0 --- .github/workflows/ci.yml | 2 +- Cargo.toml | 2 +- src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df6bf412c..4b8dcf189 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,7 +44,7 @@ jobs: - uses: dtolnay/rust-toolchain@master with: # Here, it does not trigger a PR from dependabot. - toolchain: 1.43.1 + toolchain: 1.63.0 - run: cargo no-dev-deps check test: diff --git a/Cargo.toml b/Cargo.toml index 7fb6a3980..800614657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ categories = ["algorithms", "rust-patterns", "no-std", "no-std::no-alloc"] edition = "2018" # When bumping, please resolve all `#[allow(clippy::*)]` that are newly resolvable. -rust-version = "1.43.1" +rust-version = "1.63.0" [lib] bench = false diff --git a/src/lib.rs b/src/lib.rs index 0e7e68fb8..b98e03f29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -46,7 +46,7 @@ //! //! ## Rust Version //! -//! This version of itertools requires Rust 1.43.1 or later. +//! This version of itertools requires Rust 1.63.0 or later. #[cfg(not(feature = "use_std"))] extern crate core as std; From cca638635c6a0792aa946ab6dcf3b6859ba25b61 Mon Sep 17 00:00:00 2001 From: Kinto Date: Mon, 17 Jun 2024 20:40:39 +0200 Subject: [PATCH 607/633] Drop redundant `#[allow(clippy::*)]` --- benches/specializations.rs | 2 +- tests/quick.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/benches/specializations.rs b/benches/specializations.rs index 3a5d80326..e70323f8e 100644 --- a/benches/specializations.rs +++ b/benches/specializations.rs @@ -1,4 +1,4 @@ -#![allow(unstable_name_collisions, clippy::incompatible_msrv)] +#![allow(unstable_name_collisions)] use criterion::black_box; use criterion::BenchmarkId; diff --git a/tests/quick.rs b/tests/quick.rs index 5b8fd6a21..6fe6f6923 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -253,7 +253,6 @@ where let mut it = get_it(); for _ in 0..(counts.len() - 1) { - #[allow(clippy::manual_assert)] if it.next().is_none() { panic!("Iterator shouldn't be finished, may not be deterministic"); } From 9f5256f34fb206f70d94f408b3dc33929067a43b Mon Sep 17 00:00:00 2001 From: Kinto Date: Tue, 18 Jun 2024 09:09:42 +0200 Subject: [PATCH 608/633] Resolve TODOs awaiting MSRV >1.62 --- src/adaptors/mod.rs | 7 ++----- src/combinations_with_replacement.rs | 6 +----- src/concat_impl.rs | 7 ++----- src/kmerge_impl.rs | 8 +++----- tests/quick.rs | 3 +-- 5 files changed, 9 insertions(+), 22 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index 095b08918..d717e5408 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -1108,9 +1108,7 @@ where fn next(&mut self) -> Option { let f = &mut self.f; - // TODO: once MSRV >= 1.62, use `then_some`. - self.iter - .find_map(|(count, val)| if f(val) { Some(count) } else { None }) + self.iter.find_map(|(count, val)| f(val).then_some(count)) } fn size_hint(&self) -> (usize, Option) { @@ -1138,11 +1136,10 @@ where { fn next_back(&mut self) -> Option { let f = &mut self.f; - // TODO: once MSRV >= 1.62, use `then_some`. self.iter .by_ref() .rev() - .find_map(|(count, val)| if f(val) { Some(count) } else { None }) + .find_map(|(count, val)| f(val).then_some(count)) } fn rfold(self, init: B, mut func: G) -> B diff --git a/src/combinations_with_replacement.rs b/src/combinations_with_replacement.rs index f363f9ba2..c17e75250 100644 --- a/src/combinations_with_replacement.rs +++ b/src/combinations_with_replacement.rs @@ -73,11 +73,7 @@ where Some((increment_from, increment_value)) => { // We need to update the rightmost non-max value // and all those to the right - for i in &mut self.indices[increment_from..] { - *i = increment_value; - } - // TODO: once MSRV >= 1.50, use `fill` instead: - // self.indices[increment_from..].fill(increment_value); + self.indices[increment_from..].fill(increment_value); false } // Otherwise, we're done diff --git a/src/concat_impl.rs b/src/concat_impl.rs index ec7b91c60..dc80839ce 100644 --- a/src/concat_impl.rs +++ b/src/concat_impl.rs @@ -1,8 +1,6 @@ -use crate::Itertools; - /// Combine all an iterator's elements into one element by using [`Extend`]. /// -/// [`IntoIterator`]-enabled version of [`Itertools::concat`]. +/// [`IntoIterator`]-enabled version of [`Itertools::concat`](crate::Itertools::concat). /// /// This combinator will extend the first item with each of the rest of the /// items of the iterator. If the iterator is empty, the default value of @@ -19,10 +17,9 @@ where I: IntoIterator, I::Item: Extend<<::Item as IntoIterator>::Item> + IntoIterator + Default, { - #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` iterable .into_iter() - .fold1(|mut a, b| { + .reduce(|mut a, b| { a.extend(b); a }) diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 0be3840a1..13c935b28 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -1,5 +1,4 @@ use crate::size_hint; -use crate::Itertools; use alloc::vec::Vec; use std::fmt; @@ -128,7 +127,7 @@ impl bool> KMergePredicate for F { /// Create an iterator that merges elements of the contained iterators using /// the ordering function. /// -/// [`IntoIterator`] enabled version of [`Itertools::kmerge`]. +/// [`IntoIterator`] enabled version of [`Itertools::kmerge`](crate::Itertools::kmerge). /// /// ``` /// use itertools::kmerge; @@ -172,7 +171,7 @@ where /// Create an iterator that merges elements of the contained iterators. /// -/// [`IntoIterator`] enabled version of [`Itertools::kmerge_by`]. +/// [`IntoIterator`] enabled version of [`Itertools::kmerge_by`](crate::Itertools::kmerge_by). pub fn kmerge_by( iterable: I, mut less_than: F, @@ -223,11 +222,10 @@ where } fn size_hint(&self) -> (usize, Option) { - #[allow(deprecated)] //TODO: once msrv hits 1.51. replace `fold1` with `reduce` self.heap .iter() .map(|i| i.size_hint()) - .fold1(size_hint::add) + .reduce(size_hint::add) .unwrap_or((0, Some(0))) } } diff --git a/tests/quick.rs b/tests/quick.rs index 6fe6f6923..12b6b9d5a 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -1511,13 +1511,12 @@ quickcheck! { acc + val }); - // TODO: Swap `fold1` with stdlib's `reduce` when it's stabilized let group_map_lookup = a.iter() .map(|&b| b as u64) .map(|i| (i % modulo, i)) .into_group_map() .into_iter() - .map(|(key, vals)| (key, vals.into_iter().fold1(|acc, val| acc + val).unwrap())) + .map(|(key, vals)| (key, vals.into_iter().reduce(|acc, val| acc + val).unwrap())) .collect::>(); assert_eq!(lookup, group_map_lookup); From 1c850ce53d1b7360eeac8f3e8dbc0e491e0bc07e Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Sat, 29 Jun 2024 10:33:55 +0000 Subject: [PATCH 609/633] ci: Run most tests with miri This PR additionally reduces the size of some heavy tests to reduce their running time. We use nextest as our runner because it runs tests in parallel. However, since nextest does not currenlty run doc tests, we also run those separately. --- .github/workflows/ci.yml | 15 +++++++++++++++ tests/quick.rs | 4 ++++ tests/specializations.rs | 7 +++++++ tests/test_std.rs | 20 ++++++++++++-------- 4 files changed, 38 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4b8dcf189..8e2a9d66c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,6 +54,21 @@ jobs: - uses: dtolnay/rust-toolchain@stable - run: cargo test --all-features + miri: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly + components: miri + - uses: taiki-e/install-action@nextest + - run: | + cargo miri nextest run --all-features + cargo miri test --doc + check-format: name: check format runs-on: ubuntu-latest diff --git a/tests/quick.rs b/tests/quick.rs index 12b6b9d5a..672901e7c 100644 --- a/tests/quick.rs +++ b/tests/quick.rs @@ -2,7 +2,11 @@ //! and adaptors. //! //! In particular we test the tedious size_hint and exact size correctness. +//! +//! **NOTE:** Due to performance limitations, these tests are not run with miri! +//! They cannot be relied upon to discover soundness issues. +#![cfg(not(miri))] #![allow(deprecated, unstable_name_collisions)] use itertools::free::{ diff --git a/tests/specializations.rs b/tests/specializations.rs index ff3d5c19b..3e4831024 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -1,3 +1,10 @@ +//! Test specializations of methods with default impls match the behavior of the +//! default impls. +//! +//! **NOTE:** Due to performance limitations, these tests are not run with miri! +//! They cannot be relied upon to discover soundness issues. + +#![cfg(not(miri))] #![allow(unstable_name_collisions)] use itertools::Itertools; diff --git a/tests/test_std.rs b/tests/test_std.rs index 00246d506..f626a17d7 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -491,6 +491,7 @@ fn sorted_by() { it::assert_equal(v, vec![4, 3, 2, 1, 0]); } +#[cfg(not(miri))] qc::quickcheck! { fn k_smallest_range(n: i64, m: u16, k: u16) -> () { // u16 is used to constrain k and m to 0..2¹⁶, @@ -598,7 +599,9 @@ macro_rules! generic_test { }; } +#[cfg(not(miri))] generic_test!(k_smallest_sort, u8, u16, u32, u64, i8, i16, i32, i64); +#[cfg(not(miri))] generic_test!(k_smallest_by_sort, u8, u16, u32, u64, i8, i16, i32, i64); #[test] @@ -1055,8 +1058,8 @@ fn binomial(n: usize, k: usize) -> usize { #[test] fn combinations_range_count() { - for n in 0..=10 { - for k in 0..=10 { + for n in 0..=7 { + for k in 0..=7 { let len = binomial(n, k); let mut it = (0..n).combinations(k); assert_eq!(len, it.clone().count()); @@ -1077,7 +1080,7 @@ fn combinations_range_count() { #[test] fn combinations_inexact_size_hints() { - for k in 0..=10 { + for k in 0..=7 { let mut numbers = (0..18).filter(|i| i % 2 == 0); // 9 elements let mut it = numbers.clone().combinations(k); let real_n = numbers.clone().count(); @@ -1129,8 +1132,8 @@ fn permutations_zero() { #[test] fn permutations_range_count() { - for n in 0..=7 { - for k in 0..=7 { + for n in 0..=4 { + for k in 0..=4 { let len = if k <= n { (n - k + 1..=n).product() } else { 0 }; let mut it = (0..n).permutations(k); assert_eq!(len, it.clone().count()); @@ -1162,6 +1165,7 @@ fn permutations_overflowed_size_hints() { } #[test] +#[cfg(not(miri))] fn combinations_with_replacement() { // Pool smaller than n it::assert_equal((0..1).combinations_with_replacement(2), vec![vec![0, 0]]); @@ -1190,8 +1194,8 @@ fn combinations_with_replacement() { #[test] fn combinations_with_replacement_range_count() { - for n in 0..=7 { - for k in 0..=7 { + for n in 0..=4 { + for k in 0..=4 { let len = binomial(usize::saturating_sub(n + k, 1), k); let mut it = (0..n).combinations_with_replacement(k); assert_eq!(len, it.clone().count()); @@ -1236,7 +1240,7 @@ fn powerset() { assert_eq!((0..8).powerset().count(), 1 << 8); assert_eq!((0..16).powerset().count(), 1 << 16); - for n in 0..=10 { + for n in 0..=4 { let mut it = (0..n).powerset(); let len = 2_usize.pow(n); assert_eq!(len, it.clone().count()); From fe7ef10cf217bc5c38088831d35ce0342cd98363 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 27 Apr 2024 13:22:27 +0200 Subject: [PATCH 610/633] Add k_smallest_relaxed and variants This implements the algorithm described in [1] which consumes twice the amount of memory as the existing `k_smallest` algorithm but achieves linear time in the number of elements in the input. [1] https://quickwit.io/blog/top-k-complexity --- src/k_smallest.rs | 39 ++++++++++ src/lib.rs | 187 ++++++++++++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 46 +++++++++++- 3 files changed, 270 insertions(+), 2 deletions(-) diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 7b2f62ea1..083c1b00d 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -88,6 +88,45 @@ where storage } +pub(crate) fn k_smallest_relaxed_general(iter: I, k: usize, mut comparator: F) -> Vec +where + I: Iterator, + F: FnMut(&I::Item, &I::Item) -> Ordering, +{ + if k == 0 { + iter.last(); + return Vec::new(); + } + + let mut iter = iter.fuse(); + let mut buf = iter.by_ref().take(2 * k).collect::>(); + + if buf.len() < k { + buf.sort_unstable_by(&mut comparator); + return buf; + } + + buf.select_nth_unstable_by(k - 1, &mut comparator); + buf.truncate(k); + + iter.for_each(|val| { + if comparator(&val, &buf[k - 1]) != Ordering::Less { + return; + } + + buf.push(val); + + if buf.len() == 2 * k { + buf.select_nth_unstable_by(k - 1, &mut comparator); + buf.truncate(k); + } + }); + + buf.sort_unstable_by(&mut comparator); + buf.truncate(k); + buf +} + #[inline] pub(crate) fn key_to_cmp(mut key: F) -> impl FnMut(&T, &T) -> Ordering where diff --git a/src/lib.rs b/src/lib.rs index b98e03f29..10abe964a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3153,6 +3153,105 @@ pub trait Itertools: Iterator { self.k_smallest_by(k, k_smallest::key_to_cmp(key)) } + /// Sort the k smallest elements into a new iterator, in ascending order, relaxing the amount of memory required. + /// + /// **Note:** This consumes the entire iterator, and returns the result + /// as a new iterator that owns its elements. If the input contains + /// less than k elements, the result is equivalent to `self.sorted()`. + /// + /// This is guaranteed to use `2 * k * sizeof(Self::Item) + O(1)` memory + /// and `O(n + k log k)` time, with `n` the number of elements in the input, + /// meaning it uses more memory than the minimum obtained by [`k_smallest`](Itertools::k_smallest) + /// but achieves linear time in the number of elements. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// **Note:** This is functionally-equivalent to `self.sorted().take(k)` + /// but much more efficient. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_smallest = numbers + /// .into_iter() + /// .k_smallest_relaxed(5); + /// + /// itertools::assert_equal(five_smallest, 0..5); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_smallest_relaxed(self, k: usize) -> VecIntoIter + where + Self: Sized, + Self::Item: Ord, + { + self.k_smallest_relaxed_by(k, Ord::cmp) + } + + /// Sort the k smallest elements into a new iterator using the provided comparison, relaxing the amount of memory required. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// This corresponds to `self.sorted_by(cmp).take(k)` in the same way that + /// [`k_smallest_relaxed`](Itertools::k_smallest_relaxed) corresponds to `self.sorted().take(k)`, + /// in both semantics and complexity. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_smallest = numbers + /// .into_iter() + /// .k_smallest_relaxed_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b))); + /// + /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_smallest_relaxed_by(self, k: usize, cmp: F) -> VecIntoIter + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + k_smallest::k_smallest_relaxed_general(self, k, cmp).into_iter() + } + + /// Return the elements producing the k smallest outputs of the provided function, relaxing the amount of memory required. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// This corresponds to `self.sorted_by_key(key).take(k)` in the same way that + /// [`k_smallest_relaxed`](Itertools::k_smallest_relaxed) corresponds to `self.sorted().take(k)`, + /// in both semantics and complexity. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_smallest = numbers + /// .into_iter() + /// .k_smallest_relaxed_by_key(5, |n| (n % 7, *n)); + /// + /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_smallest_relaxed_by_key(self, k: usize, key: F) -> VecIntoIter + where + Self: Sized, + F: FnMut(&Self::Item) -> K, + K: Ord, + { + self.k_smallest_relaxed_by(k, k_smallest::key_to_cmp(key)) + } + /// Sort the k largest elements into a new iterator, in descending order. /// /// The sorted iterator, if directly collected to a `Vec`, is converted @@ -3243,6 +3342,94 @@ pub trait Itertools: Iterator { self.k_largest_by(k, k_smallest::key_to_cmp(key)) } + /// Sort the k largest elements into a new iterator, in descending order, relaxing the amount of memory required. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// It is semantically equivalent to [`k_smallest_relaxed`](Itertools::k_smallest_relaxed) + /// with a reversed `Ord`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest_relaxed(5); + /// + /// itertools::assert_equal(five_largest, vec![14, 13, 12, 11, 10]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_largest_relaxed(self, k: usize) -> VecIntoIter + where + Self: Sized, + Self::Item: Ord, + { + self.k_largest_relaxed_by(k, Self::Item::cmp) + } + + /// Sort the k largest elements into a new iterator using the provided comparison, relaxing the amount of memory required. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// Functionally equivalent to [`k_smallest_relaxed_by`](Itertools::k_smallest_relaxed_by) + /// with a reversed `Ord`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest_relaxed_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b))); + /// + /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_largest_relaxed_by(self, k: usize, mut cmp: F) -> VecIntoIter + where + Self: Sized, + F: FnMut(&Self::Item, &Self::Item) -> Ordering, + { + self.k_smallest_relaxed_by(k, move |a, b| cmp(b, a)) + } + + /// Return the elements producing the k largest outputs of the provided function, relaxing the amount of memory required. + /// + /// The sorted iterator, if directly collected to a `Vec`, is converted + /// without any extra copying or allocation cost. + /// + /// Functionally equivalent to [`k_smallest_relaxed_by_key`](Itertools::k_smallest_relaxed_by_key) + /// with a reversed `Ord`. + /// + /// ``` + /// use itertools::Itertools; + /// + /// // A random permutation of 0..15 + /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5]; + /// + /// let five_largest = numbers + /// .into_iter() + /// .k_largest_relaxed_by_key(5, |n| (n % 7, *n)); + /// + /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]); + /// ``` + #[cfg(feature = "use_alloc")] + fn k_largest_relaxed_by_key(self, k: usize, key: F) -> VecIntoIter + where + Self: Sized, + F: FnMut(&Self::Item) -> K, + K: Ord, + { + self.k_largest_relaxed_by(k, k_smallest::key_to_cmp(key)) + } + /// Consumes the iterator and return an iterator of the last `n` elements. /// /// The iterator, if directly collected to a `VecDeque`, is converted diff --git a/tests/test_std.rs b/tests/test_std.rs index f626a17d7..ad391faad 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -528,6 +528,42 @@ qc::quickcheck! { it::assert_equal(largest_by, sorted_largest.clone()); it::assert_equal(largest_by_key, sorted_largest); } + + fn k_smallest_relaxed_range(n: i64, m: u16, k: u16) -> () { + // u16 is used to constrain k and m to 0..2¹⁶, + // otherwise the test could use too much memory. + let (k, m) = (k as usize, m as u64); + + let mut v: Vec<_> = (n..n.saturating_add(m as _)).collect(); + // Generate a random permutation of n..n+m + v.shuffle(&mut thread_rng()); + + // Construct the right answers for the top and bottom elements + let mut sorted = v.clone(); + sorted.sort(); + // how many elements are we checking + let num_elements = min(k, m as _); + + // Compute the top and bottom k in various combinations + let sorted_smallest = sorted[..num_elements].iter().cloned(); + let smallest = v.iter().cloned().k_smallest_relaxed(k); + let smallest_by = v.iter().cloned().k_smallest_relaxed_by(k, Ord::cmp); + let smallest_by_key = v.iter().cloned().k_smallest_relaxed_by_key(k, |&x| x); + + let sorted_largest = sorted[sorted.len() - num_elements..].iter().rev().cloned(); + let largest = v.iter().cloned().k_largest_relaxed(k); + let largest_by = v.iter().cloned().k_largest_relaxed_by(k, Ord::cmp); + let largest_by_key = v.iter().cloned().k_largest_relaxed_by_key(k, |&x| x); + + // Check the variations produce the same answers and that they're right + it::assert_equal(smallest, sorted_smallest.clone()); + it::assert_equal(smallest_by, sorted_smallest.clone()); + it::assert_equal(smallest_by_key, sorted_smallest); + + it::assert_equal(largest, sorted_largest.clone()); + it::assert_equal(largest_by, sorted_largest.clone()); + it::assert_equal(largest_by_key, sorted_largest); + } } #[derive(Clone, Debug)] @@ -572,8 +608,11 @@ where I::Item: Ord + Debug, { let j = i.clone(); + let i1 = i.clone(); + let j1 = i.clone(); let k = k as usize; - it::assert_equal(i.k_smallest(k), j.sorted().take(k)) + it::assert_equal(i.k_smallest(k), j.sorted().take(k)); + it::assert_equal(i1.k_smallest_relaxed(k), j1.sorted().take(k)); } // Similar to `k_smallest_sort` but for our custom heap implementation. @@ -583,8 +622,11 @@ where I::Item: Ord + Debug, { let j = i.clone(); + let i1 = i.clone(); + let j1 = i.clone(); let k = k as usize; - it::assert_equal(i.k_smallest_by(k, Ord::cmp), j.sorted().take(k)) + it::assert_equal(i.k_smallest_by(k, Ord::cmp), j.sorted().take(k)); + it::assert_equal(i1.k_smallest_relaxed_by(k, Ord::cmp), j1.sorted().take(k)); } macro_rules! generic_test { From e41a62bc3172cf1ea4afd8d580964691dcb50077 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sat, 27 Apr 2024 21:04:22 +0200 Subject: [PATCH 611/633] Add benchmarks for k_smallest implementation variants. --- Cargo.toml | 6 ++++- benches/k_smallest.rs | 61 +++++++++++++++++++++++++++++++++++++++++++ src/k_smallest.rs | 1 + 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 benches/k_smallest.rs diff --git a/Cargo.toml b/Cargo.toml index 800614657..6ec88a9e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ either = { version = "1.0", default-features = false } [dev-dependencies] rand = "0.7" -criterion = "0.4.0" +criterion = { version = "0.4.0", features = ["html_reports"] } paste = "1.0.0" # Used in test_std to instantiate generic tests permutohedron = "0.2" quickcheck = { version = "0.9", default_features = false } @@ -75,3 +75,7 @@ harness = false [[bench]] name = "specializations" harness = false + +[[bench]] +name = "k_smallest" +harness = false diff --git a/benches/k_smallest.rs b/benches/k_smallest.rs new file mode 100644 index 000000000..509ed7f8a --- /dev/null +++ b/benches/k_smallest.rs @@ -0,0 +1,61 @@ +use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; +use itertools::Itertools; +use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng}; + +fn strict(b: &mut Bencher, (k, vals): &(usize, &Vec)) { + b.iter(|| black_box(vals.iter()).k_smallest(*k)) +} + +fn relaxed(b: &mut Bencher, (k, vals): &(usize, &Vec)) { + b.iter(|| black_box(vals.iter()).k_smallest_relaxed(*k)) +} + +fn ascending(n: usize) -> Vec { + (0..n).collect() +} + +fn random(n: usize) -> Vec { + let mut vals = (0..n).collect_vec(); + vals.shuffle(&mut StdRng::seed_from_u64(42)); + vals +} + +fn descending(n: usize) -> Vec { + (0..n).rev().collect() +} + +fn k_smallest(c: &mut Criterion, order: &str, vals: fn(usize) -> Vec) { + let mut g = c.benchmark_group(format!("k-smallest/{order}")); + + for log_n in 20..23 { + let n = 1 << log_n; + + let vals = vals(n); + + for log_k in 7..10 { + let k = 1 << log_k; + + let params = format!("{log_n}/{log_k}"); + let input = (k, &vals); + g.bench_with_input(BenchmarkId::new("strict", ¶ms), &input, strict); + g.bench_with_input(BenchmarkId::new("relaxed", ¶ms), &input, relaxed); + } + } + + g.finish() +} + +fn k_smallest_asc(c: &mut Criterion) { + k_smallest(c, "asc", ascending); +} + +fn k_smallest_rand(c: &mut Criterion) { + k_smallest(c, "rand", random); +} + +fn k_smallest_desc(c: &mut Criterion) { + k_smallest(c, "desc", descending); +} + +criterion_group!(benches, k_smallest_asc, k_smallest_rand, k_smallest_desc); +criterion_main!(benches); diff --git a/src/k_smallest.rs b/src/k_smallest.rs index 083c1b00d..7e4ace262 100644 --- a/src/k_smallest.rs +++ b/src/k_smallest.rs @@ -114,6 +114,7 @@ where return; } + assert_ne!(buf.len(), buf.capacity()); buf.push(val); if buf.len() == 2 * k { From 30678931c1595eee7bb5106015146d1d114750ec Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:09:13 +0200 Subject: [PATCH 612/633] Fix/ignore warnings within doctests --- src/free.rs | 6 ++++-- src/kmerge_impl.rs | 1 + src/lib.rs | 31 ++++++++++++++++++++----------- src/merge_join.rs | 1 + src/zip_eq_impl.rs | 1 + 5 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/free.rs b/src/free.rs index 8d0bcf3ea..4c6820543 100644 --- a/src/free.rs +++ b/src/free.rs @@ -36,7 +36,7 @@ pub use crate::zip_eq_impl::zip_eq; /// ``` /// use itertools::intersperse; /// -/// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]); +/// itertools::assert_equal(intersperse(0..3, 8), vec![0, 8, 1, 8, 2]); /// ``` pub fn intersperse(iterable: I, element: I::Item) -> Intersperse where @@ -55,7 +55,7 @@ where /// use itertools::intersperse_with; /// /// let mut i = 10; -/// itertools::assert_equal(intersperse_with((0..3), || { i -= 1; i }), vec![0, 9, 1, 8, 2]); +/// itertools::assert_equal(intersperse_with(0..3, || { i -= 1; i }), vec![0, 9, 1, 8, 2]); /// assert_eq!(i, 8); /// ``` pub fn intersperse_with(iterable: I, element: F) -> IntersperseWith @@ -75,6 +75,7 @@ where /// /// for (i, elt) in enumerate(&[1, 2, 3]) { /// /* loop body */ +/// # let _ = (i, elt); /// } /// ``` pub fn enumerate(iterable: I) -> iter::Enumerate @@ -93,6 +94,7 @@ where /// /// for elt in rev(&[1, 2, 3]) { /// /* loop body */ +/// # let _ = elt; /// } /// ``` pub fn rev(iterable: I) -> iter::Rev diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs index 13c935b28..9ea73f9e8 100644 --- a/src/kmerge_impl.rs +++ b/src/kmerge_impl.rs @@ -134,6 +134,7 @@ impl bool> KMergePredicate for F { /// /// for elt in kmerge(vec![vec![0, 2, 4], vec![1, 3, 5], vec![6, 7]]) { /// /* loop body */ +/// # let _ = elt; /// } /// ``` pub fn kmerge(iterable: I) -> KMerge<::IntoIter> diff --git a/src/lib.rs b/src/lib.rs index 10abe964a..0f5ea8ee2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ #![warn(missing_docs, clippy::default_numeric_fallback)] #![crate_name = "itertools"] #![cfg_attr(not(feature = "use_std"), no_std)] +#![doc(test(attr(deny(warnings), allow(deprecated, unstable_name_collisions))))] //! Extra iterator adaptors, functions and macros. //! @@ -8,6 +9,7 @@ //! the [`Itertools`] trait: //! //! ``` +//! # #[allow(unused_imports)] //! use itertools::Itertools; //! ``` //! @@ -29,6 +31,7 @@ //! //! for elt in interleave(&[1, 2, 3], &[2, 3, 4]) { //! /* loop body */ +//! # let _ = elt; //! } //! ``` //! @@ -248,6 +251,7 @@ mod ziptuple; /// // from (0, 0, 0), (0, 0, 1), .., (0, 1, 0), (0, 1, 1), .. etc until (3, 3, 3) /// for (i, j, k) in iproduct!(0..4, 0..4, 0..4) { /// // .. +/// # let _ = (i, j, k); /// } /// # } /// ``` @@ -375,16 +379,16 @@ macro_rules! izip { /// /// Invocations of `chain!` with one argument expand to [`arg.into_iter()`](IntoIterator): /// ``` -/// use std::{ops::Range, slice}; +/// use std::ops::Range; /// use itertools::chain; -/// let _: as IntoIterator>::IntoIter = chain!((2..6),); // trailing comma optional! +/// let _: as IntoIterator>::IntoIter = chain!(2..6,); // trailing comma optional! /// let _: <&[_] as IntoIterator>::IntoIter = chain!(&[2, 3, 4]); /// ``` /// /// Invocations of `chain!` with multiple arguments [`.into_iter()`](IntoIterator) each /// argument, and then [`chain`] them together: /// ``` -/// use std::{iter::*, ops::Range, slice}; +/// use std::{iter::*, slice}; /// use itertools::{assert_equal, chain}; /// /// // e.g., this: @@ -1902,7 +1906,7 @@ pub trait Itertools: Iterator { /// use itertools::Itertools; /// /// let input = vec![vec![1], vec![3, 2, 1]]; - /// let it = input.into_iter().update(|mut v| v.push(0)); + /// let it = input.into_iter().update(|v| v.push(0)); /// itertools::assert_equal(it, vec![vec![1, 0], vec![3, 2, 1, 0]]); /// ``` fn update(self, updater: F) -> Update @@ -2162,7 +2166,7 @@ pub trait Itertools: Iterator { /// ``` /// use itertools::Itertools; /// - /// let mut iter = "αβγ".chars().dropping(2); + /// let iter = "αβγ".chars().dropping(2); /// itertools::assert_equal(iter, "γ".chars()); /// ``` /// @@ -2246,14 +2250,17 @@ pub trait Itertools: Iterator { /// /// fn process_dir_entries(entries: &[fs::DirEntry]) { /// // ... + /// # let _ = entries; /// } /// - /// fn do_stuff() -> std::io::Result<()> { + /// fn do_stuff() -> io::Result<()> { /// let entries: Vec<_> = fs::read_dir(".")?.try_collect()?; /// process_dir_entries(&entries); /// /// Ok(()) /// } + /// + /// # let _ = do_stuff; /// ``` fn try_collect(self) -> Result where @@ -2404,6 +2411,7 @@ pub trait Itertools: Iterator { /// accum = f(accum, 1); /// accum = f(accum, 2); /// accum = f(accum, 3); + /// # let _ = accum; /// ``` /// /// With a `start` value of 0 and an addition as folding function, @@ -2554,16 +2562,16 @@ pub trait Itertools: Iterator { /// assert_eq!((1..8).map(|x| x.to_string()).tree_reduce(f), /// Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))"))); /// - /// // Like fold1, an empty iterator produces None + /// // Like reduce, an empty iterator produces None /// assert_eq!((0..0).tree_reduce(|x, y| x * y), None); /// - /// // tree_reduce matches fold1 for associative operations... + /// // tree_reduce matches reduce for associative operations... /// assert_eq!((0..10).tree_reduce(|x, y| x + y), - /// (0..10).fold1(|x, y| x + y)); + /// (0..10).reduce(|x, y| x + y)); /// /// // ...but not for non-associative ones /// assert_ne!((0..10).tree_reduce(|x, y| x - y), - /// (0..10).fold1(|x, y| x - y)); + /// (0..10).reduce(|x, y| x - y)); /// /// let mut total_len_reduce = 0; /// let reduce_res = (1..100).map(|x| x.to_string()) @@ -4350,7 +4358,7 @@ pub trait Itertools: Iterator { /// # Examples /// ``` /// # use itertools::Itertools; - /// let counts = [1, 1, 1, 3, 3, 5].into_iter().counts(); + /// let counts = [1, 1, 1, 3, 3, 5].iter().counts(); /// assert_eq!(counts[&1], 3); /// assert_eq!(counts[&3], 2); /// assert_eq!(counts[&5], 1); @@ -4376,6 +4384,7 @@ pub trait Itertools: Iterator { /// # use itertools::Itertools; /// struct Character { /// first_name: &'static str, + /// # #[allow(dead_code)] /// last_name: &'static str, /// } /// diff --git a/src/merge_join.rs b/src/merge_join.rs index c0de35f90..5f4a605fa 100644 --- a/src/merge_join.rs +++ b/src/merge_join.rs @@ -31,6 +31,7 @@ pub type Merge = MergeBy; /// /// for elt in merge(&[1, 2, 3], &[2, 3, 4]) { /// /* loop body */ +/// # let _ = elt; /// } /// ``` pub fn merge( diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs index 6d3b68296..3240a40eb 100644 --- a/src/zip_eq_impl.rs +++ b/src/zip_eq_impl.rs @@ -21,6 +21,7 @@ pub struct ZipEq { /// let data = [1, 2, 3, 4, 5]; /// for (a, b) in zip_eq(&data[..data.len() - 1], &data[1..]) { /// /* loop body */ +/// # let _ = (a, b); /// } /// ``` pub fn zip_eq(i: I, j: J) -> ZipEq From a20444fb87729b36f97db0137c78c4473b84044c Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Fri, 12 Jul 2024 17:26:14 +0000 Subject: [PATCH 613/633] Allow `Q: ?Sized` in `Itertools::contains` --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 0f5ea8ee2..a8de36749 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2068,7 +2068,7 @@ pub trait Itertools: Iterator { where Self: Sized, Self::Item: Borrow, - Q: PartialEq, + Q: PartialEq + ?Sized, { self.any(|x| x.borrow() == query) } From a4a82e4b97eb76687c2a57cdfd2e5343ff507827 Mon Sep 17 00:00:00 2001 From: Ciel <47144701+Ciel-MC@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:52:26 +0800 Subject: [PATCH 614/633] Give `take_while_inclusive` a more intuitive doc alias --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index a8de36749..0082aa989 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1594,6 +1594,7 @@ pub trait Itertools: Iterator { /// .collect(); /// let expected: Vec<_> = vec![1, 2, 3].into_iter().map(NoCloneImpl).collect(); /// assert_eq!(filtered, expected); + #[doc(alias = "take_until")] fn take_while_inclusive(self, accept: F) -> TakeWhileInclusive where Self: Sized, From 4f49f2636aa95283cc4c921e6a3e04efe317fda5 Mon Sep 17 00:00:00 2001 From: philipp Date: Sun, 18 Aug 2024 10:47:02 +0200 Subject: [PATCH 615/633] Fix clippy 1.80 lints If documentation should be part of bullet point, we need to indent. --- src/diff.rs | 2 +- src/lib.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/diff.rs b/src/diff.rs index c6d99657e..df88d8032 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -1,7 +1,7 @@ //! "Diff"ing iterators for caching elements to sequential collections without requiring the new //! elements' iterator to be `Clone`. //! -//! - [`Diff`] (produced by the [`diff_with`] function) +//! [`Diff`] (produced by the [`diff_with`] function) //! describes the difference between two non-`Clone` iterators `I` and `J` after breaking ASAP from //! a lock-step comparison. diff --git a/src/lib.rs b/src/lib.rs index 0082aa989..0465d0801 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -427,13 +427,13 @@ macro_rules! chain { /// This trait defines a number of methods. They are divided into two groups: /// /// * *Adaptors* take an iterator and parameter as input, and return -/// a new iterator value. These are listed first in the trait. An example -/// of an adaptor is [`.interleave()`](Itertools::interleave) +/// a new iterator value. These are listed first in the trait. An example +/// of an adaptor is [`.interleave()`](Itertools::interleave) /// /// * *Regular methods* are those that don't return iterators and instead -/// return a regular value of some other kind. -/// [`.next_tuple()`](Itertools::next_tuple) is an example and the first regular -/// method in the list. +/// return a regular value of some other kind. +/// [`.next_tuple()`](Itertools::next_tuple) is an example and the first regular +/// method in the list. pub trait Itertools: Iterator { // adaptors From e2a0a6e9759f9f8851aa8735ca79e7498dfdf8e8 Mon Sep 17 00:00:00 2001 From: philipp Date: Wed, 21 Aug 2024 20:14:43 +0200 Subject: [PATCH 616/633] ConsTuples is a MapSpecialCase --- src/cons_tuples_impl.rs | 45 ++++++++++++----------------------------- 1 file changed, 13 insertions(+), 32 deletions(-) diff --git a/src/cons_tuples_impl.rs b/src/cons_tuples_impl.rs index 9ab309478..7e86260b4 100644 --- a/src/cons_tuples_impl.rs +++ b/src/cons_tuples_impl.rs @@ -1,24 +1,15 @@ +use crate::adaptors::map::{MapSpecialCase, MapSpecialCaseFn}; + macro_rules! impl_cons_iter( ($_A:ident, $_B:ident, ) => (); // stop ($A:ident, $($B:ident,)*) => ( impl_cons_iter!($($B,)*); #[allow(non_snake_case)] - impl Iterator for ConsTuples - where Iter: Iterator, - { - type Item = ($($B,)* X, ); - fn next(&mut self) -> Option { - self.iter.next().map(|(($($B,)*), x)| ($($B,)* x, )) - } - - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } - fn fold(self, accum: Acc, mut f: Fold) -> Acc - where Fold: FnMut(Acc, Self::Item) -> Acc, - { - self.iter.fold(accum, move |acc, (($($B,)*), x)| f(acc, ($($B,)* x, ))) + impl<$($B),*, X> MapSpecialCaseFn<(($($B,)*), X)> for ConsTuplesFn { + type Out = ($($B,)* X, ); + fn call(&mut self, (($($B,)*), X): (($($B,)*), X)) -> Self::Out { + ($($B,)* X, ) } } ); @@ -26,33 +17,23 @@ macro_rules! impl_cons_iter( impl_cons_iter!(A, B, C, D, E, F, G, H, I, J, K, L,); +#[derive(Debug, Clone)] +pub struct ConsTuplesFn; + /// An iterator that maps an iterator of tuples like /// `((A, B), C)` to an iterator of `(A, B, C)`. /// /// Used by the `iproduct!()` macro. -#[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -#[derive(Debug)] -pub struct ConsTuples -where - I: Iterator, -{ - iter: I, -} - -impl Clone for ConsTuples -where - I: Clone + Iterator, -{ - clone_fields!(iter); -} +pub type ConsTuples = MapSpecialCase; /// Create an iterator that maps for example iterators of /// `((A, B), C)` to `(A, B, C)`. -pub fn cons_tuples(iterable: I) -> ConsTuples +pub fn cons_tuples(iterable: I) -> ConsTuples where - I: IntoIterator, + I: IntoIterator, { ConsTuples { iter: iterable.into_iter(), + f: ConsTuplesFn, } } From 91f96182488788e01a40742c5269a0a3d02da49f Mon Sep 17 00:00:00 2001 From: Axel Karjalainen Date: Wed, 7 Aug 2024 22:24:51 +0300 Subject: [PATCH 617/633] Add note about Result to `find_or_last` and `find_or_first` See GH-983 --- src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 0465d0801..604529a59 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1999,6 +1999,15 @@ pub trait Itertools: Iterator { /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 5), Some(&4)); /// assert_eq!(numbers.iter().find_or_last(|&&x| x > 2), Some(&3)); /// assert_eq!(std::iter::empty::().find_or_last(|&x| x > 5), None); + /// + /// // An iterator of Results can return the first Ok or the last Err: + /// let input = vec![Err(()), Ok(11), Err(()), Ok(22)]; + /// assert_eq!(input.into_iter().find_or_last(Result::is_ok), Some(Ok(11))); + /// + /// let input: Vec> = vec![Err(11), Err(22)]; + /// assert_eq!(input.into_iter().find_or_last(Result::is_ok), Some(Err(22))); + /// + /// assert_eq!(std::iter::empty::>().find_or_last(Result::is_ok), None); /// ``` fn find_or_last

(mut self, mut predicate: P) -> Option where @@ -2027,6 +2036,15 @@ pub trait Itertools: Iterator { /// assert_eq!(numbers.iter().find_or_first(|&&x| x > 5), Some(&1)); /// assert_eq!(numbers.iter().find_or_first(|&&x| x > 2), Some(&3)); /// assert_eq!(std::iter::empty::().find_or_first(|&x| x > 5), None); + /// + /// // An iterator of Results can return the first Ok or the first Err: + /// let input = vec![Err(()), Ok(11), Err(()), Ok(22)]; + /// assert_eq!(input.into_iter().find_or_first(Result::is_ok), Some(Ok(11))); + /// + /// let input: Vec> = vec![Err(11), Err(22)]; + /// assert_eq!(input.into_iter().find_or_first(Result::is_ok), Some(Err(11))); + /// + /// assert_eq!(std::iter::empty::>().find_or_first(Result::is_ok), None); /// ``` fn find_or_first

(mut self, mut predicate: P) -> Option where From a31e14fc73398bb83482f1ec8fdf59dc584f7561 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 3 Jul 2024 22:04:19 +0200 Subject: [PATCH 618/633] Increase discoverability of `merge_join_by` (Unix-like `comm`) --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 604529a59..9670390a9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1091,7 +1091,9 @@ pub trait Itertools: Iterator { /// let b = (0..10).step_by(3); /// /// itertools::assert_equal( - /// a.merge_join_by(b, |i, j| i.cmp(j)), + /// // This performs a diff in the style of the Unix command comm(1), + /// // generalized to arbitrary types rather than text. + /// a.merge_join_by(b, Ord::cmp), /// vec![Both(0, 0), Left(2), Right(3), Left(4), Both(6, 6), Left(1), Right(9)] /// ); /// ``` @@ -1123,6 +1125,7 @@ pub trait Itertools: Iterator { /// ); /// ``` #[inline] + #[doc(alias = "comm")] fn merge_join_by(self, other: J, cmp_fn: F) -> MergeJoinBy where J: IntoIterator, From 1bb419b9a159abd0ca0f091a91accc9ea84aa8af Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 15:32:14 +0200 Subject: [PATCH 619/633] array_combinations using array::from_fn --- src/array_combinations.rs | 166 ++++++++++++++++++++++++++++++++++++++ src/combinations.rs | 14 +++- src/lazy_buffer.rs | 4 + src/lib.rs | 47 +++++++++++ 4 files changed, 228 insertions(+), 3 deletions(-) create mode 100644 src/array_combinations.rs diff --git a/src/array_combinations.rs b/src/array_combinations.rs new file mode 100644 index 000000000..9de275234 --- /dev/null +++ b/src/array_combinations.rs @@ -0,0 +1,166 @@ +use core::iter::FusedIterator; +use core::{array, fmt}; + +use crate::combinations::{n_and_count, remaining_for}; +use crate::lazy_buffer::LazyBuffer; + +/// An iterator to iterate through all combinations in an iterator of `Clone`-able items that +/// produces arrays of a specific size. +/// +/// See [`.array_combinations()`](crate::Itertools::array_combinations) for more +/// information. +#[derive(Clone)] +#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] +pub struct ArrayCombinations +where + I::Item: Clone, +{ + indices: [usize; K], + pool: LazyBuffer, + first: bool, +} + +/// Create a new `ArrayCombinations` from a clonable iterator. +pub fn array_combinations(iter: I) -> ArrayCombinations +where + I::Item: Clone, +{ + let indices = array::from_fn(|i| i); + let pool = LazyBuffer::new(iter); + + ArrayCombinations { + indices, + pool, + first: true, + } +} + +impl ArrayCombinations +where + I::Item: Clone, +{ + /// Returns the (current) length of the pool from which combination elements are + /// selected. This value can change between invocations of [`next`](Combinations::next). + #[inline] + pub fn n(&self) -> usize { + self.pool.len() + } + + /// Initialises the iterator by filling a buffer with elements from the + /// iterator. Returns true if there are no combinations, false otherwise. + fn init(&mut self) -> bool { + self.pool.prefill(K); + let done = K > self.n(); + if !done { + self.first = false; + } + + done + } + + /// Increments indices representing the combination to advance to the next + /// (in lexicographic order by increasing sequence) combination. For example + /// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...` + /// + /// Returns true if we've run out of combinations, false otherwise. + fn increment_indices(&mut self) -> bool { + if K == 0 { + return true; // Done + } + + // Scan from the end, looking for an index to increment + let mut i: usize = K - 1; + + // Check if we need to consume more from the iterator + if self.indices[i] == self.pool.len() - 1 { + _ = self.pool.get_next(); // may change pool size + } + + while self.indices[i] == i + self.pool.len() - K { + if i > 0 { + i -= 1; + } else { + // Reached the last combination + return true; + } + } + + // Increment index, and reset the ones to its right + self.indices[i] += 1; + for j in i + 1..K { + self.indices[j] = self.indices[j - 1] + 1; + } + + // If we've made it this far, we haven't run out of combos + false + } + + /// Returns the n-th item or the number of successful steps. + pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> + where + I::Item: Clone, + { + let done = if self.first { + self.init() + } else { + self.increment_indices() + }; + if done { + return Err(0); + } + for i in 0..n { + if self.increment_indices() { + return Err(i + 1); + } + } + Ok(self.pool.get_array(self.indices)) + } +} + +impl Iterator for ArrayCombinations +where + I::Item: Clone, +{ + type Item = [I::Item; K]; + + fn next(&mut self) -> Option { + let done = if self.first { + self.init() + } else { + self.increment_indices() + }; + + (!done).then(|| self.pool.get_array(self.indices)) + } + + fn nth(&mut self, n: usize) -> Option { + self.try_nth(n).ok() + } + + fn size_hint(&self) -> (usize, Option) { + let (mut low, mut upp) = self.pool.size_hint(); + low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); + (low, upp) + } + + #[inline] + fn count(self) -> usize { + n_and_count(self.pool, self.first, &self.indices).1 + } +} + +impl fmt::Debug for ArrayCombinations +where + I: Iterator + fmt::Debug, + I::Item: Clone + fmt::Debug, +{ + debug_fmt_fields!(ArrayCombinations, indices, pool, first); +} + +impl FusedIterator for ArrayCombinations +where + I: FusedIterator, + I::Item: Clone, +{ +} diff --git a/src/combinations.rs b/src/combinations.rs index 6bb2f3ec6..423ffa85b 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -91,8 +91,7 @@ impl Combinations { pool, first, } = self; - let n = pool.count(); - (n, remaining_for(n, first, &indices).unwrap()) + n_and_count(pool, first, &indices) } /// Initialises the iterator by filling a buffer with elements from the @@ -210,8 +209,17 @@ where { } +pub(crate) fn n_and_count( + pool: LazyBuffer, + first: bool, + indices: &[usize], +) -> (usize, usize) { + let n = pool.count(); + (n, remaining_for(n, first, indices).unwrap()) +} + /// For a given size `n`, return the count of remaining combinations or None if it would overflow. -fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { +pub(crate) fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); if n < k { Some(0) diff --git a/src/lazy_buffer.rs b/src/lazy_buffer.rs index fefcff8f5..fafa5f726 100644 --- a/src/lazy_buffer.rs +++ b/src/lazy_buffer.rs @@ -59,6 +59,10 @@ where pub fn get_at(&self, indices: &[usize]) -> Vec { indices.iter().map(|i| self.buffer[*i].clone()).collect() } + + pub fn get_array(&self, indices: [usize; K]) -> [I::Item; K] { + indices.map(|i| self.buffer[i].clone()) + } } impl Index for LazyBuffer diff --git a/src/lib.rs b/src/lib.rs index 9670390a9..c2bb0d9b8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ pub mod structs { FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, TakeWhileRef, TupleCombinations, Update, WhileSome, }; + pub use crate::array_combinations::ArrayCombinations; #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; #[cfg(feature = "use_alloc")] @@ -177,6 +178,7 @@ pub use crate::either_or_both::EitherOrBoth; pub mod free; #[doc(inline)] pub use crate::free::*; +mod array_combinations; #[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] @@ -1674,6 +1676,51 @@ pub trait Itertools: Iterator { adaptors::tuple_combinations(self) } + /// Return an iterator adaptor that iterates over the combinations of the + /// elements from an iterator. + /// + /// Iterator element can be any array of type `Self::Item`. + /// + /// # Guarantees + /// + /// If the adapted iterator is deterministic, + /// this iterator adapter yields items in a reliable order. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let mut v = Vec::new(); + /// for [a, b] in (1..5).array_combinations() { + /// v.push([a, b]); + /// } + /// assert_eq!(v, vec![[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]); + /// + /// let mut it = (1..5).array_combinations(); + /// assert_eq!(Some([1, 2, 3]), it.next()); + /// assert_eq!(Some([1, 2, 4]), it.next()); + /// assert_eq!(Some([1, 3, 4]), it.next()); + /// assert_eq!(Some([2, 3, 4]), it.next()); + /// assert_eq!(None, it.next()); + /// + /// // this requires a type hint + /// let it = (1..5).array_combinations::<3>(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); + /// + /// // you can also specify the complete type + /// use itertools::ArrayCombinations; + /// use std::ops::Range; + /// + /// let it: ArrayCombinations, 3> = (1..5).array_combinations(); + /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); + /// ``` + fn array_combinations(self) -> ArrayCombinations + where + Self: Sized + Clone, + Self::Item: Clone, + { + array_combinations::array_combinations(self) + } + /// Return an iterator adaptor that iterates over the `k`-length combinations of /// the elements from an iterator. /// From 9ec6033a12027ecafbf2b5c37b04590e275c9ad6 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 16:30:18 +0200 Subject: [PATCH 620/633] added specialization tests --- tests/specializations.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/specializations.rs b/tests/specializations.rs index 3e4831024..e6694c8e7 100644 --- a/tests/specializations.rs +++ b/tests/specializations.rs @@ -273,6 +273,16 @@ quickcheck! { test_specializations(&v.into_iter().intersperse_with(|| 0)); } + fn array_combinations(v: Vec) -> TestResult { + if v.len() > 10 { + return TestResult::discard(); + } + test_specializations(&v.iter().array_combinations::<1>()); + test_specializations(&v.iter().array_combinations::<2>()); + test_specializations(&v.iter().array_combinations::<3>()); + TestResult::passed() + } + fn combinations(a: Vec, n: u8) -> TestResult { if n > 3 || a.len() > 8 { return TestResult::discard(); From 200973422f4e39982078d761703ab1750cac3b8d Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 16:33:19 +0200 Subject: [PATCH 621/633] Documentation for `n_and_count` --- src/combinations.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/combinations.rs b/src/combinations.rs index 423ffa85b..9e7492e95 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -209,6 +209,7 @@ where { } +/// Return the length of the inner iterator and the count of remaining combinations. pub(crate) fn n_and_count( pool: LazyBuffer, first: bool, From 36f1f40d2ff5d6e7ac1d589f8889f5db64a07ba9 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 18:05:51 +0200 Subject: [PATCH 622/633] fixed copy-paste error --- src/array_combinations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/array_combinations.rs b/src/array_combinations.rs index 9de275234..bd2a067d3 100644 --- a/src/array_combinations.rs +++ b/src/array_combinations.rs @@ -40,7 +40,7 @@ where I::Item: Clone, { /// Returns the (current) length of the pool from which combination elements are - /// selected. This value can change between invocations of [`next`](Combinations::next). + /// selected. This value can change between invocations of [`next`](ArrayCombinations::next). #[inline] pub fn n(&self) -> usize { self.pool.len() From 4a6acd9da5730dc2aaf520c609f138fab5f63814 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 18:08:14 +0200 Subject: [PATCH 623/633] restricted to `use_alloc` --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c2bb0d9b8..25c94a3ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,6 +96,7 @@ pub mod structs { FilterOk, Interleave, InterleaveShortest, MapInto, MapOk, Positions, Product, PutBack, TakeWhileRef, TupleCombinations, Update, WhileSome, }; + #[cfg(feature = "use_alloc")] pub use crate::array_combinations::ArrayCombinations; #[cfg(feature = "use_alloc")] pub use crate::combinations::Combinations; @@ -178,6 +179,7 @@ pub use crate::either_or_both::EitherOrBoth; pub mod free; #[doc(inline)] pub use crate::free::*; +#[cfg(feature = "use_alloc")] mod array_combinations; #[cfg(feature = "use_alloc")] mod combinations; @@ -1713,6 +1715,7 @@ pub trait Itertools: Iterator { /// let it: ArrayCombinations, 3> = (1..5).array_combinations(); /// itertools::assert_equal(it, vec![[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]]); /// ``` + #[cfg(feature = "use_alloc")] fn array_combinations(self) -> ArrayCombinations where Self: Sized + Clone, From deb53ba21065e6adbcfdd8af120f939733cc52c6 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Tue, 10 Sep 2024 22:43:58 +0200 Subject: [PATCH 624/633] refactored to share code --- src/array_combinations.rs | 166 ----------------------------------- src/combinations.rs | 180 ++++++++++++++++++++++++++------------ src/lib.rs | 8 +- 3 files changed, 126 insertions(+), 228 deletions(-) delete mode 100644 src/array_combinations.rs diff --git a/src/array_combinations.rs b/src/array_combinations.rs deleted file mode 100644 index bd2a067d3..000000000 --- a/src/array_combinations.rs +++ /dev/null @@ -1,166 +0,0 @@ -use core::iter::FusedIterator; -use core::{array, fmt}; - -use crate::combinations::{n_and_count, remaining_for}; -use crate::lazy_buffer::LazyBuffer; - -/// An iterator to iterate through all combinations in an iterator of `Clone`-able items that -/// produces arrays of a specific size. -/// -/// See [`.array_combinations()`](crate::Itertools::array_combinations) for more -/// information. -#[derive(Clone)] -#[must_use = "this iterator adaptor is not lazy but does nearly nothing unless consumed"] -pub struct ArrayCombinations -where - I::Item: Clone, -{ - indices: [usize; K], - pool: LazyBuffer, - first: bool, -} - -/// Create a new `ArrayCombinations` from a clonable iterator. -pub fn array_combinations(iter: I) -> ArrayCombinations -where - I::Item: Clone, -{ - let indices = array::from_fn(|i| i); - let pool = LazyBuffer::new(iter); - - ArrayCombinations { - indices, - pool, - first: true, - } -} - -impl ArrayCombinations -where - I::Item: Clone, -{ - /// Returns the (current) length of the pool from which combination elements are - /// selected. This value can change between invocations of [`next`](ArrayCombinations::next). - #[inline] - pub fn n(&self) -> usize { - self.pool.len() - } - - /// Initialises the iterator by filling a buffer with elements from the - /// iterator. Returns true if there are no combinations, false otherwise. - fn init(&mut self) -> bool { - self.pool.prefill(K); - let done = K > self.n(); - if !done { - self.first = false; - } - - done - } - - /// Increments indices representing the combination to advance to the next - /// (in lexicographic order by increasing sequence) combination. For example - /// if we have n=4 & k=2 then `[0, 1] -> [0, 2] -> [0, 3] -> [1, 2] -> ...` - /// - /// Returns true if we've run out of combinations, false otherwise. - fn increment_indices(&mut self) -> bool { - if K == 0 { - return true; // Done - } - - // Scan from the end, looking for an index to increment - let mut i: usize = K - 1; - - // Check if we need to consume more from the iterator - if self.indices[i] == self.pool.len() - 1 { - _ = self.pool.get_next(); // may change pool size - } - - while self.indices[i] == i + self.pool.len() - K { - if i > 0 { - i -= 1; - } else { - // Reached the last combination - return true; - } - } - - // Increment index, and reset the ones to its right - self.indices[i] += 1; - for j in i + 1..K { - self.indices[j] = self.indices[j - 1] + 1; - } - - // If we've made it this far, we haven't run out of combos - false - } - - /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> - where - I::Item: Clone, - { - let done = if self.first { - self.init() - } else { - self.increment_indices() - }; - if done { - return Err(0); - } - for i in 0..n { - if self.increment_indices() { - return Err(i + 1); - } - } - Ok(self.pool.get_array(self.indices)) - } -} - -impl Iterator for ArrayCombinations -where - I::Item: Clone, -{ - type Item = [I::Item; K]; - - fn next(&mut self) -> Option { - let done = if self.first { - self.init() - } else { - self.increment_indices() - }; - - (!done).then(|| self.pool.get_array(self.indices)) - } - - fn nth(&mut self, n: usize) -> Option { - self.try_nth(n).ok() - } - - fn size_hint(&self) -> (usize, Option) { - let (mut low, mut upp) = self.pool.size_hint(); - low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); - (low, upp) - } - - #[inline] - fn count(self) -> usize { - n_and_count(self.pool, self.first, &self.indices).1 - } -} - -impl fmt::Debug for ArrayCombinations -where - I: Iterator + fmt::Debug, - I::Item: Clone + fmt::Debug, -{ - debug_fmt_fields!(ArrayCombinations, indices, pool, first); -} - -impl FusedIterator for ArrayCombinations -where - I: FusedIterator, - I::Item: Clone, -{ -} diff --git a/src/combinations.rs b/src/combinations.rs index 9e7492e95..0b2806f68 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,3 +1,5 @@ +use core::array; +use core::ops::IndexMut; use std::fmt; use std::iter::FusedIterator; @@ -6,45 +8,112 @@ use alloc::vec::Vec; use crate::adaptors::checked_binomial; +/// Iterator for `Vec` valued combinations returned by [`.combinations()`](crate::Itertools::combinations) +pub type Combinations = CombinationsGeneric>; +/// Iterator for const generic combinations returned by [`.array_combinations()`](crate::Itertools::array_combinations) +pub type ArrayCombinations = CombinationsGeneric; + +/// Create a new `Combinations` from a clonable iterator. +pub fn combinations(iter: I, k: usize) -> Combinations +where + I::Item: Clone, +{ + Combinations::new(iter, (0..k).collect()) +} + +/// Create a new `ArrayCombinations` from a clonable iterator. +pub fn array_combinations(iter: I) -> ArrayCombinations +where + I::Item: Clone, +{ + ArrayCombinations::new(iter, array::from_fn(|i| i)) +} + /// An iterator to iterate through all the `k`-length combinations in an iterator. /// -/// See [`.combinations()`](crate::Itertools::combinations) for more information. +/// See [`.combinations()`](crate::Itertools::combinations) and [`.array_combinations()`](crate::Itertools::array_combinations) for more information. #[must_use = "iterator adaptors are lazy and do nothing unless consumed"] -pub struct Combinations { - indices: Vec, +pub struct CombinationsGeneric { + indices: Idx, pool: LazyBuffer, first: bool, } -impl Clone for Combinations +pub trait PoolIndex: IndexMut { + type Item; + + fn len(&self) -> usize; + fn extract_item>(&self, pool: &LazyBuffer) -> Self::Item + where + T: Clone; + fn as_slice(&self) -> &[usize]; +} + +impl PoolIndex for Vec { + type Item = Vec; + + fn len(&self) -> usize { + self.len() + } + fn extract_item>(&self, pool: &LazyBuffer) -> Vec + where + T: Clone, + { + pool.get_at(self) + } + + fn as_slice(&self) -> &[usize] { + self + } +} + +impl PoolIndex for [usize; K] { + type Item = [T; K]; + + fn len(&self) -> usize { + K + } + + fn extract_item>(&self, pool: &LazyBuffer) -> [T; K] + where + T: Clone, + { + pool.get_array(*self) + } + + fn as_slice(&self) -> &[usize] { + self + } +} + +impl Clone for CombinationsGeneric where - I: Clone + Iterator, + I: Iterator + Clone, I::Item: Clone, + Idx: Clone, { clone_fields!(indices, pool, first); } -impl fmt::Debug for Combinations +impl fmt::Debug for CombinationsGeneric where I: Iterator + fmt::Debug, I::Item: fmt::Debug, + Idx: fmt::Debug, { debug_fmt_fields!(Combinations, indices, pool, first); } -/// Create a new `Combinations` from a clonable iterator. -pub fn combinations(iter: I, k: usize) -> Combinations -where - I: Iterator, -{ - Combinations { - indices: (0..k).collect(), - pool: LazyBuffer::new(iter), - first: true, +impl> CombinationsGeneric { + /// Constructor with arguments the inner iterator and the initial state for the indices. + fn new(iter: I, indices: Idx) -> Self { + Self { + indices, + pool: LazyBuffer::new(iter), + first: true, + } } -} -impl Combinations { /// Returns the length of a combination produced by this iterator. #[inline] pub fn k(&self) -> usize { @@ -64,34 +133,17 @@ impl Combinations { &self.pool } - /// Resets this `Combinations` back to an initial state for combinations of length - /// `k` over the same pool data source. If `k` is larger than the current length - /// of the data pool an attempt is made to prefill the pool so that it holds `k` - /// elements. - pub(crate) fn reset(&mut self, k: usize) { - self.first = true; - - if k < self.indices.len() { - self.indices.truncate(k); - for i in 0..k { - self.indices[i] = i; - } - } else { - for i in 0..self.indices.len() { - self.indices[i] = i; - } - self.indices.extend(self.indices.len()..k); - self.pool.prefill(k); - } - } - + /// Return the length of the inner iterator and the count of remaining combinations. pub(crate) fn n_and_count(self) -> (usize, usize) { let Self { indices, pool, first, } = self; - n_and_count(pool, first, &indices) + { + let n = pool.count(); + (n, remaining_for(n, first, indices.as_slice()).unwrap()) + } } /// Initialises the iterator by filling a buffer with elements from the @@ -112,7 +164,7 @@ impl Combinations { /// /// Returns true if we've run out of combinations, false otherwise. fn increment_indices(&mut self) -> bool { - if self.indices.is_empty() { + if self.indices.len() == 0 { return true; // Done } @@ -144,8 +196,9 @@ impl Combinations { } /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> + pub(crate) fn try_nth(&mut self, n: usize) -> Result where + I: Iterator, I::Item: Clone, { let done = if self.first { @@ -161,16 +214,17 @@ impl Combinations { return Err(i + 1); } } - Ok(self.pool.get_at(&self.indices)) + Ok(self.indices.extract_item(&self.pool)) } } -impl Iterator for Combinations +impl Iterator for CombinationsGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { - type Item = Vec; + type Item = Idx::Item; fn next(&mut self) -> Option { let done = if self.first { self.init() @@ -182,7 +236,7 @@ where return None; } - Some(self.pool.get_at(&self.indices)) + Some(self.indices.extract_item(&self.pool)) } fn nth(&mut self, n: usize) -> Option { @@ -191,8 +245,8 @@ where fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); - low = remaining_for(low, self.first, &self.indices).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, self.first, &self.indices)); + low = remaining_for(low, self.first, self.indices.as_slice()).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.as_slice())); (low, upp) } @@ -202,21 +256,35 @@ where } } -impl FusedIterator for Combinations +impl FusedIterator for CombinationsGeneric where I: Iterator, I::Item: Clone, + Idx: PoolIndex, { } -/// Return the length of the inner iterator and the count of remaining combinations. -pub(crate) fn n_and_count( - pool: LazyBuffer, - first: bool, - indices: &[usize], -) -> (usize, usize) { - let n = pool.count(); - (n, remaining_for(n, first, indices).unwrap()) +impl Combinations { + /// Resets this `Combinations` back to an initial state for combinations of length + /// `k` over the same pool data source. If `k` is larger than the current length + /// of the data pool an attempt is made to prefill the pool so that it holds `k` + /// elements. + pub(crate) fn reset(&mut self, k: usize) { + self.first = true; + + if k < self.indices.len() { + self.indices.truncate(k); + for i in 0..k { + self.indices[i] = i; + } + } else { + for i in 0..self.indices.len() { + self.indices[i] = i; + } + self.indices.extend(self.indices.len()..k); + self.pool.prefill(k); + } + } } /// For a given size `n`, return the count of remaining combinations or None if it would overflow. diff --git a/src/lib.rs b/src/lib.rs index 25c94a3ca..ccc4bd7fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,9 +97,7 @@ pub mod structs { TakeWhileRef, TupleCombinations, Update, WhileSome, }; #[cfg(feature = "use_alloc")] - pub use crate::array_combinations::ArrayCombinations; - #[cfg(feature = "use_alloc")] - pub use crate::combinations::Combinations; + pub use crate::combinations::{ArrayCombinations, Combinations}; #[cfg(feature = "use_alloc")] pub use crate::combinations_with_replacement::CombinationsWithReplacement; pub use crate::cons_tuples_impl::ConsTuples; @@ -180,8 +178,6 @@ pub mod free; #[doc(inline)] pub use crate::free::*; #[cfg(feature = "use_alloc")] -mod array_combinations; -#[cfg(feature = "use_alloc")] mod combinations; #[cfg(feature = "use_alloc")] mod combinations_with_replacement; @@ -1721,7 +1717,7 @@ pub trait Itertools: Iterator { Self: Sized + Clone, Self::Item: Clone, { - array_combinations::array_combinations(self) + combinations::array_combinations(self) } /// Return an iterator adaptor that iterates over the `k`-length combinations of From 35c78ce0bc36fbe41df2f5833827b166896e1f50 Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Wed, 11 Sep 2024 21:13:22 +0200 Subject: [PATCH 625/633] IndexMut -> BorrowMut --- src/combinations.rs | 51 +++++++++++++++++++-------------------------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index 0b2806f68..c8ec14142 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -1,5 +1,5 @@ use core::array; -use core::ops::IndexMut; +use core::borrow::BorrowMut; use std::fmt; use std::iter::FusedIterator; @@ -39,41 +39,33 @@ pub struct CombinationsGeneric { first: bool, } -pub trait PoolIndex: IndexMut { +pub trait PoolIndex: BorrowMut<[usize]> { type Item; - fn len(&self) -> usize; fn extract_item>(&self, pool: &LazyBuffer) -> Self::Item where T: Clone; - fn as_slice(&self) -> &[usize]; + + #[inline] + fn len(&self) -> usize { + self.borrow().len() + } } impl PoolIndex for Vec { type Item = Vec; - fn len(&self) -> usize { - self.len() - } fn extract_item>(&self, pool: &LazyBuffer) -> Vec where T: Clone, { pool.get_at(self) } - - fn as_slice(&self) -> &[usize] { - self - } } impl PoolIndex for [usize; K] { type Item = [T; K]; - fn len(&self) -> usize { - K - } - fn extract_item>(&self, pool: &LazyBuffer) -> [T; K] where T: Clone, @@ -81,8 +73,8 @@ impl PoolIndex for [usize; K] { pool.get_array(*self) } - fn as_slice(&self) -> &[usize] { - self + fn len(&self) -> usize { + K } } @@ -142,7 +134,7 @@ impl> CombinationsGeneric { } = self; { let n = pool.count(); - (n, remaining_for(n, first, indices.as_slice()).unwrap()) + (n, remaining_for(n, first, indices.borrow()).unwrap()) } } @@ -164,19 +156,21 @@ impl> CombinationsGeneric { /// /// Returns true if we've run out of combinations, false otherwise. fn increment_indices(&mut self) -> bool { - if self.indices.len() == 0 { + // Borrow once instead of noise each time it's indexed + let indices = self.indices.borrow_mut(); + + if indices.is_empty() { return true; // Done } - // Scan from the end, looking for an index to increment - let mut i: usize = self.indices.len() - 1; + let mut i: usize = indices.len() - 1; // Check if we need to consume more from the iterator - if self.indices[i] == self.pool.len() - 1 { + if indices[i] == self.pool.len() - 1 { self.pool.get_next(); // may change pool size } - while self.indices[i] == i + self.pool.len() - self.indices.len() { + while indices[i] == i + self.pool.len() - indices.len() { if i > 0 { i -= 1; } else { @@ -186,11 +180,10 @@ impl> CombinationsGeneric { } // Increment index, and reset the ones to its right - self.indices[i] += 1; - for j in i + 1..self.indices.len() { - self.indices[j] = self.indices[j - 1] + 1; + indices[i] += 1; + for j in i + 1..indices.len() { + indices[j] = indices[j - 1] + 1; } - // If we've made it this far, we haven't run out of combos false } @@ -245,8 +238,8 @@ where fn size_hint(&self) -> (usize, Option) { let (mut low, mut upp) = self.pool.size_hint(); - low = remaining_for(low, self.first, self.indices.as_slice()).unwrap_or(usize::MAX); - upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.as_slice())); + low = remaining_for(low, self.first, self.indices.borrow()).unwrap_or(usize::MAX); + upp = upp.and_then(|upp| remaining_for(upp, self.first, self.indices.borrow())); (low, upp) } From d0479b080781cf92a035cf2e33aee04079062b3e Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Wed, 11 Sep 2024 22:24:41 +0200 Subject: [PATCH 626/633] "nitpicks" --- src/combinations.rs | 15 ++++----------- src/lib.rs | 3 ++- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/combinations.rs b/src/combinations.rs index c8ec14142..f5aa4630e 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -46,7 +46,6 @@ pub trait PoolIndex: BorrowMut<[usize]> { where T: Clone; - #[inline] fn len(&self) -> usize { self.borrow().len() } @@ -72,10 +71,6 @@ impl PoolIndex for [usize; K] { { pool.get_array(*self) } - - fn len(&self) -> usize { - K - } } impl Clone for CombinationsGeneric @@ -132,10 +127,8 @@ impl> CombinationsGeneric { pool, first, } = self; - { - let n = pool.count(); - (n, remaining_for(n, first, indices.borrow()).unwrap()) - } + let n = pool.count(); + (n, remaining_for(n, first, indices.borrow()).unwrap()) } /// Initialises the iterator by filling a buffer with elements from the @@ -189,7 +182,7 @@ impl> CombinationsGeneric { } /// Returns the n-th item or the number of successful steps. - pub(crate) fn try_nth(&mut self, n: usize) -> Result + pub(crate) fn try_nth(&mut self, n: usize) -> Result<::Item, usize> where I: Iterator, I::Item: Clone, @@ -281,7 +274,7 @@ impl Combinations { } /// For a given size `n`, return the count of remaining combinations or None if it would overflow. -pub(crate) fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { +fn remaining_for(n: usize, first: bool, indices: &[usize]) -> Option { let k = indices.len(); if n < k { Some(0) diff --git a/src/lib.rs b/src/lib.rs index ccc4bd7fa..dc316d847 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1677,7 +1677,8 @@ pub trait Itertools: Iterator { /// Return an iterator adaptor that iterates over the combinations of the /// elements from an iterator. /// - /// Iterator element can be any array of type `Self::Item`. + /// Iterator element type is [Self::Item; K]. The iterator produces a new + /// array per iteration, and clones the iterator elements. /// /// # Guarantees /// From a447b6896043facbd234bd57213cfae58244d46d Mon Sep 17 00:00:00 2001 From: Ronno Das Date: Fri, 20 Sep 2024 07:45:34 +0200 Subject: [PATCH 627/633] doc for added trait --- src/combinations.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/combinations.rs b/src/combinations.rs index f5aa4630e..54a027551 100644 --- a/src/combinations.rs +++ b/src/combinations.rs @@ -39,6 +39,8 @@ pub struct CombinationsGeneric { first: bool, } +/// A type holding indices of elements in a pool or buffer of items from an inner iterator +/// and used to pick out different combinations in a generic way. pub trait PoolIndex: BorrowMut<[usize]> { type Item; From 5d4056b643c575c6a808ec4d6f2fb24db57acf43 Mon Sep 17 00:00:00 2001 From: MichiRecRoom <1008889+LikeLakers2@users.noreply.github.com> Date: Mon, 12 Aug 2024 01:03:36 -0400 Subject: [PATCH 628/633] `default_features` is deprecated - switch it to `default-features` --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6ec88a9e9..0335a4a35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,7 @@ rand = "0.7" criterion = { version = "0.4.0", features = ["html_reports"] } paste = "1.0.0" # Used in test_std to instantiate generic tests permutohedron = "0.2" -quickcheck = { version = "0.9", default_features = false } +quickcheck = { version = "0.9", default-features = false } [features] default = ["use_std"] From b793238ff14bb4270456264708acc9771022d6a2 Mon Sep 17 00:00:00 2001 From: Kitsu Date: Tue, 30 Jul 2024 12:25:13 -0300 Subject: [PATCH 629/633] Add track_caller for asser_equal --- src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib.rs b/src/lib.rs index dc316d847..a4bb5e46c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4571,6 +4571,7 @@ where /// assert_equal("exceed".split('c'), "excess".split('c')); /// // ^PANIC: panicked at 'Failed assertion Some("eed") == Some("ess") for iteration 1'. /// ``` +#[track_caller] pub fn assert_equal(a: I, b: J) where I: IntoIterator, From f80883b8e01cba2f8ef872cd33d6eda1a471db4c Mon Sep 17 00:00:00 2001 From: docwilco <66911096+docwilco@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:00:15 +0100 Subject: [PATCH 630/633] Fix into_group_map_by documentation errors This method returns a HashMap, not an Iterator. Bonus whitespace fix in example. See also the `into_group_map` documentation. --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index a4bb5e46c..834a48dea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3659,8 +3659,8 @@ pub trait Itertools: Iterator { group_map::into_group_map(self) } - /// Return an `Iterator` on a `HashMap`. Keys mapped to `Vec`s of values. The key is specified - /// in the closure. + /// Return a `HashMap` of keys mapped to `Vec`s of values. The key is specified + /// in the closure. The values are taken from the input iterator. /// /// Essentially a shorthand for `.into_grouping_map_by(f).collect::>()`. /// @@ -3672,7 +3672,7 @@ pub trait Itertools: Iterator { /// let lookup: HashMap> = /// data.clone().into_iter().into_group_map_by(|a| a.0); /// - /// assert_eq!(lookup[&0], vec![(0,10),(0,20)]); + /// assert_eq!(lookup[&0], vec![(0,10), (0,20)]); /// assert_eq!(lookup.get(&1), None); /// assert_eq!(lookup[&2], vec![(2,12), (2,42)]); /// assert_eq!(lookup[&3], vec![(3,13), (3,33)]); From ff0c942b2e0127dfdd311a8b1ee022bd367c237f Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 23 Dec 2024 18:53:14 +0000 Subject: [PATCH 631/633] fix clippy lints --- src/adaptors/mod.rs | 4 ++-- src/format.rs | 14 +++++++------- src/lib.rs | 1 - src/peeking_take_while.rs | 6 +++--- src/process_results_impl.rs | 6 +++--- src/rciter_impl.rs | 2 +- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs index d717e5408..77192f26e 100644 --- a/src/adaptors/mod.rs +++ b/src/adaptors/mod.rs @@ -515,7 +515,7 @@ pub struct TakeWhileRef<'a, I: 'a, F> { f: F, } -impl<'a, I, F> fmt::Debug for TakeWhileRef<'a, I, F> +impl fmt::Debug for TakeWhileRef<'_, I, F> where I: Iterator + fmt::Debug, { @@ -530,7 +530,7 @@ where TakeWhileRef { iter, f } } -impl<'a, I, F> Iterator for TakeWhileRef<'a, I, F> +impl Iterator for TakeWhileRef<'_, I, F> where I: Iterator + Clone, F: FnMut(&I::Item) -> bool, diff --git a/src/format.rs b/src/format.rs index 15cee34d6..3abdda369 100644 --- a/src/format.rs +++ b/src/format.rs @@ -47,7 +47,7 @@ where } } -impl<'a, I, F> fmt::Display for FormatWith<'a, I, F> +impl fmt::Display for FormatWith<'_, I, F> where I: Iterator, F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, @@ -71,7 +71,7 @@ where } } -impl<'a, I, F> fmt::Debug for FormatWith<'a, I, F> +impl fmt::Debug for FormatWith<'_, I, F> where I: Iterator, F: FnMut(I::Item, &mut dyn FnMut(&dyn fmt::Display) -> fmt::Result) -> fmt::Result, @@ -81,7 +81,7 @@ where } } -impl<'a, I> Format<'a, I> +impl Format<'_, I> where I: Iterator, { @@ -125,7 +125,7 @@ macro_rules! impl_format { impl_format! {Display Debug UpperExp LowerExp UpperHex LowerHex Octal Binary Pointer} -impl<'a, I, F> Clone for FormatWith<'a, I, F> +impl Clone for FormatWith<'_, I, F> where (I, F): Clone, { @@ -135,7 +135,7 @@ where inner: Option<(I, F)>, } // This ensures we preserve the state of the original `FormatWith` if `Clone` panics - impl<'r, 'a, I, F> Drop for PutBackOnDrop<'r, 'a, I, F> { + impl Drop for PutBackOnDrop<'_, '_, I, F> { fn drop(&mut self) { self.into.inner.set(self.inner.take()) } @@ -151,7 +151,7 @@ where } } -impl<'a, I> Clone for Format<'a, I> +impl Clone for Format<'_, I> where I: Clone, { @@ -161,7 +161,7 @@ where inner: Option, } // This ensures we preserve the state of the original `FormatWith` if `Clone` panics - impl<'r, 'a, I> Drop for PutBackOnDrop<'r, 'a, I> { + impl Drop for PutBackOnDrop<'_, '_, I> { fn drop(&mut self) { self.into.inner.set(self.inner.take()) } diff --git a/src/lib.rs b/src/lib.rs index 834a48dea..37c3b1f41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1055,7 +1055,6 @@ pub trait Itertools: Iterator { /// let it = a.merge_by(b, |x, y| x.1 <= y.1); /// itertools::assert_equal(it, vec![(0, 'a'), (0, 'b'), (1, 'c'), (1, 'd')]); /// ``` - fn merge_by(self, other: J, is_first: F) -> MergeBy where Self: Sized, diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs index 19872a964..f3259a919 100644 --- a/src/peeking_take_while.rs +++ b/src/peeking_take_while.rs @@ -22,7 +22,7 @@ pub trait PeekingNext: Iterator { F: FnOnce(&Self::Item) -> bool; } -impl<'a, I> PeekingNext for &'a mut I +impl PeekingNext for &mut I where I: PeekingNext, { @@ -133,7 +133,7 @@ where PeekingTakeWhile { iter, f } } -impl<'a, I, F> Iterator for PeekingTakeWhile<'a, I, F> +impl Iterator for PeekingTakeWhile<'_, I, F> where I: PeekingNext, F: FnMut(&I::Item) -> bool, @@ -148,7 +148,7 @@ where } } -impl<'a, I, F> PeekingNext for PeekingTakeWhile<'a, I, F> +impl PeekingNext for PeekingTakeWhile<'_, I, F> where I: PeekingNext, F: FnMut(&I::Item) -> bool, diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs index ad6c60d3c..31389c5fd 100644 --- a/src/process_results_impl.rs +++ b/src/process_results_impl.rs @@ -13,7 +13,7 @@ pub struct ProcessResults<'a, I, E: 'a> { iter: I, } -impl<'a, I, E> ProcessResults<'a, I, E> { +impl ProcessResults<'_, I, E> { #[inline(always)] fn next_body(&mut self, item: Option>) -> Option { match item { @@ -27,7 +27,7 @@ impl<'a, I, E> ProcessResults<'a, I, E> { } } -impl<'a, I, T, E> Iterator for ProcessResults<'a, I, E> +impl Iterator for ProcessResults<'_, I, E> where I: Iterator>, { @@ -60,7 +60,7 @@ where } } -impl<'a, I, T, E> DoubleEndedIterator for ProcessResults<'a, I, E> +impl DoubleEndedIterator for ProcessResults<'_, I, E> where I: Iterator>, I: DoubleEndedIterator, diff --git a/src/rciter_impl.rs b/src/rciter_impl.rs index e3b753206..96a0fd69c 100644 --- a/src/rciter_impl.rs +++ b/src/rciter_impl.rs @@ -87,7 +87,7 @@ where } /// Return an iterator from `&RcIter` (by simply cloning it). -impl<'a, I> IntoIterator for &'a RcIter +impl IntoIterator for &RcIter where I: Iterator, { From a1213e1c81af9d8c329239b916fb9663387ae1b0 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Mon, 30 Dec 2024 14:58:32 +0000 Subject: [PATCH 632/633] Prepare v0.14.0 release --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de9564c6a..11392488a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.14.0 + +### Breaking +- Increased MSRV to 1.63.0 (#960) +- Removed generic parameter from `cons_tuples` (#988) + +### Added +- Added `array_combinations` (#991) +- Added `k_smallest_relaxed` and variants (#925) +- Implemented `DoubleEndedIterator` for `FilterOk` (#948) +- Implemented `DoubleEndedIterator` for `FilterMapOk` (#950) + +### Changed +- Allow `Q: ?Sized` in `Itertools::contains` (#971) +- Improved hygiene of `chain!` (#943) +- Improved `into_group_map_by` documentation (#1000) +- Improved `tree_reduce` documentation (#955) +- Improved discoverability of `merge_join_by` (#966) +- Improved discoverability of `take_while_inclusive` (#972) +- Improved documentation of `find_or_last` and `find_or_first` (#984) +- Prevented exponentially large type sizes in `tuple_combinations` (#945) +- Added `track_caller` attr for `asser_equal` (#976) + +### Notable Internal Changes +- Fixed clippy lints (#956, #987, #1008) +- Addressed warnings within doctests (#964) +- CI: Run most tests with miri (#961) +- CI: Speed up "cargo-semver-checks" action (#938) +- Changed an instance of `default_features` in `Cargo.toml` to `default-features` (#985) + ## 0.13.0 ### Breaking diff --git a/Cargo.toml b/Cargo.toml index 0335a4a35..1d8a68f83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "itertools" -version = "0.13.0" +version = "0.14.0" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-itertools/itertools" diff --git a/README.md b/README.md index 982ef5dbe..46acc3fca 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ How to use with Cargo: ```toml [dependencies] -itertools = "0.13.0" +itertools = "0.14.0" ``` How to use in your crate: From a015a6831525ee1637df747d3f530a627d9741bf Mon Sep 17 00:00:00 2001 From: Orson Peters Date: Wed, 5 Jun 2024 02:29:51 +0200 Subject: [PATCH 633/633] Add `next_array` and `collect_array` Co-authored-by: Jack Wrenn Co-authored-by: Joshua Liebow-Feeser --- CHANGELOG.md | 1 + src/lib.rs | 45 ++++++++ src/next_array.rs | 269 +++++++++++++++++++++++++++++++++++++++++++++ tests/test_core.rs | 25 +++++ 4 files changed, 340 insertions(+) create mode 100644 src/next_array.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 11392488a..6b08f6830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### Added - Added `array_combinations` (#991) - Added `k_smallest_relaxed` and variants (#925) +- Added `next_array` and `collect_array` (#560) - Implemented `DoubleEndedIterator` for `FilterOk` (#948) - Implemented `DoubleEndedIterator` for `FilterMapOk` (#950) diff --git a/src/lib.rs b/src/lib.rs index 37c3b1f41..20226d88a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -209,6 +209,7 @@ mod merge_join; mod minmax; #[cfg(feature = "use_alloc")] mod multipeek_impl; +mod next_array; mod pad_tail; #[cfg(feature = "use_alloc")] mod peek_nth; @@ -1968,6 +1969,50 @@ pub trait Itertools: Iterator { } // non-adaptor methods + /// Advances the iterator and returns the next items grouped in an array of + /// a specific size. + /// + /// If there are enough elements to be grouped in an array, then the array + /// is returned inside `Some`, otherwise `None` is returned. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let mut iter = 1..5; + /// + /// assert_eq!(Some([1, 2]), iter.next_array()); + /// ``` + fn next_array(&mut self) -> Option<[Self::Item; N]> + where + Self: Sized, + { + next_array::next_array(self) + } + + /// Collects all items from the iterator into an array of a specific size. + /// + /// If the number of elements inside the iterator is **exactly** equal to + /// the array size, then the array is returned inside `Some`, otherwise + /// `None` is returned. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let iter = 1..3; + /// + /// if let Some([x, y]) = iter.collect_array() { + /// assert_eq!([x, y], [1, 2]) + /// } else { + /// panic!("Expected two elements") + /// } + /// ``` + fn collect_array(mut self) -> Option<[Self::Item; N]> + where + Self: Sized, + { + self.next_array().filter(|_| self.next().is_none()) + } + /// Advances the iterator and returns the next items grouped in a tuple of /// a specific size (up to 12). /// diff --git a/src/next_array.rs b/src/next_array.rs new file mode 100644 index 000000000..86480b197 --- /dev/null +++ b/src/next_array.rs @@ -0,0 +1,269 @@ +use core::mem::{self, MaybeUninit}; + +/// An array of at most `N` elements. +struct ArrayBuilder { + /// The (possibly uninitialized) elements of the `ArrayBuilder`. + /// + /// # Safety + /// + /// The elements of `arr[..len]` are valid `T`s. + arr: [MaybeUninit; N], + + /// The number of leading elements of `arr` that are valid `T`s, len <= N. + len: usize, +} + +impl ArrayBuilder { + /// Initializes a new, empty `ArrayBuilder`. + pub fn new() -> Self { + // SAFETY: The safety invariant of `arr` trivially holds for `len = 0`. + Self { + arr: [(); N].map(|_| MaybeUninit::uninit()), + len: 0, + } + } + + /// Pushes `value` onto the end of the array. + /// + /// # Panics + /// + /// This panics if `self.len >= N`. + #[inline(always)] + pub fn push(&mut self, value: T) { + // PANICS: This will panic if `self.len >= N`. + let place = &mut self.arr[self.len]; + // SAFETY: The safety invariant of `self.arr` applies to elements at + // indices `0..self.len` — not to the element at `self.len`. Writing to + // the element at index `self.len` therefore does not violate the safety + // invariant of `self.arr`. Even if this line panics, we have not + // created any intermediate invalid state. + *place = MaybeUninit::new(value); + // Lemma: `self.len < N`. By invariant, `self.len <= N`. Above, we index + // into `self.arr`, which has size `N`, at index `self.len`. If `self.len == N` + // at that point, that index would be out-of-bounds, and the index + // operation would panic. Thus, `self.len != N`, and since `self.len <= N`, + // that means that `self.len < N`. + // + // PANICS: Since `self.len < N`, and since `N <= usize::MAX`, + // `self.len + 1 <= usize::MAX`, and so `self.len += 1` will not + // overflow. Overflow is the only panic condition of `+=`. + // + // SAFETY: + // - We are required to uphold the invariant that `self.len <= N`. + // Since, by the preceding lemma, `self.len < N` at this point in the + // code, `self.len += 1` results in `self.len <= N`. + // - We are required to uphold the invariant that `self.arr[..self.len]` + // are valid instances of `T`. Since this invariant already held when + // this method was called, and since we only increment `self.len` + // by 1 here, we only need to prove that the element at + // `self.arr[self.len]` (using the value of `self.len` before incrementing) + // is valid. Above, we construct `place` to point to `self.arr[self.len]`, + // and then initialize `*place` to `MaybeUninit::new(value)`, which is + // a valid `T` by construction. + self.len += 1; + } + + /// Consumes the elements in the `ArrayBuilder` and returns them as an array + /// `[T; N]`. + /// + /// If `self.len() < N`, this returns `None`. + pub fn take(&mut self) -> Option<[T; N]> { + if self.len == N { + // SAFETY: Decreasing the value of `self.len` cannot violate the + // safety invariant on `self.arr`. + self.len = 0; + + // SAFETY: Since `self.len` is 0, `self.arr` may safely contain + // uninitialized elements. + let arr = mem::replace(&mut self.arr, [(); N].map(|_| MaybeUninit::uninit())); + + Some(arr.map(|v| { + // SAFETY: We know that all elements of `arr` are valid because + // we checked that `len == N`. + unsafe { v.assume_init() } + })) + } else { + None + } + } +} + +impl AsMut<[T]> for ArrayBuilder { + fn as_mut(&mut self) -> &mut [T] { + let valid = &mut self.arr[..self.len]; + // SAFETY: By invariant on `self.arr`, the elements of `self.arr` at + // indices `0..self.len` are in a valid state. Since `valid` references + // only these elements, the safety precondition of + // `slice_assume_init_mut` is satisfied. + unsafe { slice_assume_init_mut(valid) } + } +} + +impl Drop for ArrayBuilder { + // We provide a non-trivial `Drop` impl, because the trivial impl would be a + // no-op; `MaybeUninit` has no innate awareness of its own validity, and + // so it can only forget its contents. By leveraging the safety invariant of + // `self.arr`, we do know which elements of `self.arr` are valid, and can + // selectively run their destructors. + fn drop(&mut self) { + // SAFETY: + // - by invariant on `&mut [T]`, `self.as_mut()` is: + // - valid for reads and writes + // - properly aligned + // - non-null + // - the dropped `T` are valid for dropping; they do not have any + // additional library invariants that we've violated + // - no other pointers to `valid` exist (since we're in the context of + // `drop`) + unsafe { core::ptr::drop_in_place(self.as_mut()) } + } +} + +/// Assuming all the elements are initialized, get a mutable slice to them. +/// +/// # Safety +/// +/// The caller guarantees that the elements `T` referenced by `slice` are in a +/// valid state. +unsafe fn slice_assume_init_mut(slice: &mut [MaybeUninit]) -> &mut [T] { + // SAFETY: Casting `&mut [MaybeUninit]` to `&mut [T]` is sound, because + // `MaybeUninit` is guaranteed to have the same size, alignment and ABI + // as `T`, and because the caller has guaranteed that `slice` is in the + // valid state. + unsafe { &mut *(slice as *mut [MaybeUninit] as *mut [T]) } +} + +/// Equivalent to `it.next_array()`. +pub(crate) fn next_array(it: &mut I) -> Option<[I::Item; N]> +where + I: Iterator, +{ + let mut builder = ArrayBuilder::new(); + for _ in 0..N { + builder.push(it.next()?); + } + builder.take() +} + +#[cfg(test)] +mod test { + use super::ArrayBuilder; + + #[test] + fn zero_len_take() { + let mut builder = ArrayBuilder::<(), 0>::new(); + let taken = builder.take(); + assert_eq!(taken, Some([(); 0])); + } + + #[test] + #[should_panic] + fn zero_len_push() { + let mut builder = ArrayBuilder::<(), 0>::new(); + builder.push(()); + } + + #[test] + fn push_4() { + let mut builder = ArrayBuilder::<(), 4>::new(); + assert_eq!(builder.take(), None); + + builder.push(()); + assert_eq!(builder.take(), None); + + builder.push(()); + assert_eq!(builder.take(), None); + + builder.push(()); + assert_eq!(builder.take(), None); + + builder.push(()); + assert_eq!(builder.take(), Some([(); 4])); + } + + #[test] + fn tracked_drop() { + use std::panic::{catch_unwind, AssertUnwindSafe}; + use std::sync::atomic::{AtomicU16, Ordering}; + + static DROPPED: AtomicU16 = AtomicU16::new(0); + + #[derive(Debug, PartialEq)] + struct TrackedDrop; + + impl Drop for TrackedDrop { + fn drop(&mut self) { + DROPPED.fetch_add(1, Ordering::Relaxed); + } + } + + { + let builder = ArrayBuilder::::new(); + assert_eq!(DROPPED.load(Ordering::Relaxed), 0); + drop(builder); + assert_eq!(DROPPED.load(Ordering::Relaxed), 0); + } + + { + let mut builder = ArrayBuilder::::new(); + builder.push(TrackedDrop); + assert_eq!(builder.take(), None); + assert_eq!(DROPPED.load(Ordering::Relaxed), 0); + drop(builder); + assert_eq!(DROPPED.swap(0, Ordering::Relaxed), 1); + } + + { + let mut builder = ArrayBuilder::::new(); + builder.push(TrackedDrop); + builder.push(TrackedDrop); + assert!(matches!(builder.take(), Some(_))); + assert_eq!(DROPPED.swap(0, Ordering::Relaxed), 2); + drop(builder); + assert_eq!(DROPPED.load(Ordering::Relaxed), 0); + } + + { + let mut builder = ArrayBuilder::::new(); + + builder.push(TrackedDrop); + builder.push(TrackedDrop); + + assert!(catch_unwind(AssertUnwindSafe(|| { + builder.push(TrackedDrop); + })) + .is_err()); + + assert_eq!(DROPPED.load(Ordering::Relaxed), 1); + + drop(builder); + + assert_eq!(DROPPED.swap(0, Ordering::Relaxed), 3); + } + + { + let mut builder = ArrayBuilder::::new(); + + builder.push(TrackedDrop); + builder.push(TrackedDrop); + + assert!(catch_unwind(AssertUnwindSafe(|| { + builder.push(TrackedDrop); + })) + .is_err()); + + assert_eq!(DROPPED.load(Ordering::Relaxed), 1); + + assert!(matches!(builder.take(), Some(_))); + + assert_eq!(DROPPED.load(Ordering::Relaxed), 3); + + builder.push(TrackedDrop); + builder.push(TrackedDrop); + + assert!(matches!(builder.take(), Some(_))); + + assert_eq!(DROPPED.swap(0, Ordering::Relaxed), 5); + } + } +} diff --git a/tests/test_core.rs b/tests/test_core.rs index 32af246c0..493616085 100644 --- a/tests/test_core.rs +++ b/tests/test_core.rs @@ -372,3 +372,28 @@ fn product1() { assert_eq!(v[1..3].iter().cloned().product1::(), Some(2)); assert_eq!(v[1..5].iter().cloned().product1::(), Some(24)); } + +#[test] +fn next_array() { + let v = [1, 2, 3, 4, 5]; + let mut iter = v.iter(); + assert_eq!(iter.next_array(), Some([])); + assert_eq!(iter.next_array().map(|[&x, &y]| [x, y]), Some([1, 2])); + assert_eq!(iter.next_array().map(|[&x, &y]| [x, y]), Some([3, 4])); + assert_eq!(iter.next_array::<2>(), None); +} + +#[test] +fn collect_array() { + let v = [1, 2]; + let iter = v.iter().cloned(); + assert_eq!(iter.collect_array(), Some([1, 2])); + + let v = [1]; + let iter = v.iter().cloned(); + assert_eq!(iter.collect_array::<2>(), None); + + let v = [1, 2, 3]; + let iter = v.iter().cloned(); + assert_eq!(iter.collect_array::<2>(), None); +}