Skip to main content

itertools/
grouping_map.rs

1use crate::{
2    adaptors::map::{MapSpecialCase, MapSpecialCaseFn},
3    MinMaxResult,
4};
5use core::hash::BuildHasher;
6use std::collections::HashMap;
7use std::hash::Hash;
8use std::iter::Iterator;
9use std::ops::{Add, Mul};
10use std::{cmp::Ordering, collections::hash_map::RandomState};
11
12/// A wrapper to allow for an easy [`into_grouping_map_by`](crate::Itertools::into_grouping_map_by)
13pub type MapForGrouping<I, F> = MapSpecialCase<I, GroupingMapFn<F>>;
14
15#[derive(Clone)]
16pub struct GroupingMapFn<F>(F);
17
18impl<F> std::fmt::Debug for GroupingMapFn<F> {
19    debug_fmt_fields!(GroupingMapFn,);
20}
21
22impl<V, K, F: FnMut(&V) -> K> MapSpecialCaseFn<V> for GroupingMapFn<F> {
23    type Out = (K, V);
24    fn call(&mut self, v: V) -> Self::Out {
25        ((self.0)(&v), v)
26    }
27}
28
29pub(crate) fn new_map_for_grouping<K, I: Iterator, F: FnMut(&I::Item) -> K>(
30    iter: I,
31    key_mapper: F,
32) -> MapForGrouping<I, F> {
33    MapSpecialCase {
34        iter,
35        f: GroupingMapFn(key_mapper),
36    }
37}
38
39/// Creates a new `GroupingMap` from `iter`
40pub fn new<I, K, V, S>(iter: I, hash_builder: S) -> GroupingMap<I, S>
41where
42    I: Iterator<Item = (K, V)>,
43    K: Hash + Eq,
44    S: BuildHasher,
45{
46    GroupingMap { iter, hash_builder }
47}
48
49/// `GroupingMapBy` is an intermediate struct for efficient group-and-fold operations.
50///
51/// See [`GroupingMap`] for more information.
52pub type GroupingMapBy<I, F, S = RandomState> = GroupingMap<MapForGrouping<I, F>, S>;
53
54/// `GroupingMap` is an intermediate struct for efficient group-and-fold operations.
55/// It groups elements by their key and at the same time fold each group
56/// using some aggregating operation.
57///
58/// No method on this struct performs temporary allocations.
59#[derive(Clone, Debug)]
60#[must_use = "GroupingMap is lazy and do nothing unless consumed"]
61pub struct GroupingMap<I, S = RandomState>
62where
63    S: BuildHasher,
64{
65    iter: I,
66    hash_builder: S,
67}
68
69impl<I, K, V, S> GroupingMap<I, S>
70where
71    I: Iterator<Item = (K, V)>,
72    K: Hash + Eq,
73    S: BuildHasher,
74{
75    /// This is the generic way to perform any operation on a `GroupingMap`.
76    /// It's suggested to use this method only to implement custom operations
77    /// when the already provided ones are not enough.
78    ///
79    /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements
80    /// of each group sequentially, passing the previously accumulated value, a reference to the key
81    /// and the current element as arguments, and stores the results in an `HashMap`.
82    ///
83    /// The `operation` function is invoked on each element with the following parameters:
84    ///  - the current value of the accumulator of the group if there is currently one;
85    ///  - a reference to the key of the group this element belongs to;
86    ///  - the element from the source being aggregated;
87    ///
88    /// If `operation` returns `Some(element)` then the accumulator is updated with `element`,
89    /// otherwise the previous accumulation is discarded.
90    ///
91    /// Return a `HashMap` associating the key of each group with the result of aggregation of
92    /// that group's elements. If the aggregation of the last element of a group discards the
93    /// accumulator then there won't be an entry associated to that group's key.
94    ///
95    /// ```
96    /// use itertools::Itertools;
97    ///
98    /// let data = vec![2, 8, 5, 7, 9, 0, 4, 10];
99    /// let lookup = data.into_iter()
100    ///     .into_grouping_map_by(|&n| n % 4)
101    ///     .aggregate(|acc, _key, val| {
102    ///         if val == 0 || val == 10 {
103    ///             None
104    ///         } else {
105    ///             Some(acc.unwrap_or(0) + val)
106    ///         }
107    ///     });
108    ///
109    /// assert_eq!(lookup[&0], 4);        // 0 resets the accumulator so only 4 is summed
110    /// assert_eq!(lookup[&1], 5 + 9);
111    /// assert_eq!(lookup.get(&2), None); // 10 resets the accumulator and nothing is summed afterward
112    /// assert_eq!(lookup[&3], 7);
113    /// assert_eq!(lookup.len(), 3);      // The final keys are only 0, 1 and 2
114    /// ```
115    pub fn aggregate<FO, R>(self, mut operation: FO) -> HashMap<K, R, S>
116    where
117        FO: FnMut(Option<R>, &K, V) -> Option<R>,
118    {
119        let mut destination_map = HashMap::with_hasher(self.hash_builder);
120
121        self.iter.for_each(|(key, val)| {
122            let acc = destination_map.remove(&key);
123            if let Some(op_res) = operation(acc, &key, val) {
124                destination_map.insert(key, op_res);
125            }
126        });
127
128        destination_map
129    }
130
131    /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements
132    /// of each group sequentially, passing the previously accumulated value, a reference to the key
133    /// and the current element as arguments, and stores the results in a new map.
134    ///
135    /// `init` is called to obtain the initial value of each accumulator.
136    ///
137    /// `operation` is a function that is invoked on each element with the following parameters:
138    ///  - the current value of the accumulator of the group;
139    ///  - a reference to the key of the group this element belongs to;
140    ///  - the element from the source being accumulated.
141    ///
142    /// Return a `HashMap` associating the key of each group with the result of folding that group's elements.
143    ///
144    /// ```
145    /// use itertools::Itertools;
146    ///
147    /// #[derive(Debug, Default)]
148    /// struct Accumulator {
149    ///     acc: usize,
150    /// }
151    ///
152    /// let lookup = (1..=7)
153    ///     .into_grouping_map_by(|&n| n % 3)
154    ///     .fold_with(|_key, _val| Default::default(), |Accumulator { acc }, _key, val| {
155    ///         let acc = acc + val;
156    ///         Accumulator { acc }
157    ///      });
158    ///
159    /// assert_eq!(lookup[&0].acc, 3 + 6);
160    /// assert_eq!(lookup[&1].acc, 1 + 4 + 7);
161    /// assert_eq!(lookup[&2].acc, 2 + 5);
162    /// assert_eq!(lookup.len(), 3);
163    /// ```
164    pub fn fold_with<FI, FO, R>(self, mut init: FI, mut operation: FO) -> HashMap<K, R, S>
165    where
166        FI: FnMut(&K, &V) -> R,
167        FO: FnMut(R, &K, V) -> R,
168    {
169        self.aggregate(|acc, key, val| {
170            let acc = acc.unwrap_or_else(|| init(key, &val));
171            Some(operation(acc, key, val))
172        })
173    }
174
175    /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements
176    /// of each group sequentially, passing the previously accumulated value, a reference to the key
177    /// and the current element as arguments, and stores the results in a new map.
178    ///
179    /// `init` is the value from which will be cloned the initial value of each accumulator.
180    ///
181    /// `operation` is a function that is invoked on each element with the following parameters:
182    ///  - the current value of the accumulator of the group;
183    ///  - a reference to the key of the group this element belongs to;
184    ///  - the element from the source being accumulated.
185    ///
186    /// Return a `HashMap` associating the key of each group with the result of folding that group's elements.
187    ///
188    /// ```
189    /// use itertools::Itertools;
190    ///
191    /// let lookup = (1..=7)
192    ///     .into_grouping_map_by(|&n| n % 3)
193    ///     .fold(0, |acc, _key, val| acc + val);
194    ///
195    /// assert_eq!(lookup[&0], 3 + 6);
196    /// assert_eq!(lookup[&1], 1 + 4 + 7);
197    /// assert_eq!(lookup[&2], 2 + 5);
198    /// assert_eq!(lookup.len(), 3);
199    /// ```
200    pub fn fold<FO, R>(self, init: R, operation: FO) -> HashMap<K, R, S>
201    where
202        R: Clone,
203        FO: FnMut(R, &K, V) -> R,
204    {
205        self.fold_with(|_, _| init.clone(), operation)
206    }
207
208    /// Groups elements from the `GroupingMap` source by key and applies `operation` to the elements
209    /// of each group sequentially, passing the previously accumulated value, a reference to the key
210    /// and the current element as arguments, and stores the results in a new map.
211    ///
212    /// This is similar to [`fold`] but the initial value of the accumulator is the first element of the group.
213    ///
214    /// `operation` is a function that is invoked on each element with the following parameters:
215    ///  - the current value of the accumulator of the group;
216    ///  - a reference to the key of the group this element belongs to;
217    ///  - the element from the source being accumulated.
218    ///
219    /// Return a `HashMap` associating the key of each group with the result of folding that group's elements.
220    ///
221    /// [`fold`]: GroupingMap::fold
222    ///
223    /// ```
224    /// use itertools::Itertools;
225    ///
226    /// let lookup = (1..=7)
227    ///     .into_grouping_map_by(|&n| n % 3)
228    ///     .reduce(|acc, _key, val| acc + val);
229    ///
230    /// assert_eq!(lookup[&0], 3 + 6);
231    /// assert_eq!(lookup[&1], 1 + 4 + 7);
232    /// assert_eq!(lookup[&2], 2 + 5);
233    /// assert_eq!(lookup.len(), 3);
234    /// ```
235    pub fn reduce<FO>(self, mut operation: FO) -> HashMap<K, V, S>
236    where
237        FO: FnMut(V, &K, V) -> V,
238    {
239        self.aggregate(|acc, key, val| {
240            Some(match acc {
241                Some(acc) => operation(acc, key, val),
242                None => val,
243            })
244        })
245    }
246
247    /// See [`.reduce()`](GroupingMap::reduce).
248    #[deprecated(note = "Use .reduce() instead", since = "0.13.0")]
249    pub fn fold_first<FO>(self, operation: FO) -> HashMap<K, V, S>
250    where
251        FO: FnMut(V, &K, V) -> V,
252    {
253        self.reduce(operation)
254    }
255
256    /// Groups elements from the `GroupingMap` source by key and collects the elements of each group in
257    /// an instance of `C`. The iteration order is preserved when inserting elements.
258    ///
259    /// Return a `HashMap` associating the key of each group with the collection containing that group's elements.
260    ///
261    /// ```
262    /// use itertools::Itertools;
263    /// use std::collections::HashSet;
264    ///
265    /// let lookup = vec![0, 1, 2, 3, 4, 5, 6, 2, 3, 6].into_iter()
266    ///     .into_grouping_map_by(|&n| n % 3)
267    ///     .collect::<HashSet<_>>();
268    ///
269    /// assert_eq!(lookup[&0], vec![0, 3, 6].into_iter().collect::<HashSet<_>>());
270    /// assert_eq!(lookup[&1], vec![1, 4].into_iter().collect::<HashSet<_>>());
271    /// assert_eq!(lookup[&2], vec![2, 5].into_iter().collect::<HashSet<_>>());
272    /// assert_eq!(lookup.len(), 3);
273    /// ```
274    pub fn collect<C>(self) -> HashMap<K, C, S>
275    where
276        C: Default + Extend<V>,
277    {
278        let mut destination_map = HashMap::with_hasher(self.hash_builder);
279
280        self.iter.for_each(|(key, val)| {
281            destination_map
282                .entry(key)
283                .or_insert_with(C::default)
284                .extend(Some(val));
285        });
286
287        destination_map
288    }
289
290    /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group.
291    ///
292    /// If several elements are equally maximum, the last element is picked.
293    ///
294    /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements.
295    ///
296    /// ```
297    /// use itertools::Itertools;
298    ///
299    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
300    ///     .into_grouping_map_by(|&n| n % 3)
301    ///     .max();
302    ///
303    /// assert_eq!(lookup[&0], 12);
304    /// assert_eq!(lookup[&1], 7);
305    /// assert_eq!(lookup[&2], 8);
306    /// assert_eq!(lookup.len(), 3);
307    /// ```
308    pub fn max(self) -> HashMap<K, V, S>
309    where
310        V: Ord,
311    {
312        self.max_by(|_, v1, v2| V::cmp(v1, v2))
313    }
314
315    /// Groups elements from the `GroupingMap` source by key and finds the maximum of each group
316    /// with respect to the specified comparison function.
317    ///
318    /// If several elements are equally maximum, the last element is picked.
319    ///
320    /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements.
321    ///
322    /// ```
323    /// use itertools::Itertools;
324    ///
325    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
326    ///     .into_grouping_map_by(|&n| n % 3)
327    ///     .max_by(|_key, x, y| y.cmp(x));
328    ///
329    /// assert_eq!(lookup[&0], 3);
330    /// assert_eq!(lookup[&1], 1);
331    /// assert_eq!(lookup[&2], 5);
332    /// assert_eq!(lookup.len(), 3);
333    /// ```
334    pub fn max_by<F>(self, mut compare: F) -> HashMap<K, V, S>
335    where
336        F: FnMut(&K, &V, &V) -> Ordering,
337    {
338        self.reduce(|acc, key, val| match compare(key, &acc, &val) {
339            Ordering::Less | Ordering::Equal => val,
340            Ordering::Greater => acc,
341        })
342    }
343
344    /// Groups elements from the `GroupingMap` source by key and finds the element of each group
345    /// that gives the maximum from the specified function.
346    ///
347    /// If several elements are equally maximum, the last element is picked.
348    ///
349    /// Returns a `HashMap` associating the key of each group with the maximum of that group's elements.
350    ///
351    /// ```
352    /// use itertools::Itertools;
353    ///
354    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
355    ///     .into_grouping_map_by(|&n| n % 3)
356    ///     .max_by_key(|_key, &val| val % 4);
357    ///
358    /// assert_eq!(lookup[&0], 3);
359    /// assert_eq!(lookup[&1], 7);
360    /// assert_eq!(lookup[&2], 5);
361    /// assert_eq!(lookup.len(), 3);
362    /// ```
363    pub fn max_by_key<F, CK>(self, mut f: F) -> HashMap<K, V, S>
364    where
365        F: FnMut(&K, &V) -> CK,
366        CK: Ord,
367    {
368        self.max_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2)))
369    }
370
371    /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group.
372    ///
373    /// If several elements are equally minimum, the first element is picked.
374    ///
375    /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements.
376    ///
377    /// ```
378    /// use itertools::Itertools;
379    ///
380    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
381    ///     .into_grouping_map_by(|&n| n % 3)
382    ///     .min();
383    ///
384    /// assert_eq!(lookup[&0], 3);
385    /// assert_eq!(lookup[&1], 1);
386    /// assert_eq!(lookup[&2], 5);
387    /// assert_eq!(lookup.len(), 3);
388    /// ```
389    pub fn min(self) -> HashMap<K, V, S>
390    where
391        V: Ord,
392    {
393        self.min_by(|_, v1, v2| V::cmp(v1, v2))
394    }
395
396    /// Groups elements from the `GroupingMap` source by key and finds the minimum of each group
397    /// with respect to the specified comparison function.
398    ///
399    /// If several elements are equally minimum, the first element is picked.
400    ///
401    /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements.
402    ///
403    /// ```
404    /// use itertools::Itertools;
405    ///
406    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
407    ///     .into_grouping_map_by(|&n| n % 3)
408    ///     .min_by(|_key, x, y| y.cmp(x));
409    ///
410    /// assert_eq!(lookup[&0], 12);
411    /// assert_eq!(lookup[&1], 7);
412    /// assert_eq!(lookup[&2], 8);
413    /// assert_eq!(lookup.len(), 3);
414    /// ```
415    pub fn min_by<F>(self, mut compare: F) -> HashMap<K, V, S>
416    where
417        F: FnMut(&K, &V, &V) -> Ordering,
418    {
419        self.reduce(|acc, key, val| match compare(key, &acc, &val) {
420            Ordering::Less | Ordering::Equal => acc,
421            Ordering::Greater => val,
422        })
423    }
424
425    /// Groups elements from the `GroupingMap` source by key and finds the element of each group
426    /// that gives the minimum from the specified function.
427    ///
428    /// If several elements are equally minimum, the first element is picked.
429    ///
430    /// Returns a `HashMap` associating the key of each group with the minimum of that group's elements.
431    ///
432    /// ```
433    /// use itertools::Itertools;
434    ///
435    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
436    ///     .into_grouping_map_by(|&n| n % 3)
437    ///     .min_by_key(|_key, &val| val % 4);
438    ///
439    /// assert_eq!(lookup[&0], 12);
440    /// assert_eq!(lookup[&1], 4);
441    /// assert_eq!(lookup[&2], 8);
442    /// assert_eq!(lookup.len(), 3);
443    /// ```
444    pub fn min_by_key<F, CK>(self, mut f: F) -> HashMap<K, V, S>
445    where
446        F: FnMut(&K, &V) -> CK,
447        CK: Ord,
448    {
449        self.min_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2)))
450    }
451
452    /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of
453    /// each group.
454    ///
455    /// If several elements are equally maximum, the last element is picked.
456    /// If several elements are equally minimum, the first element is picked.
457    ///
458    /// See [`Itertools::minmax`](crate::Itertools::minmax) for the non-grouping version.
459    ///
460    /// Differences from the non grouping version:
461    /// - It never produces a `MinMaxResult::NoElements`
462    /// - It doesn't have any speedup
463    ///
464    /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements.
465    ///
466    /// ```
467    /// use itertools::Itertools;
468    /// use itertools::MinMaxResult::{MinMax, OneElement};
469    ///
470    /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter()
471    ///     .into_grouping_map_by(|&n| n % 3)
472    ///     .minmax();
473    ///
474    /// assert_eq!(lookup[&0], MinMax(3, 12));
475    /// assert_eq!(lookup[&1], MinMax(1, 7));
476    /// assert_eq!(lookup[&2], OneElement(5));
477    /// assert_eq!(lookup.len(), 3);
478    /// ```
479    pub fn minmax(self) -> HashMap<K, MinMaxResult<V>, S>
480    where
481        V: Ord,
482    {
483        self.minmax_by(|_, v1, v2| V::cmp(v1, v2))
484    }
485
486    /// Groups elements from the `GroupingMap` source by key and find the maximum and minimum of
487    /// each group with respect to the specified comparison function.
488    ///
489    /// If several elements are equally maximum, the last element is picked.
490    /// If several elements are equally minimum, the first element is picked.
491    ///
492    /// It has the same differences from the non-grouping version as `minmax`.
493    ///
494    /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements.
495    ///
496    /// ```
497    /// use itertools::Itertools;
498    /// use itertools::MinMaxResult::{MinMax, OneElement};
499    ///
500    /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter()
501    ///     .into_grouping_map_by(|&n| n % 3)
502    ///     .minmax_by(|_key, x, y| y.cmp(x));
503    ///
504    /// assert_eq!(lookup[&0], MinMax(12, 3));
505    /// assert_eq!(lookup[&1], MinMax(7, 1));
506    /// assert_eq!(lookup[&2], OneElement(5));
507    /// assert_eq!(lookup.len(), 3);
508    /// ```
509    pub fn minmax_by<F>(self, mut compare: F) -> HashMap<K, MinMaxResult<V>, S>
510    where
511        F: FnMut(&K, &V, &V) -> Ordering,
512    {
513        self.aggregate(|acc, key, val| {
514            Some(match acc {
515                Some(MinMaxResult::OneElement(e)) => {
516                    if compare(key, &val, &e) == Ordering::Less {
517                        MinMaxResult::MinMax(val, e)
518                    } else {
519                        MinMaxResult::MinMax(e, val)
520                    }
521                }
522                Some(MinMaxResult::MinMax(min, max)) => {
523                    if compare(key, &val, &min) == Ordering::Less {
524                        MinMaxResult::MinMax(val, max)
525                    } else if compare(key, &val, &max) != Ordering::Less {
526                        MinMaxResult::MinMax(min, val)
527                    } else {
528                        MinMaxResult::MinMax(min, max)
529                    }
530                }
531                None => MinMaxResult::OneElement(val),
532                Some(MinMaxResult::NoElements) => unreachable!(),
533            })
534        })
535    }
536
537    /// Groups elements from the `GroupingMap` source by key and find the elements of each group
538    /// that gives the minimum and maximum from the specified function.
539    ///
540    /// If several elements are equally maximum, the last element is picked.
541    /// If several elements are equally minimum, the first element is picked.
542    ///
543    /// It has the same differences from the non-grouping version as `minmax`.
544    ///
545    /// Returns a `HashMap` associating the key of each group with the minimum and maximum of that group's elements.
546    ///
547    /// ```
548    /// use itertools::Itertools;
549    /// use itertools::MinMaxResult::{MinMax, OneElement};
550    ///
551    /// let lookup = vec![1, 3, 4, 5, 7, 9, 12].into_iter()
552    ///     .into_grouping_map_by(|&n| n % 3)
553    ///     .minmax_by_key(|_key, &val| val % 4);
554    ///
555    /// assert_eq!(lookup[&0], MinMax(12, 3));
556    /// assert_eq!(lookup[&1], MinMax(4, 7));
557    /// assert_eq!(lookup[&2], OneElement(5));
558    /// assert_eq!(lookup.len(), 3);
559    /// ```
560    pub fn minmax_by_key<F, CK>(self, mut f: F) -> HashMap<K, MinMaxResult<V>, S>
561    where
562        F: FnMut(&K, &V) -> CK,
563        CK: Ord,
564    {
565        self.minmax_by(|key, v1, v2| f(key, v1).cmp(&f(key, v2)))
566    }
567
568    /// Groups elements from the `GroupingMap` source by key and sums them.
569    ///
570    /// This is just a shorthand for `self.reduce(|acc, _, val| acc + val)`.
571    /// It is more limited than `Iterator::sum` since it doesn't use the `Sum` trait.
572    ///
573    /// Returns a `HashMap` associating the key of each group with the sum of that group's elements.
574    ///
575    /// ```
576    /// use itertools::Itertools;
577    ///
578    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
579    ///     .into_grouping_map_by(|&n| n % 3)
580    ///     .sum();
581    ///
582    /// assert_eq!(lookup[&0], 3 + 9 + 12);
583    /// assert_eq!(lookup[&1], 1 + 4 + 7);
584    /// assert_eq!(lookup[&2], 5 + 8);
585    /// assert_eq!(lookup.len(), 3);
586    /// ```
587    pub fn sum(self) -> HashMap<K, V, S>
588    where
589        V: Add<V, Output = V>,
590    {
591        self.reduce(|acc, _, val| acc + val)
592    }
593
594    /// Groups elements from the `GroupingMap` source by key and multiply them.
595    ///
596    /// This is just a shorthand for `self.reduce(|acc, _, val| acc * val)`.
597    /// It is more limited than `Iterator::product` since it doesn't use the `Product` trait.
598    ///
599    /// Returns a `HashMap` associating the key of each group with the product of that group's elements.
600    ///
601    /// ```
602    /// use itertools::Itertools;
603    ///
604    /// let lookup = vec![1, 3, 4, 5, 7, 8, 9, 12].into_iter()
605    ///     .into_grouping_map_by(|&n| n % 3)
606    ///     .product();
607    ///
608    /// assert_eq!(lookup[&0], 3 * 9 * 12);
609    /// assert_eq!(lookup[&1], 1 * 4 * 7);
610    /// assert_eq!(lookup[&2], 5 * 8);
611    /// assert_eq!(lookup.len(), 3);
612    /// ```
613    pub fn product(self) -> HashMap<K, V, S>
614    where
615        V: Mul<V, Output = V>,
616    {
617        self.reduce(|acc, _, val| acc * val)
618    }
619}