Repository: rust-itertools/itertools
Pull request #964 (2024-07-03 08:02:20)

Fix/ignore warnings within doctests
#963 lead me to know about [`#![doc(test(attr(...)))]`](https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html#testattr).

Our doctests have some warnings that are ignored by default.
Here, I fix some, sometimes silently (comment out), and globally ignore `deprecated` and `unstable_name_collisions`.
Maybe I should `allow(unused_variables)` instead of many `# let _ = ...;`?

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/rust-itertools/itertools/pull/964?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) Report
Attention: Patch coverage is `91.30435%` with `2 lines` in your changes missing coverage. Please review.
> Project coverage is 94.51%. Comparing base [(`6814180`)](https://app.codecov.io/gh/rust-itertools/itertools/commit/68141809aabec2816d3250820c8d6608f3b2293f?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) to head [(`9238e2a`)](https://app.codecov.io/gh/rust-itertools/itertools/commit/9238e2ab6d8a59f470f467bea956837f745287bb?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).
> Report is 108 commits behind head on master.

| [Files](https://app.codecov.io/gh/rust-itertools/itertools/pull/964?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) | Patch % | Lines |
|---|---|---|
| [src/lib.rs](https://app.codecov.io/gh/rust-itertools/itertools/pull/964?src=pr&el=tree&filepath=src%2Flib.rs&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None#diff-c3JjL2xpYi5ycw==) | 87.50% | [2 Missing :warning: ](https://app.codecov.io/gh/rust-itertools/itertools/pull/964?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #964      +/-   ##
==========================================
+ Coverage   94.38%   94.51%   +0.12%     
==========================================
  Files          48       49       +1     
  Lines        6665     7072     +407     
==========================================
+ Hits         6291     6684     +393     
- Misses        374      388      +14     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/rust-itertools/itertools/pull/964?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=None).


Patch:
diff --git a/Cargo.toml b/Cargo.toml
index 8006146..6ec88a9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -27,7 +27,7 @@ either = { version = "1.0", default-features = false }
 
 [dev-dependencies]
 rand = "0.7"
-criterion = "0.4.0"
+criterion = { version = "0.4.0", features = ["html_reports"] }
 paste = "1.0.0"  # Used in test_std to instantiate generic tests
 permutohedron = "0.2"
 quickcheck = { version = "0.9", default_features = false }
@@ -75,3 +75,7 @@ harness = false
 [[bench]]
 name = "specializations"
 harness = false
+
+[[bench]]
+name = "k_smallest"
+harness = false
diff --git a/benches/k_smallest.rs b/benches/k_smallest.rs
new file mode 100644
index 0000000..509ed7f
--- /dev/null
+++ b/benches/k_smallest.rs
@@ -0,0 +1,61 @@
+use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion};
+use itertools::Itertools;
+use rand::{rngs::StdRng, seq::SliceRandom, SeedableRng};
+
+fn strict(b: &mut Bencher, (k, vals): &(usize, &Vec<usize>)) {
+    b.iter(|| black_box(vals.iter()).k_smallest(*k))
+}
+
+fn relaxed(b: &mut Bencher, (k, vals): &(usize, &Vec<usize>)) {
+    b.iter(|| black_box(vals.iter()).k_smallest_relaxed(*k))
+}
+
+fn ascending(n: usize) -> Vec<usize> {
+    (0..n).collect()
+}
+
+fn random(n: usize) -> Vec<usize> {
+    let mut vals = (0..n).collect_vec();
+    vals.shuffle(&mut StdRng::seed_from_u64(42));
+    vals
+}
+
+fn descending(n: usize) -> Vec<usize> {
+    (0..n).rev().collect()
+}
+
+fn k_smallest(c: &mut Criterion, order: &str, vals: fn(usize) -> Vec<usize>) {
+    let mut g = c.benchmark_group(format!("k-smallest/{order}"));
+
+    for log_n in 20..23 {
+        let n = 1 << log_n;
+
+        let vals = vals(n);
+
+        for log_k in 7..10 {
+            let k = 1 << log_k;
+
+            let params = format!("{log_n}/{log_k}");
+            let input = (k, &vals);
+            g.bench_with_input(BenchmarkId::new("strict", &params), &input, strict);
+            g.bench_with_input(BenchmarkId::new("relaxed", &params), &input, relaxed);
+        }
+    }
+
+    g.finish()
+}
+
+fn k_smallest_asc(c: &mut Criterion) {
+    k_smallest(c, "asc", ascending);
+}
+
+fn k_smallest_rand(c: &mut Criterion) {
+    k_smallest(c, "rand", random);
+}
+
+fn k_smallest_desc(c: &mut Criterion) {
+    k_smallest(c, "desc", descending);
+}
+
+criterion_group!(benches, k_smallest_asc, k_smallest_rand, k_smallest_desc);
+criterion_main!(benches);
diff --git a/src/free.rs b/src/free.rs
index 8d0bcf3..4c68205 100644
--- a/src/free.rs
+++ b/src/free.rs
@@ -36,7 +36,7 @@ pub use crate::zip_eq_impl::zip_eq;
 /// ```
 /// use itertools::intersperse;
 ///
-/// itertools::assert_equal(intersperse((0..3), 8), vec![0, 8, 1, 8, 2]);
+/// itertools::assert_equal(intersperse(0..3, 8), vec![0, 8, 1, 8, 2]);
 /// ```
 pub fn intersperse<I>(iterable: I, element: I::Item) -> Intersperse<I::IntoIter>
 where
@@ -55,7 +55,7 @@ where
 /// use itertools::intersperse_with;
 ///
 /// let mut i = 10;
-/// itertools::assert_equal(intersperse_with((0..3), || { i -= 1; i }), vec![0, 9, 1, 8, 2]);
+/// itertools::assert_equal(intersperse_with(0..3, || { i -= 1; i }), vec![0, 9, 1, 8, 2]);
 /// assert_eq!(i, 8);
 /// ```
 pub fn intersperse_with<I, F>(iterable: I, element: F) -> IntersperseWith<I::IntoIter, F>
@@ -75,6 +75,7 @@ where
 ///
 /// for (i, elt) in enumerate(&[1, 2, 3]) {
 ///     /* loop body */
+///     # let _ = (i, elt);
 /// }
 /// ```
 pub fn enumerate<I>(iterable: I) -> iter::Enumerate<I::IntoIter>
@@ -93,6 +94,7 @@ where
 ///
 /// for elt in rev(&[1, 2, 3]) {
 ///     /* loop body */
+///     # let _ = elt;
 /// }
 /// ```
 pub fn rev<I>(iterable: I) -> iter::Rev<I::IntoIter>
diff --git a/src/k_smallest.rs b/src/k_smallest.rs
index 7b2f62e..7e4ace2 100644
--- a/src/k_smallest.rs
+++ b/src/k_smallest.rs
@@ -88,6 +88,46 @@ where
     storage
 }
 
+pub(crate) fn k_smallest_relaxed_general<I, F>(iter: I, k: usize, mut comparator: F) -> Vec<I::Item>
+where
+    I: Iterator,
+    F: FnMut(&I::Item, &I::Item) -> Ordering,
+{
+    if k == 0 {
+        iter.last();
+        return Vec::new();
+    }
+
+    let mut iter = iter.fuse();
+    let mut buf = iter.by_ref().take(2 * k).collect::<Vec<_>>();
+
+    if buf.len() < k {
+        buf.sort_unstable_by(&mut comparator);
+        return buf;
+    }
+
+    buf.select_nth_unstable_by(k - 1, &mut comparator);
+    buf.truncate(k);
+
+    iter.for_each(|val| {
+        if comparator(&val, &buf[k - 1]) != Ordering::Less {
+            return;
+        }
+
+        assert_ne!(buf.len(), buf.capacity());
+        buf.push(val);
+
+        if buf.len() == 2 * k {
+            buf.select_nth_unstable_by(k - 1, &mut comparator);
+            buf.truncate(k);
+        }
+    });
+
+    buf.sort_unstable_by(&mut comparator);
+    buf.truncate(k);
+    buf
+}
+
 #[inline]
 pub(crate) fn key_to_cmp<T, K, F>(mut key: F) -> impl FnMut(&T, &T) -> Ordering
 where
diff --git a/src/kmerge_impl.rs b/src/kmerge_impl.rs
index 13c935b..9ea73f9 100644
--- a/src/kmerge_impl.rs
+++ b/src/kmerge_impl.rs
@@ -134,6 +134,7 @@ impl<T, F: FnMut(&T, &T) -> bool> KMergePredicate<T> for F {
 ///
 /// for elt in kmerge(vec![vec![0, 2, 4], vec![1, 3, 5], vec![6, 7]]) {
 ///     /* loop body */
+///     # let _ = elt;
 /// }
 /// ```
 pub fn kmerge<I>(iterable: I) -> KMerge<<I::Item as IntoIterator>::IntoIter>
diff --git a/src/lib.rs b/src/lib.rs
index b98e03f..0f5ea8e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,6 +1,7 @@
 #![warn(missing_docs, clippy::default_numeric_fallback)]
 #![crate_name = "itertools"]
 #![cfg_attr(not(feature = "use_std"), no_std)]
+#![doc(test(attr(deny(warnings), allow(deprecated, unstable_name_collisions))))]
 
 //! Extra iterator adaptors, functions and macros.
 //!
@@ -8,6 +9,7 @@
 //! the [`Itertools`] trait:
 //!
 //! ```
+//! # #[allow(unused_imports)]
 //! use itertools::Itertools;
 //! ```
 //!
@@ -29,6 +31,7 @@
 //!
 //! for elt in interleave(&[1, 2, 3], &[2, 3, 4]) {
 //!     /* loop body */
+//!     # let _ = elt;
 //! }
 //! ```
 //!
@@ -248,6 +251,7 @@ mod ziptuple;
 /// // from (0, 0, 0), (0, 0, 1), .., (0, 1, 0), (0, 1, 1), .. etc until (3, 3, 3)
 /// for (i, j, k) in iproduct!(0..4, 0..4, 0..4) {
 ///    // ..
+///    # let _ = (i, j, k);
 /// }
 /// # }
 /// ```
@@ -375,16 +379,16 @@ macro_rules! izip {
 ///
 /// Invocations of `chain!` with one argument expand to [`arg.into_iter()`](IntoIterator):
 /// ```
-/// use std::{ops::Range, slice};
+/// use std::ops::Range;
 /// use itertools::chain;
-/// let _: <Range<_> as IntoIterator>::IntoIter = chain!((2..6),); // trailing comma optional!
+/// let _: <Range<_> as IntoIterator>::IntoIter = chain!(2..6,); // trailing comma optional!
 /// let _:     <&[_] as IntoIterator>::IntoIter = chain!(&[2, 3, 4]);
 /// ```
 ///
 /// Invocations of `chain!` with multiple arguments [`.into_iter()`](IntoIterator) each
 /// argument, and then [`chain`] them together:
 /// ```
-/// use std::{iter::*, ops::Range, slice};
+/// use std::{iter::*, slice};
 /// use itertools::{assert_equal, chain};
 ///
 /// // e.g., this:
@@ -1902,7 +1906,7 @@ pub trait Itertools: Iterator {
     /// use itertools::Itertools;
     ///
     /// let input = vec![vec![1], vec![3, 2, 1]];
-    /// let it = input.into_iter().update(|mut v| v.push(0));
+    /// let it = input.into_iter().update(|v| v.push(0));
     /// itertools::assert_equal(it, vec![vec![1, 0], vec![3, 2, 1, 0]]);
     /// ```
     fn update<F>(self, updater: F) -> Update<Self, F>
@@ -2162,7 +2166,7 @@ pub trait Itertools: Iterator {
     /// ```
     /// use itertools::Itertools;
     ///
-    /// let mut iter = "αβγ".chars().dropping(2);
+    /// let iter = "αβγ".chars().dropping(2);
     /// itertools::assert_equal(iter, "γ".chars());
     /// ```
     ///
@@ -2246,14 +2250,17 @@ pub trait Itertools: Iterator {
     ///
     /// fn process_dir_entries(entries: &[fs::DirEntry]) {
     ///     // ...
+    ///     # let _ = entries;
     /// }
     ///
-    /// fn do_stuff() -> std::io::Result<()> {
+    /// fn do_stuff() -> io::Result<()> {
     ///     let entries: Vec<_> = fs::read_dir(".")?.try_collect()?;
     ///     process_dir_entries(&entries);
     ///
     ///     Ok(())
     /// }
+    ///
+    /// # let _ = do_stuff;
     /// ```
     fn try_collect<T, U, E>(self) -> Result<U, E>
     where
@@ -2404,6 +2411,7 @@ pub trait Itertools: Iterator {
     /// accum = f(accum, 1);
     /// accum = f(accum, 2);
     /// accum = f(accum, 3);
+    /// # let _ = accum;
     /// ```
     ///
     /// With a `start` value of 0 and an addition as folding function,
@@ -2554,16 +2562,16 @@ pub trait Itertools: Iterator {
     /// assert_eq!((1..8).map(|x| x.to_string()).tree_reduce(f),
     ///            Some(String::from("f(f(f(1, 2), f(3, 4)), f(f(5, 6), 7))")));
     ///
-    /// // Like fold1, an empty iterator produces None
+    /// // Like reduce, an empty iterator produces None
     /// assert_eq!((0..0).tree_reduce(|x, y| x * y), None);
     ///
-    /// // tree_reduce matches fold1 for associative operations...
+    /// // tree_reduce matches reduce for associative operations...
     /// assert_eq!((0..10).tree_reduce(|x, y| x + y),
-    ///     (0..10).fold1(|x, y| x + y));
+    ///     (0..10).reduce(|x, y| x + y));
     ///
     /// // ...but not for non-associative ones
     /// assert_ne!((0..10).tree_reduce(|x, y| x - y),
-    ///     (0..10).fold1(|x, y| x - y));
+    ///     (0..10).reduce(|x, y| x - y));
     ///
     /// let mut total_len_reduce = 0;
     /// let reduce_res = (1..100).map(|x| x.to_string())
@@ -3153,6 +3161,105 @@ pub trait Itertools: Iterator {
         self.k_smallest_by(k, k_smallest::key_to_cmp(key))
     }
 
+    /// Sort the k smallest elements into a new iterator, in ascending order, relaxing the amount of memory required.
+    ///
+    /// **Note:** This consumes the entire iterator, and returns the result
+    /// as a new iterator that owns its elements.  If the input contains
+    /// less than k elements, the result is equivalent to `self.sorted()`.
+    ///
+    /// This is guaranteed to use `2 * k * sizeof(Self::Item) + O(1)` memory
+    /// and `O(n + k log k)` time, with `n` the number of elements in the input,
+    /// meaning it uses more memory than the minimum obtained by [`k_smallest`](Itertools::k_smallest)
+    /// but achieves linear time in the number of elements.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// **Note:** This is functionally-equivalent to `self.sorted().take(k)`
+    /// but much more efficient.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_smallest = numbers
+    ///     .into_iter()
+    ///     .k_smallest_relaxed(5);
+    ///
+    /// itertools::assert_equal(five_smallest, 0..5);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_smallest_relaxed(self, k: usize) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        Self::Item: Ord,
+    {
+        self.k_smallest_relaxed_by(k, Ord::cmp)
+    }
+
+    /// Sort the k smallest elements into a new iterator using the provided comparison, relaxing the amount of memory required.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// This corresponds to `self.sorted_by(cmp).take(k)` in the same way that
+    /// [`k_smallest_relaxed`](Itertools::k_smallest_relaxed) corresponds to `self.sorted().take(k)`,
+    /// in both semantics and complexity.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_smallest = numbers
+    ///     .into_iter()
+    ///     .k_smallest_relaxed_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b)));
+    ///
+    /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_smallest_relaxed_by<F>(self, k: usize, cmp: F) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        F: FnMut(&Self::Item, &Self::Item) -> Ordering,
+    {
+        k_smallest::k_smallest_relaxed_general(self, k, cmp).into_iter()
+    }
+
+    /// Return the elements producing the k smallest outputs of the provided function, relaxing the amount of memory required.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// This corresponds to `self.sorted_by_key(key).take(k)` in the same way that
+    /// [`k_smallest_relaxed`](Itertools::k_smallest_relaxed) corresponds to `self.sorted().take(k)`,
+    /// in both semantics and complexity.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_smallest = numbers
+    ///     .into_iter()
+    ///     .k_smallest_relaxed_by_key(5, |n| (n % 7, *n));
+    ///
+    /// itertools::assert_equal(five_smallest, vec![0, 7, 14, 1, 8]);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_smallest_relaxed_by_key<F, K>(self, k: usize, key: F) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        F: FnMut(&Self::Item) -> K,
+        K: Ord,
+    {
+        self.k_smallest_relaxed_by(k, k_smallest::key_to_cmp(key))
+    }
+
     /// Sort the k largest elements into a new iterator, in descending order.
     ///
     /// The sorted iterator, if directly collected to a `Vec`, is converted
@@ -3243,6 +3350,94 @@ pub trait Itertools: Iterator {
         self.k_largest_by(k, k_smallest::key_to_cmp(key))
     }
 
+    /// Sort the k largest elements into a new iterator, in descending order, relaxing the amount of memory required.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// It is semantically equivalent to [`k_smallest_relaxed`](Itertools::k_smallest_relaxed)
+    /// with a reversed `Ord`.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_largest = numbers
+    ///     .into_iter()
+    ///     .k_largest_relaxed(5);
+    ///
+    /// itertools::assert_equal(five_largest, vec![14, 13, 12, 11, 10]);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_largest_relaxed(self, k: usize) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        Self::Item: Ord,
+    {
+        self.k_largest_relaxed_by(k, Self::Item::cmp)
+    }
+
+    /// Sort the k largest elements into a new iterator using the provided comparison, relaxing the amount of memory required.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// Functionally equivalent to [`k_smallest_relaxed_by`](Itertools::k_smallest_relaxed_by)
+    /// with a reversed `Ord`.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_largest = numbers
+    ///     .into_iter()
+    ///     .k_largest_relaxed_by(5, |a, b| (a % 7).cmp(&(b % 7)).then(a.cmp(b)));
+    ///
+    /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_largest_relaxed_by<F>(self, k: usize, mut cmp: F) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        F: FnMut(&Self::Item, &Self::Item) -> Ordering,
+    {
+        self.k_smallest_relaxed_by(k, move |a, b| cmp(b, a))
+    }
+
+    /// Return the elements producing the k largest outputs of the provided function, relaxing the amount of memory required.
+    ///
+    /// The sorted iterator, if directly collected to a `Vec`, is converted
+    /// without any extra copying or allocation cost.
+    ///
+    /// Functionally equivalent to [`k_smallest_relaxed_by_key`](Itertools::k_smallest_relaxed_by_key)
+    /// with a reversed `Ord`.
+    ///
+    /// ```
+    /// use itertools::Itertools;
+    ///
+    /// // A random permutation of 0..15
+    /// let numbers = vec![6, 9, 1, 14, 0, 4, 8, 7, 11, 2, 10, 3, 13, 12, 5];
+    ///
+    /// let five_largest = numbers
+    ///     .into_iter()
+    ///     .k_largest_relaxed_by_key(5, |n| (n % 7, *n));
+    ///
+    /// itertools::assert_equal(five_largest, vec![13, 6, 12, 5, 11]);
+    /// ```
+    #[cfg(feature = "use_alloc")]
+    fn k_largest_relaxed_by_key<F, K>(self, k: usize, key: F) -> VecIntoIter<Self::Item>
+    where
+        Self: Sized,
+        F: FnMut(&Self::Item) -> K,
+        K: Ord,
+    {
+        self.k_largest_relaxed_by(k, k_smallest::key_to_cmp(key))
+    }
+
     /// Consumes the iterator and return an iterator of the last `n` elements.
     ///
     /// The iterator, if directly collected to a `VecDeque`, is converted
@@ -4163,7 +4358,7 @@ pub trait Itertools: Iterator {
     /// # Examples
     /// ```
     /// # use itertools::Itertools;
-    /// let counts = [1, 1, 1, 3, 3, 5].into_iter().counts();
+    /// let counts = [1, 1, 1, 3, 3, 5].iter().counts();
     /// assert_eq!(counts[&1], 3);
     /// assert_eq!(counts[&3], 2);
     /// assert_eq!(counts[&5], 1);
@@ -4189,6 +4384,7 @@ pub trait Itertools: Iterator {
     /// # use itertools::Itertools;
     /// struct Character {
     ///   first_name: &'static str,
+    ///   # #[allow(dead_code)]
     ///   last_name:  &'static str,
     /// }
     ///
diff --git a/src/merge_join.rs b/src/merge_join.rs
index c0de35f..5f4a605 100644
--- a/src/merge_join.rs
+++ b/src/merge_join.rs
@@ -31,6 +31,7 @@ pub type Merge<I, J> = MergeBy<I, J, MergeLte>;
 ///
 /// for elt in merge(&[1, 2, 3], &[2, 3, 4]) {
 ///     /* loop body */
+///     # let _ = elt;
 /// }
 /// ```
 pub fn merge<I, J>(
diff --git a/src/zip_eq_impl.rs b/src/zip_eq_impl.rs
index 6d3b682..3240a40 100644
--- a/src/zip_eq_impl.rs
+++ b/src/zip_eq_impl.rs
@@ -21,6 +21,7 @@ pub struct ZipEq<I, J> {
 /// let data = [1, 2, 3, 4, 5];
 /// for (a, b) in zip_eq(&data[..data.len() - 1], &data[1..]) {
 ///     /* loop body */
+///     # let _ = (a, b);
 /// }
 /// ```
 pub fn zip_eq<I, J>(i: I, j: J) -> ZipEq<I::IntoIter, J::IntoIter>


Test patch:
diff --git a/tests/test_std.rs b/tests/test_std.rs
index f626a17..ad391fa 100644
--- a/tests/test_std.rs
+++ b/tests/test_std.rs
@@ -528,6 +528,42 @@ qc::quickcheck! {
         it::assert_equal(largest_by, sorted_largest.clone());
         it::assert_equal(largest_by_key, sorted_largest);
     }
+
+    fn k_smallest_relaxed_range(n: i64, m: u16, k: u16) -> () {
+        // u16 is used to constrain k and m to 0..2¹⁶,
+        //  otherwise the test could use too much memory.
+        let (k, m) = (k as usize, m as u64);
+
+        let mut v: Vec<_> = (n..n.saturating_add(m as _)).collect();
+        // Generate a random permutation of n..n+m
+        v.shuffle(&mut thread_rng());
+
+        // Construct the right answers for the top and bottom elements
+        let mut sorted = v.clone();
+        sorted.sort();
+        // how many elements are we checking
+        let num_elements = min(k, m as _);
+
+        // Compute the top and bottom k in various combinations
+        let sorted_smallest = sorted[..num_elements].iter().cloned();
+        let smallest = v.iter().cloned().k_smallest_relaxed(k);
+        let smallest_by = v.iter().cloned().k_smallest_relaxed_by(k, Ord::cmp);
+        let smallest_by_key = v.iter().cloned().k_smallest_relaxed_by_key(k, |&x| x);
+
+        let sorted_largest = sorted[sorted.len() - num_elements..].iter().rev().cloned();
+        let largest = v.iter().cloned().k_largest_relaxed(k);
+        let largest_by = v.iter().cloned().k_largest_relaxed_by(k, Ord::cmp);
+        let largest_by_key = v.iter().cloned().k_largest_relaxed_by_key(k, |&x| x);
+
+        // Check the variations produce the same answers and that they're right
+        it::assert_equal(smallest, sorted_smallest.clone());
+        it::assert_equal(smallest_by, sorted_smallest.clone());
+        it::assert_equal(smallest_by_key, sorted_smallest);
+
+        it::assert_equal(largest, sorted_largest.clone());
+        it::assert_equal(largest_by, sorted_largest.clone());
+        it::assert_equal(largest_by_key, sorted_largest);
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -572,8 +608,11 @@ where
     I::Item: Ord + Debug,
 {
     let j = i.clone();
+    let i1 = i.clone();
+    let j1 = i.clone();
     let k = k as usize;
-    it::assert_equal(i.k_smallest(k), j.sorted().take(k))
+    it::assert_equal(i.k_smallest(k), j.sorted().take(k));
+    it::assert_equal(i1.k_smallest_relaxed(k), j1.sorted().take(k));
 }
 
 // Similar to `k_smallest_sort` but for our custom heap implementation.
@@ -583,8 +622,11 @@ where
     I::Item: Ord + Debug,
 {
     let j = i.clone();
+    let i1 = i.clone();
+    let j1 = i.clone();
     let k = k as usize;
-    it::assert_equal(i.k_smallest_by(k, Ord::cmp), j.sorted().take(k))
+    it::assert_equal(i.k_smallest_by(k, Ord::cmp), j.sorted().take(k));
+    it::assert_equal(i1.k_smallest_relaxed_by(k, Ord::cmp), j1.sorted().take(k));
 }
 
 macro_rules! generic_test {