Skip to content

Commit

Permalink
Merge #613
Browse files Browse the repository at this point in the history
613: Minmax set (prev "Add {min,max}_set(_by{_key)?)? functions") r=jswrenn a=phimuemue

This PR supersedes #323 (I did not know how I could amend to the original PR, so sorry for the "duplicate" PR here.)

The original PR by `@zayenz` looked rather good. I adjusted the stuff that came up during discussion back then:

* Teturn type `Vec` instead of `Option` - emptiness is sufficiently well represented by `Vec`.
* Functions require `Ord` instead of `PartialOrd` - just as `Iterator::min`.
* Avoid duplicate calls to `lt` by accepting a `FnMut(...)->Ordering` - seems canonical compared to the `bool`-solution.
* Use internal iteration instead of a manual `for`-loop.

Moreover, I simplified some bits.

Co-authored-by: Mikael Zayenz Lagerkvist <zayenz@gmail.com>
Co-authored-by: philipp <descpl@yahoo.de>
  • Loading branch information
3 people committed May 23, 2022
2 parents a96ef76 + 846219f commit 1cb2c36
Show file tree
Hide file tree
Showing 4 changed files with 346 additions and 1 deletion.
48 changes: 48 additions & 0 deletions src/extrema_set.rs
@@ -0,0 +1,48 @@
use std::cmp::Ordering;

/// Implementation guts for `min_set`, `min_set_by`, and `min_set_by_key`.
pub fn min_set_impl<I, K, F, Compare>(
mut it: I,
mut key_for: F,
mut compare: Compare,
) -> Vec<I::Item>
where
I: Iterator,
F: FnMut(&I::Item) -> K,
Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering,
{
match it.next() {
None => Vec::new(),
Some(element) => {
let mut current_key = key_for(&element);
let mut result = vec![element];
it.for_each(|element| {
let key = key_for(&element);
match compare(&element, &result[0], &key, &current_key) {
Ordering::Less => {
result.clear();
result.push(element);
current_key = key;
}
Ordering::Equal => {
result.push(element);
}
Ordering::Greater => {}
}
});
result
}
}
}

/// Implementation guts for `ax_set`, `max_set_by`, and `max_set_by_key`.
pub fn max_set_impl<I, K, F, Compare>(it: I, key_for: F, mut compare: Compare) -> Vec<I::Item>
where
I: Iterator,
F: FnMut(&I::Item) -> K,
Compare: FnMut(&I::Item, &I::Item, &K, &K) -> Ordering,
{
min_set_impl(it, key_for, |it1, it2, key1, key2| {
compare(it2, it1, key2, key1)
})
}
190 changes: 190 additions & 0 deletions src/lib.rs
Expand Up @@ -197,6 +197,8 @@ mod combinations_with_replacement;
mod exactly_one_err;
mod diff;
mod flatten_ok;
#[cfg(feature = "use_std")]
mod extrema_set;
mod format;
#[cfg(feature = "use_std")]
mod grouping_map;
Expand Down Expand Up @@ -2902,6 +2904,194 @@ pub trait Itertools : Iterator {
grouping_map::new(grouping_map::MapForGrouping::new(self, key_mapper))
}

/// Return all minimum elements of an iterator.
///
/// # Examples
///
/// ```
/// use itertools::Itertools;
///
/// let a: [i32; 0] = [];
/// assert_eq!(a.iter().min_set(), Vec::<&i32>::new());
///
/// let a = [1];
/// assert_eq!(a.iter().min_set(), vec![&1]);
///
/// let a = [1, 2, 3, 4, 5];
/// assert_eq!(a.iter().min_set(), vec![&1]);
///
/// let a = [1, 1, 1, 1];
/// assert_eq!(a.iter().min_set(), vec![&1, &1, &1, &1]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn min_set(self) -> Vec<Self::Item>
where Self: Sized, Self::Item: Ord
{
extrema_set::min_set_impl(self, |_| (), |x, y, _, _| x.cmp(y))
}

/// Return all minimum elements of an iterator, as determined by
/// the specified function.
///
/// # Examples
///
/// ```
/// # use std::cmp::Ordering;
/// use itertools::Itertools;
///
/// let a: [(i32, i32); 0] = [];
/// assert_eq!(a.iter().min_set_by(|_, _| Ordering::Equal), Vec::<&(i32, i32)>::new());
///
/// let a = [(1, 2)];
/// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2)]);
///
/// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)];
/// assert_eq!(a.iter().min_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), vec![&(1, 2), &(2, 2)]);
///
/// let a = [(1, 2), (1, 3), (1, 4), (1, 5)];
/// assert_eq!(a.iter().min_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn min_set_by<F>(self, mut compare: F) -> Vec<Self::Item>
where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering
{
extrema_set::min_set_impl(
self,
|_| (),
|x, y, _, _| compare(x, y)
)
}

/// Return all minimum elements of an iterator, as determined by
/// the specified function.
///
/// # Examples
///
/// ```
/// use itertools::Itertools;
///
/// let a: [(i32, i32); 0] = [];
/// assert_eq!(a.iter().min_set_by_key(|_| ()), Vec::<&(i32, i32)>::new());
///
/// let a = [(1, 2)];
/// assert_eq!(a.iter().min_set_by_key(|&&(k,_)| k), vec![&(1, 2)]);
///
/// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)];
/// assert_eq!(a.iter().min_set_by_key(|&&(_, k)| k), vec![&(1, 2), &(2, 2)]);
///
/// let a = [(1, 2), (1, 3), (1, 4), (1, 5)];
/// assert_eq!(a.iter().min_set_by_key(|&&(k, _)| k), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn min_set_by_key<K, F>(self, key: F) -> Vec<Self::Item>
where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K
{
extrema_set::min_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky))
}

/// Return all maximum elements of an iterator.
///
/// # Examples
///
/// ```
/// use itertools::Itertools;
///
/// let a: [i32; 0] = [];
/// assert_eq!(a.iter().max_set(), Vec::<&i32>::new());
///
/// let a = [1];
/// assert_eq!(a.iter().max_set(), vec![&1]);
///
/// let a = [1, 2, 3, 4, 5];
/// assert_eq!(a.iter().max_set(), vec![&5]);
///
/// let a = [1, 1, 1, 1];
/// assert_eq!(a.iter().max_set(), vec![&1, &1, &1, &1]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn max_set(self) -> Vec<Self::Item>
where Self: Sized, Self::Item: Ord
{
extrema_set::max_set_impl(self, |_| (), |x, y, _, _| x.cmp(y))
}

/// Return all maximum elements of an iterator, as determined by
/// the specified function.
///
/// # Examples
///
/// ```
/// # use std::cmp::Ordering;
/// use itertools::Itertools;
///
/// let a: [(i32, i32); 0] = [];
/// assert_eq!(a.iter().max_set_by(|_, _| Ordering::Equal), Vec::<&(i32, i32)>::new());
///
/// let a = [(1, 2)];
/// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2)]);
///
/// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)];
/// assert_eq!(a.iter().max_set_by(|&&(_,k1), &&(_,k2)| k1.cmp(&k2)), vec![&(3, 9), &(5, 9)]);
///
/// let a = [(1, 2), (1, 3), (1, 4), (1, 5)];
/// assert_eq!(a.iter().max_set_by(|&&(k1,_), &&(k2, _)| k1.cmp(&k2)), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn max_set_by<F>(self, mut compare: F) -> Vec<Self::Item>
where Self: Sized, F: FnMut(&Self::Item, &Self::Item) -> Ordering
{
extrema_set::max_set_impl(
self,
|_| (),
|x, y, _, _| compare(x, y)
)
}

/// Return all minimum elements of an iterator, as determined by
/// the specified function.
///
/// # Examples
///
/// ```
/// use itertools::Itertools;
///
/// let a: [(i32, i32); 0] = [];
/// assert_eq!(a.iter().max_set_by_key(|_| ()), Vec::<&(i32, i32)>::new());
///
/// let a = [(1, 2)];
/// assert_eq!(a.iter().max_set_by_key(|&&(k,_)| k), vec![&(1, 2)]);
///
/// let a = [(1, 2), (2, 2), (3, 9), (4, 8), (5, 9)];
/// assert_eq!(a.iter().max_set_by_key(|&&(_, k)| k), vec![&(3, 9), &(5, 9)]);
///
/// let a = [(1, 2), (1, 3), (1, 4), (1, 5)];
/// assert_eq!(a.iter().max_set_by_key(|&&(k, _)| k), vec![&(1, 2), &(1, 3), &(1, 4), &(1, 5)]);
/// ```
///
/// The elements can be floats but no particular result is guaranteed
/// if an element is NaN.
#[cfg(feature = "use_std")]
fn max_set_by_key<K, F>(self, key: F) -> Vec<Self::Item>
where Self: Sized, K: Ord, F: FnMut(&Self::Item) -> K
{
extrema_set::max_set_impl(self, key, |_, _, kx, ky| kx.cmp(ky))
}

/// Return the minimum and maximum elements in the iterator.
///
/// The return type `MinMaxResult` is an enum of three variants:
Expand Down
59 changes: 59 additions & 0 deletions tests/quick.rs
Expand Up @@ -1693,3 +1693,62 @@ quickcheck! {
}
}

quickcheck! {
fn min_set_contains_min(a: Vec<(usize, char)>) -> bool {
let result_set = a.iter().min_set();
if let Some(result_element) = a.iter().min() {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}

fn min_set_by_contains_min(a: Vec<(usize, char)>) -> bool {
let compare = |x: &&(usize, char), y: &&(usize, char)| x.1.cmp(&y.1);
let result_set = a.iter().min_set_by(compare);
if let Some(result_element) = a.iter().min_by(compare) {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}

fn min_set_by_key_contains_min(a: Vec<(usize, char)>) -> bool {
let key = |x: &&(usize, char)| x.1;
let result_set = a.iter().min_set_by_key(&key);
if let Some(result_element) = a.iter().min_by_key(&key) {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}

fn max_set_contains_max(a: Vec<(usize, char)>) -> bool {
let result_set = a.iter().max_set();
if let Some(result_element) = a.iter().max() {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}

fn max_set_by_contains_max(a: Vec<(usize, char)>) -> bool {
let compare = |x: &&(usize, char), y: &&(usize, char)| x.1.cmp(&y.1);
let result_set = a.iter().max_set_by(compare);
if let Some(result_element) = a.iter().max_by(compare) {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}

fn max_set_by_key_contains_max(a: Vec<(usize, char)>) -> bool {
let key = |x: &&(usize, char)| x.1;
let result_set = a.iter().max_set_by_key(&key);
if let Some(result_element) = a.iter().max_by_key(&key) {
result_set.contains(&result_element)
} else {
result_set.is_empty()
}
}
}
50 changes: 49 additions & 1 deletion tests/test_std.rs
Expand Up @@ -992,6 +992,54 @@ fn diff_shorter() {
});
}

#[test]
fn extrema_set() {
use std::cmp::Ordering;

// A peculiar type: Equality compares both tuple items, but ordering only the
// first item. Used to distinguish equal elements.
#[derive(Clone, Debug, PartialEq, Eq)]
struct Val(u32, u32);

impl PartialOrd<Val> for Val {
fn partial_cmp(&self, other: &Val) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}

impl Ord for Val {
fn cmp(&self, other: &Val) -> Ordering {
self.0.cmp(&other.0)
}
}

assert_eq!(None::<u32>.iter().min_set(), Vec::<&u32>::new());
assert_eq!(None::<u32>.iter().max_set(), Vec::<&u32>::new());

assert_eq!(Some(1u32).iter().min_set(), vec![&1]);
assert_eq!(Some(1u32).iter().max_set(), vec![&1]);

let data = vec![Val(0, 1), Val(2, 0), Val(0, 2), Val(1, 0), Val(2, 1)];

let min_set = data.iter().min_set();
assert_eq!(min_set, vec![&Val(0, 1), &Val(0, 2)]);

let min_set_by_key = data.iter().min_set_by_key(|v| v.1);
assert_eq!(min_set_by_key, vec![&Val(2, 0), &Val(1, 0)]);

let min_set_by = data.iter().min_set_by(|x, y| x.1.cmp(&y.1));
assert_eq!(min_set_by, vec![&Val(2, 0), &Val(1, 0)]);

let max_set = data.iter().max_set();
assert_eq!(max_set, vec![&Val(2, 0), &Val(2, 1)]);

let max_set_by_key = data.iter().max_set_by_key(|v| v.1);
assert_eq!(max_set_by_key, vec![&Val(0, 2)]);

let max_set_by = data.iter().max_set_by(|x, y| x.1.cmp(&y.1));
assert_eq!(max_set_by, vec![&Val(0, 2)]);
}

#[test]
fn minmax() {
use std::cmp::Ordering;
Expand Down Expand Up @@ -1119,4 +1167,4 @@ fn multiunzip() {
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]));
}
}

0 comments on commit 1cb2c36

Please sign in to comment.