aggregate_map/
lib.rs

1//! Collect a list of key-value pairs into a mapping of keys to collections of values.
2//!
3//! If you have a set of data that you want to collect into a map, by default you'll only keep the
4//! last value in the data for that key. But what if you want instead to keep a collection of all
5//! the values for each key? Enter [`AggregateMap`]!
6//!
7//!
8//! ```rust
9//! # use std::collections::HashMap;
10//! # use aggregate_map::AggregateMap;
11//! let data = [
12//!     ("dog", "Terry"),
13//!     ("dog", "Zamboni"),
14//!     ("cat", "Jonathan"),
15//!     ("dog", "Priscilla"),
16//! ];
17//! let collected: AggregateMap<HashMap<_, Vec<_>>> = data.into_iter().collect();
18//! let expected = HashMap::from([
19//!     ("dog", vec!["Terry", "Zamboni", "Priscilla"]),
20//!     ("cat", vec!["Jonathan"])
21//! ]);
22//! assert_eq!(collected.into_inner(), expected);
23//! ```
24//!
25//! [`AggregateMap`] can be used with any map type that implements this crate's [`Map`] trait, such
26//! as [`HashMap`][std::collections::HashMap] or [`BTreeMap`][std::collections::BTreeMap].
27//!
28//! The collection type doesn't have to be a [`Vec`], too, it can be anything that implements
29//! [`Extend`] and [`Default`]. For instance, here's an example with a
30//! [`HashSet`][std::collections::HashSet]:
31//! ```rust
32//! # use std::collections::{HashMap, HashSet};
33//! # use aggregate_map::AggregateMap;
34//! let data = [
35//!     ("dog", "Terry"),
36//!     ("dog", "Terry"),
37//!     ("dog", "Priscilla"),
38//! ];
39//! let collected: AggregateMap<HashMap<_, HashSet<_>>> = data.into_iter().collect();
40//! let expected = HashMap::from([
41//!     ("dog", HashSet::from(["Terry", "Priscilla"])),
42//! ]);
43//! assert_eq!(collected.into_inner(), expected);
44//! ```
45//!
46//! It can even be another [`AggregateMap`] for additional levels of aggregation!
47//! ```rust
48//! # use std::collections::HashMap;
49//! # use aggregate_map::AggregateMap;
50//! let data = [
51//!     ("pet", ("dog", "Terry")),
52//!     ("pet", ("dog", "Priscilla")),
53//!     ("stray", ("cat", "Jennifer")),
54//!     ("pet", ("cat", "Absalom")),
55//! ];
56//! let collected: AggregateMap<HashMap<_, AggregateMap<HashMap<_, Vec<_>>>>> =
57//!     data.into_iter().collect();
58//! let expected = HashMap::from([
59//!     ("pet", HashMap::from([
60//!         ("dog", vec!["Terry", "Priscilla"]),
61//!         ("cat", vec!["Absalom"]),
62//!     ])),
63//!     ("stray", HashMap::from([
64//!         ("cat", vec!["Jennifer"]),
65//!     ])),
66//! ]);
67//! let collected: HashMap<_, _> = collected
68//!     .into_inner()
69//!     .into_iter()
70//!     .map(|(key, map)| (key, map.into_inner()))
71//!     .collect();
72//! assert_eq!(collected, expected);
73//! ```
74#![cfg_attr(docsrs, feature(doc_cfg))]
75#![forbid(unsafe_code)]
76#![warn(clippy::pedantic)]
77
78
79#[cfg(feature = "btreemap")]
80#[cfg_attr(docsrs, doc(cfg(feature = "btreemap")))]
81pub mod btreemap;
82#[cfg(feature = "hashmap")]
83pub mod hashmap;
84
85/// A wrapper around a "map" type that lets you collect an iterator of key-value pairs into a
86/// mapping between keys and collections of values, instead of just keys to values.
87#[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
88#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
89pub struct AggregateMap<M>(M);
90
91impl<M> AggregateMap<M> {
92    /// Consumes the [`AggregateMap`] to give you the inner map `M`.
93    pub fn into_inner(self) -> M {
94        self.0
95    }
96}
97
98impl<M> AsRef<M> for AggregateMap<M> {
99    fn as_ref(&self) -> &M {
100        &self.0
101    }
102}
103
104impl<M> AsMut<M> for AggregateMap<M> {
105    fn as_mut(&mut self) -> &mut M  {
106        &mut self.0
107    }
108}
109impl<M> From<M> for AggregateMap<M> {
110    fn from(map: M) -> Self {
111        Self(map)
112    }
113}
114
115/// A trait for "map" types (such as [`HashMap`][std::collections::HashMap]) that you can collect
116/// into with an [`AggregateMap`].
117///
118/// Implementations of this trait are provided for `std` maps, but if you have a custom map type you
119/// can implement this trait for it to be able to use it with [`AggregateMap`].
120///
121/// Implementors of this trait will generally have a key of `K`, but a value of some collection type
122/// (like [`Vec`] or [`HashSet`][std::collections::HashSet]), which contains multiple values of type
123/// `V`.
124pub trait Map<K, V> {
125    /// Insert one `value` into the collection contained at `key`.
126    fn insert(&mut self, key: K, value: V);
127}
128
129impl<M, K, V> Extend<(K, V)> for AggregateMap<M>
130where
131    M: Map<K, V>,
132{
133    fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
134        iter.into_iter()
135            .for_each(|(key, value)| self.0.insert(key, value));
136    }
137}
138
139impl<M, K, V> FromIterator<(K, V)> for AggregateMap<M>
140where
141    M: Map<K, V> + Default,
142{
143    fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
144        let mut this = Self::default();
145        this.extend(iter);
146        this
147    }
148}