From 8c6dbd1eca0a5f3a869a43954ee80f098cf4217c Mon Sep 17 00:00:00 2001 From: Guilherme Souza <19gui94@gmail.com> Date: Sat, 30 May 2020 19:31:47 +0200 Subject: [PATCH 1/4] Implement custom behavior for PartialEq, Eq, PartialOrd, Ord and Hash The observed behavior for those traits is the same as the behavior of the standard library's `BTreeSet` --- src/lib.rs | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5ad52d3..7f2e2bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -35,6 +35,7 @@ use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, I use std::cmp::{Ord, Ordering}; use std::iter::{Chain, FromIterator}; pub use range::IndexRange; +use std::hash::{Hash, Hasher}; const BITS: usize = 32; type Block = u32; @@ -50,7 +51,7 @@ fn div_rem(x: usize, d: usize) -> (usize, usize) /// /// The bit set has a fixed capacity in terms of enabling bits (and the /// capacity can grow using the `grow` method). -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[derive(Debug, Default)] pub struct FixedBitSet { data: Vec, /// length in bits @@ -770,6 +771,43 @@ impl <'a> BitXorAssign for FixedBitSet } } +/// Two `FixedBitSet`s are equal if and only if they have the exact same set of "one" elements +impl PartialEq for FixedBitSet +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.ones().eq(other.ones()) + } +} + +impl Eq for FixedBitSet {} + +impl PartialOrd for FixedBitSet +{ + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for FixedBitSet +{ + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.ones().cmp(other.ones()) + } +} + +impl Hash for FixedBitSet +{ + #[inline] + fn hash(&self, state: &mut H) { + for elt in self.ones() { + elt.hash(state); + } + } +} + #[test] fn it_works() { const N: usize = 50; @@ -1556,3 +1594,40 @@ fn display_trait() { assert_eq!(format!("{}", fb), "00101000"); assert_eq!(format!("{:#}", fb), "0b00101000"); } + +#[test] +fn comparison_and_hash() { + fn with_values(values: &[usize], capacity: usize) -> FixedBitSet { + let mut r = FixedBitSet::with_capacity(capacity); + r.extend(values.iter().copied()); + r + } + + fn hash_of(t: &T) -> u64 { + let mut s = std::collections::hash_map::DefaultHasher::new(); + t.hash(&mut s); + s.finish() + } + + let single_7_17 = with_values(&[7, 17], 32); + let single_7_10 = with_values(&[7, 10], 32); + let single_7_20 = with_values(&[7, 20], 32); + let double_7_17 = with_values(&[7, 17], 64); + let double_7_17_37 = with_values(&[7, 17, 37], 64); + + // Equality + assert_eq!(single_7_17, double_7_17); + assert_ne!(single_7_17, single_7_10); + assert_ne!(double_7_17, double_7_17_37); + + // Comparison + assert_eq!(single_7_17.cmp(&double_7_17), Ordering::Equal); + assert_eq!(single_7_17.cmp(&single_7_10), Ordering::Greater); + assert_eq!(single_7_17.cmp(&single_7_20), Ordering::Less); + assert_eq!(single_7_17.cmp(&double_7_17_37), Ordering::Less); + + // Hash + assert_eq!(hash_of(&single_7_17), hash_of(&double_7_17)); + assert_ne!(hash_of(&single_7_17), hash_of(&single_7_10)); + assert_ne!(hash_of(&double_7_17), hash_of(&double_7_17_37)); +} \ No newline at end of file From e83fa61a34e443c09c8226e483177feaa7c79d38 Mon Sep 17 00:00:00 2001 From: Guilherme Souza <19gui94@gmail.com> Date: Sat, 30 May 2020 20:47:46 +0200 Subject: [PATCH 2/4] Fix issue in test code with used Rust version --- src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7f2e2bf..7918da8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1599,7 +1599,9 @@ fn display_trait() { fn comparison_and_hash() { fn with_values(values: &[usize], capacity: usize) -> FixedBitSet { let mut r = FixedBitSet::with_capacity(capacity); - r.extend(values.iter().copied()); + for &v in values { + r.put(v); + } r } From 50308f725575b55a4d4ccf98c00492c1e96aa45a Mon Sep 17 00:00:00 2001 From: Guilherme Souza <19gui94@gmail.com> Date: Sat, 30 May 2020 21:18:22 +0200 Subject: [PATCH 3/4] Fix test code in no-std mode --- src/lib.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7918da8..ac09a1f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1605,10 +1605,23 @@ fn comparison_and_hash() { r } - fn hash_of(t: &T) -> u64 { - let mut s = std::collections::hash_map::DefaultHasher::new(); + #[derive(Default)] + struct MockHasher { + bytes: Vec + } + impl Hasher for MockHasher { + fn finish(&self) -> u64 { + unimplemented!() + } + fn write(&mut self, bytes: &[u8]) { + self.bytes.extend_from_slice(bytes); + } + } + + fn hash_of(t: &T) -> Vec { + let mut s = MockHasher::default(); t.hash(&mut s); - s.finish() + s.bytes } let single_7_17 = with_values(&[7, 17], 32); From f6124e7bb810cc2638179db89f32189e1ee51c5f Mon Sep 17 00:00:00 2001 From: Guilherme Souza <19gui94@gmail.com> Date: Sun, 31 May 2020 11:03:15 +0200 Subject: [PATCH 4/4] Use `as_trimmed_slice()` internally to implement faster versions of `PartialEq`, `Ord` and `Hash` --- src/lib.rs | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ac09a1f..042e629 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -424,6 +424,21 @@ impl FixedBitSet pub fn is_superset(&self, other: &FixedBitSet) -> bool { other.is_subset(self) } + + /// View the bitset as a slice of `u32` blocks with no trailing zero-blocks. + /// This is useful to implement the traits PartialEq, Ord and Hash + fn as_trimmed_slice(&self) -> &[Block] { + let blocks = self.as_slice(); + + // Trim zeros + for (i, &block) in blocks.iter().enumerate().rev() { + if block != 0 { + return &blocks[..i+1]; + } + } + + &[] + } } impl Binary for FixedBitSet { @@ -776,7 +791,7 @@ impl PartialEq for FixedBitSet { #[inline] fn eq(&self, other: &Self) -> bool { - self.ones().eq(other.ones()) + self.as_trimmed_slice().eq(other.as_trimmed_slice()) } } @@ -794,7 +809,7 @@ impl Ord for FixedBitSet { #[inline] fn cmp(&self, other: &Self) -> Ordering { - self.ones().cmp(other.ones()) + self.as_trimmed_slice().cmp(other.as_trimmed_slice()) } } @@ -802,9 +817,7 @@ impl Hash for FixedBitSet { #[inline] fn hash(&self, state: &mut H) { - for elt in self.ones() { - elt.hash(state); - } + self.as_trimmed_slice().hash(state) } } @@ -1645,4 +1658,18 @@ fn comparison_and_hash() { assert_eq!(hash_of(&single_7_17), hash_of(&double_7_17)); assert_ne!(hash_of(&single_7_17), hash_of(&single_7_10)); assert_ne!(hash_of(&double_7_17), hash_of(&double_7_17_37)); + + // Only trailing zeros should be trimmed + assert_eq!( + FixedBitSet::with_capacity_and_blocks(32 * 9, vec![7, 0, 7, 0, 0, 7, 0, 0, 0]), + FixedBitSet::with_capacity_and_blocks(32 * 8, vec![7, 0, 7, 0, 0, 7, 0, 0]) + ); + assert_ne!( + FixedBitSet::with_capacity_and_blocks(32 * 9, vec![7, 0, 7, 0, 0, 7, 0, 0, 0]), + FixedBitSet::with_capacity_and_blocks(32 * 9, vec![7, 0, 7, 0, 0, 17, 0, 0, 0]) + ); + assert_ne!( + FixedBitSet::with_capacity_and_blocks(32 * 8, vec![0, 7, 0, 0, 7, 0, 0, 0]), + FixedBitSet::with_capacity_and_blocks(32 * 7, vec![7, 0, 0, 7, 0, 0, 0]) + ); } \ No newline at end of file