Merge d350ae8f4117dbaed9af7c3b0b497e62a90c3306 on remote branch

Change-Id: I24551847baff7b398c0152ae5cd6ea8e7214199c
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
index b29a8ee..5b491ea 100644
--- a/.cargo_vcs_info.json
+++ b/.cargo_vcs_info.json
@@ -1,5 +1,5 @@
 {
   "git": {
-    "sha1": "20c09bd4fed037fa3df6f2a6a1e717b01be89f11"
+    "sha1": "3003c2a968f144cbccb63c768d2fec2e83a69ca4"
   }
 }
diff --git a/Android.bp b/Android.bp
index ba23bc5..09dfee1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -43,7 +43,7 @@
     host_supported: true,
     crate_name: "itertools",
     cargo_env_compat: true,
-    cargo_pkg_version: "0.10.1",
+    cargo_pkg_version: "0.10.3",
     srcs: ["src/lib.rs"],
     edition: "2018",
     features: [
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41c4ab0..5e2032c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
 # Changelog
 
+## 0.10.2
+  - Add `Itertools::multiunzip` (#362, #565)
+  - Add `intersperse` and `intersperse_with` free functions (#555)
+  - Add `Itertools::sorted_by_cached_key` (#424, #575)
+  - Specialize `ProcessResults::fold` (#563)
+  - Fix subtraction overflow in `DuplicatesBy::size_hint` (#552)
+  - Fix specialization tests (#574)
+  - More `Debug` impls (#573)
+  - Deprecate `fold1` (use `reduce` instead) (#580)
+  - Documentation fixes (`HomogenousTuple`, `into_group_map`, `into_group_map_by`, `MultiPeek::peek`) (#543 et al.)
+
 ## 0.10.1
   - Add `Itertools::contains` (#514)
   - Add `Itertools::counts_by` (#515)
diff --git a/Cargo.toml b/Cargo.toml
index fb84927..525cae5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -13,11 +13,12 @@
 [package]
 edition = "2018"
 name = "itertools"
-version = "0.10.1"
+version = "0.10.3"
 authors = ["bluss"]
 exclude = ["/bors.toml"]
 description = "Extra iterator adaptors, iterator methods, free functions, and macros."
 documentation = "https://docs.rs/itertools/"
+readme = "README.md"
 keywords = ["iterator", "data-structure", "zip", "product", "group-by"]
 categories = ["algorithms", "rust-patterns"]
 license = "MIT/Apache-2.0"
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
index d7f6b23..22a08a8 100644
--- a/Cargo.toml.orig
+++ b/Cargo.toml.orig
@@ -1,11 +1,12 @@
 [package]
 name = "itertools"
-version = "0.10.1"
+version = "0.10.3"
 
 license = "MIT/Apache-2.0"
 repository = "https://github.com/rust-itertools/itertools"
 documentation = "https://docs.rs/itertools/"
 authors = ["bluss"]
+readme = "README.md"
 
 description = "Extra iterator adaptors, iterator methods, free functions, and macros."
 
diff --git a/METADATA b/METADATA
index ce650b4..ca38654 100644
--- a/METADATA
+++ b/METADATA
@@ -7,13 +7,13 @@
   }
   url {
     type: ARCHIVE
-    value: "https://static.crates.io/crates/itertools/itertools-0.10.1.crate"
+    value: "https://static.crates.io/crates/itertools/itertools-0.10.3.crate"
   }
-  version: "0.10.1"
+  version: "0.10.3"
   license_type: NOTICE
   last_upgrade_date {
-    year: 2021
-    month: 6
-    day: 21
+    year: 2022
+    month: 3
+    day: 1
   }
 }
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4cc3f8f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,44 @@
+# Itertools
+
+Extra iterator adaptors, functions and macros.
+
+Please read the [API documentation here](https://docs.rs/itertools/).
+
+[![build_status](https://github.com/rust-itertools/itertools/actions/workflows/ci.yml/badge.svg)](https://github.com/rust-itertools/itertools/actions)
+[![crates.io](https://img.shields.io/crates/v/itertools.svg)](https://crates.io/crates/itertools)
+
+How to use with Cargo:
+
+```toml
+[dependencies]
+itertools = "0.10.2"
+```
+
+How to use in your crate:
+
+```rust
+use itertools::Itertools;
+```
+
+## How to contribute
+
+- Fix a bug or implement a new thing
+- Include tests for your new feature, preferably a QuickCheck test
+- Make a Pull Request
+
+For new features, please first consider filing a PR to [rust-lang/rust](https://github.com/rust-lang/rust),
+adding your new feature to the `Iterator` trait of the standard library, if you believe it is reasonable.
+If it isn't accepted there, proposing it for inclusion in ``itertools`` is a good idea.
+The reason for doing is this is so that we avoid future breakage as with ``.flatten()``.
+However, if your feature involves heap allocation, such as storing elements in a ``Vec<T>``,
+then it can't be accepted into ``libcore``, and you should propose it for ``itertools`` directly instead.
+
+## License
+
+Dual-licensed to be compatible with the Rust project.
+
+Licensed under the Apache License, Version 2.0
+https://www.apache.org/licenses/LICENSE-2.0 or the MIT license
+https://opensource.org/licenses/MIT, at your
+option. This file may not be copied, modified, or distributed
+except according to those terms.
diff --git a/README.rst b/README.rst
deleted file mode 100644
index aa37f6b..0000000
--- a/README.rst
+++ /dev/null
@@ -1,55 +0,0 @@
-
-Itertools
-=========
-
-Extra iterator adaptors, functions and macros.
-
-Please read the `API documentation here`__
-
-__ https://docs.rs/itertools/
-
-|build_status|_ |crates|_
-
-.. |build_status| image:: https://travis-ci.org/rust-itertools/itertools.svg?branch=master
-.. _build_status: https://travis-ci.org/rust-itertools/itertools
-
-.. |crates| image:: https://meritbadge.herokuapp.com/itertools
-.. _crates: https://crates.io/crates/itertools
-
-How to use with cargo:
-
-.. code:: toml
-
-    [dependencies]
-    itertools = "0.10.0"
-
-How to use in your crate:
-
-.. code:: rust
-
-    use itertools::Itertools;
-
-How to contribute
------------------
-
-- Fix a bug or implement a new thing
-- Include tests for your new feature, preferably a quickcheck test
-- Make a Pull Request
-
-For new features, please first consider filing a PR to `rust-lang/rust <https://github.com/rust-lang/rust>`_,
-adding your new feature to the `Iterator` trait of the standard library, if you believe it is reasonable.
-If it isn't accepted there, proposing it for inclusion in ``itertools`` is a good idea.
-The reason for doing is this is so that we avoid future breakage as with ``.flatten()``.
-However, if your feature involves heap allocation, such as storing elements in a ``Vec<T>``,
-then it can't be accepted into ``libcore``, and you should propose it for ``itertools`` directly instead.
-
-License
--------
-
-Dual-licensed to be compatible with the Rust project.
-
-Licensed under the Apache License, Version 2.0
-https://www.apache.org/licenses/LICENSE-2.0 or the MIT license
-https://opensource.org/licenses/MIT, at your
-option. This file may not be copied, modified, or distributed
-except according to those terms.
diff --git a/TEST_MAPPING b/TEST_MAPPING
index f6e148f..86d1024 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -17,11 +17,17 @@
   "presubmit": [
     {
       "name": "apkdmverity.test"
+    },
+    {
+      "name": "microdroid_manager_test"
     }
   ],
   "presubmit-rust": [
     {
       "name": "apkdmverity.test"
+    },
+    {
+      "name": "microdroid_manager_test"
     }
   ]
 }
diff --git a/src/adaptors/coalesce.rs b/src/adaptors/coalesce.rs
index 1afbee5..b1aff6e 100644
--- a/src/adaptors/coalesce.rs
+++ b/src/adaptors/coalesce.rs
@@ -119,6 +119,10 @@
 #[derive(Clone)]
 pub struct DedupPred2CoalescePred<DP>(DP);
 
+impl<DP> fmt::Debug for DedupPred2CoalescePred<DP> {
+    debug_fmt_fields!(DedupPred2CoalescePred,);
+}
+
 pub trait DedupPredicate<T> {
     // TODO replace by Fn(&T, &T)->bool once Rust supports it
     fn dedup_pair(&mut self, a: &T, b: &T) -> bool;
@@ -137,7 +141,7 @@
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct DedupEq;
 
 impl<T: PartialEq> DedupPredicate<T> for DedupEq {
@@ -186,7 +190,7 @@
 pub type DedupByWithCount<I, Pred> =
     CoalesceBy<I, DedupPredWithCount2CoalescePred<Pred>, (usize, <I as Iterator>::Item)>;
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct DedupPredWithCount2CoalescePred<DP>(DP);
 
 impl<DP, T> CoalescePredicate<T, (usize, T)> for DedupPredWithCount2CoalescePred<DP>
diff --git a/src/adaptors/map.rs b/src/adaptors/map.rs
index eee5cc3..cf5e5a0 100644
--- a/src/adaptors/map.rs
+++ b/src/adaptors/map.rs
@@ -1,7 +1,7 @@
 use std::iter::FromIterator;
 use std::marker::PhantomData;
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 #[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
 pub struct MapSpecialCase<I, F> {
     iter: I,
@@ -84,6 +84,10 @@
 #[derive(Clone)]
 pub struct MapSpecialCaseFnOk<F>(F);
 
+impl<F> std::fmt::Debug for MapSpecialCaseFnOk<F> {
+    debug_fmt_fields!(MapSpecialCaseFnOk,);
+}
+
 /// Create a new `MapOk` iterator.
 pub fn map_ok<I, F, T, U, E>(iter: I, f: F) -> MapOk<I, F>
 where
@@ -108,7 +112,7 @@
     }
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct MapSpecialCaseFnInto<U>(PhantomData<U>);
 
 /// Create a new [`MapInto`] iterator.
diff --git a/src/adaptors/mod.rs b/src/adaptors/mod.rs
index bd0d93a..2010f53 100644
--- a/src/adaptors/mod.rs
+++ b/src/adaptors/mod.rs
@@ -477,7 +477,7 @@
     fn merge_pred(&mut self, a: &T, b: &T) -> bool;
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct MergeLte;
 
 impl<T: PartialOrd> MergePredicate<T> for MergeLte {
@@ -849,6 +849,13 @@
     f: F
 }
 
+impl<I, F> fmt::Debug for FilterOk<I, F>
+where
+    I: fmt::Debug,
+{
+    debug_fmt_fields!(FilterOk, iter);
+}
+
 /// Create a new `FilterOk` iterator.
 pub fn filter_ok<I, F, T, E>(iter: I, f: F) -> FilterOk<I, F>
     where I: Iterator<Item = Result<T, E>>,
@@ -917,6 +924,13 @@
     f: F
 }
 
+impl<I, F> fmt::Debug for FilterMapOk<I, F>
+where
+    I: fmt::Debug,
+{
+    debug_fmt_fields!(FilterMapOk, iter);
+}
+
 fn transpose_result<T, E>(result: Result<Option<T>, E>) -> Option<Result<T, E>> {
     match result {
         Ok(Some(v)) => Some(Ok(v)),
@@ -995,6 +1009,13 @@
     count: usize,
 }
 
+impl<I, F> fmt::Debug for Positions<I, F>
+where
+    I: fmt::Debug,
+{
+    debug_fmt_fields!(Positions, iter, count);
+}
+
 /// Create a new `Positions` iterator.
 pub fn positions<I, F>(iter: I, f: F) -> Positions<I, F>
     where I: Iterator,
@@ -1058,6 +1079,13 @@
     f: F,
 }
 
+impl<I, F> fmt::Debug for Update<I, F>
+where
+    I: fmt::Debug,
+{
+    debug_fmt_fields!(Update, iter);
+}
+
 /// Create a new `Update` iterator.
 pub fn update<I, F>(iter: I, f: F) -> Update<I, F>
 where
diff --git a/src/adaptors/multi_product.rs b/src/adaptors/multi_product.rs
index 9708ef4..30650ed 100644
--- a/src/adaptors/multi_product.rs
+++ b/src/adaptors/multi_product.rs
@@ -18,6 +18,14 @@
     where I: Iterator + Clone,
           I::Item: Clone;
 
+impl<I> std::fmt::Debug for MultiProduct<I>
+where
+    I: Iterator + Clone + std::fmt::Debug,
+    I::Item: Clone + std::fmt::Debug,
+{
+    debug_fmt_fields!(CoalesceBy, 0);
+}
+
 /// Create a new cartesian product iterator over an arbitrary number
 /// of iterators of the same type.
 ///
diff --git a/src/duplicates_impl.rs b/src/duplicates_impl.rs
index 42049df..640d481 100644
--- a/src/duplicates_impl.rs
+++ b/src/duplicates_impl.rs
@@ -84,13 +84,18 @@
         #[inline]
         fn size_hint(&self) -> (usize, Option<usize>) {
             let (_, hi) = self.iter.size_hint();
-            // There are `hi` number of items left in the base iterator. In the best case scenario,
-            // these items are exactly the same as the ones pending (i.e items seen exactly once so
-            // far), plus (hi - pending) / 2 pairs of never seen before items.
             let hi = hi.map(|hi| {
-                let max_pending = std::cmp::min(self.meta.pending, hi);
-                let max_new = std::cmp::max(hi - self.meta.pending, 0) / 2;
-                max_pending + max_new
+                if hi <= self.meta.pending {
+                    // fewer or equally many iter-remaining elements than pending elements
+                    // => at most, each iter-remaining element is matched
+                    hi
+                } else {
+                    // fewer pending elements than iter-remaining elements
+                    // => at most:
+                    //    * each pending element is matched
+                    //    * the other iter-remaining elements come in pairs
+                    self.meta.pending + (hi - self.meta.pending) / 2
+                }
             });
             // The lower bound is always 0 since we might only get unique items from now on
             (0, hi)
@@ -117,6 +122,7 @@
     }
 
     /// Apply the identity function to elements before checking them for equality.
+    #[derive(Debug)]
     pub struct ById;
     impl<V> KeyMethod<V, V> for ById {
         type Container = JustValue<V>;
@@ -128,6 +134,9 @@
 
     /// Apply a user-supplied function to elements before checking them for equality.
     pub struct ByFn<F>(pub(crate) F);
+    impl<F> fmt::Debug for ByFn<F> {
+        debug_fmt_fields!(ByFn,);
+    }
     impl<K, V, F> KeyMethod<K, V> for ByFn<F>
     where
         F: FnMut(&V) -> K,
@@ -147,6 +156,7 @@
         fn value(self) -> V;
     }
 
+    #[derive(Debug)]
     pub struct KeyValue<K, V>(K, V);
     impl<K, V> KeyXorValue<K, V> for KeyValue<K, V> {
         fn key_ref(&self) -> &K {
@@ -160,6 +170,7 @@
         }
     }
 
+    #[derive(Debug)]
     pub struct JustValue<V>(V);
     impl<V> KeyXorValue<V, V> for JustValue<V> {
         fn key_ref(&self) -> &V {
diff --git a/src/free.rs b/src/free.rs
index c78dc1d..6674030 100644
--- a/src/free.rs
+++ b/src/free.rs
@@ -14,8 +14,8 @@
     string::String,
 };
 
-#[cfg(feature = "use_alloc")]
 use crate::Itertools;
+use crate::intersperse::{Intersperse, IntersperseWith};
 
 pub use crate::adaptors::{
     interleave,
@@ -35,6 +35,41 @@
 #[cfg(feature = "use_alloc")]
 pub use crate::rciter_impl::rciter;
 
+/// Iterate `iterable` with a particular value inserted between each element.
+///
+/// [`IntoIterator`] enabled version of [`Iterator::intersperse`].
+///
+/// ```
+/// use itertools::intersperse;
+///
+/// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]);
+/// ```
+pub fn intersperse<I>(iterable: I, element: I::Item) -> Intersperse<I::IntoIter>
+    where I: IntoIterator,
+          <I as IntoIterator>::Item: Clone
+{
+    Itertools::intersperse(iterable.into_iter(), element)
+}
+
+/// Iterate `iterable` with a particular value created by a function inserted
+/// between each element.
+///
+/// [`IntoIterator`] enabled version of [`Iterator::intersperse_with`].
+///
+/// ```
+/// use itertools::intersperse_with;
+///
+/// let mut i = 10;
+/// itertools::assert_equal(intersperse_with((0..3), || { i -= 1; i }), vec![0, 9, 1, 8, 2]);
+/// assert_eq!(i, 8);
+/// ```
+pub fn intersperse_with<I, F>(iterable: I, element: F) -> IntersperseWith<I::IntoIter, F>
+    where I: IntoIterator,
+          F: FnMut() -> I::Item
+{
+    Itertools::intersperse_with(iterable.into_iter(), element)
+}
+
 /// Iterate `iterable` with a running index.
 ///
 /// [`IntoIterator`] enabled version of [`Iterator::enumerate`].
diff --git a/src/impl_macros.rs b/src/impl_macros.rs
index 3da8c63..5772bae 100644
--- a/src/impl_macros.rs
+++ b/src/impl_macros.rs
@@ -2,7 +2,7 @@
 //! Implementation's internal macros
 
 macro_rules! debug_fmt_fields {
-    ($tyname:ident, $($($field:ident).+),*) => {
+    ($tyname:ident, $($($field:tt/*TODO ideally we would accept ident or tuple element here*/).+),*) => {
         fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
             f.debug_struct(stringify!($tyname))
                 $(
diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs
index dce5b78..bd56b03 100644
--- a/src/kmerge_impl.rs
+++ b/src/kmerge_impl.rs
@@ -111,7 +111,7 @@
     fn kmerge_pred(&mut self, a: &T, b: &T) -> bool;
 }
 
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 pub struct KMergeByLt;
 
 impl<T: PartialOrd> KMergePredicate<T> for KMergeByLt {
diff --git a/src/lib.rs b/src/lib.rs
index 7dd6241..df95e19 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -179,6 +179,7 @@
 #[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 duplicates_impl;
 #[cfg(feature = "use_std")]
 mod unique_impl;
+mod unziptuple;
 mod with_position;
 mod zip_eq_impl;
 mod zip_longest;
@@ -660,7 +662,7 @@
     }
 
     /// Return an iterator over all contiguous windows producing tuples of
-    /// a specific size (up to 4).
+    /// a specific size (up to 12).
     ///
     /// `tuple_windows` clones the iterator elements so that they can be
     /// part of successive windows, this makes it most suited for iterators
@@ -702,7 +704,7 @@
 
     /// Return an iterator over all windows, wrapping back to the first
     /// elements when the window would otherwise exceed the length of the
-    /// iterator, producing tuples of a specific size (up to 4).
+    /// iterator, producing tuples of a specific size (up to 12).
     ///
     /// `circular_tuple_windows` clones the iterator elements so that they can be
     /// part of successive windows, this makes it most suited for iterators
@@ -735,7 +737,7 @@
         tuple_impl::circular_tuple_windows(self)
     }
     /// Return an iterator that groups the items in tuples of a specific size
-    /// (up to 4).
+    /// (up to 12).
     ///
     /// See also the method [`.next_tuple()`](Itertools::next_tuple).
     ///
@@ -2241,6 +2243,7 @@
     /// assert_eq!((0..10).fold1(|x, y| x + y).unwrap_or(0), 45);
     /// assert_eq!((0..0).fold1(|x, y| x * y), None);
     /// ```
+    #[deprecated(since = "0.10.2", note = "Use `Iterator::reduce` instead")]
     fn fold1<F>(mut self, f: F) -> Option<Self::Item>
         where F: FnMut(Self::Item, Self::Item) -> Self::Item,
               Self: Sized,
@@ -2665,6 +2668,43 @@
         v.into_iter()
     }
 
+    /// Sort all iterator elements into a new iterator in ascending order. The key function is
+    /// called exactly once per key.
+    ///
+    /// **Note:** This consumes the entire iterator, uses the
+    /// [`slice::sort_by_cached_key`] method and returns the result as a new
+    /// iterator that owns its elements.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // sort people in descending order by age
+    /// let people = vec![("Jane", 20), ("John", 18), ("Jill", 30), ("Jack", 27)];
+    ///
+    /// let oldest_people_first = people
+    ///     .into_iter()
+    ///     .sorted_by_cached_key(|x| -x.1)
+    ///     .map(|(person, _age)| person);
+    ///
+    /// itertools::assert_equal(oldest_people_first,
+    ///                         vec!["Jill", "Jack", "Jane", "John"]);
+    /// ```
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn sorted_by_cached_key<K, F>(self, f: F) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        K: Ord,
+        F: FnMut(&Self::Item) -> K,
+    {
+        let mut v = Vec::from_iter(self);
+        v.sort_by_cached_key(f);
+        v.into_iter()
+    }
+
     /// Sort the k smallest elements into a new iterator, in ascending order.
     ///
     /// **Note:** This consumes the entire iterator, and returns the result
@@ -2770,6 +2810,8 @@
     /// Return a `HashMap` of keys mapped to `Vec`s of values. Keys and values
     /// are taken from `(Key, Value)` tuple pairs yielded by the input iterator.
     ///
+    /// Essentially a shorthand for `.into_grouping_map().collect::<Vec<_>>()`.
+    ///
     /// ```
     /// use itertools::Itertools;
     ///
@@ -2791,8 +2833,8 @@
 
     /// Return an `Iterator` on a `HashMap`. Keys mapped to `Vec`s of values. The key is specified
     /// in the closure.
-    /// Different to `into_group_map_by` because the key is still present. It is also more general.
-    /// You can also fold the `group_map`.
+    ///
+    /// Essentially a shorthand for `.into_grouping_map_by(f).collect::<Vec<_>>()`.
     ///
     /// ```
     /// use itertools::Itertools;
@@ -3401,6 +3443,33 @@
     {
         self.map(f).counts()
     }
+
+    /// 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::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, vec![1, 4, 7]);
+    /// assert_eq!(b, vec![2, 5, 8]);
+    /// assert_eq!(c, vec![3, 6, 9]);
+    /// ```
+    fn multiunzip<FromI>(self) -> FromI
+    where
+        Self: Sized + MultiUnzip<FromI>,
+    {
+        MultiUnzip::multiunzip(self)
+    }
 }
 
 impl<T: ?Sized> Itertools for T where T: Iterator { }
diff --git a/src/multipeek_impl.rs b/src/multipeek_impl.rs
index 93b0227..5917681 100644
--- a/src/multipeek_impl.rs
+++ b/src/multipeek_impl.rs
@@ -38,6 +38,7 @@
     /// Works exactly like `.next()` with the only difference that it doesn't
     /// advance itself. `.peek()` can be called multiple times, to peek
     /// further ahead.
+    /// When `.next()` is called, reset the peeking “cursor”.
     pub fn peek(&mut self) -> Option<&I::Item> {
         let ret = if self.index < self.buf.len() {
             Some(&self.buf[self.index])
diff --git a/src/pad_tail.rs b/src/pad_tail.rs
index 03867cb..de57ee4 100644
--- a/src/pad_tail.rs
+++ b/src/pad_tail.rs
@@ -16,6 +16,13 @@
     filler: F,
 }
 
+impl<I, F> std::fmt::Debug for PadUsing<I, F>
+where
+    I: std::fmt::Debug,
+{
+    debug_fmt_fields!(PadUsing, iter, min, pos);
+}
+
 /// Create a new **PadUsing** iterator.
 pub fn pad_using<I, F>(iter: I, min: usize, filler: F) -> PadUsing<I, F>
     where I: Iterator,
diff --git a/src/peeking_take_while.rs b/src/peeking_take_while.rs
index f9f2134..cd0945a 100644
--- a/src/peeking_take_while.rs
+++ b/src/peeking_take_while.rs
@@ -83,6 +83,13 @@
     f: F,
 }
 
+impl<'a, I: 'a, F> std::fmt::Debug for PeekingTakeWhile<'a, I, F>
+where
+    I: Iterator + std::fmt::Debug,
+{
+    debug_fmt_fields!(PeekingTakeWhile, iter);
+}
+
 /// Create a PeekingTakeWhile
 pub fn peeking_take_while<I, F>(iter: &mut I, f: F) -> PeekingTakeWhile<I, F>
     where I: Iterator,
diff --git a/src/process_results_impl.rs b/src/process_results_impl.rs
index 9da108b..44308f3 100644
--- a/src/process_results_impl.rs
+++ b/src/process_results_impl.rs
@@ -30,6 +30,23 @@
     fn size_hint(&self) -> (usize, Option<usize>) {
         (0, self.iter.size_hint().1)
     }
+
+    fn fold<B, F>(mut self, init: B, mut f: F) -> B
+    where
+        Self: Sized,
+        F: FnMut(B, Self::Item) -> B,
+    {
+        let error = self.error;
+        self.iter
+            .try_fold(init, |acc, opt| match opt {
+                Ok(x) => Ok(f(acc, x)),
+                Err(e) => {
+                    *error = Err(e);
+                    Err(acc)
+                }
+            })
+            .unwrap_or_else(|e| e)
+    }
 }
 
 /// “Lift” a function of the values of an iterator so that it can process
diff --git a/src/tuple_impl.rs b/src/tuple_impl.rs
index ca8b97c..d914e03 100644
--- a/src/tuple_impl.rs
+++ b/src/tuple_impl.rs
@@ -11,7 +11,7 @@
 // hiding the implementation details of `TupleCollect`.
 // See https://github.com/rust-itertools/itertools/issues/387
 
-/// Implemented for homogeneous tuples of size up to 4.
+/// Implemented for homogeneous tuples of size up to 12.
 pub trait HomogeneousTuple
     : TupleCollect
 {}
@@ -77,7 +77,7 @@
 /// An iterator that groups the items in tuples of a specific size.
 ///
 /// See [`.tuples()`](crate::Itertools::tuples) for more information.
-#[derive(Clone)]
+#[derive(Clone, Debug)]
 #[must_use = "iterator adaptors are lazy and do nothing unless consumed"]
 pub struct Tuples<I, T>
     where I: Iterator<Item = T::Item>,
diff --git a/src/unziptuple.rs b/src/unziptuple.rs
new file mode 100644
index 0000000..f468f05
--- /dev/null
+++ b/src/unziptuple.rs
@@ -0,0 +1,80 @@
+/// 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;
+///
+/// let inputs = vec![(1, 2, 3), (4, 5, 6), (7, 8, 9)];
+///
+/// let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = multiunzip(inputs);
+///
+/// assert_eq!(a, vec![1, 4, 7]);
+/// assert_eq!(b, vec![2, 5, 8]);
+/// assert_eq!(c, vec![3, 6, 9]);
+/// ```
+///
+/// [`multizip`]: crate::multizip
+pub fn multiunzip<FromI, I>(i: I) -> FromI
+where
+    I: IntoIterator,
+    I::IntoIter: MultiUnzip<FromI>,
+{
+    i.into_iter().multiunzip()
+}
+
+/// An iterator that can be unzipped into multiple collections.
+///
+/// See [`.multiunzip()`](crate::Itertools::multiunzip) for more information.
+pub trait MultiUnzip<FromI>: Iterator {
+    /// Unzip this iterator into multiple collections.
+    fn multiunzip(self) -> FromI;
+}
+
+macro_rules! impl_unzip_iter {
+    ($($T:ident => $FromT:ident),*) => (
+        #[allow(non_snake_case)]
+        impl<IT: Iterator<Item = ($($T,)*)>, $($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;
+
+                // 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!();
+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/fold_specialization.rs b/tests/fold_specialization.rs
deleted file mode 100644
index a984b40..0000000
--- a/tests/fold_specialization.rs
+++ /dev/null
@@ -1,13 +0,0 @@
-use itertools::Itertools;
-
-#[test]
-fn specialization_intersperse() {
-    let mut iter = (1..2).intersperse(0);
-    iter.clone().for_each(|x| assert_eq!(Some(x), iter.next()));
-
-    let mut iter = (1..3).intersperse(0);
-    iter.clone().for_each(|x| assert_eq!(Some(x), iter.next()));
-
-    let mut iter = (1..4).intersperse(0);
-    iter.clone().for_each(|x| assert_eq!(Some(x), iter.next()));
-}
diff --git a/tests/specializations.rs b/tests/specializations.rs
index bc337c2..199cf56 100644
--- a/tests/specializations.rs
+++ b/tests/specializations.rs
@@ -15,16 +15,16 @@
     }
 }
 
-fn check_specialized<'a, V, IterItem, Iter, F>(iterator: &Iter, mapper: F)
-where
-    V: Eq + Debug,
-    Iter: Iterator<Item = IterItem> + Clone + 'a,
-    F: Fn(Box<dyn Iterator<Item = IterItem> + 'a>) -> V,
-{
-    assert_eq!(
-        mapper(Box::new(Unspecialized(iterator.clone()))),
-        mapper(Box::new(iterator.clone()))
-    )
+macro_rules! check_specialized {
+    ($src:expr, |$it:pat| $closure:expr) => {
+        let $it = $src.clone();
+        let v1 = $closure;
+
+        let $it = Unspecialized($src.clone());
+        let v2 = $closure;
+
+        assert_eq!(v1, v2);
+    }
 }
 
 fn test_specializations<IterItem, Iter>(
@@ -33,10 +33,10 @@
     IterItem: Eq + Debug + Clone,
     Iter: Iterator<Item = IterItem> + Clone,
 {
-    check_specialized(it, |i| i.count());
-    check_specialized(it, |i| i.last());
-    check_specialized(it, |i| i.collect::<Vec<_>>());
-    check_specialized(it, |i| {
+    check_specialized!(it, |i| i.count());
+    check_specialized!(it, |i| i.last());
+    check_specialized!(it, |i| i.collect::<Vec<_>>());
+    check_specialized!(it, |i| {
         let mut parameters_from_fold = vec![];
         let fold_result = i.fold(vec![], |mut acc, v: IterItem| {
             parameters_from_fold.push((acc.clone(), v.clone()));
@@ -45,7 +45,7 @@
         });
         (parameters_from_fold, fold_result)
     });
-    check_specialized(it, |mut i| {
+    check_specialized!(it, |mut i| {
         let mut parameters_from_all = vec![];
         let first = i.next();
         let all_result = i.all(|x| {
@@ -56,7 +56,7 @@
     });
     let size = it.clone().count();
     for n in 0..size + 2 {
-        check_specialized(it, |mut i| i.nth(n));
+        check_specialized!(it, |mut i| i.nth(n));
     }
     // size_hint is a bit harder to check
     let mut it_sh = it.clone();
@@ -73,6 +73,12 @@
 }
 
 quickcheck! {
+    fn intersperse(v: Vec<u8>) -> () {
+        test_specializations(&v.into_iter().intersperse(0));
+    }
+}
+
+quickcheck! {
     fn put_back_qc(test_vec: Vec<i32>) -> () {
         test_specializations(&itertools::put_back(test_vec.iter()));
         let mut pb = itertools::put_back(test_vec.into_iter());
@@ -98,3 +104,50 @@
         test_specializations(&v.into_iter().map_ok(|u| u.checked_add(1)));
     }
 }
+
+quickcheck! {
+    fn process_results(v: Vec<Result<u8, u8>>) -> () {
+        helper(v.iter().copied());
+        helper(v.iter().copied().filter(Result::is_ok));
+
+        fn helper(it: impl Iterator<Item = Result<u8, u8>> + Clone) {
+            macro_rules! check_results_specialized {
+                ($src:expr, |$it:pat| $closure:expr) => {
+                    assert_eq!(
+                        itertools::process_results($src.clone(), |$it| $closure),
+                        itertools::process_results($src.clone(), |i| {
+                            let $it = Unspecialized(i);
+                            $closure
+                        }),
+                    )
+                }
+            }
+
+            check_results_specialized!(it, |i| i.count());
+            check_results_specialized!(it, |i| i.last());
+            check_results_specialized!(it, |i| i.collect::<Vec<_>>());
+            check_results_specialized!(it, |i| {
+                let mut parameters_from_fold = vec![];
+                let fold_result = i.fold(vec![], |mut acc, v| {
+                    parameters_from_fold.push((acc.clone(), v.clone()));
+                    acc.push(v);
+                    acc
+                });
+                (parameters_from_fold, fold_result)
+            });
+            check_results_specialized!(it, |mut i| {
+                let mut parameters_from_all = vec![];
+                let first = i.next();
+                let all_result = i.all(|x| {
+                    parameters_from_all.push(x.clone());
+                    Some(x)==first
+                });
+                (parameters_from_all, all_result)
+            });
+            let size = it.clone().count();
+            for n in 0..size + 2 {
+                check_results_specialized!(it, |mut i| i.nth(n));
+            }
+        }
+    }
+}
diff --git a/tests/test_core.rs b/tests/test_core.rs
index bcdca0e..a7b7449 100644
--- a/tests/test_core.rs
+++ b/tests/test_core.rs
@@ -9,6 +9,8 @@
 use itertools as it;
 use crate::it::Itertools;
 use crate::it::interleave;
+use crate::it::intersperse;
+use crate::it::intersperse_with;
 use crate::it::multizip;
 use crate::it::free::put_back;
 use crate::it::iproduct;
@@ -136,6 +138,23 @@
     it::assert_equal(it, rs.iter());
 }
 
+#[test]
+fn test_intersperse() {
+    let xs = [1u8, 2, 3];
+    let ys = [1u8, 0, 2, 0, 3];
+    let it = intersperse(&xs, &0);
+    it::assert_equal(it, ys.iter());
+}
+
+#[test]
+fn test_intersperse_with() {
+    let xs = [1u8, 2, 3];
+    let ys = [1u8, 10, 2, 10, 3];
+    let i = 10;
+    let it = intersperse_with(&xs, || &i);
+    it::assert_equal(it, ys.iter());
+}
+
 #[allow(deprecated)]
 #[test]
 fn foreach() {
diff --git a/tests/test_std.rs b/tests/test_std.rs
index 7cda9b5..2049d15 100644
--- a/tests/test_std.rs
+++ b/tests/test_std.rs
@@ -84,6 +84,13 @@
     it::assert_equal(ys.iter(), xs.iter().rev().duplicates().rev());
     let ys_rev = [1, 0];
     it::assert_equal(ys_rev.iter(), xs.iter().duplicates().rev());
+
+    let xs = vec![0, 1, 2, 1, 2];
+    let ys = vec![1, 2];
+    assert_eq!(ys, xs.iter().duplicates().cloned().collect_vec());
+    assert_eq!(ys, xs.iter().rev().duplicates().rev().cloned().collect_vec());
+    let ys_rev = vec![2, 1];
+    assert_eq!(ys_rev, xs.iter().duplicates().rev().cloned().collect_vec());
 }
 
 #[test]
@@ -504,6 +511,30 @@
 }
 
 #[test]
+fn sorted_by_cached_key() {
+    // Track calls to key function
+    let mut ncalls = 0;
+
+    let sorted = [3, 4, 1, 2].iter().cloned().sorted_by_cached_key(|&x| {
+        ncalls += 1;
+        x.to_string()
+    });
+    it::assert_equal(sorted, vec![1, 2, 3, 4]);
+    // Check key function called once per element
+    assert_eq!(ncalls, 4);
+
+    let mut ncalls = 0;
+
+    let sorted = (0..5).sorted_by_cached_key(|&x| {
+        ncalls += 1;
+        -x
+    });
+    it::assert_equal(sorted, vec![4, 3, 2, 1, 0]);
+    // Check key function called once per element
+    assert_eq!(ncalls, 5);
+}
+
+#[test]
 fn test_multipeek() {
     let nums = vec![1u8,2,3,4,5];
 
@@ -1080,3 +1111,12 @@
     [].iter().exactly_one()?;
     Ok(())
 }
+
+#[test]
+fn multiunzip() {
+    let (a, b, c): (Vec<_>, Vec<_>, Vec<_>) = [(0, 1, 2), (3, 4, 5), (6, 7, 8)].iter().cloned().multiunzip();    
+    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