Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement XxxAssign operations on HashSets #529

Merged
merged 9 commits into from
Jun 18, 2024
148 changes: 148 additions & 0 deletions benches/set_ops.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
//! This file contains benchmarks for the ops traits implemented by HashSet.
//! Each test is intended to have a defined larger and smaller set,
//! but using a larger size for the "small" set works just as well.
//!
//! Each assigning test is done in the configuration that is faster. Cheating, I know.
//! The exception to this is Sub, because there the result differs. So I made two benchmarks for Sub.

#![feature(test)]

extern crate test;

use hashbrown::HashSet;
use test::Bencher;

/// The number of items to generate for the larger of the sets.
const LARGE_SET_SIZE: usize = 1000;

/// The number of items to generate for the smaller of the sets.
const SMALL_SET_SIZE: usize = 100;

/// The number of keys present in both sets.
const OVERLAPP: usize =
[LARGE_SET_SIZE, SMALL_SET_SIZE][(LARGE_SET_SIZE < SMALL_SET_SIZE) as usize] / 2;

/// Creates a set containing end - start unique string elements.
fn create_set(start: usize, end: usize) -> HashSet<String> {
(start..end).map(|nr| format!("key{}", nr)).collect()
}

#[bench]
fn set_ops_bit_or(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| &large_set | &small_set)
}

#[bench]
fn set_ops_bit_and(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| &large_set & &small_set)
}

#[bench]
fn set_ops_bit_xor(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| &large_set ^ &small_set)
}

#[bench]
fn set_ops_sub_large_small(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| &large_set - &small_set)
}

#[bench]
fn set_ops_sub_small_large(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| &small_set - &large_set)
}

#[bench]
fn set_ops_bit_or_assign(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| {
let mut set = large_set.clone();
set |= &small_set;
set
});
}

#[bench]
fn set_ops_bit_and_assign(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| {
let mut set = small_set.clone();
set &= &large_set;
set
});
}

#[bench]
fn set_ops_bit_xor_assign(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| {
let mut set = large_set.clone();
set ^= &small_set;
set
});
}

#[bench]
fn set_ops_sub_assign_large_small(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| {
let mut set = large_set.clone();
set -= &small_set;
set
});
}

#[bench]
fn set_ops_sub_assign_small_large(b: &mut Bencher) {
let large_set = create_set(0, LARGE_SET_SIZE);
let small_set = create_set(
LARGE_SET_SIZE - OVERLAPP,
LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP,
);
b.iter(|| {
let mut set = small_set.clone();
set -= &large_set;
set
});
}
172 changes: 159 additions & 13 deletions src/set.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use alloc::borrow::ToOwned;
use core::fmt;
use core::hash::{BuildHasher, Hash};
use core::iter::{Chain, FusedIterator};
use core::ops::{BitAnd, BitOr, BitXor, Sub};
use core::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign};

use super::map::{self, DefaultHashBuilder, HashMap, Keys};
use crate::raw::{Allocator, Global, RawExtractIf};
Expand Down Expand Up @@ -1410,9 +1410,9 @@ impl<T, S, A> BitOr<&HashSet<T, S, A>> for &HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
A: Allocator,
A: Allocator + Default,
{
type Output = HashSet<T, S>;
type Output = HashSet<T, S, A>;

/// Returns the union of `self` and `rhs` as a new `HashSet<T, S>`.
///
Expand All @@ -1434,7 +1434,7 @@ where
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitor(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S> {
fn bitor(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S, A> {
self.union(rhs).cloned().collect()
}
}
Expand All @@ -1443,9 +1443,9 @@ impl<T, S, A> BitAnd<&HashSet<T, S, A>> for &HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
A: Allocator,
A: Allocator + Default,
{
type Output = HashSet<T, S>;
type Output = HashSet<T, S, A>;

/// Returns the intersection of `self` and `rhs` as a new `HashSet<T, S>`.
///
Expand All @@ -1467,17 +1467,18 @@ where
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitand(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S> {
fn bitand(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S, A> {
self.intersection(rhs).cloned().collect()
}
}

impl<T, S> BitXor<&HashSet<T, S>> for &HashSet<T, S>
impl<T, S, A> BitXor<&HashSet<T, S, A>> for &HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
A: Allocator + Default,
{
type Output = HashSet<T, S>;
type Output = HashSet<T, S, A>;

/// Returns the symmetric difference of `self` and `rhs` as a new `HashSet<T, S>`.
///
Expand All @@ -1499,17 +1500,18 @@ where
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitxor(self, rhs: &HashSet<T, S>) -> HashSet<T, S> {
fn bitxor(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S, A> {
self.symmetric_difference(rhs).cloned().collect()
}
}

impl<T, S> Sub<&HashSet<T, S>> for &HashSet<T, S>
impl<T, S, A> Sub<&HashSet<T, S, A>> for &HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher + Default,
A: Allocator + Default,
{
type Output = HashSet<T, S>;
type Output = HashSet<T, S, A>;

/// Returns the difference of `self` and `rhs` as a new `HashSet<T, S>`.
///
Expand All @@ -1531,11 +1533,155 @@ where
/// }
/// assert_eq!(i, expected.len());
/// ```
fn sub(self, rhs: &HashSet<T, S>) -> HashSet<T, S> {
fn sub(self, rhs: &HashSet<T, S, A>) -> HashSet<T, S, A> {
self.difference(rhs).cloned().collect()
}
}

impl<T, S, A> BitOrAssign<&HashSet<T, S, A>> for HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher,
A: Allocator,
{
/// Modifies this set to contain the union of `self` and `rhs`.
///
/// # Examples
///
/// ```
/// use hashbrown::HashSet;
///
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// a |= &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 3, 4, 5];
/// for x in &a {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitor_assign(&mut self, rhs: &HashSet<T, S, A>) {
for item in rhs {
if !self.contains(item) {
self.insert(item.clone());
}
}
}
}

impl<T, S, A> BitAndAssign<&HashSet<T, S, A>> for HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher,
A: Allocator,
{
/// Modifies this set to contain the intersection of `self` and `rhs`.
///
/// # Examples
///
/// ```
/// use hashbrown::HashSet;
///
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect();
///
/// a &= &b;
///
/// let mut i = 0;
/// let expected = [2, 3];
/// for x in &a {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitand_assign(&mut self, rhs: &HashSet<T, S, A>) {
self.retain(|item| rhs.contains(item));
}
}

impl<T, S, A> BitXorAssign<&HashSet<T, S, A>> for HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher,
A: Allocator,
{
/// Modifies this set to contain the symmetric difference of `self` and `rhs`.
///
/// # Examples
///
/// ```
/// use hashbrown::HashSet;
///
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// a ^= &b;
///
/// let mut i = 0;
/// let expected = [1, 2, 4, 5];
/// for x in &a {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn bitxor_assign(&mut self, rhs: &HashSet<T, S, A>) {
for item in rhs {
let entry = self.map.raw_entry_mut().from_key(item);
match entry {
map::RawEntryMut::Occupied(e) => {
e.remove();
}
map::RawEntryMut::Vacant(e) => {
e.insert(item.to_owned(), ());
}
};
}
}
}

impl<T, S, A> SubAssign<&HashSet<T, S, A>> for HashSet<T, S, A>
where
T: Eq + Hash + Clone,
S: BuildHasher,
A: Allocator,
{
/// Modifies this set to contain the difference of `self` and `rhs`.
///
/// # Examples
///
/// ```
/// use hashbrown::HashSet;
///
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
///
/// a -= &b;
///
/// let mut i = 0;
/// let expected = [1, 2];
/// for x in &a {
/// assert!(expected.contains(x));
/// i += 1;
/// }
/// assert_eq!(i, expected.len());
/// ```
fn sub_assign(&mut self, rhs: &HashSet<T, S, A>) {
if rhs.len() < self.len() {
for item in rhs {
self.remove(item);
}
} else {
self.retain(|item| !rhs.contains(item));
}
}
}

/// An iterator over the items of a `HashSet`.
///
/// This `struct` is created by the [`iter`] method on [`HashSet`].
Expand Down
Loading