@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()
);
}