Reputation: 4872
I have a Vec<(A, B)>
and I want to group by A
but not just consecutive but all elements in the Vec
. The closest thing I found is Itertools::group_by
which only works on consecutive values. I understand that the consecutiveness is related to optimizing allocation but I just want a regular C# group by. Priority is to not have to use a new library just for this.
A
is not hashable, only Ord
. I want a resulting Vec<(A, Vec<(A, B))>
or equivalent
Upvotes: 7
Views: 4554
Reputation: 602155
Assuming that "comparable" means A: Ord
, i.e. that there is a total ordering on A
, you can fold an iterator over items of type (A, B)
into a BTreeMap
from A
to Vec<B>
:
use std::collections::BTreeMap;
fn group_pairs<A, B, I>(v: I) -> BTreeMap<A, Vec<B>>
where
A: Ord,
I: IntoIterator<Item = (A, B)>,
{
v.into_iter().fold(BTreeMap::new(), |mut acc, (a, b)| {
acc.entry(a).or_default().push(b);
acc
})
}
Some people prefer a for loop over a fold:
fn group_pairs<A, B, I>(v: I) -> BTreeMap<A, Vec<B>>
where
A: Ord,
I: IntoIterator<Item = (A, B)>,
{
let mut result = BTreeMap::<A, Vec<B>>::new();
for (a, b) in v {
result.entry(a).or_default().push(b);
}
result
}
Example:
let data = vec![(1, 2), (2, 3), (1, 1), (2, 4), (3, 5)];
let grouped = vec![(1, vec![2, 1]), (2, vec![3, 4]), (3, vec![5])];
assert_eq!(group_pairs(data).into_iter().collect::<Vec<_>>(), grouped);
Upvotes: 17