diff --git a/src/generic_containers.rs b/src/generic_containers.rs new file mode 100644 index 000000000..339f7a9a7 --- /dev/null +++ b/src/generic_containers.rs @@ -0,0 +1,113 @@ +//! **Private** generalizations of containers: +//! - `Map`: `BTreeMap`, `HashMap` (any hasher) and _unordered_ `Vec`. + +#![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")] +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 aggregate(&mut self, key: Self::Key, t: T, mut operation: F) + where + F: FnMut(Option, &Self::Key, T) -> Option, + { + let opt_value = self.remove(&key); + if let Some(value) = operation(opt_value, &key, t) { + self.insert(key, value); + } + } + 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() + } +} + +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 aggregate(&mut self, key: K, t: T, mut operation: F) + where + F: FnMut(Option, &K, T) -> Option, + { + let opt_value = Map::remove(self, &key); + if let Some(value) = operation(opt_value, &key, t) { + // The key was removed so a single push is enough to insert it back. + self.push((key, value)); + } + } + 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 + } +} diff --git a/src/grouping_map.rs b/src/grouping_map.rs index f910e5fd3..0f6314934 100644 --- a/src/grouping_map.rs +++ b/src/grouping_map.rs @@ -1,15 +1,43 @@ -#![cfg(feature = "use_std")] +#![cfg(feature = "use_alloc")] use crate::{ adaptors::map::{MapSpecialCase, MapSpecialCaseFn}, + generic_containers::Map, MinMaxResult, }; use std::cmp::Ordering; -use std::collections::HashMap; -use std::hash::Hash; use std::iter::Iterator; use std::ops::{Add, Mul}; +#[cfg(feature = "use_std")] +pub use with_hashmap::{GroupingMap, GroupingMapBy}; + +#[cfg(feature = "use_std")] +mod with_hashmap { + use super::*; + use std::collections::HashMap; + + // This is used to infer `K` when `I::Item = (K, V)` since we can't write `I::Item.0`. + pub trait KeyValue { + type Key; + } + + impl KeyValue for (K, V) { + type Key = K; + } + + /// `GroupingMap` is an intermediate struct for efficient group-and-fold operations. + /// + /// See [`GroupingGenericMap`] for more informations. + pub type GroupingMap = + GroupingGenericMap::Item as KeyValue>::Key, R>>; + + /// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations. + /// + /// See [`GroupingGenericMap`] for more informations. + pub type GroupingMapBy = GroupingMap, R>; +} + /// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by) pub type MapForGrouping = MapSpecialCase>; @@ -37,41 +65,43 @@ pub(crate) fn new_map_for_grouping K>( } } -/// Creates a new `GroupingMap` from `iter` -pub fn new(iter: I) -> GroupingMap +pub fn new_in(iter: I, map: M) -> GroupingGenericMap where I: Iterator, - K: Hash + Eq, + K: Eq, + M: Map, { - GroupingMap { iter } + GroupingGenericMap { iter, map } } -/// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations. +/// `GroupingGenericMapBy` is an intermediate struct for efficient group-and-fold operations. /// -/// See [`GroupingMap`] for more informations. -pub type GroupingMapBy = GroupingMap>; +/// See [`GroupingGenericMap`] for more informations. +pub type GroupingGenericMapBy = GroupingGenericMap, M>; -/// `GroupingMap` is an intermediate struct for efficient group-and-fold operations. +/// `GroupingGenericMap` is an intermediate struct for efficient group-and-fold operations. /// It groups elements by their key and at the same time fold each group /// using some aggregating operation. /// /// No method on this struct performs temporary allocations. #[derive(Clone, Debug)] -#[must_use = "GroupingMap is lazy and do nothing unless consumed"] -pub struct GroupingMap { +#[must_use = "GroupingGenericMap is lazy and do nothing unless consumed"] +pub struct GroupingGenericMap { iter: I, + map: M, } -impl GroupingMap +impl GroupingGenericMap where I: Iterator, - K: Hash + Eq, + K: Eq, + M: Map, { - /// This is the generic way to perform any operation on a `GroupingMap`. + /// This is the generic way to perform any operation on a `GroupingGenericMap`. /// It's suggested to use this method only to implement custom operations /// when the already provided ones are not enough. /// - /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements + /// Groups elements from the `GroupingGenericMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in an `HashMap`. /// @@ -107,23 +137,20 @@ 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, mut operation: FO) -> M where FO: FnMut(Option, &K, V) -> Option, + M: Map, { - let mut destination_map = HashMap::new(); + let mut destination_map = self.map; - 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); - } - }); + self.iter + .for_each(|(key, val)| destination_map.aggregate(key, val, &mut operation)); destination_map } - /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements + /// Groups elements from the `GroupingGenericMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in a new map. /// @@ -156,10 +183,11 @@ 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, mut init: FI, mut operation: FO) -> M where FI: FnMut(&K, &V) -> R, FO: FnMut(R, &K, V) -> R, + M: Map, { self.aggregate(|acc, key, val| { let acc = acc.unwrap_or_else(|| init(key, &val)); @@ -167,7 +195,7 @@ where }) } - /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements + /// Groups elements from the `GroupingGenericMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in a new map. /// @@ -192,15 +220,16 @@ where /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn fold(self, init: R, operation: FO) -> HashMap + pub fn fold(self, init: R, operation: FO) -> M where R: Clone, FO: FnMut(R, &K, V) -> R, + M: Map, { self.fold_with(|_, _| init.clone(), operation) } - /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements + /// Groups elements from the `GroupingGenericMap` source by key and applies `operation` to the elements /// of each group sequentially, passing the previously accumulated value, a reference to the key /// and the current element as arguments, and stores the results in a new map. /// @@ -213,7 +242,7 @@ where /// /// Return a `HashMap` associating the key of each group with the result of folding that group's elements. /// - /// [`fold`]: GroupingMap::fold + /// [`fold`]: GroupingGenericMap::fold /// /// ``` /// use itertools::Itertools; @@ -227,9 +256,10 @@ where /// assert_eq!(lookup[&2], 2 + 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn reduce(self, mut operation: FO) -> HashMap + pub fn reduce(self, mut operation: FO) -> M where FO: FnMut(V, &K, V) -> V, + M: Map, { self.aggregate(|acc, key, val| { Some(match acc { @@ -239,16 +269,17 @@ where }) } - /// See [`.reduce()`](GroupingMap::reduce). + /// See [`.reduce()`](GroupingGenericMap::reduce). #[deprecated(note = "Use .reduce() instead", since = "0.13.0")] - pub fn fold_first(self, operation: FO) -> HashMap + pub fn fold_first(self, operation: FO) -> M where FO: FnMut(V, &K, V) -> V, + M: Map, { self.reduce(operation) } - /// Groups elements from the `GroupingMap` source by key and collects the elements of each group in + /// Groups elements from the `GroupingGenericMap` source by key and collects the elements of each group in /// an instance of `C`. The iteration order is preserved when inserting elements. /// /// Return a `HashMap` associating the key of each group with the collection containing that group's elements. @@ -266,23 +297,21 @@ where /// assert_eq!(lookup[&2], vec![2, 5].into_iter().collect::>()); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn collect(self) -> HashMap + pub fn collect(self) -> M where C: Default + Extend, + M: Map, { - let mut destination_map = HashMap::new(); + let mut destination_map = self.map; self.iter.for_each(|(key, val)| { - destination_map - .entry(key) - .or_insert_with(C::default) - .extend(Some(val)); + destination_map.entry_or_default(key).extend(Some(val)); }); destination_map } - /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group. + /// Groups elements from the `GroupingGenericMap` source by key and finds the maximum of each group. /// /// If several elements are equally maximum, the last element is picked. /// @@ -300,14 +329,15 @@ where /// assert_eq!(lookup[&2], 8); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn max(self) -> HashMap + pub fn max(self) -> M where V: Ord, + M: Map, { self.max_by(|_, v1, v2| V::cmp(v1, v2)) } - /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group + /// Groups elements from the `GroupingGenericMap` source by key and finds the maximum of each group /// with respect to the specified comparison function. /// /// If several elements are equally maximum, the last element is picked. @@ -326,9 +356,10 @@ 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, mut compare: F) -> M where F: FnMut(&K, &V, &V) -> Ordering, + M: Map, { self.reduce(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => val, @@ -336,7 +367,7 @@ where }) } - /// Groups elements from the `GroupingMap` source by key and finds the element of each group + /// Groups elements from the `GroupingGenericMap` source by key and finds the element of each group /// that gives the maximum from the specified function. /// /// If several elements are equally maximum, the last element is picked. @@ -355,15 +386,16 @@ 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, mut f: F) -> M where F: FnMut(&K, &V) -> CK, CK: Ord, + M: Map, { self.max_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } - /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group. + /// Groups elements from the `GroupingGenericMap` source by key and finds the minimum of each group. /// /// If several elements are equally minimum, the first element is picked. /// @@ -381,14 +413,15 @@ where /// assert_eq!(lookup[&2], 5); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn min(self) -> HashMap + pub fn min(self) -> M where V: Ord, + M: Map, { self.min_by(|_, v1, v2| V::cmp(v1, v2)) } - /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group + /// Groups elements from the `GroupingGenericMap` source by key and finds the minimum of each group /// with respect to the specified comparison function. /// /// If several elements are equally minimum, the first element is picked. @@ -407,9 +440,10 @@ 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, mut compare: F) -> M where F: FnMut(&K, &V, &V) -> Ordering, + M: Map, { self.reduce(|acc, key, val| match compare(key, &acc, &val) { Ordering::Less | Ordering::Equal => acc, @@ -417,7 +451,7 @@ where }) } - /// Groups elements from the `GroupingMap` source by key and finds the element of each group + /// Groups elements from the `GroupingGenericMap` source by key and finds the element of each group /// that gives the minimum from the specified function. /// /// If several elements are equally minimum, the first element is picked. @@ -436,15 +470,16 @@ 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, mut f: F) -> M where F: FnMut(&K, &V) -> CK, CK: Ord, + M: Map, { self.min_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } - /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of + /// Groups elements from the `GroupingGenericMap` source by key and find the maximum and minimum of /// each group. /// /// If several elements are equally maximum, the last element is picked. @@ -471,14 +506,15 @@ where /// assert_eq!(lookup[&2], OneElement(5)); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn minmax(self) -> HashMap> + pub fn minmax(self) -> M where V: Ord, + M: Map>, { self.minmax_by(|_, v1, v2| V::cmp(v1, v2)) } - /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of + /// Groups elements from the `GroupingGenericMap` source by key and find the maximum and minimum of /// each group with respect to the specified comparison function. /// /// If several elements are equally maximum, the last element is picked. @@ -501,9 +537,10 @@ 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, mut compare: F) -> M where F: FnMut(&K, &V, &V) -> Ordering, + M: Map>, { self.aggregate(|acc, key, val| { Some(match acc { @@ -529,7 +566,7 @@ where }) } - /// Groups elements from the `GroupingMap` source by key and find the elements of each group + /// Groups elements from the `GroupingGenericMap` source by key and find the elements of each group /// that gives the minimum and maximum from the specified function. /// /// If several elements are equally maximum, the last element is picked. @@ -552,15 +589,16 @@ 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, mut f: F) -> M where F: FnMut(&K, &V) -> CK, CK: Ord, + M: Map>, { self.minmax_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2))) } - /// Groups elements from the `GroupingMap` source by key and sums them. + /// Groups elements from the `GroupingGenericMap` source by key and sums them. /// /// This is just a shorthand for `self.reduce(|acc, _, val| acc + val)`. /// It is more limited than `Iterator::sum` since it doesn't use the `Sum` trait. @@ -579,14 +617,15 @@ where /// assert_eq!(lookup[&2], 5 + 8); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn sum(self) -> HashMap + pub fn sum(self) -> M where V: Add, + M: Map, { self.reduce(|acc, _, val| acc + val) } - /// Groups elements from the `GroupingMap` source by key and multiply them. + /// Groups elements from the `GroupingGenericMap` source by key and multiply them. /// /// This is just a shorthand for `self.reduce(|acc, _, val| acc * val)`. /// It is more limited than `Iterator::product` since it doesn't use the `Product` trait. @@ -605,9 +644,10 @@ where /// assert_eq!(lookup[&2], 5 * 8); /// assert_eq!(lookup.len(), 3); /// ``` - pub fn product(self) -> HashMap + pub fn product(self) -> M where V: Mul, + M: Map, { self.reduce(|acc, _, val| acc * val) } diff --git a/src/lib.rs b/src/lib.rs index a297a6d80..0ce69be8e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,6 +77,9 @@ type VecDequeIntoIter = alloc::collections::vec_deque::IntoIter; type VecIntoIter = alloc::vec::IntoIter; use std::iter::FromIterator; +#[cfg(feature = "use_alloc")] +use crate::generic_containers::Map; + #[macro_use] mod impl_macros; @@ -108,6 +111,8 @@ 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_alloc")] + pub use crate::grouping_map::{GroupingGenericMap, GroupingGenericMapBy}; #[cfg(feature = "use_std")] pub use crate::grouping_map::{GroupingMap, GroupingMapBy}; pub use crate::intersperse::{Intersperse, IntersperseWith}; @@ -188,10 +193,12 @@ 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; -#[cfg(feature = "use_std")] +#[cfg(feature = "use_alloc")] mod grouping_map; mod intersperse; #[cfg(feature = "use_alloc")] @@ -3342,15 +3349,15 @@ pub trait Itertools: Iterator { /// value of type `K` will be used as key to identify the groups and the /// value of type `V` as value for the folding operation. /// - /// See [`GroupingMap`] for more informations + /// See [`GroupingGenericMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] - fn into_grouping_map(self) -> GroupingMap + fn into_grouping_map(self) -> GroupingMap where Self: Iterator + Sized, K: Hash + Eq, { - grouping_map::new(self) + self.into_grouping_map_in(HashMap::new()) } /// Constructs a `GroupingMap` to be used later with one of the efficient @@ -3359,16 +3366,59 @@ pub trait Itertools: Iterator { /// The values from this iterator will be used as values for the folding operation /// while the keys will be obtained from the values by calling `key_mapper`. /// - /// See [`GroupingMap`] for more informations + /// See [`GroupingGenericMap`] for more informations /// on what operations are available. #[cfg(feature = "use_std")] - fn into_grouping_map_by(self, key_mapper: F) -> GroupingMapBy + fn into_grouping_map_by(self, key_mapper: F) -> GroupingMapBy where Self: Iterator + Sized, K: Hash + Eq, F: FnMut(&V) -> K, { - grouping_map::new(grouping_map::new_map_for_grouping(self, key_mapper)) + self.into_grouping_map_by_in(key_mapper, HashMap::new()) + } + + /// Constructs a `GroupingGenericMap` to be used later with one of the efficient + /// group-and-fold operations it allows to perform. + /// + /// The input iterator must yield item in the form of `(K, V)` where the + /// value of type `K` will be used as key to identify the groups and the + /// value of type `V` as value for the folding operation. + /// + /// See [`GroupingGenericMap`] for more informations + /// on what operations are available. + #[cfg(feature = "use_alloc")] + fn into_grouping_map_in(self, map: M) -> GroupingGenericMap + where + Self: Iterator + Sized, + K: Eq, + M: Map, + { + grouping_map::new_in(self, map) + } + + /// Constructs a `GroupingGenericMap` to be used later with one of the efficient + /// group-and-fold operations it allows to perform. + /// + /// The values from this iterator will be used as values for the folding operation + /// while the keys will be obtained from the values by calling `key_mapper`. + /// + /// See [`GroupingGenericMap`] for more informations + /// on what operations are available. + #[cfg(feature = "use_alloc")] + fn into_grouping_map_by_in( + self, + key_mapper: F, + map: M, + ) -> GroupingGenericMapBy + where + Self: Iterator + Sized, + K: Eq, + F: FnMut(&V) -> K, + M: Map, + { + let iter = grouping_map::new_map_for_grouping(self, key_mapper); + grouping_map::new_in(iter, map) } /// Return all minimum elements of an iterator. diff --git a/tests/laziness.rs b/tests/laziness.rs index 2ce29cb8a..c1f7d2e6c 100644 --- a/tests/laziness.rs +++ b/tests/laziness.rs @@ -229,10 +229,10 @@ must_use_tests! { } // Not iterator themselves but still lazy. into_grouping_map { - let _ = Panicking.map(|x| (x, x + 1)).into_grouping_map(); + let _ = Panicking.map(|x| (x, x + 1)).into_grouping_map::<_, _, u8>(); } into_grouping_map_by { - let _ = Panicking.into_grouping_map_by(|x| *x); + let _ = Panicking.into_grouping_map_by::<_, _, _, u8>(|x| *x); } // Macros: iproduct {