From ca064c85942f1ccb9f15534311c2673fdb14fc01 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:47:11 +0100 Subject: [PATCH 01/15] Generic container: new `Map` private trait Note: `BTreeMap::remove` and `BTreeMap::remove` both accept a more general `Q` rather than just `Self::Key`. If ever needed, `Map` would be possible (with the same bound on `Q` than `Self::Key`: `Ord` or `Eq + Hash`). --- src/generic_containers.rs | 62 +++++++++++++++++++++++++++++++++++++++ src/lib.rs | 2 ++ 2 files changed, 64 insertions(+) create mode 100644 src/generic_containers.rs diff --git a/src/generic_containers.rs b/src/generic_containers.rs new file mode 100644 index 000000000..011244477 --- /dev/null +++ b/src/generic_containers.rs @@ -0,0 +1,62 @@ +//! **Private** generalizations of containers: +//! - `Map`: `BTreeMap` and `HashMap` (any hasher). + +#![cfg(feature = "use_alloc")] + +use alloc::collections::BTreeMap; +#[cfg(feature = "use_std")] +use core::hash::{BuildHasher, Hash}; +#[cfg(feature = "use_std")] +use std::collections::HashMap; + +pub trait Map { + type Key; + type Value; + fn insert(&mut self, key: Self::Key, value: Self::Value) -> Option; + fn remove(&mut self, key: &Self::Key) -> Option; + fn entry_or_default(&mut self, key: Self::Key) -> &mut Self::Value + where + Self::Value: Default; +} + +impl Map for BTreeMap +where + K: Ord, +{ + type Key = K; + type Value = V; + fn insert(&mut self, key: K, value: V) -> Option { + self.insert(key, value) + } + fn remove(&mut self, key: &K) -> Option { + self.remove(key) + } + fn entry_or_default(&mut self, key: K) -> &mut V + where + V: Default, + { + self.entry(key).or_default() + } +} + +#[cfg(feature = "use_std")] +impl Map for HashMap +where + K: Eq + Hash, + S: BuildHasher, +{ + type Key = K; + type Value = V; + fn insert(&mut self, key: K, value: V) -> Option { + self.insert(key, value) + } + fn remove(&mut self, key: &K) -> Option { + self.remove(key) + } + fn entry_or_default(&mut self, key: K) -> &mut V + where + V: Default, + { + self.entry(key).or_default() + } +} diff --git a/src/lib.rs b/src/lib.rs index ec374c469..ca99607af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -186,6 +186,8 @@ mod extrema_set; mod flatten_ok; mod format; #[cfg(feature = "use_alloc")] +mod generic_containers; +#[cfg(feature = "use_alloc")] mod group_map; #[cfg(feature = "use_alloc")] mod groupbylazy; From f6da4367ab3dae2ea4d115ef40158e85a6d7d55a Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 12 Mar 2024 10:56:17 +0100 Subject: [PATCH 02/15] `grouping_map` module behind `use_alloc` feature Relax `K: Hash + Eq` bounds to soon accept `BTreeMap`. --- src/grouping_map.rs | 7 +++++-- src/lib.rs | 12 ++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index f910e5fd3..9af4a86e8 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -1,11 +1,13 @@ -#![cfg(feature = "use_std")] +#![cfg(feature = "use_alloc")] use crate::{ adaptors::map::{MapSpecialCase, MapSpecialCaseFn}, MinMaxResult, }; use std::cmp::Ordering; +#[cfg(feature = "use_std")] use std::collections::HashMap; +#[cfg(feature = "use_std")] use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; @@ -41,7 +43,7 @@ pub(crate) fn new_map_for_grouping K>( pub fn new(iter: I) -> GroupingMap where I: Iterator, - K: Hash + Eq, + K: Eq, { GroupingMap { iter } } @@ -62,6 +64,7 @@ pub struct GroupingMap { iter: I, } +#[cfg(feature = "use_std")] impl GroupingMap where I: Iterator, diff --git a/src/lib.rs b/src/lib.rs index ca99607af..95478ecce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,7 @@ pub mod structs { pub use crate::groupbylazy::GroupBy; #[cfg(feature = "use_alloc")] pub use crate::groupbylazy::{Chunk, ChunkBy, Chunks, Group, Groups, IntoChunks}; - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] pub use crate::grouping_map::{GroupingMap, GroupingMapBy}; pub use crate::intersperse::{Intersperse, IntersperseWith}; #[cfg(feature = "use_alloc")] @@ -191,7 +191,7 @@ mod generic_containers; mod group_map; #[cfg(feature = "use_alloc")] mod groupbylazy; -#[cfg(feature = "use_std")] +#[cfg(feature = "use_alloc")] mod grouping_map; mod intersperse; #[cfg(feature = "use_alloc")] @@ -3283,11 +3283,11 @@ pub trait Itertools: Iterator { /// /// See [`GroupingMap`] for more informations /// on what operations are available. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn into_grouping_map(self) -> GroupingMap where Self: Iterator + Sized, - K: Hash + Eq, + K: Eq, { grouping_map::new(self) } @@ -3300,11 +3300,11 @@ pub trait Itertools: Iterator { /// /// See [`GroupingMap`] for more informations /// on what operations are available. - #[cfg(feature = "use_std")] + #[cfg(feature = "use_alloc")] fn into_grouping_map_by(self, key_mapper: F) -> GroupingMapBy where Self: Iterator + Sized, - K: Hash + Eq, + K: Eq, F: FnMut(&V) -> K, { grouping_map::new(grouping_map::new_map_for_grouping(self, key_mapper)) From 79ea22b3ae6ec2e327b987acdb4fb27804019dca Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:08:40 +0100 Subject: [PATCH 03/15] New `GroupingMap::aggregate_in` --- src/grouping_map.rs | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 9af4a86e8..200787507 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -2,6 +2,7 @@ use crate::{ adaptors::map::{MapSpecialCase, MapSpecialCaseFn}, + generic_containers::Map, MinMaxResult, }; use std::cmp::Ordering; @@ -110,20 +111,11 @@ where /// assert_eq!(lookup[&3], 7); /// assert_eq!(lookup.len(), 3); // The final keys are only 0, 1 and 2 /// ``` - pub fn aggregate(self, mut operation: FO) -> HashMap + pub fn aggregate(self, operation: FO) -> HashMap where FO: FnMut(Option, &K, V) -> Option, { - let mut destination_map = HashMap::new(); - - self.iter.for_each(|(key, val)| { - let acc = destination_map.remove(&key); - if let Some(op_res) = operation(acc, &key, val) { - destination_map.insert(key, op_res); - } - }); - - destination_map + self.aggregate_in(operation, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements @@ -615,3 +607,25 @@ where self.reduce(|acc, _, val| acc * val) } } + +impl GroupingMap +where + I: Iterator, + K: Eq, +{ + /// Apply [`aggregate`](Self::aggregate) with a provided map. + pub fn aggregate_in(self, mut operation: FO, mut map: M) -> M + where + FO: FnMut(Option, &K, V) -> Option, + M: Map, + { + self.iter.for_each(|(key, val)| { + let acc = map.remove(&key); + if let Some(op_res) = operation(acc, &key, val) { + map.insert(key, op_res); + } + }); + + map + } +} From fc2fcb7eafe225fd7e9c88acca5799b97525cda6 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 14:58:02 +0100 Subject: [PATCH 04/15] New `GroupingMap::fold_with_in` --- src/grouping_map.rs | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 200787507..666c2ac44 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -151,15 +151,12 @@ where /// assert_eq!(lookup[&2].acc, 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn fold_with(self, mut init: FI, mut operation: FO) -> HashMap + pub fn fold_with(self, init: FI, operation: FO) -> HashMap where FI: FnMut(&K, &V) -> R, FO: FnMut(R, &K, V) -> R, { - self.aggregate(|acc, key, val| { - let acc = acc.unwrap_or_else(|| init(key, &val)); - Some(operation(acc, key, val)) - }) + self.fold_with_in(init, operation, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements @@ -628,4 +625,20 @@ where map } + + /// Apply [`fold_with`](Self::fold_with) with a provided map. + pub fn fold_with_in(self, mut init: FI, mut operation: FO, map: M) -> M + where + FI: FnMut(&K, &V) -> R, + FO: FnMut(R, &K, V) -> R, + M: Map, + { + self.aggregate_in( + |acc, key, val| { + let acc = acc.unwrap_or_else(|| init(key, &val)); + Some(operation(acc, key, val)) + }, + map, + ) + } } From cb72ea1eb62caa4bc071d9d8c6bc75b79bbecbc9 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:03:08 +0100 Subject: [PATCH 05/15] New `GroupingMap::fold_in` --- src/grouping_map.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 666c2ac44..00426e370 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -189,7 +189,7 @@ where R: Clone, FO: FnMut(R, &K, V) -> R, { - self.fold_with(|_, _| init.clone(), operation) + self.fold_in(init, operation, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements @@ -641,4 +641,14 @@ where map, ) } + + /// Apply [`fold`](Self::fold) with a provided map. + pub fn fold_in(self, init: R, operation: FO, map: M) -> M + where + R: Clone, + FO: FnMut(R, &K, V) -> R, + M: Map, + { + self.fold_with_in(|_, _| init.clone(), operation, map) + } } From 742a67841f5dd96e12cbdd03cd51fba143e974b3 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:21:45 +0100 Subject: [PATCH 06/15] New `GroupingMap::reduce_in` --- src/grouping_map.rs | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 00426e370..4e77ce11b 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -219,16 +219,11 @@ where /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn reduce(self, mut operation: FO) -> HashMap + pub fn reduce(self, operation: FO) -> HashMap where FO: FnMut(V, &K, V) -> V, { - self.aggregate(|acc, key, val| { - Some(match acc { - Some(acc) => operation(acc, key, val), - None => val, - }) - }) + self.reduce_in(operation, HashMap::new()) } /// See [`.reduce()`](GroupingMap::reduce). @@ -651,4 +646,21 @@ where { self.fold_with_in(|_, _| init.clone(), operation, map) } + + /// Apply [`reduce`](Self::reduce) with a provided map. + pub fn reduce_in(self, mut operation: FO, map: M) -> M + where + FO: FnMut(V, &K, V) -> V, + M: Map, + { + self.aggregate_in( + |acc, key, val| { + Some(match acc { + Some(acc) => operation(acc, key, val), + None => val, + }) + }, + map, + ) + } } From f8c7d19a12df594e3e63f2a93dba2ff02267ad14 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:28:32 +0100 Subject: [PATCH 07/15] New `GroupingMap::collect_in` --- src/grouping_map.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 4e77ce11b..d55c07ccb 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -257,16 +257,7 @@ where where C: Default + Extend, { - let mut destination_map = HashMap::new(); - - self.iter.for_each(|(key, val)| { - destination_map - .entry(key) - .or_insert_with(C::default) - .extend(Some(val)); - }); - - destination_map + self.collect_in(HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group. @@ -663,4 +654,17 @@ where map, ) } + + /// Apply [`collect`](Self::collect) with a provided map. + pub fn collect_in(self, mut map: M) -> M + where + C: Default + Extend, + M: Map, + { + self.iter.for_each(|(key, val)| { + map.entry_or_default(key).extend(Some(val)); + }); + + map + } } From fbf63e8ce7f75973dd466b42cd396cdb6781ef59 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:37:09 +0100 Subject: [PATCH 08/15] New `GroupingMap::max[_by[_key]]_in` --- src/grouping_map.rs | 47 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index d55c07ccb..ffa9afd39 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -282,7 +282,7 @@ where where V: Ord, { - self.max_by(|_, v1, v2| V::cmp(v1, v2)) + self.max_in(HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group @@ -304,14 +304,11 @@ where /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn max_by(self, mut compare: F) -> HashMap + pub fn max_by(self, compare: F) -> HashMap where F: FnMut(&K, &V, &V) -> Ordering, { - self.reduce(|acc, key, val| match compare(key, &acc, &val) { - Ordering::Less | Ordering::Equal => val, - Ordering::Greater => acc, - }) + self.max_by_in(compare, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the element of each group @@ -333,12 +330,12 @@ where /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn max_by_key(self, mut f: F) -> HashMap + pub fn max_by_key(self, f: F) -> HashMap where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.max_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) + self.max_by_key_in(f, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group. @@ -667,4 +664,38 @@ where map } + + /// Apply [`max`](Self::max) with a provided map. + pub fn max_in(self, map: M) -> M + where + V: Ord, + M: Map, + { + self.max_by_in(|_, v1, v2| V::cmp(v1, v2), map) + } + + /// Apply [`max_by`](Self::max_by) with a provided map. + pub fn max_by_in(self, mut compare: F, map: M) -> M + where + F: FnMut(&K, &V, &V) -> Ordering, + M: Map, + { + self.reduce_in( + |acc, key, val| match compare(key, &acc, &val) { + Ordering::Less | Ordering::Equal => val, + Ordering::Greater => acc, + }, + map, + ) + } + + /// Apply [`max_by_key`](Self::max_by_key) with a provided map. + pub fn max_by_key_in(self, mut f: F, map: M) -> M + where + F: FnMut(&K, &V) -> CK, + CK: Ord, + M: Map, + { + self.max_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) + } } From 99a0a64a825afcf9c79bcbdff0896e58b9a95884 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:43:54 +0100 Subject: [PATCH 09/15] New `GroupingMap::min[_by[_key]]_in` --- src/grouping_map.rs | 47 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index ffa9afd39..767e53054 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -360,7 +360,7 @@ where where V: Ord, { - self.min_by(|_, v1, v2| V::cmp(v1, v2)) + self.min_in(HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group @@ -382,14 +382,11 @@ where /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn min_by(self, mut compare: F) -> HashMap + pub fn min_by(self, compare: F) -> HashMap where F: FnMut(&K, &V, &V) -> Ordering, { - self.reduce(|acc, key, val| match compare(key, &acc, &val) { - Ordering::Less | Ordering::Equal => acc, - Ordering::Greater => val, - }) + self.min_by_in(compare, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and finds the element of each group @@ -411,12 +408,12 @@ where /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn min_by_key(self, mut f: F) -> HashMap + pub fn min_by_key(self, f: F) -> HashMap where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.min_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) + self.min_by_key_in(f, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of @@ -698,4 +695,38 @@ where { self.max_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) } + + /// Apply [`min`](Self::min) with a provided map. + pub fn min_in(self, map: M) -> M + where + V: Ord, + M: Map, + { + self.min_by_in(|_, v1, v2| V::cmp(v1, v2), map) + } + + /// Apply [`min_by`](Self::min_by) with a provided map. + pub fn min_by_in(self, mut compare: F, map: M) -> M + where + F: FnMut(&K, &V, &V) -> Ordering, + M: Map, + { + self.reduce_in( + |acc, key, val| match compare(key, &acc, &val) { + Ordering::Less | Ordering::Equal => acc, + Ordering::Greater => val, + }, + map, + ) + } + + /// Apply [`min_by_key`](Self::min_by_key) with a provided map. + pub fn min_by_key_in(self, mut f: F, map: M) -> M + where + F: FnMut(&K, &V) -> CK, + CK: Ord, + M: Map, + { + self.min_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) + } } From 41bdf4b78094e50b29a20e8020010ce90dd18f7b Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:51:18 +0100 Subject: [PATCH 10/15] New `GroupingMap::minmax[_by[_key]]_in` --- src/grouping_map.rs | 83 +++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 767e53054..7804beec1 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -447,7 +447,7 @@ where where V: Ord, { - self.minmax_by(|_, v1, v2| V::cmp(v1, v2)) + self.minmax_in(HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of @@ -473,32 +473,11 @@ where /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn minmax_by(self, mut compare: F) -> HashMap> + pub fn minmax_by(self, compare: F) -> HashMap> where F: FnMut(&K, &V, &V) -> Ordering, { - self.aggregate(|acc, key, val| { - Some(match acc { - Some(MinMaxResult::OneElement(e)) => { - if compare(key, &val, &e) == Ordering::Less { - MinMaxResult::MinMax(val, e) - } else { - MinMaxResult::MinMax(e, val) - } - } - Some(MinMaxResult::MinMax(min, max)) => { - if compare(key, &val, &min) == Ordering::Less { - MinMaxResult::MinMax(val, max) - } else if compare(key, &val, &max) != Ordering::Less { - MinMaxResult::MinMax(min, val) - } else { - MinMaxResult::MinMax(min, max) - } - } - None => MinMaxResult::OneElement(val), - Some(MinMaxResult::NoElements) => unreachable!(), - }) - }) + self.minmax_by_in(compare, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and find the elements of each group @@ -524,12 +503,12 @@ where /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn minmax_by_key(self, mut f: F) -> HashMap> + pub fn minmax_by_key(self, f: F) -> HashMap> where F: FnMut(&K, &V) -> CK, CK: Ord, { - self.minmax_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) + self.minmax_by_key_in(f, HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and sums them. @@ -729,4 +708,56 @@ where { self.min_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) } + + /// Apply [`minmax`](Self::minmax) with a provided map. + pub fn minmax_in(self, map: M) -> M + where + V: Ord, + M: Map>, + { + self.minmax_by_in(|_, v1, v2| V::cmp(v1, v2), map) + } + + /// Apply [`minmax_by`](Self::minmax_by) with a provided map. + pub fn minmax_by_in(self, mut compare: F, map: M) -> M + where + F: FnMut(&K, &V, &V) -> Ordering, + M: Map>, + { + self.aggregate_in( + |acc, key, val| { + Some(match acc { + Some(MinMaxResult::OneElement(e)) => { + if compare(key, &val, &e) == Ordering::Less { + MinMaxResult::MinMax(val, e) + } else { + MinMaxResult::MinMax(e, val) + } + } + Some(MinMaxResult::MinMax(min, max)) => { + if compare(key, &val, &min) == Ordering::Less { + MinMaxResult::MinMax(val, max) + } else if compare(key, &val, &max) != Ordering::Less { + MinMaxResult::MinMax(min, val) + } else { + MinMaxResult::MinMax(min, max) + } + } + None => MinMaxResult::OneElement(val), + Some(MinMaxResult::NoElements) => unreachable!(), + }) + }, + map, + ) + } + + /// Apply [`minmax_by_key`](Self::minmax_by_key) with a provided map. + pub fn minmax_by_key_in(self, mut f: F, map: M) -> M + where + F: FnMut(&K, &V) -> CK, + CK: Ord, + M: Map>, + { + self.minmax_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) + } } From 2cfc8683fa21746dae3bfc8225bf03463588c34c Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:54:57 +0100 Subject: [PATCH 11/15] New `GroupingMap::sum_in` --- src/grouping_map.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 7804beec1..08d943a5f 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -534,7 +534,7 @@ where where V: Add, { - self.reduce(|acc, _, val| acc + val) + self.sum_in(HashMap::new()) } /// Groups elements from the `GroupingMap` source by key and multiply them. @@ -760,4 +760,13 @@ where { self.minmax_by_in(|key, v1, v2| f(key, v1).cmp(&f(key, v2)), map) } + + /// Apply [`sum`](Self::sum) with a provided map. + pub fn sum_in(self, map: M) -> M + where + V: Add, + M: Map, + { + self.reduce_in(|acc, _, val| acc + val, map) + } } From 4b5d32dcf6e6d4eb9aae455534a0b575f8537dca Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Wed, 13 Mar 2024 15:57:32 +0100 Subject: [PATCH 12/15] New `GroupingMap::product_in` --- src/grouping_map.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 08d943a5f..ed0626464 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -560,7 +560,7 @@ where where V: Mul, { - self.reduce(|acc, _, val| acc * val) + self.product_in(HashMap::new()) } } @@ -769,4 +769,13 @@ where { self.reduce_in(|acc, _, val| acc + val, map) } + + /// Apply [`product`](Self::product) with a provided map. + pub fn product_in(self, map: M) -> M + where + V: Mul, + M: Map, + { + self.reduce_in(|acc, _, val| acc * val, map) + } } From 56213c14d6a63034e1faeada26f1e66d7400f5a8 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Fri, 15 Mar 2024 14:10:25 +0100 Subject: [PATCH 13/15] Implementation of `Map` for simple `Vec<(_, _)>` Note it uses `swap_remove`. --- src/generic_containers.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/generic_containers.rs b/src/generic_containers.rs index 011244477..79e901bb8 100644 --- a/src/generic_containers.rs +++ b/src/generic_containers.rs @@ -4,6 +4,7 @@ #![cfg(feature = "use_alloc")] use alloc::collections::BTreeMap; +use alloc::vec::Vec; #[cfg(feature = "use_std")] use core::hash::{BuildHasher, Hash}; #[cfg(feature = "use_std")] @@ -60,3 +61,34 @@ where self.entry(key).or_default() } } + +impl Map for Vec<(K, V)> +where + K: Eq, +{ + type Key = K; + type Value = V; + fn insert(&mut self, key: K, value: V) -> Option { + match self.iter_mut().find(|(k, _)| k == &key) { + Some((_, v)) => Some(core::mem::replace(v, value)), + None => { + self.push((key, value)); + None + } + } + } + fn remove(&mut self, key: &K) -> Option { + let index = self.iter().position(|(k, _)| k == key)?; + Some(self.swap_remove(index).1) + } + fn entry_or_default(&mut self, key: K) -> &mut V + where + V: Default, + { + let index = self.iter().position(|(k, _)| k == &key).unwrap_or_else(|| { + self.push((key, V::default())); + self.len() - 1 + }); + &mut self[index].1 + } +} From bf8e4aa206edde6351f64fe9c333f360ecdb5926 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:12:25 +0100 Subject: [PATCH 14/15] `GroupingMap`: add doctests using `BTreeMap` Copied from their non-suffixed variants and minimally updated. --- src/grouping_map.rs | 243 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index ed0626464..1541ed9c1 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -570,6 +570,28 @@ where K: Eq, { /// Apply [`aggregate`](Self::aggregate) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let data = vec![2, 8, 5, 7, 9, 0, 4, 10]; + /// let lookup = data.into_iter() + /// .into_grouping_map_by(|&n| n % 4) + /// .aggregate_in(|acc, _key, val| { + /// if val == 0 || val == 10 { + /// None + /// } else { + /// Some(acc.unwrap_or(0) + val) + /// } + /// }, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 4); // 0 resets the accumulator so only 4 is summed + /// assert_eq!(lookup[&1], 5 + 9); + /// assert_eq!(lookup.get(&2), None); // 10 resets the accumulator and nothing is summed afterward + /// assert_eq!(lookup[&3], 7); + /// assert_eq!(lookup.len(), 3); // The final keys are only 0, 1 and 2 + /// ``` pub fn aggregate_in(self, mut operation: FO, mut map: M) -> M where FO: FnMut(Option, &K, V) -> Option, @@ -586,6 +608,28 @@ where } /// Apply [`fold_with`](Self::fold_with) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// #[derive(Debug, Default)] + /// struct Accumulator { + /// acc: usize, + /// } + /// + /// let lookup = (1..=7) + /// .into_grouping_map_by(|&n| n % 3) + /// .fold_with_in(|_key, _val| Default::default(), |Accumulator { acc }, _key, val| { + /// let acc = acc + val; + /// Accumulator { acc } + /// }, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0].acc, 3 + 6); + /// assert_eq!(lookup[&1].acc, 1 + 4 + 7); + /// assert_eq!(lookup[&2].acc, 2 + 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn fold_with_in(self, mut init: FI, mut operation: FO, map: M) -> M where FI: FnMut(&K, &V) -> R, @@ -602,6 +646,20 @@ where } /// Apply [`fold`](Self::fold) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = (1..=7) + /// .into_grouping_map_by(|&n| n % 3) + /// .fold_in(0, |acc, _key, val| acc + val, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3 + 6); + /// assert_eq!(lookup[&1], 1 + 4 + 7); + /// assert_eq!(lookup[&2], 2 + 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn fold_in(self, init: R, operation: FO, map: M) -> M where R: Clone, @@ -612,6 +670,20 @@ where } /// Apply [`reduce`](Self::reduce) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = (1..=7) + /// .into_grouping_map_by(|&n| n % 3) + /// .reduce_in(|acc, _key, val| acc + val, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3 + 6); + /// assert_eq!(lookup[&1], 1 + 4 + 7); + /// assert_eq!(lookup[&2], 2 + 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn reduce_in(self, mut operation: FO, map: M) -> M where FO: FnMut(V, &K, V) -> V, @@ -629,6 +701,20 @@ where } /// Apply [`collect`](Self::collect) with a provided map. + /// + /// ``` + /// use std::collections::{BTreeMap, BTreeSet}; + /// use itertools::Itertools; + /// + /// let lookup = vec![0, 1, 2, 3, 4, 5, 6, 2, 3, 6].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .collect_in::, _>(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], vec![0, 3, 6].into_iter().collect::>()); + /// assert_eq!(lookup[&1], vec![1, 4].into_iter().collect::>()); + /// assert_eq!(lookup[&2], vec![2, 5].into_iter().collect::>()); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn collect_in(self, mut map: M) -> M where C: Default + Extend, @@ -642,6 +728,20 @@ where } /// Apply [`max`](Self::max) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .max_in(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 12); + /// assert_eq!(lookup[&1], 7); + /// assert_eq!(lookup[&2], 8); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn max_in(self, map: M) -> M where V: Ord, @@ -651,6 +751,20 @@ where } /// Apply [`max_by`](Self::max_by) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .max_by_in(|_key, x, y| y.cmp(x), BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3); + /// assert_eq!(lookup[&1], 1); + /// assert_eq!(lookup[&2], 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn max_by_in(self, mut compare: F, map: M) -> M where F: FnMut(&K, &V, &V) -> Ordering, @@ -666,6 +780,20 @@ where } /// Apply [`max_by_key`](Self::max_by_key) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .max_by_key_in(|_key, &val| val % 4, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3); + /// assert_eq!(lookup[&1], 7); + /// assert_eq!(lookup[&2], 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn max_by_key_in(self, mut f: F, map: M) -> M where F: FnMut(&K, &V) -> CK, @@ -676,6 +804,20 @@ where } /// Apply [`min`](Self::min) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .min_in(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3); + /// assert_eq!(lookup[&1], 1); + /// assert_eq!(lookup[&2], 5); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn min_in(self, map: M) -> M where V: Ord, @@ -685,6 +827,20 @@ where } /// Apply [`min_by`](Self::min_by) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .min_by_in(|_key, x, y| y.cmp(x), BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 12); + /// assert_eq!(lookup[&1], 7); + /// assert_eq!(lookup[&2], 8); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn min_by_in(self, mut compare: F, map: M) -> M where F: FnMut(&K, &V, &V) -> Ordering, @@ -700,6 +856,20 @@ where } /// Apply [`min_by_key`](Self::min_by_key) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .min_by_key_in(|_key, &val| val % 4, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 12); + /// assert_eq!(lookup[&1], 4); + /// assert_eq!(lookup[&2], 8); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn min_by_key_in(self, mut f: F, map: M) -> M where F: FnMut(&K, &V) -> CK, @@ -710,6 +880,21 @@ where } /// Apply [`minmax`](Self::minmax) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// use itertools::MinMaxResult::{OneElement, MinMax}; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .minmax_in(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], MinMax(3, 12)); + /// assert_eq!(lookup[&1], MinMax(1, 7)); + /// assert_eq!(lookup[&2], OneElement(5)); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn minmax_in(self, map: M) -> M where V: Ord, @@ -719,6 +904,21 @@ where } /// Apply [`minmax_by`](Self::minmax_by) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// use itertools::MinMaxResult::{OneElement, MinMax}; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .minmax_by_in(|_key, x, y| y.cmp(x), BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], MinMax(12, 3)); + /// assert_eq!(lookup[&1], MinMax(7, 1)); + /// assert_eq!(lookup[&2], OneElement(5)); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn minmax_by_in(self, mut compare: F, map: M) -> M where F: FnMut(&K, &V, &V) -> Ordering, @@ -752,6 +952,21 @@ where } /// Apply [`minmax_by_key`](Self::minmax_by_key) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// use itertools::MinMaxResult::{OneElement, MinMax}; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .minmax_by_key_in(|_key, &val| val % 4, BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], MinMax(12, 3)); + /// assert_eq!(lookup[&1], MinMax(4, 7)); + /// assert_eq!(lookup[&2], OneElement(5)); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn minmax_by_key_in(self, mut f: F, map: M) -> M where F: FnMut(&K, &V) -> CK, @@ -762,6 +977,20 @@ where } /// Apply [`sum`](Self::sum) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .sum_in(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3 + 9 + 12); + /// assert_eq!(lookup[&1], 1 + 4 + 7); + /// assert_eq!(lookup[&2], 5 + 8); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn sum_in(self, map: M) -> M where V: Add, @@ -771,6 +1000,20 @@ where } /// Apply [`product`](Self::product) with a provided map. + /// + /// ``` + /// use std::collections::BTreeMap; + /// use itertools::Itertools; + /// + /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter() + /// .into_grouping_map_by(|&n| n % 3) + /// .product_in(BTreeMap::new()); + /// + /// assert_eq!(lookup[&0], 3 * 9 * 12); + /// assert_eq!(lookup[&1], 1 * 4 * 7); + /// assert_eq!(lookup[&2], 5 * 8); + /// assert_eq!(lookup.len(), 3); + /// ``` pub fn product_in(self, map: M) -> M where V: Mul, From 5a71e63a0badc51517fd828b09cc15e3b5f0a638 Mon Sep 17 00:00:00 2001 From: Philippe-Cholet <44676486+Philippe-Cholet@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:26:45 +0100 Subject: [PATCH 15/15] Update documentation of `GroupingMap` --- src/grouping_map.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/grouping_map.rs b/src/grouping_map.rs index 1541ed9c1..1b3c20ea9 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -58,6 +58,12 @@ pub type GroupingMapBy = GroupingMap>; /// It groups elements by their key and at the same time fold each group /// using some aggregating operation. /// +/// Each method have a `_in`-suffixed version where you can provide a map other than +/// `HashMap::new()` such as: +/// - `BTreeMap::new()` when the values are `Ord` and not necessarily `Hash + Eq`. +/// - `HashMap::default()` to use a different hasher. +/// - `HashMap::with_capacity(100)` to pre-allocate. +/// /// No method on this struct performs temporary allocations. #[derive(Clone, Debug)] #[must_use = "GroupingMap is lazy and do nothing unless consumed"]