From 22fa6f0fb80a01bdf1be14c97899b121f242bf14 Mon Sep 17 00:00:00 2001 From: Thomas BESSOU Date: Fri, 25 Mar 2022 15:21:29 +0100 Subject: [PATCH 01/49] 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 02/49] 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 11ca7914824850459164b195774d2b56521104ba Mon Sep 17 00:00:00 2001 From: Erik Rhodes Date: Mon, 23 May 2022 15:43:38 -0600 Subject: [PATCH 03/49] 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 04/49] 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 05/49] 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 06/49] 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 07/49] 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 08/49] 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 09/49] 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 dcea15ba8e75f479fcd07f321fb0ee0fc27a9d60 Mon Sep 17 00:00:00 2001 From: JoJoJet Date: Mon, 4 Jul 2022 18:14:50 -0400 Subject: [PATCH 10/49] 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 11/49] 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 12/49] 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 13/49] 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 14/49] 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 15/49] 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 16/49] 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 590fdef8ca30c734729c7b57e08b66ea705842fa Mon Sep 17 00:00:00 2001 From: Sean Olson Date: Fri, 9 Sep 2022 15:24:57 -0700 Subject: [PATCH 17/49] 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 18/49] 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 ce37330e662063f10750d634b41ee2664429c8f4 Mon Sep 17 00:00:00 2001 From: Alex Touchet Date: Tue, 20 Sep 2022 15:03:48 -0700 Subject: [PATCH 19/49] 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 20/49] 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 21/49] 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 22/49] 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 23/49] 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 24/49] 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 25/49] 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 26/49] 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 27/49] 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 28/49] 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 29/49] 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 30/49] 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 31/49] 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 32/49] 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 33/49] 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 34/49] 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 35/49] 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 36/49] 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 37/49] 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 38/49] 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 39/49] 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 40/49] 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 41/49] 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 42/49] 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 43/49] 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 44/49] 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 45/49] 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 46/49] `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 47/49] 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 48/49] 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 49/49] 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"