diff --git a/roaring/src/bitmap/cmp.rs b/roaring/src/bitmap/cmp.rs index 370bfb08..04b0531b 100644 --- a/roaring/src/bitmap/cmp.rs +++ b/roaring/src/bitmap/cmp.rs @@ -1,5 +1,6 @@ use core::borrow::Borrow; use core::cmp::Ordering; +use core::hash::{Hash, Hasher}; use core::iter::Peekable; use super::container::Container; @@ -150,3 +151,15 @@ where } } } + +impl Hash for RoaringBitmap { + fn hash(&self, state: &mut H) { + // Bitmaps holding the same values are equal even when their containers + // use different internal representations (array, bitmap or run), so the + // hash has to be built from the values rather than from the layout. + self.len().hash(state); + for value in self { + value.hash(state); + } + } +} diff --git a/roaring/src/treemap/mod.rs b/roaring/src/treemap/mod.rs index 93ca126d..83a87846 100644 --- a/roaring/src/treemap/mod.rs +++ b/roaring/src/treemap/mod.rs @@ -36,7 +36,7 @@ pub use self::iter::{BitmapIter, IntoIter, Iter}; /// rb.insert(7); /// println!("total bits set to true: {}", rb.len()); /// ``` -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Hash)] pub struct RoaringTreemap { map: BTreeMap, } diff --git a/roaring/tests/hash.rs b/roaring/tests/hash.rs new file mode 100644 index 00000000..36a44d03 --- /dev/null +++ b/roaring/tests/hash.rs @@ -0,0 +1,61 @@ +extern crate roaring; + +use std::collections::hash_map::DefaultHasher; +use std::collections::HashSet; +use std::hash::{Hash, Hasher}; + +use roaring::RoaringBitmap; + +fn hash_of(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn equal_bitmaps_have_equal_hashes() { + let a = (0..2000).collect::(); + let b = (0..2000).collect::(); + + assert_eq!(a, b); + assert_eq!(hash_of(&a), hash_of(&b)); +} + +#[test] +fn empty_bitmaps_hash_equal() { + assert_eq!(hash_of(&RoaringBitmap::new()), hash_of(&RoaringBitmap::new())); +} + +#[test] +fn array_and_run_hash_equal() { + // A short contiguous range is stored as an array, but `optimize` rewrites it + // into a run. The two share the same values so they must hash the same. + let array = (0..100).collect::(); + let mut run = array.clone(); + assert!(run.optimize()); + + assert_eq!(array, run); + assert_eq!(hash_of(&array), hash_of(&run)); +} + +#[test] +fn bitmap_and_run_hash_equal() { + let plain = (0..6000).chain(1_000_000..1_012_000).collect::(); + let mut optimized = plain.clone(); + assert!(optimized.optimize()); + + assert_eq!(plain, optimized); + assert_eq!(hash_of(&plain), hash_of(&optimized)); +} + +#[test] +fn usable_as_hashset_key() { + let mut set = HashSet::new(); + set.insert((0..10).collect::()); + set.insert((0..10).collect::()); + set.insert((5..15).collect::()); + + assert_eq!(set.len(), 2); + assert!(set.contains(&(0..10).collect::())); + assert!(!set.contains(&(100..110).collect::())); +} diff --git a/roaring/tests/treemap_hash.rs b/roaring/tests/treemap_hash.rs new file mode 100644 index 00000000..3a729073 --- /dev/null +++ b/roaring/tests/treemap_hash.rs @@ -0,0 +1,53 @@ +extern crate roaring; + +use std::collections::hash_map::DefaultHasher; +use std::collections::HashSet; +use std::hash::{Hash, Hasher}; + +use roaring::RoaringTreemap; + +fn hash_of(value: &T) -> u64 { + let mut hasher = DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn equal_treemaps_have_equal_hashes() { + let a = (0..2000u64).collect::(); + let b = (0..2000u64).collect::(); + + assert_eq!(a, b); + assert_eq!(hash_of(&a), hash_of(&b)); +} + +#[test] +fn spanning_multiple_bitmaps_hash_equal() { + let values = [0u64, 1, u32::MAX as u64, u32::MAX as u64 + 1, u64::MAX]; + let a = values.iter().copied().collect::(); + let b = values.iter().rev().copied().collect::(); + + assert_eq!(a, b); + assert_eq!(hash_of(&a), hash_of(&b)); +} + +#[test] +fn same_values_different_representation_hash_equal() { + let plain = (0..6000u64).collect::(); + let mut optimized = plain.clone(); + assert!(optimized.optimize()); + + assert_eq!(plain, optimized); + assert_eq!(hash_of(&plain), hash_of(&optimized)); +} + +#[test] +fn usable_as_hashset_key() { + let mut set = HashSet::new(); + set.insert((0..10u64).collect::()); + set.insert((0..10u64).collect::()); + set.insert((5..15u64).collect::()); + + assert_eq!(set.len(), 2); + assert!(set.contains(&(0..10u64).collect::())); +}