@simonbuchan is right about the iterator part. reduce infers the type of acc from the iterator item type, which, in this case, is &HashSet<T>, and reduce cannot accumulate the result in that. However, the compiler's suggestion is indeed bogus and simplistic (though imagine what rustc could do if it had AI built in
). Also, & operator creates a new HashSet on every individual intersection, so that would result in a bunch of excessive allocations when you intersect multiple sets. What works in your case is:
use std::collections::HashSet;
use std::hash::Hash;
// Note: don't need a Vec here, a slice is sufficient.
// An iterator would be more generic, of course.
// Also, you don't need to consume (move) the input sets,
// hence a slice of `&HashSet<T>`.
fn intersections<T>(sets: &[&HashSet<T>]) -> HashSet<T>
where
T: Clone + Eq + Hash,
{
match sets.len() {
0 => HashSet::new(),
_ => sets[1..].iter().fold(sets[0].clone(), |mut acc, set| {
acc.retain(|item| set.contains(item));
acc
}),
}
}
#[test]
fn test_intersections() {
let a: HashSet<_> = [1, 2, 3, 4].into();
let b: HashSet<_> = [2, 3, 4, 5].into();
let c: HashSet<_> = [3, 4, 5, 6].into();
assert_eq!(intersections::<i32>(&[]), HashSet::new());
assert_eq!(intersections(&[&[1].into()]), [1].into());
assert_eq!(intersections(&[&a, &b, &c]), [3, 4].into());
assert_eq!(intersections(&[&[1,2].into(), &[3,4].into()]), HashSet::new());
}
As to why you have to specify the Eq + Hash bound, that's only because they're required for & to work:
impl<T, S> BitAnd<&HashSet<T, S>> for &HashSet<T, S>where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
but HashSet struct itself has no bounds on it, so, for example, this function compiles just fine without the need to specify any bounds:
fn not_intersections<T>(_sets: &[HashSet<T>]) -> HashSet<T> {
HashSet::new()
}
That would be all, but as a bonus, here's a version that accepts an iterator:
use std::collections::HashSet;
use std::hash::Hash;
use std::iter::{empty, once};
// Note: don't need a Vec here, a slice is sufficient.
// An iterator would be more generic, of course.
// Also, you don't need to consume (move) the input sets,
// hence a slice of `&HashSet<T>`.
fn intersections<'a, T>(mut sets: impl Iterator<Item = &'a HashSet<T>>) -> HashSet<T>
where
T: Clone + Eq + Hash + 'a,
{
match sets.next() {
Some(first) => sets.fold(first.clone(), |mut acc, set| {
acc.retain(|item| set.contains(item));
acc
}),
None => HashSet::new(),
}
}
#[test]
fn test_intersections() {
let a: HashSet<_> = [1, 2, 3, 4].into();
let b: HashSet<_> = [2, 3, 4, 5].into();
let c: HashSet<_> = [3, 4, 5, 6].into();
assert_eq!(intersections::<i32>(empty()), HashSet::new());
assert_eq!(intersections(once(&[1].into())), [1].into());
assert_eq!(intersections([a, b, c].iter()), [3, 4].into());
assert_eq!(
intersections(once(&[1, 2].into()).chain(once(&[3, 4].into()))),
HashSet::new()
);
}