Skip to content

Commit 6e0aec2

Browse files
committed
Implement XxxAssign operations for HashSets
Also add a set of benchmarks for set operations
1 parent caea18d commit 6e0aec2

File tree

2 files changed

+334
-1
lines changed

2 files changed

+334
-1
lines changed

benches/set_ops.rs

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
//! This file contains benchmarks for the ops traits implemented by HashSet.
2+
//! Each test is intended to have a defined larger and smaller set,
3+
//! but using a larger size for the "small" set works just as well.
4+
//!
5+
//! Each assigning test is done in the configuration that is faster. Cheating, I know.
6+
//! The exception to this is Sub, because there the result differs. So I made two benchmarks for Sub.
7+
8+
#![feature(test)]
9+
10+
extern crate test;
11+
12+
use hashbrown::HashSet;
13+
use test::Bencher;
14+
15+
/// The number of items to generate for the larger of the sets.
16+
const LARGE_SET_SIZE: usize = 1000;
17+
18+
/// The number of items to generate for the smaller of the sets.
19+
const SMALL_SET_SIZE: usize = 100;
20+
21+
/// The number of keys present in both sets.
22+
const OVERLAPP: usize =
23+
[LARGE_SET_SIZE, SMALL_SET_SIZE][(LARGE_SET_SIZE < SMALL_SET_SIZE) as usize] / 2;
24+
25+
#[bench]
26+
fn set_ops_bit_or(b: &mut Bencher) {
27+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
28+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
29+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
30+
.map(|nr| format!("key{}", nr))
31+
.collect();
32+
b.iter(|| &large_set | &small_set)
33+
}
34+
35+
#[bench]
36+
fn set_ops_bit_and(b: &mut Bencher) {
37+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
38+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
39+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
40+
.map(|nr| format!("key{}", nr))
41+
.collect();
42+
b.iter(|| &large_set & &small_set)
43+
}
44+
45+
#[bench]
46+
fn set_ops_bit_xor(b: &mut Bencher) {
47+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
48+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
49+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
50+
.map(|nr| format!("key{}", nr))
51+
.collect();
52+
b.iter(|| &large_set ^ &small_set)
53+
}
54+
55+
#[bench]
56+
fn set_ops_add(b: &mut Bencher) {
57+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
58+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
59+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
60+
.map(|nr| format!("key{}", nr))
61+
.collect();
62+
b.iter(|| &large_set + &small_set)
63+
}
64+
65+
#[bench]
66+
fn set_ops_sub_large_small(b: &mut Bencher) {
67+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
68+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
69+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
70+
.map(|nr| format!("key{}", nr))
71+
.collect();
72+
b.iter(|| &large_set - &small_set)
73+
}
74+
75+
#[bench]
76+
fn set_ops_sub_small_large(b: &mut Bencher) {
77+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
78+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
79+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
80+
.map(|nr| format!("key{}", nr))
81+
.collect();
82+
b.iter(|| &small_set - &large_set)
83+
}
84+
85+
#[bench]
86+
fn set_ops_bit_or_assign(b: &mut Bencher) {
87+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
88+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
89+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
90+
.map(|nr| format!("key{}", nr))
91+
.collect();
92+
b.iter(|| {
93+
let mut set = large_set.clone();
94+
set |= &small_set;
95+
set
96+
});
97+
}
98+
99+
#[bench]
100+
fn set_ops_bit_and_assign(b: &mut Bencher) {
101+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
102+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
103+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
104+
.map(|nr| format!("key{}", nr))
105+
.collect();
106+
b.iter(|| {
107+
let mut set = small_set.clone();
108+
set &= &large_set;
109+
set
110+
});
111+
}
112+
113+
#[bench]
114+
fn set_ops_bit_xor_assign(b: &mut Bencher) {
115+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
116+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
117+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
118+
.map(|nr| format!("key{}", nr))
119+
.collect();
120+
b.iter(|| {
121+
let mut set = large_set.clone();
122+
set ^= &small_set;
123+
set
124+
});
125+
}
126+
127+
#[bench]
128+
fn set_ops_add_assign(b: &mut Bencher) {
129+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
130+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
131+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
132+
.map(|nr| format!("key{}", nr))
133+
.collect();
134+
b.iter(|| {
135+
let mut set = large_set.clone();
136+
set += &small_set;
137+
set
138+
});
139+
}
140+
141+
#[bench]
142+
fn set_ops_sub_assign_large_small(b: &mut Bencher) {
143+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
144+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
145+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
146+
.map(|nr| format!("key{}", nr))
147+
.collect();
148+
b.iter(|| {
149+
let mut set = large_set.clone();
150+
set -= &small_set;
151+
set
152+
});
153+
}
154+
155+
#[bench]
156+
fn set_ops_sub_assign_small_large(b: &mut Bencher) {
157+
let large_set: HashSet<_> = (0..LARGE_SET_SIZE).map(|nr| format!("key{}", nr)).collect();
158+
let small_set: HashSet<_> = ((LARGE_SET_SIZE - OVERLAPP)
159+
..(LARGE_SET_SIZE + SMALL_SET_SIZE - OVERLAPP))
160+
.map(|nr| format!("key{}", nr))
161+
.collect();
162+
b.iter(|| {
163+
let mut set = small_set.clone();
164+
set -= &large_set;
165+
set
166+
});
167+
}

src/set.rs

Lines changed: 167 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ use alloc::borrow::ToOwned;
55
use core::fmt;
66
use core::hash::{BuildHasher, Hash};
77
use core::iter::{Chain, FusedIterator};
8-
use core::ops::{Add, BitAnd, BitOr, BitXor, Sub};
8+
use core::ops::{
9+
Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Sub, SubAssign,
10+
};
911

1012
use super::map::{self, DefaultHashBuilder, HashMap, Keys};
1113
use crate::raw::{Allocator, Global, RawExtractIf};
@@ -1566,6 +1568,170 @@ where
15661568
}
15671569
}
15681570

1571+
impl<T, S> BitOrAssign<&HashSet<T, S>> for HashSet<T, S>
1572+
where
1573+
T: Eq + Hash + Clone,
1574+
S: BuildHasher + Default,
1575+
{
1576+
/// Modifies this set to contain the union of `self` and `rhs`.
1577+
///
1578+
/// # Examples
1579+
///
1580+
/// ```
1581+
/// use hashbrown::HashSet;
1582+
///
1583+
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
1584+
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
1585+
///
1586+
/// a |= &b;
1587+
///
1588+
/// let mut i = 0;
1589+
/// let expected = [1, 2, 3, 4, 5];
1590+
/// for x in &a {
1591+
/// assert!(expected.contains(x));
1592+
/// i += 1;
1593+
/// }
1594+
/// assert_eq!(i, expected.len());
1595+
/// ```
1596+
fn bitor_assign(&mut self, rhs: &HashSet<T, S>) {
1597+
for item in rhs {
1598+
if !self.contains(item) {
1599+
self.insert(item.clone());
1600+
}
1601+
}
1602+
}
1603+
}
1604+
1605+
impl<T, S> BitAndAssign<&HashSet<T, S>> for HashSet<T, S>
1606+
where
1607+
T: Eq + Hash + Clone,
1608+
S: BuildHasher + Default,
1609+
{
1610+
/// Modifies this set to contain the intersection of `self` and `rhs`.
1611+
///
1612+
/// # Examples
1613+
///
1614+
/// ```
1615+
/// use hashbrown::HashSet;
1616+
///
1617+
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
1618+
/// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect();
1619+
///
1620+
/// a &= &b;
1621+
///
1622+
/// let mut i = 0;
1623+
/// let expected = [2, 3];
1624+
/// for x in &a {
1625+
/// assert!(expected.contains(x));
1626+
/// i += 1;
1627+
/// }
1628+
/// assert_eq!(i, expected.len());
1629+
/// ```
1630+
fn bitand_assign(&mut self, rhs: &HashSet<T, S>) {
1631+
self.retain(|item| rhs.contains(item));
1632+
}
1633+
}
1634+
1635+
impl<T, S> BitXorAssign<&HashSet<T, S>> for HashSet<T, S>
1636+
where
1637+
T: Eq + Hash + Clone,
1638+
S: BuildHasher + Default,
1639+
{
1640+
/// Modifies this set to contain the symmetric difference of `self` and `rhs`.
1641+
///
1642+
/// # Examples
1643+
///
1644+
/// ```
1645+
/// use hashbrown::HashSet;
1646+
///
1647+
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
1648+
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
1649+
///
1650+
/// a ^= &b;
1651+
///
1652+
/// let mut i = 0;
1653+
/// let expected = [1, 2, 4, 5];
1654+
/// for x in &a {
1655+
/// assert!(expected.contains(x));
1656+
/// i += 1;
1657+
/// }
1658+
/// assert_eq!(i, expected.len());
1659+
/// ```
1660+
fn bitxor_assign(&mut self, rhs: &HashSet<T, S>) {
1661+
for item in rhs {
1662+
if self.contains(item) {
1663+
self.remove(item);
1664+
} else {
1665+
self.insert(item.clone());
1666+
}
1667+
}
1668+
}
1669+
}
1670+
1671+
impl<T, S> AddAssign<&HashSet<T, S>> for HashSet<T, S>
1672+
where
1673+
T: Eq + Hash + Clone,
1674+
S: BuildHasher + Default,
1675+
{
1676+
/// Modifies this set to contain the union of `self` and `rhs`.
1677+
///
1678+
/// # Examples
1679+
///
1680+
/// ```
1681+
/// use hashbrown::HashSet;
1682+
///
1683+
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
1684+
/// let b: HashSet<_> = vec![2, 3, 4].into_iter().collect();
1685+
///
1686+
/// a += &b;
1687+
///
1688+
/// let mut i = 0;
1689+
/// let expected = [1, 2, 3, 4];
1690+
/// for x in &a {
1691+
/// assert!(expected.contains(x));
1692+
/// i += 1;
1693+
/// }
1694+
/// assert_eq!(i, expected.len());
1695+
/// ```
1696+
fn add_assign(&mut self, rhs: &HashSet<T, S>) {
1697+
for item in rhs {
1698+
if !self.contains(item) {
1699+
self.insert(item.clone());
1700+
}
1701+
}
1702+
}
1703+
}
1704+
1705+
impl<T, S> SubAssign<&HashSet<T, S>> for HashSet<T, S>
1706+
where
1707+
T: Eq + Hash + Clone,
1708+
S: BuildHasher + Default,
1709+
{
1710+
/// Modifies this set to contain the difference of `self` and `rhs`.
1711+
///
1712+
/// # Examples
1713+
///
1714+
/// ```
1715+
/// use hashbrown::HashSet;
1716+
///
1717+
/// let mut a: HashSet<_> = vec![1, 2, 3].into_iter().collect();
1718+
/// let b: HashSet<_> = vec![3, 4, 5].into_iter().collect();
1719+
///
1720+
/// a -= &b;
1721+
///
1722+
/// let mut i = 0;
1723+
/// let expected = [1, 2];
1724+
/// for x in &a {
1725+
/// assert!(expected.contains(x));
1726+
/// i += 1;
1727+
/// }
1728+
/// assert_eq!(i, expected.len());
1729+
/// ```
1730+
fn sub_assign(&mut self, rhs: &HashSet<T, S>) {
1731+
self.retain(|item| !rhs.contains(item));
1732+
}
1733+
}
1734+
15691735
/// An iterator over the items of a `HashSet`.
15701736
///
15711737
/// This `struct` is created by the [`iter`] method on [`HashSet`].

0 commit comments

Comments
 (0)