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