From be148d7c10bcf0f8e1490f7496a3cd79cd0fef8d Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Sat, 24 Jul 2021 18:19:32 +0200 Subject: [PATCH] Implement Itertools::multiunzip --- src/lib.rs | 25 ++++++++++++++++++++ src/unziptuple.rs | 58 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test_std.rs | 6 +++++ 3 files changed, 89 insertions(+) create mode 100644 src/unziptuple.rs diff --git a/src/lib.rs b/src/lib.rs index 7dd624135..935db388a 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,29 @@ 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<_>) = multiunzip(inputs); + /// + /// assert_eq!((a, b, c), (vec![1, 4, 7], vec![2, 7, 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..047880bc7 --- /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, 7, 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..6230240d3 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![1, 4, 7], vec![2, 7, 8], vec![3, 6, 9])); +} \ No newline at end of file