From aad12603a9a08217c06fb622e92db0bce0ee7758 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 24 Jul 2021 18:19:32 +0200 Subject: [PATCH 1/3] Implement Itertools::multiunzip --- src/lib.rs | 27 ++++++++++++++++++++++ src/unziptuple.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 6 +++++ 3 files changed, 91 insertions(+) create mode 100644 src/unziptuple.rs diff --git a/src/lib.rs b/src/lib.rs index 7dd624135..9f84937c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -179,6 +179,7 @@ pub use crate::repeatn::repeat_n; #[allow(deprecated)] pub use crate::sources::{repeat_call, unfold, iterate}; pub use crate::with_position::Position; +pub use crate::unziptuple::{multiunzip, MultiUnzip}; pub use crate::ziptuple::multizip; mod adaptors; mod either_or_both; @@ -237,6 +238,7 @@ mod tuple_impl; mod duplicates_impl; #[cfg(feature = "use_std")] mod unique_impl; +mod unziptuple; mod with_position; mod zip_eq_impl; mod zip_longest; @@ -3401,6 +3403,31 @@ pub trait Itertools : Iterator { { self.map(f).counts() } + + /// Unzips an iterator over tuples into a tuple of containers. + /// + /// The first element of each tuple will be put into the first container, the second element into + /// the second, .... + /// + /// It can be thought of as the reverse operation to [`Itertools::multiunzip`]. + /// + /// ``` + /// use itertools::Itertools; + /// + /// let inputs = vec![(1, 2, 3), (4, 5, 6), (7, 8, 9)]; + /// + /// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = inputs + /// .into_iter() + /// .multiunzip(); + /// + /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); + /// ``` + fn multiunzip(self) -> FromI + where + Self: Sized + MultiUnzip, + { + MultiUnzip::multiunzip(self) + } } impl Itertools for T where T: Iterator { } diff --git a/src/unziptuple.rs b/src/unziptuple.rs new file mode 100644 index 000000000..699e39a62 --- /dev/null +++ b/src/unziptuple.rs @@ -0,0 +1,58 @@ +/// Unzips an iterator over tuples into a tuple of containers. +/// +/// ``` +/// use itertools::multiunzip; +/// +/// let inputs = vec![(1, 2, 3), (4, 5, 6), (7, 8, 9)]; +/// +/// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(inputs); +/// +/// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); +/// ``` +pub fn multiunzip(i: I) -> FromI +where + I: IntoIterator, + I::IntoIter: MultiUnzip, +{ + i.into_iter().multiunzip() +} + +/// An iterator that can be unzipped into multiple collections. +/// +/// See [`.multiunzip()`](crate::Itertools::multiunzip) for more information. +pub trait MultiUnzip: Iterator { + /// Unzip this iterator into multiple collections. + fn multiunzip(self) -> FromI; +} + +macro_rules! impl_unzip_iter { + ($($T:ident => $FromT:ident),*) => ( + impl_unzip_iter!(@rec $($T => $FromT,)*); + ); + (@rec) => (); + (@rec $__:ident => $___:ident, $($T:ident => $FromT:ident,)*) => ( + #[allow(non_snake_case)] + impl, $($T, $FromT: Default + Extend<$T>),* > MultiUnzip<($($FromT,)*)> for IT { + fn multiunzip(self) -> ($($FromT,)*) { + let mut res = ($($FromT::default(),)*); + let ($($FromT,)*) = &mut res; + + // Still unstable #72631 + // let (lower_bound, _) = self.size_hint(); + // if lower_bound > 0 { + // $($FromT.extend_reserve(lower_bound);)* + // } + + self.fold((), |(), ($($T,)*)| { + // Still unstable #72631 + // $( $FromT.extend_one($T); )* + $( $FromT.extend(std::iter::once($T)); )* + }); + res + } + } + impl_unzip_iter!(@rec $($T => $FromT,)*); + ); +} + +impl_unzip_iter!(L => FromL, K => FromK, J => FromJ, I => FromI, H => FromH, G => FromG, F => FromF, E => FromE, D => FromD, C => FromC, B => FromB, A => FromA); diff --git a/tests/test_std.rs b/tests/test_std.rs index 7cda9b5e5..65e224276 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1080,3 +1080,9 @@ fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); + assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); +} \ No newline at end of file From 7d1a7396650d1310b957bd476368d17fe34b2dd4 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 31 Jul 2021 20:22:38 +0200 Subject: [PATCH 2/3] Add more multiunzip tests, fix up multiunzip doc comments --- src/lib.rs | 8 ++++---- src/unziptuple.rs | 34 +++++++++++++++++++++++++++------- tests/test_std.rs | 3 +++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9f84937c7..5d7acca8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3404,12 +3404,12 @@ pub trait Itertools : Iterator { self.map(f).counts() } - /// Unzips an iterator over tuples into a tuple of containers. + /// Converts an iterator of tuples into a tuple of containers. /// - /// The first element of each tuple will be put into the first container, the second element into - /// the second, .... + /// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each + /// column. /// - /// It can be thought of as the reverse operation to [`Itertools::multiunzip`]. + /// This function is, in some sense, the opposite of [`multizip`]. /// /// ``` /// use itertools::Itertools; diff --git a/src/unziptuple.rs b/src/unziptuple.rs index 699e39a62..b1d198e58 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -1,4 +1,9 @@ -/// Unzips an iterator over tuples into a tuple of containers. +/// Converts an iterator of tuples into a tuple of containers. +/// +/// `unzip()` consumes an entire iterator of n-ary tuples, producing `n` collections, one for each +/// column. +/// +/// This function is, in some sense, the opposite of [`multizip`]. /// /// ``` /// use itertools::multiunzip; @@ -9,6 +14,8 @@ /// /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); /// ``` +/// +/// [`multizip`]: crate::multizip pub fn multiunzip(i: I) -> FromI where I: IntoIterator, @@ -27,13 +34,15 @@ pub trait MultiUnzip: Iterator { macro_rules! impl_unzip_iter { ($($T:ident => $FromT:ident),*) => ( - impl_unzip_iter!(@rec $($T => $FromT,)*); - ); - (@rec) => (); - (@rec $__:ident => $___:ident, $($T:ident => $FromT:ident,)*) => ( #[allow(non_snake_case)] impl, $($T, $FromT: Default + Extend<$T>),* > MultiUnzip<($($FromT,)*)> for IT { fn multiunzip(self) -> ($($FromT,)*) { + // This implementation mirrors the logic of Iterator::unzip as close as possible. + // Unfortunately a lot of the used api there is still unstable represented by + // the commented out parts that follow. + // + // https://doc.rust-lang.org/src/core/iter/traits/iterator.rs.html#2816-2844 + let mut res = ($($FromT::default(),)*); let ($($FromT,)*) = &mut res; @@ -51,8 +60,19 @@ macro_rules! impl_unzip_iter { res } } - impl_unzip_iter!(@rec $($T => $FromT,)*); ); } -impl_unzip_iter!(L => FromL, K => FromK, J => FromJ, I => FromI, H => FromH, G => FromG, F => FromF, E => FromE, D => FromD, C => FromC, B => FromB, A => FromA); +impl_unzip_iter!(); +impl_unzip_iter!(A => FromA); +impl_unzip_iter!(A => FromA, B => FromB); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ, K => FromK); +impl_unzip_iter!(A => FromA, B => FromB, C => FromC, D => FromD, E => FromE, F => FromF, G => FromG, H => FromH, I => FromI, J => FromJ, K => FromK, L => FromL); diff --git a/tests/test_std.rs b/tests/test_std.rs index 65e224276..1cac30dc8 100644 --- a/tests/test_std.rs +++ b/tests/test_std.rs @@ -1085,4 +1085,7 @@ fn exactly_one_question_mark_return() -> Result<(), ExactlyOneError, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip(); assert_eq!((a, b, c), (vec![0, 3, 6], vec![1, 4, 7], vec![2, 5, 8])); + let (): () = [(), (), ()].iter().cloned().multiunzip(); + let t: (Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)].iter().cloned().multiunzip(); + assert_eq!(t, (vec![0], vec![1], vec![2], vec![3], vec![4], vec![5], vec![6], vec![7], vec![8], vec![9], vec![10], vec![11])); } \ No newline at end of file From f39d142630b13b082b3e8e97ae72c66a5ae4300f Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 31 Jul 2021 20:26:42 +0200 Subject: [PATCH 3/3] Adjust multiunzip doc examples --- src/lib.rs | 4 +++- src/unziptuple.rs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 5d7acca8e..96600f55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3420,7 +3420,9 @@ pub trait Itertools : Iterator { /// .into_iter() /// .multiunzip(); /// - /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); + /// assert_eq!(a, vec![1, 4, 7]); + /// assert_eq!(b, vec![2, 5, 8]); + /// assert_eq!(c, vec![3, 6, 9]); /// ``` fn multiunzip(self) -> FromI where diff --git a/src/unziptuple.rs b/src/unziptuple.rs index b1d198e58..f468f05e0 100644 --- a/src/unziptuple.rs +++ b/src/unziptuple.rs @@ -12,7 +12,9 @@ /// /// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(inputs); /// -/// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 5, 8], vec![3, 6, 9])); +/// assert_eq!(a, vec![1, 4, 7]); +/// assert_eq!(b, vec![2, 5, 8]); +/// assert_eq!(c, vec![3, 6, 9]); /// ``` /// /// [`multizip`]: crate::multizip